Bug 1599139 - Add API to control whether extensions can run in private browsing. r=snorp,esawin

Differential Revision: https://phabricator.services.mozilla.com/D65551

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Agi Sferro 2020-03-07 00:59:46 +00:00
Родитель f6f2f88fa3
Коммит 97c5dd8d40
8 изменённых файлов: 147 добавлений и 7 удалений

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

@ -93,6 +93,7 @@ GeckoViewStartup.prototype = {
"GeckoView:WebExtension:List",
"GeckoView:WebExtension:PortDisconnect",
"GeckoView:WebExtension:PortMessageFromApp",
"GeckoView:WebExtension:SetPBAllowed",
"GeckoView:WebExtension:Uninstall",
"GeckoView:WebExtension:Update",
],

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

@ -1565,6 +1565,7 @@ package org.mozilla.geckoview {
public class WebExtension.MetaData {
ctor protected MetaData();
field public final boolean allowedInPrivateBrowsing;
field @NonNull public final String baseUrl;
field public final int blocklistState;
field @Nullable public final String creatorName;
@ -1643,6 +1644,7 @@ package org.mozilla.geckoview {
method @UiThread @Nullable @Deprecated public WebExtensionController.TabDelegate getTabDelegate();
method @NonNull @AnyThread public GeckoResult<WebExtension> install(@NonNull String);
method @AnyThread @NonNull public GeckoResult<List<WebExtension>> list();
method @NonNull @AnyThread public GeckoResult<WebExtension> setAllowedInPrivateBrowsing(@NonNull WebExtension, boolean);
method @UiThread public void setDebuggerDelegate(@NonNull WebExtensionController.DebuggerDelegate);
method @UiThread public void setPromptDelegate(@Nullable WebExtensionController.PromptDelegate);
method @AnyThread public void setTabActive(@NonNull GeckoSession, boolean);

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

@ -19,6 +19,7 @@ import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.util.Callbacks
import org.mozilla.geckoview.WebExtension.DisabledFlags
import org.mozilla.geckoview.WebExtensionController.EnableSource
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
import java.util.UUID
@ -221,6 +222,58 @@ class WebExtensionTest : BaseSessionTest() {
assertBodyBorderEqualTo("")
}
@Test
@Setting.List(Setting(key = Setting.Key.USE_PRIVATE_MODE, value = "true"))
fun runInPrivateBrowsing() {
mainSession.loadUri("example.com")
sessionRule.waitForPageStop()
// Make sure border is empty before running the extension
assertBodyBorderEqualTo("")
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
@AssertCalled(count=1)
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny> {
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
})
var borderify = sessionRule.waitForResult(
controller.install("resource://android/assets/web_extensions/borderify.xpi"))
// Make sure private mode is enabled
assertTrue(mainSession.settings.usePrivateMode)
assertFalse(borderify.metaData!!.allowedInPrivateBrowsing)
// Check that the WebExtension was not applied to a private mode page
assertBodyBorderEqualTo("")
borderify = sessionRule.waitForResult(
controller.setAllowedInPrivateBrowsing(borderify, true))
assertTrue(borderify.metaData!!.allowedInPrivateBrowsing)
// Check that the WebExtension was applied to a private mode page now that the extension
// is enabled in private mode
mainSession.reload();
sessionRule.waitForPageStop()
assertBodyBorderEqualTo("red")
borderify = sessionRule.waitForResult(
controller.setAllowedInPrivateBrowsing(borderify, false))
assertFalse(borderify.metaData!!.allowedInPrivateBrowsing)
// Check that the WebExtension was not applied to a private mode page after being
// not allowed to run in private mode
mainSession.reload();
sessionRule.waitForPageStop()
assertBodyBorderEqualTo("")
// Unregister WebExtension and check again
sessionRule.waitForResult(controller.uninstall(borderify))
mainSession.reload();
sessionRule.waitForPageStop()
assertBodyBorderEqualTo("")
}
@Test
fun optionsPageMetadata() {
// dummy.xpi is not signed, but it could be

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

@ -1783,6 +1783,12 @@ public class WebExtension {
*/
public final @NonNull String baseUrl;
/**
* Whether this extension is allowed to run in private browsing or not.
* To modify this value use {@link WebExtensionController#setAllowedInPrivateBrowsing}.
*/
public final boolean allowedInPrivateBrowsing;
/**
* Whether this extension is enabled or not.
*/
@ -1807,6 +1813,7 @@ public class WebExtension {
disabledFlags = 0;
enabled = true;
baseUrl = null;
allowedInPrivateBrowsing = false;
}
/* package */ MetaData(final GeckoBundle bundle) {
@ -1824,6 +1831,7 @@ public class WebExtension {
blocklistState = bundle.getInt("blocklistState", BlocklistStateFlags.NOT_BLOCKED);
enabled = bundle.getBoolean("enabled", false);
baseUrl = bundle.getString("baseURL");
allowedInPrivateBrowsing = bundle.getBoolean("privateBrowsingAllowed", false);
int signedState = bundle.getInt("signedState", SignedStateFlags.UNKNOWN);
if (signedState <= SignedStateFlags.LAST) {

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

@ -492,6 +492,35 @@ public class WebExtensionController {
});
}
/**
* Set whether an extension should be allowed to run in private browsing or not.
*
* @param extension the {@link WebExtension} instance to modify.
* @param allowed true if this extension should be allowed to run in private browsing pages,
* false otherwise.
* @return the updated {@link WebExtension} instance.
*/
@NonNull
@AnyThread
public GeckoResult<WebExtension> setAllowedInPrivateBrowsing(
final @NonNull WebExtension extension,
final boolean allowed) {
final WebExtensionController.WebExtensionResult result =
new WebExtensionController.WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(2);
bundle.putString("extensionId", extension.id);
bundle.putBoolean("allowed", allowed);
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:SetPBAllowed",
bundle, result);
return result.then(newExtension -> {
registerWebExtension(newExtension);
return GeckoResult.fromValue(newExtension);
});
}
// TODO: Bug 1601067 make public
GeckoResult<WebExtension> installBuiltIn(final String uri) {
final WebExtensionResult result = new WebExtensionResult("extension");

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

@ -56,6 +56,12 @@ exclude: true
- Added [`baseUrl`][75.24] to [`WebExtension.MetaData`][75.25] to expose the
base URL for all WebExtension pages for a given extension.
([bug 1560048]({{bugzilla}}1560048))
- Added [`allowedInPrivateBrowsing`][75.26] and
[`setAllowedInPrivateBrowsing`][75.27] to control whether an extension can
run in private browsing or not. Extensions installed with
[`registerWebExtension`][67.15] will always be allowed to run in private
browsing.
([bug 1599139]({{bugzilla}}1599139))
[75.1]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#useMultiprocess-boolean-
[75.2]: {{javadoc_uri}}/WebExtensionController.DebuggerDelegate.html#onExtensionListUpdated--
@ -82,6 +88,8 @@ exclude: true
[75.23]: {{javadoc_uri}}/GeckoResult.CancellationDelegate.html
[75.24]: {{javadoc_uri}}/WebExtension.MetaData.html#baseUrl
[75.25]: {{javadoc_uri}}/WebExtension.MetaData.html
[75.26]: {{javadoc_uri}}/WebExtension.MetaData.html#allowedInPrivateBrowsing
[75.27]: {{javadoc_uri}}/WebExtensionController.html#setAllowedInPrivateBrowsing-org.mozilla.geckoview.WebExtension-boolean-
## v74
- Added [`WebExtensionController.enable`][74.1] and [`disable`][74.2] to
@ -645,4 +653,4 @@ exclude: true
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 9a5f829b35bacd2c1f1a6f94c394ffb93b1b0513
[api-version]: 6b0849430f800a3e2226c1a87d26830d1cbe73ee

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

@ -22,10 +22,16 @@ const { EventEmitter } = ChromeUtils.import(
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const PRIVATE_BROWSING_PERMISSION = {
permissions: ["internal:privateBrowsingAllowed"],
origins: [],
};
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
EventDispatcher: "resource://gre/modules/Messaging.jsm",
Extension: "resource://gre/modules/Extension.jsm",
ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
GeckoViewTabBridge: "resource://gre/modules/GeckoViewTab.jsm",
Management: "resource://gre/modules/Extension.jsm",
});
@ -268,9 +274,11 @@ function exportExtension(aAddon, aPermissions, aSourceURI) {
disabledFlags.push("appDisabled");
}
let baseURL = "";
let privateBrowsingAllowed = false;
const policy = WebExtensionPolicy.getByID(id);
if (policy) {
baseURL = policy.getURL();
privateBrowsingAllowed = policy.privateBrowsingAllowed;
}
return {
webExtensionId: id,
@ -294,6 +302,7 @@ function exportExtension(aAddon, aPermissions, aSourceURI) {
signedState,
icons,
baseURL,
privateBrowsingAllowed,
},
};
}
@ -488,6 +497,8 @@ var GeckoViewWebExtension = {
const scope = Extension.getBootstrapScope(aId, file);
scope.allowContentMessaging = allowContentMessaging;
// Always allow built-in extensions to run in private browsing
ExtensionPermissions.add(aId, PRIVATE_BROWSING_PERMISSION);
this.extensionScopes.set(aId, scope);
await scope.startup(params, undefined);
@ -509,7 +520,7 @@ var GeckoViewWebExtension = {
},
async extensionById(aId) {
let scope = this.extensionScopes.get(aId);
const scope = this.extensionScopes.get(aId);
if (!scope) {
// Check if this is an installed extension we haven't seen yet
const addon = await AddonManager.getAddonByID(aId);
@ -517,10 +528,7 @@ var GeckoViewWebExtension = {
debug`Could not find extension with id=${aId}`;
return null;
}
scope = {
allowContentMessaging: false,
extension: addon,
};
return addon;
}
return scope.extension;
@ -544,6 +552,23 @@ var GeckoViewWebExtension = {
return promise;
},
async setPrivateBrowsingAllowed(aId, aAllowed) {
if (aAllowed) {
await ExtensionPermissions.add(aId, PRIVATE_BROWSING_PERMISSION);
} else {
await ExtensionPermissions.remove(aId, PRIVATE_BROWSING_PERMISSION);
}
// Reload the extension if it is already enabled. This ensures any change
// on the private browsing permission is properly handled.
const addon = await this.extensionById(aId);
if (addon.isActive) {
await addon.reload();
}
return exportExtension(addon, addon.userPermissions, /* aSourceURI */ null);
},
async uninstallWebExtension(aId) {
const extension = await this.extensionById(aId);
if (!extension) {
@ -756,6 +781,20 @@ var GeckoViewWebExtension = {
break;
}
case "GeckoView:WebExtension:SetPBAllowed": {
const { extensionId, allowed } = aData;
try {
const extension = await this.setPrivateBrowsingAllowed(
extensionId,
allowed
);
aCallback.onSuccess({ extension });
} catch (ex) {
aCallback.onError(`Unexpected error: ${ex}`);
}
break;
}
case "GeckoView:WebExtension:Install": {
const uri = Services.io.newURI(aData.locationUri);
if (uri == null) {

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

@ -3118,7 +3118,7 @@
# Private browsing opt-in is only supported on Firefox desktop.
- name: extensions.allowPrivateBrowsingByDefault
type: bool
value: @IS_ANDROID@
value: false
mirror: always
# This pref governs whether we enable content script CSP in extensions.