Bug 1697633 - Update onLocationChange to include permission reporting; add getAllPermissions and getPermissions to StorageController. r=agi,geckoview-reviewers,owlish

Differential Revision: https://phabricator.services.mozilla.com/D107927
This commit is contained in:
Dylan Roeh 2021-03-25 16:20:25 +00:00
Родитель a0f12f0bd5
Коммит ab0bb6167e
11 изменённых файлов: 250 добавлений и 12 удалений

Просмотреть файл

@ -107,6 +107,8 @@ class GeckoViewStartup {
"GeckoView:ClearData",
"GeckoView:ClearSessionContextData",
"GeckoView:ClearHostData",
"GeckoView:GetAllPermissions",
"GeckoView:GetPermissionsByURI",
],
});

Просмотреть файл

@ -899,7 +899,8 @@ package org.mozilla.geckoview {
method @UiThread default public void onCanGoForward(@NonNull GeckoSession, boolean);
method @Nullable @UiThread default public GeckoResult<String> onLoadError(@NonNull GeckoSession, @Nullable String, @NonNull WebRequestError);
method @Nullable @UiThread default public GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
method @UiThread default public void onLocationChange(@NonNull GeckoSession, @Nullable String);
method @DeprecationSchedule(id="location-permissions",version=92) @UiThread default public void onLocationChange(@NonNull GeckoSession, @Nullable String);
method @UiThread default public void onLocationChange(@NonNull GeckoSession, @Nullable String, @NonNull List<GeckoSession.PermissionDelegate.ContentPermission>);
method @Nullable @UiThread default public GeckoResult<GeckoSession> onNewSession(@NonNull GeckoSession, @NonNull String);
method @Nullable @UiThread default public GeckoResult<AllowOrDeny> onSubframeLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
field public static final int LOAD_REQUEST_IS_REDIRECT = 8388608;
@ -936,6 +937,17 @@ package org.mozilla.geckoview {
method @UiThread default public void reject();
}
public static class GeckoSession.PermissionDelegate.ContentPermission {
ctor protected ContentPermission();
field public static final int VALUE_ALLOW = 1;
field public static final int VALUE_DENY = 2;
field public static final int VALUE_PROMPT = 3;
field public final int permission;
field public final boolean privateMode;
field @NonNull public final String uri;
field public final int value;
}
public static interface GeckoSession.PermissionDelegate.MediaCallback {
method @UiThread default public void grant(@Nullable String, @Nullable String);
method @UiThread default public void grant(@Nullable GeckoSession.PermissionDelegate.MediaSource, @Nullable GeckoSession.PermissionDelegate.MediaSource);
@ -1642,6 +1654,8 @@ package org.mozilla.geckoview {
method @AnyThread @NonNull public GeckoResult<Void> clearData(long);
method @AnyThread public void clearDataForSessionContext(@NonNull String);
method @AnyThread @NonNull public GeckoResult<Void> clearDataFromHost(@NonNull String, long);
method @AnyThread @NonNull public GeckoResult<List<GeckoSession.PermissionDelegate.ContentPermission>> getAllPermissions();
method @AnyThread @NonNull public GeckoResult<List<GeckoSession.PermissionDelegate.ContentPermission>> getPermissions(@NonNull String);
}
public static class StorageController.ClearFlags {

Просмотреть файл

@ -1171,7 +1171,7 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
}
})
@ -1190,7 +1190,7 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(count = 1, order = [2])
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
}
@ -1224,7 +1224,7 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(count = 1, order = [2])
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
}
@ -1949,7 +1949,7 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(count = 1)
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URI should match", url, endsWith("#test1"))
}
})
@ -1965,7 +1965,7 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(count = 1)
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URI should match", url, endsWith("#test2"))
}
})

Просмотреть файл

@ -32,7 +32,7 @@ class ProgressDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate,
Callbacks.NavigationDelegate {
@AssertCalled
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("LocationChange is called", url, endsWith(path))
}
@AssertCalled
@ -361,7 +361,7 @@ class ProgressDelegateTest : BaseSessionTest() {
session.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("URI should match", url, equalTo(startUri))
}
})
@ -379,7 +379,7 @@ class ProgressDelegateTest : BaseSessionTest() {
session.goBack()
session.waitUntilCalled(object: Callbacks.NavigationDelegate {
override fun onLocationChange(session: GeckoSession, url: String?) {
override fun onLocationChange(session: GeckoSession, url: String?, perms : MutableList<GeckoSession.PermissionDelegate.ContentPermission>) {
assertThat("History should be preserved", url, equalTo(helloUri))
}
})

Просмотреть файл

@ -34,6 +34,7 @@ import android.view.Surface;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List;
public class TestRunnerActivity extends Activity {
private static final String LOGTAG = "TestRunnerActivity";

Просмотреть файл

@ -14,6 +14,7 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.AbstractSequentialList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@ -466,8 +467,11 @@ public class GeckoSession {
Log.d(LOGTAG, "handleMessage " + event + " uri=" + message.getString("uri"));
if ("GeckoView:LocationChange".equals(event)) {
if (message.getBoolean("isTopLevel")) {
final GeckoBundle[] perms = message.getBundleArray("permissions");
final List<PermissionDelegate.ContentPermission> permList =
PermissionDelegate.ContentPermission.fromBundleArray(perms);
delegate.onLocationChange(GeckoSession.this,
message.getString("uri"));
message.getString("uri"), permList);
}
delegate.onCanGoBack(GeckoSession.this,
message.getBoolean("canGoBack"));
@ -3645,8 +3649,20 @@ public class GeckoSession {
* @param url The resource being loaded.
*/
@UiThread
@DeprecationSchedule(id = "location-permissions", version = 92)
default void onLocationChange(@NonNull final GeckoSession session, @Nullable final String url) {}
/**
* A view has started loading content from the network.
* @param session The GeckoSession that initiated the callback.
* @param url The resource being loaded.
* @param perms The permissions currently associated with this url.
*/
@UiThread
default void onLocationChange(@NonNull GeckoSession session, @Nullable String url, final @NonNull List<PermissionDelegate.ContentPermission> perms) {
session.getNavigationDelegate().onLocationChange(session, url);
}
/**
* The view's ability to go back has changed.
* @param session The GeckoSession that initiated the callback.
@ -5365,6 +5381,110 @@ public class GeckoSession {
*/
int PERMISSION_MEDIA_KEY_SYSTEM_ACCESS = 6;
/**
* Represents a content permission -- including the type of permission,
* the present value of the permission, the URL the permission pertains to,
* and other information.
*/
class ContentPermission {
@Retention(RetentionPolicy.SOURCE)
@IntDef({VALUE_PROMPT, VALUE_DENY, VALUE_ALLOW})
/* package */ @interface Value {}
/**
* The corresponding permission is currently set to default/prompt behavior.
*/
final public static int VALUE_PROMPT = 3;
/**
* The corresponding permission is currently set to deny.
*/
final public static int VALUE_DENY = 2;
/**
* The corresponding permission is currently set to allow.
*/
final public static int VALUE_ALLOW = 1;
/**
* The URI associated with this content permission.
*/
final public @NonNull String uri;
/**
* A boolean indicating whether this content permission is associated with
* private browsing.
*/
final public boolean privateMode;
/**
* The type of this permission; one of {@link #PERMISSION_GEOLOCATION PERMISSION_*}.
*/
final public int permission;
/**
* The value of the permission; one of {@link #VALUE_PROMPT VALUE_}.
*/
final public @Value int value;
final private String mPrincipal;
protected ContentPermission() {
this.uri = "";
this.privateMode = false;
this.permission = PERMISSION_GEOLOCATION;
this.value = VALUE_ALLOW;
this.mPrincipal = "";
}
private ContentPermission(final @NonNull GeckoBundle bundle) {
this.uri = bundle.getString("uri");
this.mPrincipal = bundle.getString("principal");
this.privateMode = bundle.getBoolean("privateMode");
final String permission = bundle.getString("type");
this.permission = convertType(permission);
this.value = bundle.getInt("value");
}
private static int convertType(final @NonNull String type) {
if ("geolocation".equals(type)) {
return PERMISSION_GEOLOCATION;
} else if ("desktop-notification".equals(type)) {
return PERMISSION_DESKTOP_NOTIFICATION;
} else if ("persistent-storage".equals(type)) {
return PERMISSION_PERSISTENT_STORAGE;
} else if ("xr".equals(type)) {
return PERMISSION_XR;
} else if ("autoplay-media-inaudible".equals(type)) {
return PERMISSION_AUTOPLAY_INAUDIBLE;
} else if ("autoplay-media-audible".equals(type)) {
return PERMISSION_AUTOPLAY_AUDIBLE;
} else if ("media-key-system-access".equals(type)) {
return PERMISSION_MEDIA_KEY_SYSTEM_ACCESS;
} else {
return -1;
}
}
/* package */ static @NonNull ArrayList<ContentPermission> fromBundleArray(final @NonNull GeckoBundle[] bundleArray) {
final ArrayList<ContentPermission> res = new ArrayList<ContentPermission>();
if (bundleArray == null) {
return res;
}
for (final GeckoBundle bundle : bundleArray) {
final ContentPermission temp = new ContentPermission(bundle);
if (temp.permission == -1 || temp.value < 1 || temp.value > 3) {
continue;
}
res.add(temp);
}
return res;
}
}
/**
* Callback interface for notifying the result of a permission request.
*/

Просмотреть файл

@ -9,6 +9,7 @@ package org.mozilla.geckoview;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigInteger;
import java.util.List;
import java.util.Locale;
import androidx.annotation.AnyThread;
@ -18,6 +19,7 @@ import androidx.annotation.Nullable;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission;
/**
* Manage runtime storage data.
@ -181,4 +183,36 @@ public final class StorageController {
return String.format("gvctx%x", new BigInteger(contextId.getBytes()))
.toLowerCase(Locale.ROOT);
}
/**
* Get all currently stored permissions.
*
* @return A {@link GeckoResult} that will complete with a list of all
* currently stored {@link ContentPermission}s.
*/
@AnyThread
public @NonNull GeckoResult<List<ContentPermission>> getAllPermissions() {
return EventDispatcher.getInstance().queryBundle("GeckoView:GetAllPermissions").map(bundle -> {
final GeckoBundle[] permsArray = bundle.getBundleArray("permissions");
return ContentPermission.fromBundleArray(permsArray);
});
}
/**
* Get all currently stored permissions for a given URI.
*
* @param uri A String representing the URI to get permissions for.
*
* @return A {@link GeckoResult} that will complete with a list of all
* currently stored {@link ContentPermission}s for the URI.
*/
@AnyThread
public @NonNull GeckoResult<List<ContentPermission>> getPermissions(final @NonNull String uri) {
final GeckoBundle msg = new GeckoBundle(1);
msg.putString("uri", uri);
return EventDispatcher.getInstance().queryBundle("GeckoView:GetPermissionsByURI", msg).map(bundle -> {
final GeckoBundle[] permsArray = bundle.getBundleArray("permissions");
return ContentPermission.fromBundleArray(permsArray);
});
}
}

Просмотреть файл

@ -13,6 +13,20 @@ exclude: true
⚠️ breaking change and deprecation notices
## v89
- Added [`ContentPermission`][89.1], which is used to report what permissions content
is loaded with in `onLocationChange`.
- Added [`StorageController.getPermissions`][89.2] and [`StorageController.getAllPermissions`][89.3],
allowing inspection of what permissions have been set for a given URI and for all URIs.
- ⚠️ Deprecated [`NavigationDelegate.onLocationChange`][89.4], to be removed in v92. The
new `onLocationChange` callback simply adds permissions information, migration of existing
functionality should only require updating the function signature.
[89.1]: {{javadoc_uri}}/GeckoSession.PermissionDelegate.ContentPermission.html
[89.2]: {{javadoc_uri}}/StorageController.html#getPermissions-java.lang.String-
[89.3]: {{javadoc_uri}}/StorageController.html#getAllPermissions--
[89.4]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onLocationChange-org.mozilla.geckoview.GeckoSession-java.lang.String-
## v88
- Added [`WebExtension.Download#update`][88.1] that can be used to
implement the WebExtension `downloads` API. This method is used to communicate
@ -934,4 +948,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]: f6ad9d26fbb3880d60970a1f25e406096c9efca0
[api-version]: 638023f7c8ddb3ccf732df09b0bdee4416c04237

Просмотреть файл

@ -1872,7 +1872,7 @@ public class GeckoViewActivity
private class ExampleNavigationDelegate implements GeckoSession.NavigationDelegate {
@Override
public void onLocationChange(GeckoSession session, final String url) {
public void onLocationChange(GeckoSession session, final String url, final List<GeckoSession.PermissionDelegate.ContentPermission> perms) {
mToolbarView.getLocationView().setText(url);
TabSession tabSession = mTabSessionManager.getSession(session);
if (tabSession != null) {

Просмотреть файл

@ -558,12 +558,29 @@ class GeckoViewNavigation extends GeckoViewModule {
return;
}
let permissions;
if (this.browser.contentPrincipal) {
const rawPerms = Services.perms.getAllForPrincipal(
this.browser.contentPrincipal
);
permissions = rawPerms.map(p => {
return {
uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
principal: E10SUtils.serializePrincipal(p.principal),
type: p.type,
value: p.capability,
privateMode: p.principal.privateBrowsingId != 0,
};
});
}
const message = {
type: "GeckoView:LocationChange",
uri: fixedURI.displaySpec,
canGoBack: this.browser.canGoBack,
canGoForward: this.browser.canGoForward,
isTopLevel: aWebProgress.isTopLevel,
permissions,
};
this.eventDispatcher.sendRequest(message);

Просмотреть файл

@ -13,6 +13,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { PrincipalsCollector } = ChromeUtils.import(
"resource://gre/modules/PrincipalsCollector.jsm"
);
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
const { debug, warn } = GeckoViewUtils.initLogging(
"GeckoViewStorageController"
@ -107,6 +110,39 @@ const GeckoViewStorageController = {
this.clearHostData(aData.host, aData.flags, aCallback);
break;
}
case "GeckoView:GetAllPermissions": {
const rawPerms = Services.perms.all;
const permissions = rawPerms.map(p => {
return {
uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
principal: E10SUtils.serializePrincipal(p.principal),
type: p.type,
value: p.capability,
privateMode: p.principal.privateBrowsingId != 0,
};
});
aCallback.onSuccess({ permissions });
break;
}
case "GeckoView:GetPermissionsByURI": {
const uri = Services.io.newURI(aData.uri);
const prin = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
const rawPerms = Services.perms.getAllForPrincipal(prin);
const permissions = rawPerms.map(p => {
return {
uri: Services.io.createExposableURI(p.principal.URI).displaySpec,
principal: E10SUtils.serializePrincipal(p.principal),
type: p.type,
value: p.capability,
privateMode: p.principal.privateBrowsingId != 0,
};
});
aCallback.onSuccess({ permissions });
break;
}
}
},