summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt')
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt1132
1 files changed, 1132 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt
new file mode 100644
index 0000000000..9ab2d2515f
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PermissionDelegateTest.kt
@@ -0,0 +1,1132 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.LocationManager
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports
+import org.json.JSONArray
+import org.junit.Assert.fail
+import org.junit.Assume.assumeThat
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSession.NavigationDelegate
+import org.mozilla.geckoview.GeckoSession.PermissionDelegate
+import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission
+import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaCallback
+import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource
+import org.mozilla.geckoview.GeckoSessionSettings
+import org.mozilla.geckoview.StorageController.ClearFlags
+import org.mozilla.geckoview.test.TrackingPermissionService.TrackingPermissionInstance
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.RejectedPromiseException
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class PermissionDelegateTest : BaseSessionTest() {
+ private val targetContext
+ get() = InstrumentationRegistry.getInstrumentation().targetContext
+
+ private fun hasPermission(permission: String): Boolean {
+ if (Build.VERSION.SDK_INT < 23) {
+ return true
+ }
+ return PackageManager.PERMISSION_GRANTED ==
+ InstrumentationRegistry.getInstrumentation().targetContext.checkSelfPermission(permission)
+ }
+
+ private fun isEmulator(): Boolean {
+ return "generic" == Build.DEVICE || Build.DEVICE.startsWith("generic_")
+ }
+
+ private val storageController
+ get() = sessionRule.runtime.storageController
+
+ @Test fun media() {
+ // TODO: needs bug 1700243
+ assumeThat(sessionRule.env.isIsolatedProcess, equalTo(false))
+
+ assertInAutomationThat(
+ "Should have camera permission",
+ hasPermission(Manifest.permission.CAMERA),
+ equalTo(true),
+ )
+
+ assertInAutomationThat(
+ "Should have microphone permission",
+ hasPermission(Manifest.permission.RECORD_AUDIO),
+ equalTo(true),
+ )
+
+ mainSession.loadTestPath(HELLO_HTML_PATH)
+ mainSession.waitForPageStop()
+
+ val devices = mainSession.evaluateJS(
+ "window.navigator.mediaDevices.enumerateDevices()",
+ ) as JSONArray
+
+ var hasVideo = false
+ var hasAudio = false
+ for (i in 0 until devices.length()) {
+ if (devices.getJSONObject(i).getString("kind") == "videoinput") {
+ hasVideo = true
+ }
+ if (devices.getJSONObject(i).getString("kind") == "audioinput") {
+ hasAudio = true
+ }
+ }
+
+ assertThat(
+ "Device list should contain camera device",
+ hasVideo,
+ equalTo(true),
+ )
+ assertThat(
+ "Device list should contain microphone device",
+ hasAudio,
+ equalTo(true),
+ )
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onMediaPermissionRequest(
+ session: GeckoSession,
+ uri: String,
+ video: Array<out MediaSource>?,
+ audio: Array<out MediaSource>?,
+ callback: MediaCallback,
+ ) {
+ assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
+ assertThat("Video source should be valid", video, not(emptyArray()))
+
+ if (isEmulator()) {
+ callback.grant(video!![0], null)
+ } else {
+ assertThat("Audio source should be valid", audio, not(emptyArray()))
+ callback.grant(video!![0], audio!![0])
+ }
+ }
+ })
+
+ // Start a video stream, with audio if on a real device.
+ val code = if (isEmulator()) {
+ """this.stream = window.navigator.mediaDevices.getUserMedia({
+ video: { width: 320, height: 240, frameRate: 10 },
+ });"""
+ } else {
+ """this.stream = window.navigator.mediaDevices.getUserMedia({
+ video: { width: 320, height: 240, frameRate: 10 },
+ audio: true
+ });"""
+ }
+
+ // Stop the stream and check active flag and id
+ val isActive = mainSession.waitForJS(
+ """$code
+ this.stream.then(stream => {
+ if (!stream.active || stream.id == '') {
+ return false;
+ }
+
+ stream.getTracks().forEach(track => track.stop());
+ return true;
+ })
+ """.trimMargin(),
+ ) as Boolean
+
+ assertThat("Stream should be active and id should not be empty.", isActive, equalTo(true))
+
+ // Now test rejecting the request.
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onMediaPermissionRequest(
+ session: GeckoSession,
+ uri: String,
+ video: Array<out MediaSource>?,
+ audio: Array<out MediaSource>?,
+ callback: MediaCallback,
+ ) {
+ callback.reject()
+ }
+ })
+
+ try {
+ if (isEmulator()) {
+ mainSession.waitForJS(
+ """
+ window.navigator.mediaDevices.getUserMedia({ video: true })""",
+ )
+ } else {
+ mainSession.waitForJS(
+ """
+ window.navigator.mediaDevices.getUserMedia({ audio: true, video: true })""",
+ )
+ }
+ fail("Request should have failed")
+ } catch (e: RejectedPromiseException) {
+ assertThat(
+ "Error should be correct",
+ e.reason as String,
+ containsString("NotAllowedError"),
+ )
+ }
+ }
+
+ @Test fun geolocation() {
+ assertInAutomationThat(
+ "Should have location permission",
+ hasPermission(Manifest.permission.ACCESS_FINE_LOCATION),
+ equalTo(true),
+ )
+
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ // Set location for test
+ sessionRule.setPrefsUntilTestEnd(mapOf("geo.provider.testing" to false))
+ var context = InstrumentationRegistry.getInstrumentation().targetContext
+ var locManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ var locProvider = sessionRule.MockLocationProvider(
+ locManager,
+ "permissionsLocationProvider",
+ 1.1111,
+ 2.2222,
+ false,
+ )
+ locProvider.postLocation()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ // Ensure the content permission is asked first, before the Android permission.
+ @AssertCalled(count = 1, order = [1])
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_GEOLOCATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+
+ @AssertCalled(count = 1, order = [2])
+ override fun onAndroidPermissionsRequest(
+ session: GeckoSession,
+ permissions: Array<out String>?,
+ callback: PermissionDelegate.Callback,
+ ) {
+ assertThat(
+ "Permissions list should be correct",
+ listOf(*permissions!!),
+ hasItems(Manifest.permission.ACCESS_FINE_LOCATION),
+ )
+ callback.grant()
+ }
+ })
+
+ try {
+ val hasPosition = mainSession.waitForJS(
+ """new Promise((resolve, reject) =>
+ window.navigator.geolocation.getCurrentPosition(
+ position => resolve(
+ position.coords.latitude !== undefined &&
+ position.coords.longitude !== undefined),
+ error => reject(error.code)))""",
+ ) as Boolean
+
+ assertThat("Request should succeed", hasPosition, equalTo(true))
+ } catch (ex: RejectedPromiseException) {
+ assertThat(
+ "Error should not because the permission was denied.",
+ ex.reason as String,
+ not("1"),
+ )
+ }
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_GEOLOCATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Geolocation permission should be set to allow", permFound, equalTo(true))
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_GEOLOCATION &&
+ perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Geolocation permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+ locProvider.removeMockLocationProvider()
+ }
+
+ @Test fun geolocation_reject() {
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ return GeckoResult.fromValue(ContentPermission.VALUE_DENY)
+ }
+
+ @AssertCalled(count = 0)
+ override fun onAndroidPermissionsRequest(
+ session: GeckoSession,
+ permissions: Array<out String>?,
+ callback: PermissionDelegate.Callback,
+ ) {
+ }
+ })
+
+ val errorCode = mainSession.waitForJS(
+ """new Promise((resolve, reject) =>
+ window.navigator.geolocation.getCurrentPosition(reject,
+ error => resolve(error.code)
+ ))""",
+ )
+
+ // Error code 1 means permission denied.
+ assertThat("Request should fail", errorCode as Double, equalTo(1.0))
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_GEOLOCATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_DENY
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Geolocation permission should be set to allow", permFound, equalTo(true))
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_GEOLOCATION &&
+ perm.value == ContentPermission.VALUE_DENY
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Geolocation permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+ }
+
+ @ClosedSessionAtStart
+ @Test
+ fun trackingProtection() {
+ // Tests that we get a tracking protection permission for every load, we
+ // can set the value of the permission and that the permission persists
+ // across sessions
+ trackingProtection(privateBrowsing = false, permanent = true)
+ }
+
+ @ClosedSessionAtStart
+ @Test
+ fun trackingProtectionPrivateBrowsing() {
+ // Tests that we get a tracking protection permission for every load, we
+ // can set the value of the permission in private browsing and that the
+ // permission does not persists across private sessions
+ trackingProtection(privateBrowsing = true, permanent = false)
+ }
+
+ @ClosedSessionAtStart
+ @Test
+ fun trackingProtectionPrivateBrowsingPermanent() {
+ // Tests that we get a tracking protection permission for every load, we
+ // can set the value of the permission permanently in private browsing
+ // and that the permanent permission _does_ persists across private sessions
+ trackingProtection(privateBrowsing = true, permanent = true)
+ }
+
+ private fun trackingProtection(privateBrowsing: Boolean, permanent: Boolean) {
+ // Make sure we start with a clean slate
+ storageController.clearDataFromHost(TEST_HOST, ClearFlags.PERMISSIONS)
+
+ assertThat(
+ "Non-permanent only makes sense with private browsing " +
+ "(because non-private browsing exceptions are always permanent",
+ permanent || privateBrowsing,
+ equalTo(true),
+ )
+
+ val runtime0 = TrackingPermissionInstance.start(
+ targetContext,
+ temporaryProfile.get(),
+ privateBrowsing,
+ )
+
+ sessionRule.waitForResult(runtime0.loadTestPath(TRACKERS_PATH))
+ var permission = sessionRule.waitForResult(runtime0.trackingPermission)
+
+ assertThat(
+ "Permission value should start at DENY",
+ permission,
+ equalTo(ContentPermission.VALUE_DENY),
+ )
+
+ if (privateBrowsing && permanent) {
+ runtime0.setPrivateBrowsingPermanentTrackingPermission(
+ ContentPermission.VALUE_ALLOW,
+ )
+ } else {
+ runtime0.setTrackingPermission(ContentPermission.VALUE_ALLOW)
+ }
+
+ sessionRule.waitForResult(runtime0.reload())
+
+ permission = sessionRule.waitForResult(runtime0.trackingPermission)
+ assertThat(
+ "Permission value should be ALLOW after setting",
+ permission,
+ equalTo(ContentPermission.VALUE_ALLOW),
+ )
+
+ sessionRule.waitForResult(runtime0.quit())
+
+ // Restart the runtime and verifies that the value is still stored
+ val runtime1 = TrackingPermissionInstance.start(
+ targetContext,
+ temporaryProfile.get(),
+ privateBrowsing,
+ )
+
+ sessionRule.waitForResult(runtime1.loadTestPath(TRACKERS_PATH))
+
+ val trackingPermission = sessionRule.waitForResult(runtime1.trackingPermission)
+ assertThat(
+ "Tracking permissions should persist only if permanent",
+ trackingPermission,
+ equalTo(
+ when {
+ permanent -> ContentPermission.VALUE_ALLOW
+ else -> ContentPermission.VALUE_DENY
+ },
+ ),
+ )
+
+ sessionRule.waitForResult(runtime1.quit())
+ }
+
+ private fun assertTrackingProtectionPermission(value: Int?) {
+ var found = false
+ mainSession.waitUntilCalled(object : NavigationDelegate {
+ @AssertCalled
+ override fun onLocationChange(
+ session: GeckoSession,
+ url: String?,
+ perms: MutableList<ContentPermission>,
+ ) {
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_TRACKING) {
+ if (value != null) {
+ assertThat(
+ "Value should match",
+ perm.value,
+ equalTo(value),
+ )
+ }
+ found = true
+ }
+ }
+ }
+ })
+
+ assertThat(
+ "Permission should have been found if expected",
+ found,
+ equalTo(value != null),
+ )
+ }
+
+ // Tests that all pages have a PERMISSION_TRACKING permission,
+ // except for pages that belong to Gecko like about:blank or about:config.
+ @Test fun trackingProtectionPermissionOnAllPages() {
+ val settings = sessionRule.runtime.settings
+ val aboutConfigEnabled = settings.aboutConfigEnabled
+ settings.aboutConfigEnabled = true
+
+ mainSession.loadUri("about:config")
+ assertTrackingProtectionPermission(null)
+
+ settings.aboutConfigEnabled = aboutConfigEnabled
+
+ mainSession.loadUri("about:blank")
+ assertTrackingProtectionPermission(null)
+
+ mainSession.loadTestPath(HELLO_HTML_PATH)
+ assertTrackingProtectionPermission(ContentPermission.VALUE_DENY)
+ }
+
+ @Test fun notification() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+
+ val result2 = mainSession.waitForJS("Notification.permission")
+
+ assertThat(
+ "Permission should be granted",
+ result2 as String,
+ equalTo("granted"),
+ )
+ }
+
+ @Ignore("disable test for frequently failing Bug 1542525")
+ @Test
+ fun notification_reject() {
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ return GeckoResult.fromValue(ContentPermission.VALUE_DENY)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should not be granted",
+ result as String,
+ equalTo("denied"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_DENY
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_DENY
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+ }
+
+ @Test
+ fun autoplayReject() {
+ // Bug 1810736
+ assumeThat(sessionRule.env.isIsolatedProcess, equalTo(false))
+
+ // The profile used in automation sets this to false, so we need to hack it back to true here.
+ sessionRule.setPrefsUntilTestEnd(
+ mapOf(
+ "media.geckoview.autoplay.request" to true,
+ ),
+ )
+
+ mainSession.loadTestPath(AUTOPLAY_PATH)
+
+ mainSession.waitUntilCalled(object : PermissionDelegate {
+ @AssertCalled(count = 2)
+ override fun onContentPermissionRequest(session: GeckoSession, perm: ContentPermission):
+ GeckoResult<Int> {
+ val expectedType = if (sessionRule.currentCall.counter == 1) PermissionDelegate.PERMISSION_AUTOPLAY_AUDIBLE else PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE
+ assertThat("Type should match", perm.permission, equalTo(expectedType))
+ return GeckoResult.fromValue(ContentPermission.VALUE_DENY)
+ }
+ })
+ }
+
+ @Test
+ fun contextId() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ assertThat("Context ID should match", perm.contextId, equalTo(mainSession.settings.contextId))
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url, false))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+
+ val session2 = sessionRule.createOpenSession(
+ GeckoSessionSettings.Builder()
+ .contextId("foo")
+ .build(),
+ )
+
+ session2.loadUri(url)
+ session2.waitForPageStop()
+
+ session2.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ assertThat(
+ "Context ID should match",
+ perm.contextId,
+ equalTo(session2.settings.contextId),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result2 = session2.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result2 as String,
+ equalTo("granted"),
+ )
+
+ val perms2 = sessionRule.waitForResult(storageController.getPermissions(url, false))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ permFound = false
+ for (perm in perms2) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ session2.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_ALLOW &&
+ perm.contextId == session2.settings.contextId
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ session2.reload()
+ session2.waitForPageStop()
+ }
+
+ @Test fun setPermissionAllow() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_DENY)
+ }
+ })
+ mainSession.waitForJS("Notification.requestPermission()")
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ var notificationPerm: ContentPermission? = null
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_DENY
+ ) {
+ notificationPerm = perm
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ storageController.setPermission(
+ notificationPerm!!,
+ ContentPermission.VALUE_ALLOW,
+ )
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+
+ val result = mainSession.waitForJS("Notification.permission")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+ }
+
+ @Test fun setPermissionDeny() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ var notificationPerm: ContentPermission? = null
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ notificationPerm = perm
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ storageController.setPermission(
+ notificationPerm!!,
+ ContentPermission.VALUE_DENY,
+ )
+
+ mainSession.delegateDuringNextWait(object : NavigationDelegate {
+ @AssertCalled(count = 1)
+ override fun onLocationChange(session: GeckoSession, url: String?, perms: MutableList<ContentPermission>) {
+ var permFound2 = false
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ perm.value == ContentPermission.VALUE_DENY
+ ) {
+ permFound2 = true
+ }
+ }
+ assertThat("Notification permission must be present on refresh", permFound2, equalTo(true))
+ }
+ })
+ mainSession.reload()
+ mainSession.waitForPageStop()
+
+ val result2 = mainSession.waitForJS("Notification.permission")
+
+ assertThat(
+ "Permission should be denied",
+ result2 as String,
+ equalTo("denied"),
+ )
+ }
+
+ @Test fun setPermissionPrompt() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ var notificationPerm: ContentPermission? = null
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ notificationPerm = perm
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ storageController.setPermission(
+ notificationPerm!!,
+ ContentPermission.VALUE_PROMPT,
+ )
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ return GeckoResult.fromValue(ContentPermission.VALUE_PROMPT)
+ }
+ })
+
+ val result2 = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be default",
+ result2 as String,
+ equalTo("default"),
+ )
+ }
+
+ @Test fun permissionJsonConversion() {
+ sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false))
+ val url = createTestUrl(HELLO_HTML_PATH)
+ mainSession.loadUri(url)
+ mainSession.waitForPageStop()
+
+ mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ @AssertCalled(count = 1)
+ override fun onContentPermissionRequest(
+ session: GeckoSession,
+ perm: ContentPermission,
+ ):
+ GeckoResult<Int> {
+ assertThat("URI should match", perm.uri, endsWith(url))
+ assertThat(
+ "Type should match",
+ perm.permission,
+ equalTo(PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION),
+ )
+ return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW)
+ }
+ })
+
+ val result = mainSession.waitForJS("Notification.requestPermission()")
+
+ assertThat(
+ "Permission should be granted",
+ result as String,
+ equalTo("granted"),
+ )
+
+ val perms = sessionRule.waitForResult(storageController.getPermissions(url))
+
+ assertThat("Permissions should not be null", perms, notNullValue())
+ var permFound = false
+ var notificationPerm: ContentPermission? = null
+ for (perm in perms) {
+ if (perm.permission == PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION &&
+ url.startsWith(perm.uri) && perm.value == ContentPermission.VALUE_ALLOW
+ ) {
+ notificationPerm = perm
+ permFound = true
+ }
+ }
+
+ assertThat("Notification permission should be set to allow", permFound, equalTo(true))
+
+ val jsonPerm = notificationPerm?.toJson()
+ assertThat("JSON export should not be null", jsonPerm, notNullValue())
+
+ val importedPerm = ContentPermission.fromJson(jsonPerm!!)
+ assertThat("JSON import should not be null", importedPerm, notNullValue())
+
+ assertThat("URIs should match", importedPerm?.uri, equalTo(notificationPerm?.uri))
+ assertThat("Types should match", importedPerm?.permission, equalTo(notificationPerm?.permission))
+ assertThat("Values should match", importedPerm?.value, equalTo(notificationPerm?.value))
+ assertThat("Context IDs should match", importedPerm?.contextId, equalTo(notificationPerm?.contextId))
+ assertThat("Private mode should match", importedPerm?.privateMode, equalTo(notificationPerm?.privateMode))
+ }
+
+ // @Test fun persistentStorage() {
+ // mainSession.loadTestPath(HELLO_HTML_PATH)
+ // mainSession.waitForPageStop()
+
+ // // Persistent storage can be rejected
+ // mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ // @AssertCalled(count = 1)
+ // override fun onContentPermissionRequest(
+ // session: GeckoSession, uri: String?, type: Int,
+ // callback: PermissionDelegate.Callback) {
+ // callback.reject()
+ // }
+ // })
+
+ // var success = mainSession.waitForJS("""window.navigator.storage.persist()""")
+
+ // assertThat("Request should fail",
+ // success as Boolean, equalTo(false))
+
+ // // Persistent storage can be granted
+ // mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ // // Ensure the content permission is asked first, before the Android permission.
+ // @AssertCalled(count = 1, order = [1])
+ // override fun onContentPermissionRequest(
+ // session: GeckoSession, uri: String?, type: Int,
+ // callback: PermissionDelegate.Callback) {
+ // assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
+ // assertThat("Type should match", type,
+ // equalTo(PermissionDelegate.PERMISSION_PERSISTENT_STORAGE))
+ // callback.grant()
+ // }
+ // })
+
+ // success = mainSession.waitForJS("""window.navigator.storage.persist()""")
+
+ // assertThat("Request should succeed",
+ // success as Boolean,
+ // equalTo(true))
+
+ // // after permission granted further requests will always return true, regardless of response
+ // mainSession.delegateDuringNextWait(object : PermissionDelegate {
+ // @AssertCalled(count = 1)
+ // override fun onContentPermissionRequest(
+ // session: GeckoSession, uri: String?, type: Int,
+ // callback: PermissionDelegate.Callback) {
+ // callback.reject()
+ // }
+ // })
+
+ // success = mainSession.waitForJS("""window.navigator.storage.persist()""")
+
+ // assertThat("Request should succeed",
+ // success as Boolean, equalTo(true))
+ // }
+}