3 const { AddonManager } = ChromeUtils.import(
4 "resource://gre/modules/AddonManager.jsm"
6 const { ExtensionPermissions } = ChromeUtils.import(
7 "resource://gre/modules/ExtensionPermissions.jsm"
10 Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
12 // ExtensionParent.jsm is being imported lazily because when it is imported Services.appinfo will be
13 // retrieved and cached (as a side-effect of Schemas.jsm being imported), and so Services.appinfo
14 // will not be returning the version set by AddonTestUtils.createAppInfo and this test will
15 // fail on non-nightly builds (because the cached appinfo.version will be undefined and
16 // AddonManager startup will fail).
17 ChromeUtils.defineModuleGetter(
20 "resource://gre/modules/ExtensionParent.jsm"
23 const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
25 AddonTestUtils.init(this);
26 AddonTestUtils.overrideCertDB();
27 AddonTestUtils.createAppInfo(
28 "xpcshell@tests.mozilla.org",
34 add_task(async function setup() {
35 // Bug 1646182: Force ExtensionPermissions to run in rkv mode, the legacy
36 // storage mode will run in xpcshell-legacy-ep.ini
37 await ExtensionPermissions._uninit();
39 optionalPermissionsPromptHandler.init();
41 await AddonTestUtils.promiseStartupManager();
42 AddonTestUtils.usePrivilegedSignatures = false;
45 add_task(async function test_permissions_on_startup() {
46 let extensionId = "@permissionTest";
47 let extension = ExtensionTestUtils.loadExtension({
50 gecko: { id: extensionId },
52 permissions: ["tabs"],
54 useAddonManager: "permanent",
56 let perms = await browser.permissions.getAll();
57 browser.test.sendMessage("permissions", perms);
61 permissions: ["internal:privateBrowsingAllowed"],
64 await extension.startup();
65 let perms = await extension.awaitMessage("permissions");
66 equal(perms.permissions.length, 1, "one permission");
67 equal(perms.permissions[0], "tabs", "internal permission not present");
69 const { StartupCache } = ExtensionParent;
71 // StartupCache.permissions will not contain the extension permissions.
72 let manifestData = await StartupCache.permissions.get(extensionId, () => {
73 return { permissions: [], origins: [] };
75 equal(manifestData.permissions.length, 0, "no permission");
77 perms = await ExtensionPermissions.get(extensionId);
78 equal(perms.permissions.length, 0, "no permissions");
79 await ExtensionPermissions.add(extensionId, adding);
81 // Restart the extension and re-test the permissions.
82 await ExtensionPermissions._uninit();
83 await AddonTestUtils.promiseRestartManager();
84 let restarted = extension.awaitMessage("permissions");
85 await extension.awaitStartup();
86 perms = await restarted;
88 manifestData = await StartupCache.permissions.get(extensionId, () => {
89 return { permissions: [], origins: [] };
92 manifestData.permissions,
94 "StartupCache.permissions contains permission"
97 equal(perms.permissions.length, 1, "one permission");
98 equal(perms.permissions[0], "tabs", "internal permission not present");
99 let added = await ExtensionPermissions._get(extensionId);
100 deepEqual(added, adding, "permissions were retained");
102 await extension.unload();
105 async function test_permissions({
107 granted_host_permissions,
111 const REQUIRED_PERMISSIONS = ["downloads"];
112 const REQUIRED_ORIGINS = ["*://site.com/", "*://*.domain.com/"];
113 const REQUIRED_ORIGINS_EXPECTED = expectAllGranted
114 ? ["*://site.com/*", "*://*.domain.com/*"]
117 const OPTIONAL_PERMISSIONS = ["idle", "clipboardWrite"];
118 const OPTIONAL_ORIGINS = [
119 "http://optionalsite.com/",
120 "https://*.optionaldomain.com/",
122 const OPTIONAL_ORIGINS_NORMALIZED = [
123 "http://optionalsite.com/*",
124 "https://*.optionaldomain.com/*",
127 function background() {
128 browser.test.onMessage.addListener(async (method, arg) => {
129 if (method == "getAll") {
130 let perms = await browser.permissions.getAll();
131 let url = browser.runtime.getURL("*");
132 perms.origins = perms.origins.filter(i => i != url);
133 browser.test.sendMessage("getAll.result", perms);
134 } else if (method == "contains") {
135 let result = await browser.permissions.contains(arg);
136 browser.test.sendMessage("contains.result", result);
137 } else if (method == "request") {
139 let result = await browser.permissions.request(arg);
140 browser.test.sendMessage("request.result", {
145 browser.test.sendMessage("request.result", {
147 message: err.message,
150 } else if (method == "remove") {
151 let result = await browser.permissions.remove(arg);
152 browser.test.sendMessage("remove.result", result);
157 let extension = ExtensionTestUtils.loadExtension({
161 permissions: REQUIRED_PERMISSIONS,
162 host_permissions: REQUIRED_ORIGINS,
163 optional_permissions: [...OPTIONAL_PERMISSIONS, ...OPTIONAL_ORIGINS],
164 granted_host_permissions,
169 await extension.startup();
171 function call(method, arg) {
172 extension.sendMessage(method, arg);
173 return extension.awaitMessage(`${method}.result`);
176 let result = await call("getAll");
177 deepEqual(result.permissions, REQUIRED_PERMISSIONS);
178 deepEqual(result.origins, REQUIRED_ORIGINS_EXPECTED);
180 for (let perm of REQUIRED_PERMISSIONS) {
181 result = await call("contains", { permissions: [perm] });
182 equal(result, true, `contains() returns true for fixed permission ${perm}`);
184 for (let origin of REQUIRED_ORIGINS) {
185 result = await call("contains", { origins: [origin] });
189 `contains() returns true for fixed origin ${origin}`
193 // None of the optional permissions should be available yet
194 for (let perm of OPTIONAL_PERMISSIONS) {
195 result = await call("contains", { permissions: [perm] });
196 equal(result, false, `contains() returns false for permission ${perm}`);
198 for (let origin of OPTIONAL_ORIGINS) {
199 result = await call("contains", { origins: [origin] });
200 equal(result, false, `contains() returns false for origin ${origin}`);
203 result = await call("contains", {
204 permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
209 "contains() returns false for a mix of available and unavailable permissions"
212 let perm = OPTIONAL_PERMISSIONS[0];
213 result = await call("request", { permissions: [perm] });
217 "request() fails if not called from an event handler"
220 /request may only be called from a user input handler/.test(result.message),
221 "error message for calling request() outside an event handler is reasonable"
223 result = await call("contains", { permissions: [perm] });
227 "Permission requested outside an event handler was not granted"
230 await withHandlingUserInput(extension, async () => {
231 result = await call("request", { permissions: ["notifications"] });
235 "request() for permission not in optional_permissions should fail"
238 /since it was not declared in optional_permissions/.test(result.message),
239 "error message for undeclared optional_permission is reasonable"
242 // Check request() when the prompt is canceled.
243 optionalPermissionsPromptHandler.acceptPrompt = false;
244 result = await call("request", { permissions: [perm] });
245 equal(result.status, "success", "request() returned cleanly");
249 "request() returned false for rejected permission"
252 result = await call("contains", { permissions: [perm] });
253 equal(result, false, "Rejected permission was not granted");
255 // Call request() and accept the prompt
256 optionalPermissionsPromptHandler.acceptPrompt = true;
258 permissions: OPTIONAL_PERMISSIONS,
259 origins: OPTIONAL_ORIGINS,
261 result = await call("request", allOptional);
262 equal(result.status, "success", "request() returned cleanly");
266 "request() returned true for accepted permissions"
269 // Verify that requesting a permission/origin in the wrong field fails
270 let originsAsPerms = {
271 permissions: OPTIONAL_ORIGINS,
273 let permsAsOrigins = {
274 origins: OPTIONAL_PERMISSIONS,
277 result = await call("request", originsAsPerms);
281 "Requesting an origin as a permission should fail"
284 /Type error for parameter permissions \(Error processing permissions/.test(
287 "Error message for origin as permission is reasonable"
290 result = await call("request", permsAsOrigins);
294 "Requesting a permission as an origin should fail"
297 /Type error for parameter permissions \(Error processing origins/.test(
300 "Error message for permission as origin is reasonable"
304 let allPermissions = {
305 permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
306 origins: [...REQUIRED_ORIGINS_EXPECTED, ...OPTIONAL_ORIGINS_NORMALIZED],
309 result = await call("getAll");
313 "getAll() returns required and runtime requested permissions"
316 result = await call("contains", allPermissions);
320 "contains() returns true for runtime requested permissions"
323 // Restart extension, verify permissions are still present.
324 if (useAddonManager === "permanent") {
325 await AddonTestUtils.promiseRestartManager();
327 // Manually reload for temporarily loaded.
328 await extension.addon.reload();
330 await extension.awaitBackgroundStarted();
332 result = await call("getAll");
336 "Runtime requested permissions are still present after restart"
340 result = await call("remove", { permissions: OPTIONAL_PERMISSIONS });
341 equal(result, true, "remove() succeeded");
344 permissions: REQUIRED_PERMISSIONS,
345 origins: [...REQUIRED_ORIGINS_EXPECTED, ...OPTIONAL_ORIGINS_NORMALIZED],
348 result = await call("getAll");
349 deepEqual(result, perms, "Expected permissions remain after removing some");
351 result = await call("remove", { origins: OPTIONAL_ORIGINS });
352 equal(result, true, "remove() succeeded");
354 perms.origins = REQUIRED_ORIGINS_EXPECTED;
355 result = await call("getAll");
356 deepEqual(result, perms, "Back to default permissions after removing more");
358 await extension.unload();
361 add_task(function test_normal_mv2() {
362 return test_permissions({
364 useAddonManager: "permanent",
365 expectAllGranted: true,
369 add_task(function test_normal_mv3() {
370 return test_permissions({
372 useAddonManager: "permanent",
373 expectAllGranted: false,
377 add_task(function test_granted_for_temporary_mv3() {
378 return test_permissions({
380 granted_host_permissions: true,
381 useAddonManager: "temporary",
382 expectAllGranted: true,
386 add_task(async function test_granted_for_privileged_mv3() {
387 const { AddonSettings } = ChromeUtils.import(
388 "resource://gre/modules/addons/AddonSettings.jsm"
390 // This makes temporary loaded extensions Privileged.
391 Services.prefs.setBoolPref("extensions.experiments.enabled", true);
393 await test_permissions({
395 granted_host_permissions: true,
396 useAddonManager: "temporary",
397 // Expect granted only in configurations where experiments are enabled.
398 expectAllGranted: AddonSettings.EXPERIMENTS_ENABLED,
401 Services.prefs.setBoolPref("extensions.experiments.enabled", false);
405 add_task(async function test_startup() {
406 async function background() {
407 browser.test.onMessage.addListener(async perms => {
408 await browser.permissions.request(perms);
409 browser.test.sendMessage("requested");
412 let all = await browser.permissions.getAll();
413 let url = browser.runtime.getURL("*");
414 all.origins = all.origins.filter(i => i != url);
415 browser.test.sendMessage("perms", all);
419 permissions: ["clipboardRead", "tabs"],
422 origins: ["https://site2.com/*"],
425 let extension1 = ExtensionTestUtils.loadExtension({
428 optional_permissions: PERMS1.permissions,
430 useAddonManager: "permanent",
432 let extension2 = ExtensionTestUtils.loadExtension({
435 optional_permissions: PERMS2.origins,
437 useAddonManager: "permanent",
440 await extension1.startup();
441 await extension2.startup();
443 let perms = await extension1.awaitMessage("perms");
444 perms = await extension2.awaitMessage("perms");
446 await withHandlingUserInput(extension1, async () => {
447 extension1.sendMessage(PERMS1);
448 await extension1.awaitMessage("requested");
451 await withHandlingUserInput(extension2, async () => {
452 extension2.sendMessage(PERMS2);
453 await extension2.awaitMessage("requested");
456 // Restart everything, and force the permissions store to be
457 // re-read on startup
458 await ExtensionPermissions._uninit();
459 await AddonTestUtils.promiseRestartManager();
460 await extension1.awaitStartup();
461 await extension2.awaitStartup();
463 async function checkPermissions(extension, permissions) {
464 perms = await extension.awaitMessage("perms");
465 let expect = Object.assign({ permissions: [], origins: [] }, permissions);
466 deepEqual(perms, expect, "Extension got correct permissions on startup");
469 await checkPermissions(extension1, PERMS1);
470 await checkPermissions(extension2, PERMS2);
472 await extension1.unload();
473 await extension2.unload();
476 // Test that we don't prompt for permissions an extension already has.
477 async function test_alreadyGranted(manifest_version) {
478 const REQUIRED_PERMISSIONS = ["geolocation"];
479 const REQUIRED_ORIGINS = [
480 "*://required-host.com/",
481 "*://*.required-domain.com/",
483 const OPTIONAL_PERMISSIONS = [
484 ...REQUIRED_PERMISSIONS,
487 "*://optional-host.com/",
488 "*://*.optional-domain.com/",
491 function pageScript() {
492 browser.test.onMessage.addListener(async (msg, arg) => {
493 if (msg == "request") {
494 let result = await browser.permissions.request(arg);
495 browser.test.sendMessage("request.result", result);
496 } else if (msg == "remove") {
497 let result = await browser.permissions.remove(arg);
498 browser.test.sendMessage("remove.result", result);
499 } else if (msg == "close") {
504 browser.test.sendMessage("page-ready");
507 let extension = ExtensionTestUtils.loadExtension({
509 browser.test.sendMessage("ready", browser.runtime.getURL("page.html"));
514 permissions: REQUIRED_PERMISSIONS,
515 host_permissions: REQUIRED_ORIGINS,
516 optional_permissions: OPTIONAL_PERMISSIONS,
517 granted_host_permissions: true,
519 temporarilyInstalled: true,
522 "page.html": `<html><head>
523 <script src="page.js"><\/script>
526 "page.js": pageScript,
530 await extension.startup();
532 await withHandlingUserInput(extension, async () => {
533 let url = await extension.awaitMessage("ready");
534 let page = await ExtensionTestUtils.loadContentPage(url, { extension });
535 await extension.awaitMessage("page-ready");
537 async function checkRequest(arg, expectPrompt, msg) {
538 optionalPermissionsPromptHandler.sawPrompt = false;
539 extension.sendMessage("request", arg);
540 let result = await extension.awaitMessage("request.result");
541 ok(result, "request() call succeeded");
543 optionalPermissionsPromptHandler.sawPrompt,
545 `Got ${expectPrompt ? "" : "no "}permission prompt for ${msg}`
550 { permissions: ["geolocation"] },
552 "required permission from manifest"
555 { origins: ["http://required-host.com/"] },
557 "origin permission from manifest"
560 { origins: ["http://host.required-domain.com/"] },
562 "wildcard origin permission from manifest"
566 { permissions: ["clipboardRead"] },
568 "optional permission"
571 { permissions: ["clipboardRead"] },
573 "already granted optional permission"
577 { origins: ["http://optional-host.com/"] },
582 { origins: ["http://optional-host.com/"] },
584 "already granted origin permission"
588 { origins: ["http://*.optional-domain.com/"] },
590 "optional wildcard origin"
593 { origins: ["http://*.optional-domain.com/"] },
595 "already granted optional wildcard origin"
598 { origins: ["http://host.optional-domain.com/"] },
600 "host matching optional wildcard origin"
605 await extension.unload();
607 add_task(async function test_alreadyGranted_mv2() {
608 return test_alreadyGranted(2);
610 add_task(async function test_alreadyGranted_mv3() {
611 return test_alreadyGranted(3);
614 // IMPORTANT: Do not change this list without review from a Web Extensions peer!
616 const GRANTED_WITHOUT_USER_PROMPT = [
622 "contextualIdentities",
629 "menus.overrideContext",
632 "normandyAddonStudy",
641 "webRequestBlocking",
642 "webRequestFilterResponse.serviceWorkerScript",
645 add_task(function test_permissions_have_localization_strings() {
646 let noPromptNames = Schemas.getPermissionNames([
647 "PermissionNoPrompt",
648 "OptionalPermissionNoPrompt",
651 GRANTED_WITHOUT_USER_PROMPT,
653 "List of no-prompt permissions is correct."
656 const bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
658 for (const perm of Schemas.getPermissionNames()) {
660 const str = bundle.GetStringFromName(`webextPerms.description.${perm}`);
662 ok(str.length, `Found localization string for '${perm}' permission`);
665 GRANTED_WITHOUT_USER_PROMPT.includes(perm),
666 `Permission '${perm}' intentionally granted without prompting the user`
672 // Check <all_urls> used as an optional API permission.
673 add_task(async function test_optional_all_urls() {
674 let extension = ExtensionTestUtils.loadExtension({
676 optional_permissions: ["<all_urls>"],
680 browser.test.onMessage.addListener(async () => {
681 let before = !!browser.tabs.captureVisibleTab;
682 let granted = await browser.permissions.request({
683 origins: ["<all_urls>"],
685 let after = !!browser.tabs.captureVisibleTab;
687 browser.test.sendMessage("results", [before, granted, after]);
692 await extension.startup();
694 await withHandlingUserInput(extension, async () => {
695 extension.sendMessage("request");
696 let [before, granted, after] = await extension.awaitMessage("results");
701 "captureVisibleTab() unavailable before optional permission request()"
703 equal(granted, true, "request() for optional permissions granted");
707 "captureVisibleTab() available after optional permission request()"
711 await extension.unload();
714 // Check when content_script match patterns are treated as optional origins.
715 async function test_content_script_is_optional(manifest_version) {
716 function background() {
717 browser.test.onMessage.addListener(async (msg, arg) => {
718 if (msg == "request") {
720 let result = await browser.permissions.request(arg);
721 browser.test.sendMessage("result", result);
723 browser.test.sendMessage("result", e.message);
726 if (msg === "getAll") {
727 let result = await browser.permissions.getAll(arg);
728 browser.test.sendMessage("granted", result);
733 const CS_ORIGIN = "https://test2.example.com/*";
735 let extension = ExtensionTestUtils.loadExtension({
741 matches: [CS_ORIGIN],
748 await extension.startup();
750 extension.sendMessage("getAll");
751 let initial = await extension.awaitMessage("granted");
752 deepEqual(initial.origins, [], "Nothing granted on install.");
754 await withHandlingUserInput(extension, async () => {
755 extension.sendMessage("request", {
757 origins: [CS_ORIGIN],
759 let result = await extension.awaitMessage("result");
760 if (manifest_version < 3) {
763 `Cannot request origin permission for ${CS_ORIGIN} since it was not declared in the manifest`,
764 "Content script match pattern is not a requestable optional origin in MV2"
767 equal(result, true, "request() for optional permissions succeeded");
771 extension.sendMessage("getAll");
772 let granted = await extension.awaitMessage("granted");
775 manifest_version < 3 ? [] : [CS_ORIGIN],
776 "Granted content script origin in MV3."
779 await extension.unload();
781 add_task(() => test_content_script_is_optional(2));
782 add_task(() => test_content_script_is_optional(3));
784 // Check that optional permissions are not included in update prompts
785 async function test_permissions_prompt(manifest_version) {
786 function background() {
787 browser.test.onMessage.addListener(async (msg, arg) => {
788 if (msg == "request") {
789 let result = await browser.permissions.request(arg);
790 browser.test.sendMessage("result", result);
792 if (msg === "getAll") {
793 let result = await browser.permissions.getAll(arg);
794 browser.test.sendMessage("granted", result);
799 let extension = ExtensionTestUtils.loadExtension({
802 name: "permissions test",
803 description: "permissions test",
807 permissions: ["tabs"],
808 host_permissions: ["https://test1.example.com/*"],
809 optional_permissions: ["clipboardWrite", "<all_urls>"],
813 matches: ["https://test2.example.com/*"],
818 useAddonManager: "permanent",
821 await extension.startup();
823 await withHandlingUserInput(extension, async () => {
824 extension.sendMessage("request", {
825 permissions: ["clipboardWrite"],
826 origins: ["https://test2.example.com/*"],
828 let result = await extension.awaitMessage("result");
829 equal(result, true, "request() for optional permissions succeeded");
832 if (manifest_version >= 3) {
833 await withHandlingUserInput(extension, async () => {
834 extension.sendMessage("request", {
835 origins: ["https://test1.example.com/*"],
837 let result = await extension.awaitMessage("result");
838 equal(result, true, "request() for host_permissions in mv3 succeeded");
842 const PERMS = ["history", "tabs"];
843 const ORIGINS = ["https://test1.example.com/*", "https://test3.example.com/"];
844 let xpi = AddonTestUtils.createTempWebExtensionFile({
847 name: "permissions test",
848 description: "permissions test",
852 applications: { gecko: { id: extension.id } },
855 host_permissions: ORIGINS,
856 optional_permissions: ["clipboardWrite", "<all_urls>"],
860 let install = await AddonManager.getInstallForFile(xpi);
863 install.promptHandler = info => {
865 return Promise.resolve();
868 await AddonTestUtils.promiseCompleteInstall(install);
869 await extension.awaitStartup();
871 notEqual(perminfo, undefined, "Permission handler was invoked");
872 let perms = perminfo.addon.userPermissions;
876 "Update details includes only manifest api permissions"
880 manifest_version < 3 ? ORIGINS : [],
881 "Update details includes only manifest origin permissions"
884 let EXPECTED = ["https://test1.example.com/*", "https://test2.example.com/*"];
885 if (manifest_version < 3) {
886 EXPECTED.push("https://test3.example.com/*");
889 extension.sendMessage("getAll");
890 let granted = await extension.awaitMessage("granted");
892 granted.origins.sort(),
894 "Granted origins persisted after update."
897 await extension.unload();
899 add_task(async function test_permissions_prompt_mv2() {
900 return test_permissions_prompt(2);
902 add_task(async function test_permissions_prompt_mv3() {
903 return test_permissions_prompt(3);
906 // Check that internal permissions can not be set and are not returned by the API.
907 add_task(async function test_internal_permissions() {
908 function background() {
909 browser.test.onMessage.addListener(async (method, arg) => {
911 if (method == "getAll") {
912 let perms = await browser.permissions.getAll();
913 browser.test.sendMessage("getAll.result", perms);
914 } else if (method == "contains") {
915 let result = await browser.permissions.contains(arg);
916 browser.test.sendMessage("contains.result", {
920 } else if (method == "request") {
921 let result = await browser.permissions.request(arg);
922 browser.test.sendMessage("request.result", {
926 } else if (method == "remove") {
927 let result = await browser.permissions.remove(arg);
928 browser.test.sendMessage("remove.result", result);
931 browser.test.sendMessage(`${method}.result`, {
933 message: err.message,
939 let extension = ExtensionTestUtils.loadExtension({
942 name: "permissions test",
943 description: "permissions test",
948 useAddonManager: "permanent",
949 incognitoOverride: "spanning",
952 let perm = "internal:privateBrowsingAllowed";
954 await extension.startup();
956 function call(method, arg) {
957 extension.sendMessage(method, arg);
958 return extension.awaitMessage(`${method}.result`);
961 let result = await call("getAll");
962 ok(!result.permissions.includes(perm), "internal not returned");
964 result = await call("contains", { permissions: [perm] });
966 /Type error for parameter permissions \(Error processing permissions/.test(
969 `Unable to check for internal permission: ${result.message}`
972 result = await call("remove", { permissions: [perm] });
974 /Type error for parameter permissions \(Error processing permissions/.test(
977 `Unable to remove for internal permission ${result.message}`
980 await withHandlingUserInput(extension, async () => {
981 result = await call("request", {
986 /Type error for parameter permissions \(Error processing permissions/.test(
989 `Unable to request internal permission ${result.message}`
993 await extension.unload();