diff options
Diffstat (limited to 'mobile/android/geckoview/src')
21 files changed, 710 insertions, 107 deletions
diff --git a/mobile/android/geckoview/src/androidTest/AndroidManifest.xml b/mobile/android/geckoview/src/androidTest/AndroidManifest.xml index 4dc4760d0f..2a6e47aa92 100644 --- a/mobile/android/geckoview/src/androidTest/AndroidManifest.xml +++ b/mobile/android/geckoview/src/androidTest/AndroidManifest.xml @@ -17,8 +17,7 @@ android:allowBackup="true" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme" - android:name="androidx.multidex.MultiDexApplication"> + android:theme="@style/AppTheme"> <activity android:name=".GeckoViewTestActivity" android:exported="true"/> <!-- This is used for crash handling in GeckoSessionTestRule --> <service diff --git a/mobile/android/geckoview/src/androidTest/assets/web_extensions/langpack_signed.xpi b/mobile/android/geckoview/src/androidTest/assets/web_extensions/langpack_signed.xpi Binary files differindex f60d00348e..ab7db3926e 100644 --- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/langpack_signed.xpi +++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/langpack_signed.xpi diff --git a/mobile/android/geckoview/src/androidTest/assets/web_extensions/options_page_alias/manifest.json b/mobile/android/geckoview/src/androidTest/assets/web_extensions/options_page_alias/manifest.json new file mode 100644 index 0000000000..3e7b0c1f4c --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/options_page_alias/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest_version": 2, + "name": "options_page_alias", + "version": "1.0", + "description": "A test add-on using options_page instead of options_ui.page", + "browser_specific_settings": { + "gecko": { + "id": "options_page_alias@tests.mozilla.org" + } + }, + "options_page": "dummy.html" +} diff --git a/mobile/android/geckoview/src/androidTest/assets/www/badVideoPath.html b/mobile/android/geckoview/src/androidTest/assets/www/badVideoPath.html index d9b34843fd..2b3300ba5c 100644 --- a/mobile/android/geckoview/src/androidTest/assets/www/badVideoPath.html +++ b/mobile/android/geckoview/src/androidTest/assets/www/badVideoPath.html @@ -5,7 +5,7 @@ </head> <body> <video controls preload> - <source src="videos/fileDoesNotExist.ogg"></source> + <source src="videos/fileDoesNotExist.webm"></source> </video> </body> </html> diff --git a/mobile/android/geckoview/src/androidTest/assets/www/ogg.html b/mobile/android/geckoview/src/androidTest/assets/www/ogg.html deleted file mode 100644 index dd478d3b3f..0000000000 --- a/mobile/android/geckoview/src/androidTest/assets/www/ogg.html +++ /dev/null @@ -1,11 +0,0 @@ -<html> - <head> - <meta charset="utf-8"> - <title>OGG Video</title> - </head> - <body> - <video controls preload> - <source src="videos/video.ogg"></source> - </video> - </body> -</html> diff --git a/mobile/android/geckoview/src/androidTest/assets/www/videos/video.ogg b/mobile/android/geckoview/src/androidTest/assets/www/videos/video.ogg Binary files differdeleted file mode 100644 index ac7ece3519..0000000000 --- a/mobile/android/geckoview/src/androidTest/assets/www/videos/video.ogg +++ /dev/null diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt index 6e2d79e0b0..8e87c1d478 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt @@ -1071,7 +1071,8 @@ class AccessibilityTest : BaseSessionTest() { override fun onFocused(event: AccessibilityEvent) { nodeId = getSourceId(event) val node = createNodeInfo(nodeId) - assertThat("Focused outsideSelectable", node.text.toString(), equalTo("outside selectable")) + val nodeChild = createNodeInfo(node.getChildId(0)) + assertThat("Focused outsideSelectable", nodeChild.text.toString(), equalTo("outside selectable ")) } }) } diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt index d57cd8f157..9bcba0f5b7 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt @@ -76,7 +76,6 @@ open class BaseSessionTest( const val TEST_GIF_PATH = "/assets/www/images/test.gif" const val TITLE_CHANGE_HTML_PATH = "/assets/www/titleChange.html" const val TRACKERS_PATH = "/assets/www/trackers.html" - const val VIDEO_OGG_PATH = "/assets/www/ogg.html" const val VIDEO_MP4_PATH = "/assets/www/mp4.html" const val VIDEO_WEBM_PATH = "/assets/www/webm.html" const val VIDEO_BAD_PATH = "/assets/www/badVideoPath.html" diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt index 511d58b5a6..a404935192 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt @@ -37,6 +37,88 @@ class ContentDelegateChildTest : BaseSessionTest() { mainSession.panZoomController.onTouchEvent(event) } + private fun sendRightClickDown(x: Float, y: Float) { + val downTime = SystemClock.uptimeMillis() + var eventTime = SystemClock.uptimeMillis() + + var pp = arrayOf(MotionEvent.PointerProperties()) + pp[0].id = 0 + pp[0].toolType = MotionEvent.TOOL_TYPE_MOUSE + + var pc = arrayOf(MotionEvent.PointerCoords()) + pc[0].x = x + pc[0].y = y + pc[0].pressure = 1.0f + pc[0].size = 1.0f + + var event = MotionEvent.obtain( + downTime, + eventTime, + MotionEvent.ACTION_DOWN, + /* pointerCount */ + 1, + pp, + pc, + /* metaState */ + 0, + MotionEvent.BUTTON_SECONDARY, + /* xPrecision */ + 1.0f, + /* yPrecision */ + 1.0f, + /* deviceId */ + 0, + /* edgeFlags */ + 0, + InputDevice.SOURCE_MOUSE, + /* flags */ + 0, + ) + mainSession.panZoomController.onTouchEvent(event) + } + + private fun sendRightClickUp(x: Float, y: Float) { + val downTime = SystemClock.uptimeMillis() + var eventTime = SystemClock.uptimeMillis() + + var pp = arrayOf(MotionEvent.PointerProperties()) + pp[0].id = 0 + pp[0].toolType = MotionEvent.TOOL_TYPE_MOUSE + + var pc = arrayOf(MotionEvent.PointerCoords()) + pc[0].x = x + pc[0].y = y + pc[0].pressure = 1.0f + pc[0].size = 1.0f + + var event = MotionEvent.obtain( + downTime, + eventTime, + MotionEvent.ACTION_UP, + /* pointerCount */ + 1, + pp, + pc, + /* metaState */ + 0, + // buttonState is unset in ACTION_UP + /* buttonState */ + 0, + /* xPrecision */ + 1.0f, + /* yPrecision */ + 1.0f, + /* deviceId */ + 0, + /* edgeFlags */ + 0, + InputDevice.SOURCE_MOUSE, + /* flags */ + 0, + ) + mainSession.panZoomController.onTouchEvent(event) + } + @WithDisplay(width = 100, height = 100) @Test fun requestContextMenuOnAudio() { @@ -278,6 +360,120 @@ class ContentDelegateChildTest : BaseSessionTest() { @WithDisplay(width = 100, height = 100) @Test + fun requestContextMenuOnLinkRightClickMouseUp() { + sessionRule.setPrefsUntilTestEnd( + mapOf( + "ui.context_menus.after_mouseup" to true, + ), + ) + mainSession.loadTestPath(CONTEXT_MENU_LINK_HTML_PATH) + mainSession.waitForPageStop() + + sendRightClickDown(50f, 50f) + + mainSession.delegateDuringNextWait(object : ContentDelegate { + @AssertCalled(false) + override fun onContextMenu( + session: GeckoSession, + screenX: Int, + screenY: Int, + element: ContextElement, + ) {} + }) + + sendRightClickUp(50f, 50f) + + mainSession.delegateUntilTestEnd(object : ContentDelegate { + @AssertCalled(count = 1) + override fun onContextMenu( + session: GeckoSession, + screenX: Int, + screenY: Int, + element: ContextElement, + ) { + assertThat( + "Type should be none.", + element.type, + equalTo(ContextElement.TYPE_NONE), + ) + assertThat( + "The element link title should be the title of the anchor.", + element.title, + equalTo("Hello Link Title"), + ) + assertThat( + "The element link URI should be the href of the anchor.", + element.linkUri, + endsWith("hello.html"), + ) + assertThat( + "The element link text content should be the text content of the anchor.", + element.textContent, + equalTo("Hello World"), + ) + } + }) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun requestContextMenuOnLinkRightClickMouseDown() { + sessionRule.setPrefsUntilTestEnd( + mapOf( + "ui.context_menus.after_mouseup" to false, + ), + ) + mainSession.loadTestPath(CONTEXT_MENU_LINK_HTML_PATH) + mainSession.waitForPageStop() + + sendRightClickDown(50f, 50f) + + mainSession.delegateDuringNextWait(object : ContentDelegate { + @AssertCalled(count = 1) + override fun onContextMenu( + session: GeckoSession, + screenX: Int, + screenY: Int, + element: ContextElement, + ) { + assertThat( + "Type should be none.", + element.type, + equalTo(ContextElement.TYPE_NONE), + ) + assertThat( + "The element link title should be the title of the anchor.", + element.title, + equalTo("Hello Link Title"), + ) + assertThat( + "The element link URI should be the href of the anchor.", + element.linkUri, + endsWith("hello.html"), + ) + assertThat( + "The element link text content should be the text content of the anchor.", + element.textContent, + equalTo("Hello World"), + ) + } + }) + + sendRightClickUp(50f, 50f) + + mainSession.delegateUntilTestEnd(object : ContentDelegate { + @AssertCalled(false) + override fun onContextMenu( + session: GeckoSession, + screenX: Int, + screenY: Int, + element: ContextElement, + ) {} + }) + } + + @WithDisplay(width = 100, height = 100) + @Test fun notRequestContextMenuWithPreventDefault() { mainSession.loadTestPath(CONTEXT_MENU_LINK_HTML_PATH) mainSession.waitForPageStop() diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt index 839ac4b468..4aec796d3e 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt @@ -2223,6 +2223,30 @@ class NavigationDelegateTest : BaseSessionTest() { ) } + @Test fun loadUriInPrivateSessionReferrerSession() { + val uri = "https://example.com/bar" + val referrer = "https://example.org/" + + mainSession.loadUri(referrer) + mainSession.waitForPageStop() + + val privateSettings = GeckoSessionSettings.Builder().usePrivateMode(true).build() + val newSession = sessionRule.createOpenSession(privateSettings) + newSession.load( + Loader() + .uri(uri) + .referrer(mainSession) + .flags(GeckoSession.LOAD_FLAGS_NONE), + ) + newSession.waitForPageStop() + + assertThat( + "Referrer should not sent", + newSession.evaluateJS("document.referrer") as String, + equalTo(""), + ) + } + @Test fun loadUriReferrerSessionFileUrl() { val uri = "file:///system/etc/fonts.xml" val referrer = "https://example.org" diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt index 2e340c09c2..5a3b1892ce 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt @@ -11,7 +11,6 @@ import androidx.test.filters.MediumTest import org.hamcrest.Matchers import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.notNullValue -import org.junit.Assume.assumeThat import org.junit.Test import org.junit.runner.RunWith import org.mozilla.geckoview.GeckoResult @@ -71,8 +70,6 @@ class VerticalClippingTest : BaseSessionTest() { @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH) @Test fun verticalClippingSucceeds() { - // Disable failing test on Webrender. Bug 1670267 - assumeThat(sessionRule.env.isWebrender, equalTo(false)) sessionRule.display?.setVerticalClipping(45) mainSession.loadTestPath(FIXED_BOTTOM) sessionRule.waitUntilCalled(object : ContentDelegate { diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt index 702ba4d23b..fa5caa8693 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt @@ -268,6 +268,220 @@ class WebExtensionTest : BaseSessionTest() { sessionRule.waitForResult(controller.uninstall(extension)) } + @Test + fun updateOptionalPermissions() { + var extension = sessionRule.waitForResult( + controller.ensureBuiltIn( + "resource://android/assets/web_extensions/optional-permission-request/", + "optional-permission-request@example.com", + ), + ) + + assertEquals("optional-permission-request@example.com", extension.id) + + var grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + var grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + // Without adding any optional permissions. + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + + // Only adding an origin permission. + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf(), + arrayOf("*://example.com/*"), + ), + ) + + grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 1.", grantedOptionalOrigins.size, equalTo(1)) + assertThat( + "grantedOptionalOrigins must be *://example.com/*.", + grantedOptionalOrigins.first(), + equalTo("*://example.com/*"), + ) + + // Adding "nothing" to verify that nothing gets changed. + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf(), + arrayOf(), + ), + ) + + assertThat("grantedOptionalOrigins must be 1.", grantedOptionalOrigins.size, equalTo(1)) + assertThat( + "grantedOptionalOrigins must be *://example.com/*.", + grantedOptionalOrigins.first(), + equalTo("*://example.com/*"), + ) + + // Adding an optional permission. + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf("activeTab"), + arrayOf(), + ), + ) + + grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + // Both optional and origin permissions must be granted. + assertThat( + "grantedOptionalPermissions must be 1.", + grantedOptionalPermissions.size, + equalTo(1), + ) + assertThat( + "grantedOptionalPermissions must be activeTab.", + grantedOptionalPermissions.first(), + equalTo("activeTab"), + ) + assertThat("grantedOptionalOrigins must be 1.", grantedOptionalOrigins.size, equalTo(1)) + assertThat( + "grantedOptionalOrigins must be *://example.com/*.", + grantedOptionalOrigins.first(), + equalTo("*://example.com/*"), + ) + + // Removing "nothing" to verify that nothing gets changed. + extension = sessionRule.waitForResult( + controller.removeOptionalPermissions( + extension.id, + arrayOf(), + arrayOf(), + ), + ) + + assertThat("grantedOptionalOrigins must be 1.", grantedOptionalOrigins.size, equalTo(1)) + assertThat( + "grantedOptionalOrigins must be *://example.com/*.", + grantedOptionalOrigins.first(), + equalTo("*://example.com/*"), + ) + + // Remove an activeTab optional permission. + extension = sessionRule.waitForResult( + controller.removeOptionalPermissions( + extension.id, + arrayOf("activeTab"), + arrayOf(), + ), + ) + + grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 1.", grantedOptionalOrigins.size, equalTo(1)) + assertThat( + "grantedOptionalOrigins must be *://example.com/*.", + grantedOptionalOrigins.first(), + equalTo("*://example.com/*"), + ) + + // Remove an `*://example.com/*` origin permission. + extension = sessionRule.waitForResult( + controller.removeOptionalPermissions( + extension.id, + arrayOf(), + arrayOf("*://example.com/*"), + ), + ) + + grantedOptionalPermissions = extension.metaData.grantedOptionalPermissions + grantedOptionalOrigins = extension.metaData.grantedOptionalOrigins + + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + + // Missing origins from the manifest. + // Must throw! + try { + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf(), + arrayOf("*://missing-origins.com/*"), + ), + ) + fail() + } catch (_: Exception) { + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + } + + // Permission no in the manifest. + // Must throw! + try { + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf("clipboardRead"), + arrayOf(), + ), + ) + fail() + } catch (_: Exception) { + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + } + + // Missing origins from the manifest. + // Must throw! + try { + extension = sessionRule.waitForResult( + controller.addOptionalPermissions( + extension.id, + arrayOf(), + arrayOf("<all_urls>"), + ), + ) + fail() + } catch (_: Exception) { + assertThat( + "grantedOptionalPermissions must be 0.", + grantedOptionalPermissions.size, + equalTo(0), + ) + assertThat("grantedOptionalOrigins must be 0.", grantedOptionalOrigins.size, equalTo(0)) + } + + sessionRule.waitForResult(controller.uninstall(extension)) + } + private fun assertBodyBorderEqualTo(expected: String) { val color = mainSession.evaluateJS("document.body.style.borderColor") assertThat( @@ -589,7 +803,7 @@ class WebExtensionTest : BaseSessionTest() { } @Test - fun optionsPageMetadata() { + fun optionsUIPageMetadata() { // dummy.xpi is not signed, but it could be sessionRule.setPrefsUntilTestEnd( mapOf( @@ -611,6 +825,7 @@ class WebExtensionTest : BaseSessionTest() { assertTrue(extension.metaData.baseUrl.matches("^moz-extension://[0-9a-f\\-]*/$".toRegex())) assertNotNull(extension.metaData.optionsPageUrl) assertTrue((extension.metaData.optionsPageUrl ?: "").matches("^moz-extension://[0-9a-f\\-]*/options.html$".toRegex())) + assertEquals(true, extension.metaData.openOptionsPageInTab) onReadyResult.complete(null) super.onReady(extension) } @@ -640,6 +855,41 @@ class WebExtensionTest : BaseSessionTest() { } @Test + fun optionsPageAliasMetadata() { + // NOTE: This test case tests options_page is considered an alternative alias for + // options_ui.page and the metadata to be set so that it is opened in a tab. + + // Wait for the onReady AddonManagerDelegate method to be called, and assert + // that the baseUrl and optionsPageUrl are both available as expected. + val onReadyResult = GeckoResult<Void>() + sessionRule.addExternalDelegateUntilTestEnd( + WebExtensionController.AddonManagerDelegate::class, + { delegate -> controller.setAddonManagerDelegate(delegate) }, + { controller.setAddonManagerDelegate(null) }, + object : WebExtensionController.AddonManagerDelegate { + @AssertCalled(count = 1) + override fun onReady(extension: WebExtension) { + assertNotNull(extension.metaData.baseUrl) + assertTrue(extension.metaData.baseUrl.matches("^moz-extension://[0-9a-f\\-]*/$".toRegex())) + assertEquals("${extension.metaData.baseUrl}dummy.html", extension.metaData.optionsPageUrl) + assertEquals(true, extension.metaData.openOptionsPageInTab) + onReadyResult.complete(null) + super.onReady(extension) + } + }, + ) + + val testExt = sessionRule.waitForResult( + controller.installBuiltIn( + "resource://android/assets/web_extensions/options_page_alias/", + ), + ) + + sessionRule.waitForResult(onReadyResult) + sessionRule.waitForResult(controller.uninstall(testExt)) + } + + @Test fun installMultiple() { // dummy.xpi is not signed, but it could be sessionRule.setPrefsUntilTestEnd( diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java index bcd5762a92..c6aab05068 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java @@ -1231,7 +1231,7 @@ public class GeckoAppShell { @WrapForJNI(calledFrom = "gecko") private static double[] getCurrentBatteryInformation() { - return GeckoBatteryManager.getCurrentInformation(); + return GeckoBatteryManager.getCurrentInformation(getApplicationContext()); } /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */ diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java index 19f489b399..224362d066 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java @@ -54,10 +54,20 @@ public class GeckoBatteryManager extends BroadcastReceiver { mApplicationContext = context.getApplicationContext(); // registerReceiver will return null if registering fails. - if (mApplicationContext.registerReceiver(this, mFilter) == null) { + final Intent intent = mApplicationContext.registerReceiver(this, mFilter); + if (intent == null) { Log.e(LOGTAG, "Registering receiver failed"); + return; + } + + mIsEnabled = true; + final double current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + final double max = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + if (current == -1 || max == -1) { + Log.e(LOGTAG, "Failed to get battery level!"); + sLevel = kDefaultLevel; } else { - mIsEnabled = true; + sLevel = current / max; } } @@ -194,7 +204,10 @@ public class GeckoBatteryManager extends BroadcastReceiver { sNotificationsEnabled = false; } - public static double[] getCurrentInformation() { + public static double[] getCurrentInformation(final Context context) { + if (!getInstance().mIsEnabled) { + getInstance().start(context); + } return new double[] {getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime()}; } } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java index bc9eff98f0..8afa61b4b2 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java @@ -48,7 +48,7 @@ import org.mozilla.gecko.annotation.WrapForJNI; Log.e(LOGTAG, "Cannot open the uri: " + aUri + " (invalid header)"); close(); } - } catch (final IOException | SecurityException e) { + } catch (final Exception e) { Log.e(LOGTAG, "Cannot open the uri: " + aUri, e); close(); } @@ -146,7 +146,7 @@ import org.mozilla.gecko.annotation.WrapForJNI; Log.d(LOGTAG, "The uri is readable: " + uri); return true; } - } catch (final IOException | SecurityException e) { + } catch (final Exception e) { // A SecurityException could happen if the uri is no more valid or if // we're in an isolated process. Log.e(LOGTAG, "Cannot read the uri: " + uri, e); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java index d365f303c2..8750f344a8 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java @@ -1169,7 +1169,9 @@ import org.mozilla.geckoview.SessionTextInput.EditableListener.IMEState; } // Preserve enter and tab keys for the browser - if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_TAB) { + if (keyCode == KeyEvent.KEYCODE_ENTER + || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER + || keyCode == KeyEvent.KEYCODE_TAB) { return true; } // BaseKeyListener returns false even if it handled these keys for us, diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java index a662b3a82d..5b6923ac1e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java @@ -415,7 +415,7 @@ public class MediaSession { public String toString() { final StringBuilder builder = new StringBuilder("Metadata {"); builder - .append(", title=") + .append("title=") .append(title) .append(", artist=") .append(artist) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java index 6fae35f320..0c8457e7a4 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java @@ -6,7 +6,6 @@ package org.mozilla.geckoview; import android.app.PendingIntent; -import android.content.Intent; import android.net.Uri; import android.util.Base64; import android.util.Log; @@ -21,11 +20,13 @@ import com.google.android.gms.fido.fido2.api.common.AuthenticationExtensions; import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse; +import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse; import com.google.android.gms.fido.fido2.api.common.AuthenticatorSelectionCriteria; import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions; import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions; import com.google.android.gms.fido.fido2.api.common.EC2Algorithm; import com.google.android.gms.fido.fido2.api.common.FidoAppIdExtension; +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor; import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters; @@ -128,16 +129,19 @@ import org.mozilla.gecko.util.GeckoBundle; public final byte[] keyHandle; public final byte[] attestationObject; public final String[] transports; + public final String authenticatorAttachment; public MakeCredentialResponse( final byte[] clientDataJson, final byte[] keyHandle, final byte[] attestationObject, - final String[] transports) { + final String[] transports, + final String authenticatorAttachment) { this.clientDataJson = clientDataJson; this.keyHandle = keyHandle; this.attestationObject = attestationObject; this.transports = transports; + this.authenticatorAttachment = authenticatorAttachment; } } @@ -290,42 +294,63 @@ import org.mozilla.gecko.util.GeckoBundle; .startActivityForResult(pendingIntent) .accept( intent -> { - final WebAuthnTokenManager.Exception error = parseErrorIntent(intent); + if (!intent.hasExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)) { + Log.w(LOGTAG, "Failed to get credential data in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + final byte[] rspData = + intent.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA); + final PublicKeyCredential publicKeyCredentialData = + PublicKeyCredential.deserializeFromBytes(rspData); + + final AuthenticatorResponse response = publicKeyCredentialData.getResponse(); + final WebAuthnTokenManager.Exception error = parseErrorResponse(response); if (error != null) { result.completeExceptionally(error); return; } - final byte[] rspData = intent.getByteArrayExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA); - if (rspData != null) { - final AuthenticatorAttestationResponse responseData = - AuthenticatorAttestationResponse.deserializeFromBytes(rspData); - - Log.d( - LOGTAG, - "key handle: " - + Base64.encodeToString(responseData.getKeyHandle(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "clientDataJSON: " - + Base64.encodeToString( - responseData.getClientDataJSON(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "attestation Object: " - + Base64.encodeToString( - responseData.getAttestationObject(), Base64.DEFAULT)); - - Log.d( - LOGTAG, "transports: " + String.join(", ", responseData.getTransports())); - - result.complete( - new WebAuthnTokenManager.MakeCredentialResponse( - responseData.getClientDataJSON(), - responseData.getKeyHandle(), - responseData.getAttestationObject(), - responseData.getTransports())); + if (!(response instanceof AuthenticatorAttestationResponse)) { + Log.w(LOGTAG, "Failed to get attestation response in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; } + + final AuthenticatorAttestationResponse responseData = + (AuthenticatorAttestationResponse) response; + + Log.d( + LOGTAG, + "key handle: " + + Base64.encodeToString( + publicKeyCredentialData.getRawId(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "clientDataJSON: " + + Base64.encodeToString( + responseData.getClientDataJSON(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "attestation Object: " + + Base64.encodeToString( + responseData.getAttestationObject(), Base64.DEFAULT)); + + Log.d(LOGTAG, "transports: " + String.join(", ", responseData.getTransports())); + Log.d( + LOGTAG, + "authenticatorAttachment :" + + publicKeyCredentialData.getAuthenticatorAttachment()); + + result.complete( + new WebAuthnTokenManager.MakeCredentialResponse( + responseData.getClientDataJSON(), + publicKeyCredentialData.getRawId(), + responseData.getAttestationObject(), + responseData.getTransports(), + publicKeyCredentialData.getAuthenticatorAttachment())); }, e -> { Log.w(LOGTAG, "Failed to launch activity: ", e); @@ -390,29 +415,31 @@ import org.mozilla.gecko.util.GeckoBundle; public final byte[] authData; public final byte[] signature; public final byte[] userHandle; + public final String authenticatorAttachment; public GetAssertionResponse( final byte[] clientDataJson, final byte[] keyHandle, final byte[] authData, final byte[] signature, - final byte[] userHandle) { + final byte[] userHandle, + final String authenticatorAttachment) { this.clientDataJson = clientDataJson; this.keyHandle = keyHandle; this.authData = authData; this.signature = signature; this.userHandle = userHandle; + this.authenticatorAttachment = authenticatorAttachment; } } - private static WebAuthnTokenManager.Exception parseErrorIntent(final Intent intent) { - if (!intent.hasExtra(Fido.FIDO2_KEY_ERROR_EXTRA)) { + private static WebAuthnTokenManager.Exception parseErrorResponse( + final AuthenticatorResponse response) { + if (!(response instanceof AuthenticatorErrorResponse)) { return null; } - final byte[] errData = intent.getByteArrayExtra(Fido.FIDO2_KEY_ERROR_EXTRA); - final AuthenticatorErrorResponse responseData = - AuthenticatorErrorResponse.deserializeFromBytes(errData); + final AuthenticatorErrorResponse responseData = (AuthenticatorErrorResponse) response; Log.e(LOGTAG, "errorCode.name: " + responseData.getErrorCode()); Log.e(LOGTAG, "errorMessage: " + responseData.getErrorMessage()); @@ -485,51 +512,73 @@ import org.mozilla.gecko.util.GeckoBundle; .startActivityForResult(pendingIntent) .accept( intent -> { - final WebAuthnTokenManager.Exception error = parseErrorIntent(intent); + if (!intent.hasExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)) { + Log.w(LOGTAG, "Failed to get credential data in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + + final byte[] rspData = + intent.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA); + final PublicKeyCredential publicKeyCredentialData = + PublicKeyCredential.deserializeFromBytes(rspData); + final AuthenticatorResponse response = publicKeyCredentialData.getResponse(); + + final WebAuthnTokenManager.Exception error = parseErrorResponse(response); if (error != null) { result.completeExceptionally(error); return; } - if (intent.hasExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA)) { - final byte[] rspData = - intent.getByteArrayExtra(Fido.FIDO2_KEY_RESPONSE_EXTRA); - final AuthenticatorAssertionResponse responseData = - AuthenticatorAssertionResponse.deserializeFromBytes(rspData); - - Log.d( - LOGTAG, - "key handle: " - + Base64.encodeToString(responseData.getKeyHandle(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "clientDataJSON: " - + Base64.encodeToString( - responseData.getClientDataJSON(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "auth data: " - + Base64.encodeToString( - responseData.getAuthenticatorData(), Base64.DEFAULT)); - Log.d( - LOGTAG, - "signature: " - + Base64.encodeToString(responseData.getSignature(), Base64.DEFAULT)); - - // Nullable field - byte[] userHandle = responseData.getUserHandle(); - if (userHandle == null) { - userHandle = new byte[0]; - } - - result.complete( - new WebAuthnTokenManager.GetAssertionResponse( - responseData.getClientDataJSON(), - responseData.getKeyHandle(), - responseData.getAuthenticatorData(), - responseData.getSignature(), - userHandle)); + if (!(response instanceof AuthenticatorAssertionResponse)) { + Log.w(LOGTAG, "Failed to get assertion response in FIDO intent"); + result.completeExceptionally( + new WebAuthnTokenManager.Exception("UNKNOWN_ERR")); + return; + } + + final AuthenticatorAssertionResponse responseData = + (AuthenticatorAssertionResponse) publicKeyCredentialData.getResponse(); + + Log.d( + LOGTAG, + "key handle: " + + Base64.encodeToString( + publicKeyCredentialData.getRawId(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "clientDataJSON: " + + Base64.encodeToString( + responseData.getClientDataJSON(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "auth data: " + + Base64.encodeToString( + responseData.getAuthenticatorData(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "signature: " + + Base64.encodeToString(responseData.getSignature(), Base64.DEFAULT)); + Log.d( + LOGTAG, + "authenticatorAttachment :" + + publicKeyCredentialData.getAuthenticatorAttachment()); + + // Nullable field + byte[] userHandle = responseData.getUserHandle(); + if (userHandle == null) { + userHandle = new byte[0]; } + + result.complete( + new WebAuthnTokenManager.GetAssertionResponse( + responseData.getClientDataJSON(), + publicKeyCredentialData.getRawId(), + responseData.getAuthenticatorData(), + responseData.getSignature(), + userHandle, + publicKeyCredentialData.getAuthenticatorAttachment())); }, e -> { Log.w(LOGTAG, "Failed to get FIDO intent", e); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java index 1caa5508ed..bf5d431cf1 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java @@ -1811,15 +1811,28 @@ public class WebExtension { public final @NonNull Image icon; /** - * API permissions requested or granted to this extension. + * List of permissions to be prompted to the users. * * <p>Permission identifiers match entries in the manifest, see <a * href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#API_permissions"> * API permissions </a>. + * + * @deprecated Use {@link MetaData#promptPermissions} instead. */ + @Deprecated + @DeprecationSchedule(id = "web-extension-permission", version = 131) public final @NonNull String[] permissions; /** + * List of permissions to be prompted to the users. + * + * <p>Permission identifiers match entries in the manifest, see <a + * href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#API_permissions"> + * API permissions </a>. + */ + public final @NonNull String[] promptPermissions; + + /** * API <a * href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/optional_permissions">optional * permissions</a> requested or granted to this extension. @@ -2043,6 +2056,7 @@ public class WebExtension { protected MetaData() { icon = null; permissions = null; + promptPermissions = null; optionalPermissions = null; grantedOptionalPermissions = null; grantedOptionalOrigins = null; @@ -2077,6 +2091,7 @@ public class WebExtension { /* package */ MetaData(final GeckoBundle bundle) { // We only expose permissions that the embedder should prompt for permissions = bundle.getStringArray("promptPermissions"); + promptPermissions = bundle.getStringArray("promptPermissions"); optionalPermissions = bundle.getStringArray("optionalPermissions"); grantedOptionalPermissions = bundle.getStringArray("grantedOptionalPermissions"); optionalOrigins = bundle.getStringArray("optionalOrigins"); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java index 7e936f84f7..07e848b079 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java @@ -709,6 +709,56 @@ public class WebExtensionController { } /** + * Add the provided permissions to the {@link WebExtension} with the given id. + * + * @param extensionId the id of {@link WebExtension} instance to modify. + * @param permissions the permissions to add, pass an empty array to not update. + * @param origins the origins to add, pass an empty array to not update. + * @return the updated {@link WebExtension} instance. + */ + @NonNull + @AnyThread + public GeckoResult<WebExtension> addOptionalPermissions( + final @NonNull String extensionId, + @NonNull final String[] permissions, + @NonNull final String[] origins) { + final GeckoBundle bundle = new GeckoBundle(3); + bundle.putString("extensionId", extensionId); + bundle.putStringArray("permissions", permissions); + bundle.putStringArray("origins", origins); + + return EventDispatcher.getInstance() + .queryBundle("GeckoView:WebExtension:AddOptionalPermissions", bundle) + .map(ext -> WebExtension.fromBundle(mDelegateControllerProvider, ext)) + .map(this::registerWebExtension); + } + + /** + * Remove the provided permissions from the {@link WebExtension} with the given id. + * + * @param extensionId the id of {@link WebExtension} instance to modify. + * @param permissions the permissions to remove, pass an empty array to not update. + * @param origins the origins to remove, pass an empty array to not update. + * @return the updated {@link WebExtension} instance. + */ + @NonNull + @AnyThread + public GeckoResult<WebExtension> removeOptionalPermissions( + final @NonNull String extensionId, + @NonNull final String[] permissions, + @NonNull final String[] origins) { + final GeckoBundle bundle = new GeckoBundle(3); + bundle.putString("extensionId", extensionId); + bundle.putStringArray("permissions", permissions); + bundle.putStringArray("origins", origins); + + return EventDispatcher.getInstance() + .queryBundle("GeckoView:WebExtension:RemoveOptionalPermissions", bundle) + .map(ext -> WebExtension.fromBundle(mDelegateControllerProvider, ext)) + .map(this::registerWebExtension); + } + + /** * Install a built-in extension. * * <p>Built-in extensions have access to native messaging, don't need to be signed and are diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md index 5776cf5afc..e2df6df21b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -23,6 +23,9 @@ while a user gesture was active (e.g., a tap). - ⚠️ Deprecated [`RuntimeTelemetry`][125.5], [`GeckoRuntimeSettings.getTelemetryDelegate`][125.6] and [`GeckoRuntimeSettings.telemetryDelegate`][125.7], to be removed in v127. ([bug 1877836]({{bugzilla}}1877836)) - Added [`WebExtension.MetaData.grantedOptionalPermissions`][125.8] and [`WebExtension.MetaData.grantedOptionalOrigins`][125.9] which expose the granted optional and origin optional permissions of an extension ([bug 1879543]({{bugzilla}}1879543)). +- Added [`WebExtension.MetaData.promptPermissions`][125.10] which exposes a list of permissions which needs to be prompted to users ([bug 1879547]({{bugzilla}}1879547)). +- ⚠️ Deprecate the [`WebExtension.MetaData.permissions`][125.11] API to be removed in v131. Please use `WebExtension.MetaData.promptPermissions`][125.10] instead. +- Added [`WebExtensionController.addOptionalPermissions`][125.12] and [`WebExtensionController.removeOptionalPermissions`][125.13], which allow to add and remove optional permissions/origins of an extension ([bug 1796176]({{bugzilla}}1796176)). [125.1]: {{javadoc_uri}}/GeckoSession.NavigationDelegate#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List) [125.2]: {{javadoc_uri}}/GeckoSession.NavigationDelegate#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List,boolean) @@ -33,6 +36,10 @@ while a user gesture was active (e.g., a tap). [125.7]: {{javadoc_uri}}/GeckoRuntimeSettings.html#telemetryDelegate [125.8]: {{javadoc_uri}}/WebExtension.MetaData.html#grantedOptionalPermissions [125.9]: {{javadoc_uri}}/WebExtension.MetaData.html#grantedOptionalOrigins +[125.10]: {{javadoc_uri}}/WebExtension.MetaData.html#promptPermissions +[125.11]: {{javadoc_uri}}/WebExtension.MetaData.html#permissions +[125.12]: {{javadoc_uri}}/WebExtensionController.html#addOptionalPermissions +[125.13]: {{javadoc_uri}}/WebExtensionController.html#removeOptionalPermissions ## v124 @@ -1540,4 +1547,4 @@ to allow adding gecko profiler markers. [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String) [65.25]: {{javadoc_uri}}/GeckoResult.html -[api-version]: fc9fd590333bebf38058b7abddbb7a860cd6e4de +[api-version]: 2c319e9f18adb4178ce09d71088a173b56d1a694 |