From ff4369ad7c4559268baf395c38998fa6f58bed6b Mon Sep 17 00:00:00 2001 From: Dylan Roeh Date: Mon, 17 May 2021 15:55:21 +0000 Subject: [PATCH] Bug 1699480 - Add setPermission, update onContentPermissionRequest to use ContentPermission, and let Gecko manage and persist permissions in GV. r=agi,geckoview-reviewers,owlish Differential Revision: https://phabricator.services.mozilla.com/D112042 --- extensions/permissions/PermissionManager.cpp | 15 ---- .../geckoview/GeckoViewPermission.jsm | 37 ++++---- .../components/geckoview/GeckoViewStartup.jsm | 1 + .../mozilla/geckoview/test/OpenWindowTest.kt | 6 +- .../geckoview/test/PermissionDelegateTest.kt | 40 +++++---- .../geckoview/test/TestRunnerActivity.java | 4 +- .../geckoview/test/WebNotificationTest.kt | 7 +- .../org/mozilla/geckoview/test/WebPushTest.kt | 7 +- .../org/mozilla/geckoview/GeckoSession.java | 88 +++++++++++-------- .../mozilla/geckoview/StorageController.java | 47 +++++++++- .../BasicGeckoViewPrompt.java | 14 +-- .../geckoview_example/GeckoViewActivity.java | 68 ++++++-------- .../modules/geckoview/GeckoViewNavigation.jsm | 3 +- .../geckoview/GeckoViewStorageController.jsm | 22 +++-- 14 files changed, 208 insertions(+), 151 deletions(-) diff --git a/extensions/permissions/PermissionManager.cpp b/extensions/permissions/PermissionManager.cpp index 521324516c07..d00e14c21716 100644 --- a/extensions/permissions/PermissionManager.cpp +++ b/extensions/permissions/PermissionManager.cpp @@ -127,16 +127,6 @@ static const nsLiteralCString kPreloadPermissions[] = { // removed. See bug 1428130. "cookie"_ns}; -// Certain permissions should never be persisted to disk under GeckoView; it's -// the responsibility of the app to manage storing these beyond the scope of -// a single session. -#ifdef ANDROID -static const nsLiteralCString kGeckoViewRestrictedPermissions[] = { - "MediaManagerVideo"_ns, "geolocation"_ns, - "desktop-notification"_ns, "persistent-storage"_ns, - "trackingprotection"_ns, "trackingprotection-pb"_ns}; -#endif - // NOTE: nullptr can be passed as aType - if it is this function will return // "false" unconditionally. bool IsPreloadPermission(const nsACString& aType) { @@ -554,11 +544,6 @@ bool IsExpandedPrincipal(nsIPrincipal* aPrincipal) { bool IsPersistentExpire(uint32_t aExpire, const nsACString& aType) { bool res = (aExpire != nsIPermissionManager::EXPIRE_SESSION && aExpire != nsIPermissionManager::EXPIRE_POLICY); -#ifdef ANDROID - for (const auto& perm : kGeckoViewRestrictedPermissions) { - res = res && !perm.Equals(aType); - } -#endif return res; } diff --git a/mobile/android/components/geckoview/GeckoViewPermission.jsm b/mobile/android/components/geckoview/GeckoViewPermission.jsm index a90e514f6fd8..41da4792b2a5 100644 --- a/mobile/android/components/geckoview/GeckoViewPermission.jsm +++ b/mobile/android/components/geckoview/GeckoViewPermission.jsm @@ -10,6 +10,7 @@ const { XPCOMUtils } = ChromeUtils.import( ); XPCOMUtils.defineLazyModuleGetters(this, { + E10SUtils: "resource://gre/modules/E10SUtils.jsm", GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm", Services: "resource://gre/modules/Services.jsm", }); @@ -245,31 +246,37 @@ class GeckoViewPermission { .sendRequestForResult({ type: "GeckoView:ContentPermission", uri: aRequest.principal.URI.displaySpec, + principal: E10SUtils.serializePrincipal(aRequest.principal), perm: perm.type, + value: perm.capability, + contextId: + aRequest.principal.originAttributes.geckoViewSessionContextId ?? null, + privateMode: aRequest.principal.privateBrowsingId != 0, }) - .then(granted => { - if (!granted) { - return false; + .then(value => { + if (value == Services.perms.ALLOW_ACTION) { + // Ask for app permission after asking for content permission. + if (perm.type === "geolocation") { + return this.getAppPermissions(dispatcher, [ + PERM_ACCESS_FINE_LOCATION, + ]); + } } - // Ask for app permission after asking for content permission. - if (perm.type === "geolocation") { - return this.getAppPermissions(dispatcher, [ - PERM_ACCESS_FINE_LOCATION, - ]); - } - return true; + return value; }) .catch(error => { Cu.reportError("Permission error: " + error); - return /* granted */ false; + return /* value */ Services.perms.DENY_ACTION; }) - .then(granted => { - (granted ? aRequest.allow : aRequest.cancel)(); + .then(value => { + (value == Services.perms.ALLOW_ACTION + ? aRequest.allow + : aRequest.cancel)(); Services.perms.addFromPrincipal( aRequest.principal, perm.type, - granted ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION, - Services.perms.EXPIRE_SESSION + value, + Services.perms.EXPIRE_NEVER ); // Manually release the target request here to facilitate garbage collection. aRequest = undefined; diff --git a/mobile/android/components/geckoview/GeckoViewStartup.jsm b/mobile/android/components/geckoview/GeckoViewStartup.jsm index 3603c3743fa1..9120e756ce21 100644 --- a/mobile/android/components/geckoview/GeckoViewStartup.jsm +++ b/mobile/android/components/geckoview/GeckoViewStartup.jsm @@ -103,6 +103,7 @@ class GeckoViewStartup { "GeckoView:ClearHostData", "GeckoView:GetAllPermissions", "GeckoView:GetPermissionsByURI", + "GeckoView:SetPermission", ], }); diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/OpenWindowTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/OpenWindowTest.kt index 0736b6a52d06..3bd12174c442 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/OpenWindowTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/OpenWindowTest.kt @@ -25,9 +25,9 @@ class OpenWindowTest : BaseSessionTest() { // Grant "desktop notification" permission mainSession.delegateUntilTestEnd(object : Callbacks.PermissionDelegate { - override fun onContentPermissionRequest(session: GeckoSession, uri: String?, type: Int, callback: GeckoSession.PermissionDelegate.Callback) { - assertThat("Should grant DESKTOP_NOTIFICATIONS permission", type, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) - callback.grant() + override fun onContentPermissionRequest(session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): GeckoResult? { + assertThat("Should grant DESKTOP_NOTIFICATIONS permission", perm.permission, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW); } }) } 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 index d16a351c3a5f..e6417404fd14 100644 --- 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 @@ -4,6 +4,7 @@ package org.mozilla.geckoview.test +import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.RejectedPromiseException @@ -157,12 +158,12 @@ class PermissionDelegateTest : BaseSessionTest() { // 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: GeckoSession.PermissionDelegate.Callback) { - assertThat("URI should match", uri, endsWith(url)) - assertThat("Type should match", type, + session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + assertThat("URI should match", perm.uri, endsWith(url)) + assertThat("Type should match", perm.permission, equalTo(GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION)) - callback.grant() + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW) } @AssertCalled(count = 1, order = [2]) @@ -227,9 +228,9 @@ class PermissionDelegateTest : BaseSessionTest() { mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate { @AssertCalled(count = 1) override fun onContentPermissionRequest( - session: GeckoSession, uri: String?, type: Int, - callback: GeckoSession.PermissionDelegate.Callback) { - callback.reject() + session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY) } @AssertCalled(count = 0) @@ -286,12 +287,12 @@ class PermissionDelegateTest : BaseSessionTest() { mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate { @AssertCalled(count = 1) override fun onContentPermissionRequest( - session: GeckoSession, uri: String?, type: Int, - callback: GeckoSession.PermissionDelegate.Callback) { - assertThat("URI should match", uri, endsWith(url)) - assertThat("Type should match", type, + session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + assertThat("URI should match", perm.uri, endsWith(url)) + assertThat("Type should match", perm.permission, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) - callback.grant() + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW) } }) @@ -339,9 +340,9 @@ class PermissionDelegateTest : BaseSessionTest() { mainSession.delegateDuringNextWait(object : Callbacks.PermissionDelegate { @AssertCalled(count = 1) override fun onContentPermissionRequest( - session: GeckoSession, uri: String?, type: Int, - callback: GeckoSession.PermissionDelegate.Callback) { - callback.reject() + session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY) } }) @@ -390,10 +391,11 @@ class PermissionDelegateTest : BaseSessionTest() { mainSession.waitUntilCalled(object : Callbacks.PermissionDelegate { @AssertCalled(count = 2) - override fun onContentPermissionRequest(session: GeckoSession, uri: String?, type: Int, callback: GeckoSession.PermissionDelegate.Callback) { + override fun onContentPermissionRequest(session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { val expectedType = if (sessionRule.currentCall.counter == 1) GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY_AUDIBLE else GeckoSession.PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE - assertThat("Type should match", type, equalTo(expectedType)) - callback.reject() + assertThat("Type should match", perm.permission, equalTo(expectedType)) + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY) } }) } diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java index 64b6f6bce43a..8d72ab24466a 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java @@ -100,8 +100,8 @@ public class TestRunnerActivity extends Activity { private GeckoSession.PermissionDelegate mPermissionDelegate = new GeckoSession.PermissionDelegate() { @Override - public void onContentPermissionRequest(@NonNull final GeckoSession session, @Nullable final String uri, final int type, @NonNull final Callback callback) { - callback.grant(); + public GeckoResult onContentPermissionRequest(@NonNull final GeckoSession session, @NonNull GeckoSession.PermissionDelegate.ContentPermission perm) { + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW); } @Override diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebNotificationTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebNotificationTest.kt index d5c5b6296a63..4603fb3f2cd5 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebNotificationTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebNotificationTest.kt @@ -25,9 +25,10 @@ class WebNotificationTest : BaseSessionTest() { // Grant "desktop notification" permission mainSession.delegateUntilTestEnd(object : Callbacks.PermissionDelegate { - override fun onContentPermissionRequest(session: GeckoSession, uri: String?, type: Int, callback: GeckoSession.PermissionDelegate.Callback) { - assertThat("Should grant DESKTOP_NOTIFICATIONS permission", type, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) - callback.grant() + override fun onContentPermissionRequest(session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + assertThat("Should grant DESKTOP_NOTIFICATIONS permission", perm.permission, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW) } }) diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebPushTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebPushTest.kt index f1be05b9b1fb..a1680acfd4d4 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebPushTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/WebPushTest.kt @@ -60,9 +60,10 @@ class WebPushTest : BaseSessionTest() { sessionRule.setPrefsUntilTestEnd(mapOf("dom.webnotifications.requireuserinteraction" to false)) // Grant "desktop notification" permission mainSession.delegateUntilTestEnd(object : Callbacks.PermissionDelegate { - override fun onContentPermissionRequest(session: GeckoSession, uri: String?, type: Int, callback: GeckoSession.PermissionDelegate.Callback) { - assertThat("Should grant DESKTOP_NOTIFICATIONS permission", type, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) - callback.grant() + override fun onContentPermissionRequest(session: GeckoSession, perm: GeckoSession.PermissionDelegate.ContentPermission): + GeckoResult? { + assertThat("Should grant DESKTOP_NOTIFICATIONS permission", perm.permission, equalTo(GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION)) + return GeckoResult.fromValue(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW) } }) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 72050bb0c89a..6946dee12dab 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -716,33 +716,13 @@ public class GeckoSession { GeckoSession.this, message.getStringArray("perms"), new PermissionCallback("android", callback)); } else if ("GeckoView:ContentPermission".equals(event)) { - final String typeString = message.getString("perm"); - final int type; - if ("geolocation".equals(typeString)) { - type = PermissionDelegate.PERMISSION_GEOLOCATION; - } else if ("desktop-notification".equals(typeString)) { - type = PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION; - } else if ("persistent-storage".equals(typeString)) { - type = PermissionDelegate.PERMISSION_PERSISTENT_STORAGE; - } else if ("xr".equals(typeString)) { - type = PermissionDelegate.PERMISSION_XR; - } else if ("midi".equals(typeString)) { - // We can get this from WPT and presumably other content, but Gecko - // doesn't support Web MIDI. - callback.sendError("Unsupported"); + final GeckoResult res = delegate.onContentPermissionRequest(GeckoSession.this, new PermissionDelegate.ContentPermission(message)); + if (res == null) { + callback.sendSuccess(PermissionDelegate.ContentPermission.VALUE_PROMPT); return; - } else if ("autoplay-media-inaudible".equals(typeString)) { - type = PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE; - } else if ("autoplay-media-audible".equals(typeString)) { - type = PermissionDelegate.PERMISSION_AUTOPLAY_AUDIBLE; - } else if ("media-key-system-access".equals(typeString)) { - type = PermissionDelegate.PERMISSION_MEDIA_KEY_SYSTEM_ACCESS; - } else { - throw new IllegalArgumentException("Unknown permission request: " + typeString); } - delegate.onContentPermissionRequest( - GeckoSession.this, message.getString("uri"), - type, new PermissionCallback(typeString, callback)); + + callback.resolveTo(res); } else if ("GeckoView:MediaPermission".equals(event)) { final GeckoBundle[] videoBundles = message.getBundleArray("video"); final GeckoBundle[] audioBundles = message.getBundleArray("audio"); @@ -5495,6 +5475,12 @@ public class GeckoSession { */ final public @Value int value; + /** + * The context ID associated with the permission if any. + * @see GeckoSessionSettings.Builder#contextId + */ + final public @Nullable String contextId; + final private String mPrincipal; protected ContentPermission() { @@ -5503,6 +5489,7 @@ public class GeckoSession { this.permission = PERMISSION_GEOLOCATION; this.value = VALUE_ALLOW; this.mPrincipal = ""; + this.contextId = null; } private ContentPermission(final @NonNull GeckoBundle bundle) { @@ -5510,10 +5497,11 @@ public class GeckoSession { this.mPrincipal = bundle.getString("principal"); this.privateMode = bundle.getBoolean("privateMode"); - final String permission = bundle.getString("type"); + final String permission = bundle.getString("perm"); this.permission = convertType(permission); this.value = bundle.getInt("value"); + this.contextId = StorageController.retrieveUnsafeSessionContextId(bundle.getString("contextId")); } private static int convertType(final @NonNull String type) { @@ -5536,6 +5524,27 @@ public class GeckoSession { } } + private static String convertType(final int type) { + switch (type) { + case PERMISSION_GEOLOCATION: + return "geolocation"; + case PERMISSION_DESKTOP_NOTIFICATION: + return "desktop-notification"; + case PERMISSION_PERSISTENT_STORAGE: + return "persistent-storage"; + case PERMISSION_XR: + return "xr"; + case PERMISSION_AUTOPLAY_INAUDIBLE: + return "autoplay-media-inaudible"; + case PERMISSION_AUTOPLAY_AUDIBLE: + return "autoplay-media-audible"; + case PERMISSION_MEDIA_KEY_SYSTEM_ACCESS: + return "media-key-system-access"; + default: + return ""; + } + } + /* package */ static @NonNull ArrayList fromBundleArray(final @NonNull GeckoBundle[] bundleArray) { final ArrayList res = new ArrayList(); if (bundleArray == null) { @@ -5551,6 +5560,17 @@ public class GeckoSession { } return res; } + + /* package */ @NonNull GeckoBundle toGeckoBundle() { + final GeckoBundle res = new GeckoBundle(5); + res.putString("uri", uri); + res.putString("principal", mPrincipal); + res.putBoolean("privateMode", privateMode); + res.putString("perm", convertType(permission)); + res.putInt("value", value); + res.putString("contextId", contextId); + return res; + } } /** @@ -5599,18 +5619,14 @@ public class GeckoSession { * from being redisplayed to the user. * * @param session GeckoSession instance requesting the permission. - * @param uri The URI of the content requesting the permission. - * @param type The type of the requested permission; possible values are, - * PERMISSION_GEOLOCATION - * PERMISSION_DESKTOP_NOTIFICATION - * PERMISSION_PERSISTENT_STORAGE - * PERMISSION_XR - * @param callback Callback interface. + * @param perm An {@link ContentPermission} describing the permission being requested and its current status. + * + * @return A {@link GeckoResult} resolving to one of {@link ContentPermission#VALUE_PROMPT VALUE_*}, determining + * the response to the permission request and updating the permissions for this site. */ @UiThread - default void onContentPermissionRequest(@NonNull final GeckoSession session, @Nullable final String uri, - @Permission final int type, @NonNull final Callback callback) { - callback.reject(); + default @Nullable GeckoResult onContentPermissionRequest(@NonNull final GeckoSession session, @NonNull ContentPermission perm) { + return GeckoResult.fromValue(ContentPermission.VALUE_PROMPT); } class MediaSource { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java index 87b5e31afee9..179e7870ac89 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java @@ -9,6 +9,7 @@ package org.mozilla.geckoview; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.math.BigInteger; +import java.nio.charset.Charset; import java.util.List; import java.util.Locale; @@ -168,7 +169,7 @@ public final class StorageController { "GeckoView:ClearSessionContextData", bundle); } - /* package */ static @NonNull String createSafeSessionContextId( + /* package */ static @Nullable String createSafeSessionContextId( final @Nullable String contextId) { if (contextId == null) { return null; @@ -184,6 +185,18 @@ public final class StorageController { .toLowerCase(Locale.ROOT); } + /* package */ static @Nullable String retrieveUnsafeSessionContextId( + final @Nullable String contextId) { + if (contextId == null || contextId.isEmpty()) { + return null; + } + if ("gvctxempty".equals(contextId)) { + return ""; + } + final byte[] bytes = new BigInteger(contextId.substring(5), 16).toByteArray(); + return new String(bytes, Charset.forName("UTF-8")); + } + /** * Get all currently stored permissions. * @@ -199,7 +212,7 @@ public final class StorageController { } /** - * Get all currently stored permissions for a given URI. + * Get all currently stored permissions for a given URI and default (unset) context ID. * * @param uri A String representing the URI to get permissions for. * @@ -208,11 +221,39 @@ public final class StorageController { */ @AnyThread public @NonNull GeckoResult> getPermissions(final @NonNull String uri) { - final GeckoBundle msg = new GeckoBundle(1); + return getPermissions(uri, null); + } + + /** + * Get all currently stored permissions for a given URI and context ID. + * + * @param uri A String representing the URI to get permissions for. + * @param contextId A String specifying the context ID. + * + * @return A {@link GeckoResult} that will complete with a list of all + * currently stored {@link ContentPermission}s for the URI. + */ + @AnyThread + public @NonNull GeckoResult> getPermissions(final @NonNull String uri, final @Nullable String contextId) { + final GeckoBundle msg = new GeckoBundle(2); msg.putString("uri", uri); + msg.putString("contextId", createSafeSessionContextId(contextId)); return EventDispatcher.getInstance().queryBundle("GeckoView:GetPermissionsByURI", msg).map(bundle -> { final GeckoBundle[] permsArray = bundle.getBundleArray("permissions"); return ContentPermission.fromBundleArray(permsArray); }); } + + /** + * Set a new value for an existing permission. + * + * @param perm A {@link ContentPermission} that you wish to update the value of. + * @param value The new value for the permission. + */ + @AnyThread + public void setPermission(final @NonNull ContentPermission perm, final @ContentPermission.Value int value) { + final GeckoBundle msg = perm.toGeckoBundle(); + msg.putInt("newValue", value); + EventDispatcher.getInstance().dispatch("GeckoView:SetPermission", msg); + } } diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java index 686c6a0f7155..9cd9d887605e 100644 --- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java @@ -869,30 +869,32 @@ final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate { } } - public void onPermissionPrompt(final GeckoSession session, final String title, - final GeckoSession.PermissionDelegate.Callback callback) { + public GeckoResult onPermissionPrompt(final GeckoSession session, final String title, + final GeckoSession.PermissionDelegate.ContentPermission perm) { final Activity activity = mActivity; + final GeckoResult res = new GeckoResult<>(); if (activity == null) { - callback.reject(); - return; + res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_PROMPT); + return res; } final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(title) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { - callback.reject(); + res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_DENY); } }) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { - callback.grant(); + res.complete(GeckoSession.PermissionDelegate.ContentPermission.VALUE_ALLOW); } }); final AlertDialog dialog = builder.create(); dialog.show(); + return res; } public void onSlowScriptPrompt(GeckoSession geckoSession, String title, GeckoResult reportAction) { diff --git a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java index 24c7cfb0ef32..a48ab2496c79 100644 --- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java @@ -1779,51 +1779,39 @@ public class GeckoViewActivity } @Override - public void onContentPermissionRequest(final GeckoSession session, final String uri, - final int type, final Callback callback) { + public GeckoResult onContentPermissionRequest(final GeckoSession session, final ContentPermission perm) { final int resId; - Callback contentPermissionCallback = callback; - if (PERMISSION_GEOLOCATION == type) { - resId = R.string.request_geolocation; - } else if (PERMISSION_DESKTOP_NOTIFICATION == type) { - if (mShowNotificationsRejected) { - Log.w(LOGTAG, "Desktop notifications already denied by user."); - callback.reject(); - return; - } - resId = R.string.request_notification; - contentPermissionCallback = new ExampleNotificationCallback(callback); - } else if (PERMISSION_PERSISTENT_STORAGE == type) { - if (mAcceptedPersistentStorage.contains(uri)) { - Log.w(LOGTAG, "Persistent Storage for " + uri + " already granted by user."); - callback.grant(); - return; - } - resId = R.string.request_storage; - contentPermissionCallback = new ExamplePersistentStorageCallback(callback, uri); - } else if (PERMISSION_XR == type) { - resId = R.string.request_xr; - } else if (PERMISSION_AUTOPLAY_AUDIBLE == type || PERMISSION_AUTOPLAY_INAUDIBLE == type) { - if (!mAllowAutoplay.value()) { - Log.d(LOGTAG, "Rejecting autoplay request"); - callback.reject(); - } else { - Log.d(LOGTAG, "Granting autoplay request"); - callback.grant(); - } - return; - } else if (PERMISSION_MEDIA_KEY_SYSTEM_ACCESS == type) { - resId = R.string.request_media_key_system_access; - } else { - Log.w(LOGTAG, "Unknown permission: " + type); - callback.reject(); - return; + switch (perm.permission) { + case PERMISSION_GEOLOCATION: + resId = R.string.request_geolocation; + break; + case PERMISSION_DESKTOP_NOTIFICATION: + resId = R.string.request_notification; + break; + case PERMISSION_PERSISTENT_STORAGE: + resId = R.string.request_storage; + break; + case PERMISSION_XR: + resId = R.string.request_xr; + break; + case PERMISSION_AUTOPLAY_AUDIBLE: + case PERMISSION_AUTOPLAY_INAUDIBLE: + if (!mAllowAutoplay.value()) { + return GeckoResult.fromValue(ContentPermission.VALUE_DENY); + } else { + return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW); + } + case PERMISSION_MEDIA_KEY_SYSTEM_ACCESS: + resId = R.string.request_media_key_system_access; + break; + default: + return GeckoResult.fromValue(ContentPermission.VALUE_DENY); } - final String title = getString(resId, Uri.parse(uri).getAuthority()); + final String title = getString(resId, Uri.parse(perm.uri).getAuthority()); final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt) mTabSessionManager.getCurrentSession().getPromptDelegate(); - prompt.onPermissionPrompt(session, title, contentPermissionCallback); + return prompt.onPermissionPrompt(session, title, perm); } private String[] normalizeMediaName(final MediaSource[] sources) { diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm index bafae1dafb26..183256521bb1 100644 --- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm +++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm @@ -567,8 +567,9 @@ class GeckoViewNavigation extends GeckoViewModule { return { uri: Services.io.createExposableURI(p.principal.URI).displaySpec, principal: E10SUtils.serializePrincipal(p.principal), - type: p.type, + perm: p.type, value: p.capability, + contextId: p.principal.originAttributes.geckoViewSessionContextId, privateMode: p.principal.privateBrowsingId != 0, }; }); diff --git a/mobile/android/modules/geckoview/GeckoViewStorageController.jsm b/mobile/android/modules/geckoview/GeckoViewStorageController.jsm index 294102318ed2..ed8b9e73b0b1 100644 --- a/mobile/android/modules/geckoview/GeckoViewStorageController.jsm +++ b/mobile/android/modules/geckoview/GeckoViewStorageController.jsm @@ -115,8 +115,9 @@ const GeckoViewStorageController = { return { uri: Services.io.createExposableURI(p.principal.URI).displaySpec, principal: E10SUtils.serializePrincipal(p.principal), - type: p.type, + perm: p.type, value: p.capability, + contextId: p.principal.originAttributes.geckoViewSessionContextId, privateMode: p.principal.privateBrowsingId != 0, }; }); @@ -125,23 +126,34 @@ const GeckoViewStorageController = { } case "GeckoView:GetPermissionsByURI": { const uri = Services.io.newURI(aData.uri); - const prin = Services.scriptSecurityManager.createContentPrincipal( + const principal = Services.scriptSecurityManager.createContentPrincipal( uri, - {} + aData.contextId ? { geckoViewSessionContextId: aData.contextId } : {} ); - const rawPerms = Services.perms.getAllForPrincipal(prin); + const rawPerms = Services.perms.getAllForPrincipal(principal); const permissions = rawPerms.map(p => { return { uri: Services.io.createExposableURI(p.principal.URI).displaySpec, principal: E10SUtils.serializePrincipal(p.principal), - type: p.type, + perm: p.type, value: p.capability, + contextId: p.principal.originAttributes.geckoViewSessionContextId, privateMode: p.principal.privateBrowsingId != 0, }; }); aCallback.onSuccess({ permissions }); break; } + case "GeckoView:SetPermission": { + const principal = E10SUtils.deserializePrincipal(aData.principal); + Services.perms.addFromPrincipal( + principal, + aData.perm, + aData.newValue, + Ci.nsIPermissionManager.EXPIRE_NEVER + ); + break; + } } },