summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src')
-rw-r--r--mobile/android/geckoview/src/androidTest/AndroidManifest.xml3
-rw-r--r--mobile/android/geckoview/src/androidTest/assets/web_extensions/langpack_signed.xpibin4452 -> 7593 bytes
-rw-r--r--mobile/android/geckoview/src/androidTest/assets/web_extensions/options_page_alias/manifest.json12
-rw-r--r--mobile/android/geckoview/src/androidTest/assets/www/badVideoPath.html2
-rw-r--r--mobile/android/geckoview/src/androidTest/assets/www/ogg.html11
-rw-r--r--mobile/android/geckoview/src/androidTest/assets/www/videos/video.oggbin285310 -> 0 bytes
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt3
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt1
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt196
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt24
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt3
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebExtensionTest.kt252
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java2
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoBatteryManager.java19
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentInputStream.java4
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java4
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java2
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java203
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtension.java17
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java50
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md9
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
index 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
Binary files differ
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
deleted file mode 100644
index ac7ece3519..0000000000
--- a/mobile/android/geckoview/src/androidTest/assets/www/videos/video.ogg
+++ /dev/null
Binary files differ
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