Bug 1745818 - Treat host permissions as optional in mv3, r=robwu
[gecko.git] / toolkit / components / extensions / test / xpcshell / test_ext_permissions.js
blob3cea95569dd8bbc9c2b0bff60b35ed02de37b56a
1 "use strict";
3 const { AddonManager } = ChromeUtils.import(
4   "resource://gre/modules/AddonManager.jsm"
5 );
6 const { ExtensionPermissions } = ChromeUtils.import(
7   "resource://gre/modules/ExtensionPermissions.jsm"
8 );
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(
18   this,
19   "ExtensionParent",
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",
29   "XPCShell",
30   "1",
31   "42"
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;
43 });
45 add_task(async function test_permissions_on_startup() {
46   let extensionId = "@permissionTest";
47   let extension = ExtensionTestUtils.loadExtension({
48     manifest: {
49       applications: {
50         gecko: { id: extensionId },
51       },
52       permissions: ["tabs"],
53     },
54     useAddonManager: "permanent",
55     async background() {
56       let perms = await browser.permissions.getAll();
57       browser.test.sendMessage("permissions", perms);
58     },
59   });
60   let adding = {
61     permissions: ["internal:privateBrowsingAllowed"],
62     origins: [],
63   };
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: [] };
74   });
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: [] };
90   });
91   deepEqual(
92     manifestData.permissions,
93     adding.permissions,
94     "StartupCache.permissions contains permission"
95   );
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({
106   manifest_version,
107   granted_host_permissions,
108   useAddonManager,
109   expectAllGranted,
110 }) {
111   const REQUIRED_PERMISSIONS = ["downloads"];
112   const REQUIRED_ORIGINS = ["*://site.com/", "*://*.domain.com/"];
113   const REQUIRED_ORIGINS_EXPECTED = expectAllGranted
114     ? ["*://site.com/*", "*://*.domain.com/*"]
115     : [];
117   const OPTIONAL_PERMISSIONS = ["idle", "clipboardWrite"];
118   const OPTIONAL_ORIGINS = [
119     "http://optionalsite.com/",
120     "https://*.optionaldomain.com/",
121   ];
122   const OPTIONAL_ORIGINS_NORMALIZED = [
123     "http://optionalsite.com/*",
124     "https://*.optionaldomain.com/*",
125   ];
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") {
138         try {
139           let result = await browser.permissions.request(arg);
140           browser.test.sendMessage("request.result", {
141             status: "success",
142             result,
143           });
144         } catch (err) {
145           browser.test.sendMessage("request.result", {
146             status: "error",
147             message: err.message,
148           });
149         }
150       } else if (method == "remove") {
151         let result = await browser.permissions.remove(arg);
152         browser.test.sendMessage("remove.result", result);
153       }
154     });
155   }
157   let extension = ExtensionTestUtils.loadExtension({
158     background,
159     manifest: {
160       manifest_version,
161       permissions: REQUIRED_PERMISSIONS,
162       host_permissions: REQUIRED_ORIGINS,
163       optional_permissions: [...OPTIONAL_PERMISSIONS, ...OPTIONAL_ORIGINS],
164       granted_host_permissions,
165     },
166     useAddonManager,
167   });
169   await extension.startup();
171   function call(method, arg) {
172     extension.sendMessage(method, arg);
173     return extension.awaitMessage(`${method}.result`);
174   }
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}`);
183   }
184   for (let origin of REQUIRED_ORIGINS) {
185     result = await call("contains", { origins: [origin] });
186     equal(
187       result,
188       expectAllGranted,
189       `contains() returns true for fixed origin ${origin}`
190     );
191   }
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}`);
197   }
198   for (let origin of OPTIONAL_ORIGINS) {
199     result = await call("contains", { origins: [origin] });
200     equal(result, false, `contains() returns false for origin ${origin}`);
201   }
203   result = await call("contains", {
204     permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
205   });
206   equal(
207     result,
208     false,
209     "contains() returns false for a mix of available and unavailable permissions"
210   );
212   let perm = OPTIONAL_PERMISSIONS[0];
213   result = await call("request", { permissions: [perm] });
214   equal(
215     result.status,
216     "error",
217     "request() fails if not called from an event handler"
218   );
219   ok(
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"
222   );
223   result = await call("contains", { permissions: [perm] });
224   equal(
225     result,
226     false,
227     "Permission requested outside an event handler was not granted"
228   );
230   await withHandlingUserInput(extension, async () => {
231     result = await call("request", { permissions: ["notifications"] });
232     equal(
233       result.status,
234       "error",
235       "request() for permission not in optional_permissions should fail"
236     );
237     ok(
238       /since it was not declared in optional_permissions/.test(result.message),
239       "error message for undeclared optional_permission is reasonable"
240     );
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");
246     equal(
247       result.result,
248       false,
249       "request() returned false for rejected permission"
250     );
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;
257     let allOptional = {
258       permissions: OPTIONAL_PERMISSIONS,
259       origins: OPTIONAL_ORIGINS,
260     };
261     result = await call("request", allOptional);
262     equal(result.status, "success", "request() returned cleanly");
263     equal(
264       result.result,
265       true,
266       "request() returned true for accepted permissions"
267     );
269     // Verify that requesting a permission/origin in the wrong field fails
270     let originsAsPerms = {
271       permissions: OPTIONAL_ORIGINS,
272     };
273     let permsAsOrigins = {
274       origins: OPTIONAL_PERMISSIONS,
275     };
277     result = await call("request", originsAsPerms);
278     equal(
279       result.status,
280       "error",
281       "Requesting an origin as a permission should fail"
282     );
283     ok(
284       /Type error for parameter permissions \(Error processing permissions/.test(
285         result.message
286       ),
287       "Error message for origin as permission is reasonable"
288     );
290     result = await call("request", permsAsOrigins);
291     equal(
292       result.status,
293       "error",
294       "Requesting a permission as an origin should fail"
295     );
296     ok(
297       /Type error for parameter permissions \(Error processing origins/.test(
298         result.message
299       ),
300       "Error message for permission as origin is reasonable"
301     );
302   });
304   let allPermissions = {
305     permissions: [...REQUIRED_PERMISSIONS, ...OPTIONAL_PERMISSIONS],
306     origins: [...REQUIRED_ORIGINS_EXPECTED, ...OPTIONAL_ORIGINS_NORMALIZED],
307   };
309   result = await call("getAll");
310   deepEqual(
311     result,
312     allPermissions,
313     "getAll() returns required and runtime requested permissions"
314   );
316   result = await call("contains", allPermissions);
317   equal(
318     result,
319     true,
320     "contains() returns true for runtime requested permissions"
321   );
323   // Restart extension, verify permissions are still present.
324   if (useAddonManager === "permanent") {
325     await AddonTestUtils.promiseRestartManager();
326   } else {
327     // Manually reload for temporarily loaded.
328     await extension.addon.reload();
329   }
330   await extension.awaitBackgroundStarted();
332   result = await call("getAll");
333   deepEqual(
334     result,
335     allPermissions,
336     "Runtime requested permissions are still present after restart"
337   );
339   // Check remove()
340   result = await call("remove", { permissions: OPTIONAL_PERMISSIONS });
341   equal(result, true, "remove() succeeded");
343   let perms = {
344     permissions: REQUIRED_PERMISSIONS,
345     origins: [...REQUIRED_ORIGINS_EXPECTED, ...OPTIONAL_ORIGINS_NORMALIZED],
346   };
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({
363     manifest_version: 2,
364     useAddonManager: "permanent",
365     expectAllGranted: true,
366   });
369 add_task(function test_normal_mv3() {
370   return test_permissions({
371     manifest_version: 3,
372     useAddonManager: "permanent",
373     expectAllGranted: false,
374   });
377 add_task(function test_granted_for_temporary_mv3() {
378   return test_permissions({
379     manifest_version: 3,
380     granted_host_permissions: true,
381     useAddonManager: "temporary",
382     expectAllGranted: true,
383   });
386 add_task(async function test_granted_for_privileged_mv3() {
387   const { AddonSettings } = ChromeUtils.import(
388     "resource://gre/modules/addons/AddonSettings.jsm"
389   );
390   // This makes temporary loaded extensions Privileged.
391   Services.prefs.setBoolPref("extensions.experiments.enabled", true);
392   try {
393     await test_permissions({
394       manifest_version: 3,
395       granted_host_permissions: true,
396       useAddonManager: "temporary",
397       // Expect granted only in configurations where experiments are enabled.
398       expectAllGranted: AddonSettings.EXPERIMENTS_ENABLED,
399     });
400   } finally {
401     Services.prefs.setBoolPref("extensions.experiments.enabled", false);
402   }
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");
410     });
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);
416   }
418   const PERMS1 = {
419     permissions: ["clipboardRead", "tabs"],
420   };
421   const PERMS2 = {
422     origins: ["https://site2.com/*"],
423   };
425   let extension1 = ExtensionTestUtils.loadExtension({
426     background,
427     manifest: {
428       optional_permissions: PERMS1.permissions,
429     },
430     useAddonManager: "permanent",
431   });
432   let extension2 = ExtensionTestUtils.loadExtension({
433     background,
434     manifest: {
435       optional_permissions: PERMS2.origins,
436     },
437     useAddonManager: "permanent",
438   });
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");
449   });
451   await withHandlingUserInput(extension2, async () => {
452     extension2.sendMessage(PERMS2);
453     await extension2.awaitMessage("requested");
454   });
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");
467   }
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/",
482   ];
483   const OPTIONAL_PERMISSIONS = [
484     ...REQUIRED_PERMISSIONS,
485     ...REQUIRED_ORIGINS,
486     "clipboardRead",
487     "*://optional-host.com/",
488     "*://*.optional-domain.com/",
489   ];
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") {
500         window.close();
501       }
502     });
504     browser.test.sendMessage("page-ready");
505   }
507   let extension = ExtensionTestUtils.loadExtension({
508     background() {
509       browser.test.sendMessage("ready", browser.runtime.getURL("page.html"));
510     },
512     manifest: {
513       manifest_version,
514       permissions: REQUIRED_PERMISSIONS,
515       host_permissions: REQUIRED_ORIGINS,
516       optional_permissions: OPTIONAL_PERMISSIONS,
517       granted_host_permissions: true,
518     },
519     temporarilyInstalled: true,
521     files: {
522       "page.html": `<html><head>
523           <script src="page.js"><\/script>
524         </head></html>`,
526       "page.js": pageScript,
527     },
528   });
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");
542       equal(
543         optionalPermissionsPromptHandler.sawPrompt,
544         expectPrompt,
545         `Got ${expectPrompt ? "" : "no "}permission prompt for ${msg}`
546       );
547     }
549     await checkRequest(
550       { permissions: ["geolocation"] },
551       false,
552       "required permission from manifest"
553     );
554     await checkRequest(
555       { origins: ["http://required-host.com/"] },
556       false,
557       "origin permission from manifest"
558     );
559     await checkRequest(
560       { origins: ["http://host.required-domain.com/"] },
561       false,
562       "wildcard origin permission from manifest"
563     );
565     await checkRequest(
566       { permissions: ["clipboardRead"] },
567       true,
568       "optional permission"
569     );
570     await checkRequest(
571       { permissions: ["clipboardRead"] },
572       false,
573       "already granted optional permission"
574     );
576     await checkRequest(
577       { origins: ["http://optional-host.com/"] },
578       true,
579       "optional origin"
580     );
581     await checkRequest(
582       { origins: ["http://optional-host.com/"] },
583       false,
584       "already granted origin permission"
585     );
587     await checkRequest(
588       { origins: ["http://*.optional-domain.com/"] },
589       true,
590       "optional wildcard origin"
591     );
592     await checkRequest(
593       { origins: ["http://*.optional-domain.com/"] },
594       false,
595       "already granted optional wildcard origin"
596     );
597     await checkRequest(
598       { origins: ["http://host.optional-domain.com/"] },
599       false,
600       "host matching optional wildcard origin"
601     );
602     await page.close();
603   });
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 = [
617   "activeTab",
618   "activityLog",
619   "alarms",
620   "captivePortal",
621   "contextMenus",
622   "contextualIdentities",
623   "cookies",
624   "dns",
625   "geckoProfiler",
626   "identity",
627   "idle",
628   "menus",
629   "menus.overrideContext",
630   "mozillaAddons",
631   "networkStatus",
632   "normandyAddonStudy",
633   "scripting",
634   "search",
635   "storage",
636   "telemetry",
637   "theme",
638   "unlimitedStorage",
639   "urlbar",
640   "webRequest",
641   "webRequestBlocking",
642   "webRequestFilterResponse.serviceWorkerScript",
645 add_task(function test_permissions_have_localization_strings() {
646   let noPromptNames = Schemas.getPermissionNames([
647     "PermissionNoPrompt",
648     "OptionalPermissionNoPrompt",
649   ]);
650   Assert.deepEqual(
651     GRANTED_WITHOUT_USER_PROMPT,
652     noPromptNames,
653     "List of no-prompt permissions is correct."
654   );
656   const bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
658   for (const perm of Schemas.getPermissionNames()) {
659     try {
660       const str = bundle.GetStringFromName(`webextPerms.description.${perm}`);
662       ok(str.length, `Found localization string for '${perm}' permission`);
663     } catch (e) {
664       ok(
665         GRANTED_WITHOUT_USER_PROMPT.includes(perm),
666         `Permission '${perm}' intentionally granted without prompting the user`
667       );
668     }
669   }
672 // Check <all_urls> used as an optional API permission.
673 add_task(async function test_optional_all_urls() {
674   let extension = ExtensionTestUtils.loadExtension({
675     manifest: {
676       optional_permissions: ["<all_urls>"],
677     },
679     background() {
680       browser.test.onMessage.addListener(async () => {
681         let before = !!browser.tabs.captureVisibleTab;
682         let granted = await browser.permissions.request({
683           origins: ["<all_urls>"],
684         });
685         let after = !!browser.tabs.captureVisibleTab;
687         browser.test.sendMessage("results", [before, granted, after]);
688       });
689     },
690   });
692   await extension.startup();
694   await withHandlingUserInput(extension, async () => {
695     extension.sendMessage("request");
696     let [before, granted, after] = await extension.awaitMessage("results");
698     equal(
699       before,
700       false,
701       "captureVisibleTab() unavailable before optional permission request()"
702     );
703     equal(granted, true, "request() for optional permissions granted");
704     equal(
705       after,
706       true,
707       "captureVisibleTab() available after optional permission request()"
708     );
709   });
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") {
719         try {
720           let result = await browser.permissions.request(arg);
721           browser.test.sendMessage("result", result);
722         } catch (e) {
723           browser.test.sendMessage("result", e.message);
724         }
725       }
726       if (msg === "getAll") {
727         let result = await browser.permissions.getAll(arg);
728         browser.test.sendMessage("granted", result);
729       }
730     });
731   }
733   const CS_ORIGIN = "https://test2.example.com/*";
735   let extension = ExtensionTestUtils.loadExtension({
736     background,
737     manifest: {
738       manifest_version,
739       content_scripts: [
740         {
741           matches: [CS_ORIGIN],
742           js: [],
743         },
744       ],
745     },
746   });
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", {
756       permissions: [],
757       origins: [CS_ORIGIN],
758     });
759     let result = await extension.awaitMessage("result");
760     if (manifest_version < 3) {
761       equal(
762         result,
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"
765       );
766     } else {
767       equal(result, true, "request() for optional permissions succeeded");
768     }
769   });
771   extension.sendMessage("getAll");
772   let granted = await extension.awaitMessage("granted");
773   deepEqual(
774     granted.origins,
775     manifest_version < 3 ? [] : [CS_ORIGIN],
776     "Granted content script origin in MV3."
777   );
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);
791       }
792       if (msg === "getAll") {
793         let result = await browser.permissions.getAll(arg);
794         browser.test.sendMessage("granted", result);
795       }
796     });
797   }
799   let extension = ExtensionTestUtils.loadExtension({
800     background,
801     manifest: {
802       name: "permissions test",
803       description: "permissions test",
804       manifest_version,
805       version: "1.0",
807       permissions: ["tabs"],
808       host_permissions: ["https://test1.example.com/*"],
809       optional_permissions: ["clipboardWrite", "<all_urls>"],
811       content_scripts: [
812         {
813           matches: ["https://test2.example.com/*"],
814           js: [],
815         },
816       ],
817     },
818     useAddonManager: "permanent",
819   });
821   await extension.startup();
823   await withHandlingUserInput(extension, async () => {
824     extension.sendMessage("request", {
825       permissions: ["clipboardWrite"],
826       origins: ["https://test2.example.com/*"],
827     });
828     let result = await extension.awaitMessage("result");
829     equal(result, true, "request() for optional permissions succeeded");
830   });
832   if (manifest_version >= 3) {
833     await withHandlingUserInput(extension, async () => {
834       extension.sendMessage("request", {
835         origins: ["https://test1.example.com/*"],
836       });
837       let result = await extension.awaitMessage("result");
838       equal(result, true, "request() for host_permissions in mv3 succeeded");
839     });
840   }
842   const PERMS = ["history", "tabs"];
843   const ORIGINS = ["https://test1.example.com/*", "https://test3.example.com/"];
844   let xpi = AddonTestUtils.createTempWebExtensionFile({
845     background,
846     manifest: {
847       name: "permissions test",
848       description: "permissions test",
849       manifest_version,
850       version: "2.0",
852       applications: { gecko: { id: extension.id } },
854       permissions: PERMS,
855       host_permissions: ORIGINS,
856       optional_permissions: ["clipboardWrite", "<all_urls>"],
857     },
858   });
860   let install = await AddonManager.getInstallForFile(xpi);
862   let perminfo;
863   install.promptHandler = info => {
864     perminfo = info;
865     return Promise.resolve();
866   };
868   await AddonTestUtils.promiseCompleteInstall(install);
869   await extension.awaitStartup();
871   notEqual(perminfo, undefined, "Permission handler was invoked");
872   let perms = perminfo.addon.userPermissions;
873   deepEqual(
874     perms.permissions,
875     PERMS,
876     "Update details includes only manifest api permissions"
877   );
878   deepEqual(
879     perms.origins,
880     manifest_version < 3 ? ORIGINS : [],
881     "Update details includes only manifest origin permissions"
882   );
884   let EXPECTED = ["https://test1.example.com/*", "https://test2.example.com/*"];
885   if (manifest_version < 3) {
886     EXPECTED.push("https://test3.example.com/*");
887   }
889   extension.sendMessage("getAll");
890   let granted = await extension.awaitMessage("granted");
891   deepEqual(
892     granted.origins.sort(),
893     EXPECTED,
894     "Granted origins persisted after update."
895   );
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) => {
910       try {
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", {
917             status: "success",
918             result,
919           });
920         } else if (method == "request") {
921           let result = await browser.permissions.request(arg);
922           browser.test.sendMessage("request.result", {
923             status: "success",
924             result,
925           });
926         } else if (method == "remove") {
927           let result = await browser.permissions.remove(arg);
928           browser.test.sendMessage("remove.result", result);
929         }
930       } catch (err) {
931         browser.test.sendMessage(`${method}.result`, {
932           status: "error",
933           message: err.message,
934         });
935       }
936     });
937   }
939   let extension = ExtensionTestUtils.loadExtension({
940     background,
941     manifest: {
942       name: "permissions test",
943       description: "permissions test",
944       manifest_version: 2,
945       version: "1.0",
946       permissions: [],
947     },
948     useAddonManager: "permanent",
949     incognitoOverride: "spanning",
950   });
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`);
959   }
961   let result = await call("getAll");
962   ok(!result.permissions.includes(perm), "internal not returned");
964   result = await call("contains", { permissions: [perm] });
965   ok(
966     /Type error for parameter permissions \(Error processing permissions/.test(
967       result.message
968     ),
969     `Unable to check for internal permission: ${result.message}`
970   );
972   result = await call("remove", { permissions: [perm] });
973   ok(
974     /Type error for parameter permissions \(Error processing permissions/.test(
975       result.message
976     ),
977     `Unable to remove for internal permission ${result.message}`
978   );
980   await withHandlingUserInput(extension, async () => {
981     result = await call("request", {
982       permissions: [perm],
983       origins: [],
984     });
985     ok(
986       /Type error for parameter permissions \(Error processing permissions/.test(
987         result.message
988       ),
989       `Unable to request internal permission ${result.message}`
990     );
991   });
993   await extension.unload();