--- a/toolkit/components/extensions/ext-permissions.js
+++ b/toolkit/components/extensions/ext-permissions.js
@@ -31,16 +31,23 @@ this.permissions = class extends Extensi
let optionalOrigins = context.extension.optionalOrigins;
for (let origin of origins) {
if (!optionalOrigins.subsumes(new MatchPattern(origin))) {
throw new ExtensionError(`Cannot request origin permission for ${origin} since it was not declared in optional_permissions`);
}
}
if (promptsEnabled) {
+ permissions = permissions.filter(perm => !context.extension.hasPermission(perm));
+ origins = origins.filter(origin => !context.extension.whiteListedHosts.subsumes(new MatchPattern(origin)));
+
+ if (permissions.length == 0 && origins.length == 0) {
+ return true;
+ }
+
let browser = context.pendingEventBrowser || context.xulBrowser;
let allow = await new Promise(resolve => {
let subject = {
wrappedJSObject: {
browser,
name: context.extension.name,
icon: context.extension.iconURL,
permissions: {permissions, origins},
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -21,42 +21,46 @@ function findWinUtils(extension) {
bgwin = view.contentWindow;
}
}
notEqual(bgwin, null, "Found background window for the test extension");
return bgwin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
}
+let sawPrompt = false;
+let acceptPrompt = false;
+const observer = {
+ observe(subject, topic, data) {
+ if (topic == "webextension-optional-permission-prompt") {
+ sawPrompt = true;
+ let {resolve} = subject.wrappedJSObject;
+ resolve(acceptPrompt);
+ }
+ },
+};
+
+add_task(function setup() {
+ Services.prefs.setBoolPref("extensions.webextOptionalPermissionPrompts", true);
+ Services.obs.addObserver(observer, "webextension-optional-permission-prompt");
+ do_register_cleanup(() => {
+ Services.obs.removeObserver(observer, "webextension-optional-permission-prompt");
+ Services.prefs.clearUserPref("extensions.webextOptionalPermissionPrompts");
+ });
+});
+
add_task(async function test_permissions() {
const REQUIRED_PERMISSIONS = ["downloads"];
const REQUIRED_ORIGINS = ["*://site.com/", "*://*.domain.com/"];
const REQUIRED_ORIGINS_NORMALIZED = ["*://site.com/*", "*://*.domain.com/*"];
const OPTIONAL_PERMISSIONS = ["idle", "clipboardWrite"];
const OPTIONAL_ORIGINS = ["http://optionalsite.com/", "https://*.optionaldomain.com/"];
const OPTIONAL_ORIGINS_NORMALIZED = ["http://optionalsite.com/*", "https://*.optionaldomain.com/*"];
- let acceptPrompt = false;
- const observer = {
- observe(subject, topic, data) {
- if (topic == "webextension-optional-permission-prompt") {
- let {resolve} = subject.wrappedJSObject;
- resolve(acceptPrompt);
- }
- },
- };
-
- Services.prefs.setBoolPref("extensions.webextOptionalPermissionPrompts", true);
- Services.obs.addObserver(observer, "webextension-optional-permission-prompt");
- do_register_cleanup(() => {
- Services.obs.removeObserver(observer, "webextension-optional-permission-prompt");
- Services.prefs.clearUserPref("extensions.webextOptionalPermissionPrompts");
- });
-
await AddonTestUtils.promiseStartupManager();
function background() {
browser.test.onMessage.addListener(async (method, arg) => {
if (method == "getAll") {
let perms = await browser.permissions.getAll();
let url = browser.extension.getURL("*");
perms.origins = perms.origins.filter(i => i != url);
@@ -227,19 +231,17 @@ add_task(async function test_startup() {
manifest: {optional_permissions: PERMS2.origins},
useAddonManager: "permanent",
});
await extension1.startup();
await extension2.startup();
let perms = await extension1.awaitMessage("perms");
- dump(`perms1 ${JSON.stringify(perms)}\n`);
perms = await extension2.awaitMessage("perms");
- dump(`perms2 ${JSON.stringify(perms)}\n`);
let winUtils = findWinUtils(extension1);
let handle = winUtils.setHandlingUserInput(true);
extension1.sendMessage(PERMS1);
await extension1.awaitMessage("requested");
handle.destruct();
winUtils = findWinUtils(extension2);
@@ -262,8 +264,103 @@ add_task(async function test_startup() {
}
await checkPermissions(extension1, PERMS1);
await checkPermissions(extension2, PERMS2);
await extension1.unload();
await extension2.unload();
});
+
+// Test that we don't prompt for permissions an extension already has.
+add_task(async function test_alreadyGranted() {
+ const REQUIRED_PERMISSIONS = [
+ "geolocation",
+ "*://required-host.com/",
+ "*://*.required-domain.com/",
+ ];
+ const OPTIONAL_PERMISSIONS = [
+ ...REQUIRED_PERMISSIONS,
+ "clipboardRead",
+ "*://optional-host.com/",
+ "*://*.optional-domain.com/",
+ ];
+
+ function pageScript() {
+ browser.test.onMessage.addListener(async (msg, arg) => {
+ if (msg == "request") {
+ let result = await browser.permissions.request(arg);
+ browser.test.sendMessage("request.result", result);
+ } else if (msg == "remove") {
+ let result = await browser.permissions.remove(arg);
+ browser.test.sendMessage("remove.result", result);
+ } else if (msg == "close") {
+ window.close();
+ }
+ });
+
+ browser.test.sendMessage("page-ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.test.sendMessage("ready", browser.runtime.getURL("page.html"));
+ },
+
+ manifest: {
+ permissions: REQUIRED_PERMISSIONS,
+ optional_permissions: OPTIONAL_PERMISSIONS,
+ },
+
+ files: {
+ "page.html": `<html><head>
+ <script src="page.js"><\/script>
+ </head></html>`,
+
+ "page.js": pageScript,
+ },
+ });
+
+ await extension.startup();
+
+ let winUtils = findWinUtils(extension);
+ let handle = winUtils.setHandlingUserInput(true);
+
+ let url = await extension.awaitMessage("ready");
+ await ExtensionTestUtils.loadContentPage(url);
+ await extension.awaitMessage("page-ready");
+
+ async function checkRequest(arg, expectPrompt, msg) {
+ sawPrompt = false;
+ extension.sendMessage("request", arg);
+ let result = await extension.awaitMessage("request.result");
+ ok(result, "request() call succeeded");
+ equal(sawPrompt, expectPrompt,
+ `Got ${expectPrompt ? "" : "no "}permission prompt for ${msg}`);
+ }
+
+ await checkRequest({permissions: ["geolocation"]}, false,
+ "required permission from manifest");
+ await checkRequest({origins: ["http://required-host.com/"]}, false,
+ "origin permission from manifest");
+ await checkRequest({origins: ["http://host.required-domain.com/"]}, false,
+ "wildcard origin permission from manifest");
+
+ await checkRequest({permissions: ["clipboardRead"]}, true,
+ "optional permission");
+ await checkRequest({permissions: ["clipboardRead"]}, false,
+ "already granted optional permission");
+
+ await checkRequest({origins: ["http://optional-host.com/"]}, true,
+ "optional origin");
+ await checkRequest({origins: ["http://optional-host.com/"]}, false,
+ "already granted origin permission");
+
+ await checkRequest({origins: ["http://*.optional-domain.com/"]}, true,
+ "optional wildcard origin");
+ await checkRequest({origins: ["http://*.optional-domain.com/"]}, false,
+ "already granted optional wildcard origin");
+ await checkRequest({origins: ["http://host.optional-domain.com/"]}, false,
+ "host matching optional wildcard origin");
+
+ handle.destruct();
+ await extension.unload();
+});