1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * PermissionUI is responsible for exposing both a prototype
7 * PermissionPrompt that can be used by arbitrary browser
8 * components and add-ons, but also hosts the implementations of
9 * built-in permission prompts.
11 * If you're developing a feature that requires web content to ask
12 * for special permissions from the user, this module is for you.
14 * Suppose a system add-on wants to add a new prompt for a new request
15 * for getting more low-level access to the user's sound card, and the
16 * permission request is coming up from content by way of the
17 * nsContentPermissionHelper. The system add-on could then do the following:
19 * const { Integration } = ChromeUtils.importESModule(
20 * "resource://gre/modules/Integration.sys.mjs"
22 * const { PermissionUI } = ChromeUtils.importESModule(
23 * "resource:///modules/PermissionUI.sys.mjs"
26 * const SoundCardIntegration = base => {
27 * let soundCardObj = {
28 * createPermissionPrompt(type, request) {
29 * if (type != "sound-api") {
30 * return super.createPermissionPrompt(...arguments);
33 * let permissionPrompt = {
34 * get permissionKey() {
35 * return "sound-permission";
37 * // etc - see the documentation for PermissionPrompt for
38 * // a better idea of what things one can and should override.
40 * Object.setPrototypeOf(
42 * PermissionUI.PermissionPromptForRequest
44 * return permissionPrompt;
47 * Object.setPrototypeOf(soundCardObj, base);
48 * return soundCardObj;
52 * Integration.contentPermission.register(SoundCardIntegration);
55 * Integration.contentPermission.unregister(SoundCardIntegration);
57 * Note that PermissionPromptForRequest must be used as the
58 * prototype, since the prompt is wrapping an nsIContentPermissionRequest,
59 * and going through nsIContentPermissionPrompt.
61 * It is, however, possible to take advantage of PermissionPrompt without
62 * having to go through nsIContentPermissionPrompt or with a
63 * nsIContentPermissionRequest. The PermissionPrompt can be
64 * imported, subclassed, and have prompt() called directly, without
65 * the caller having called into createPermissionPrompt.
67 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
71 ChromeUtils.defineESModuleGetters(lazy, {
72 AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
73 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
74 SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
77 XPCOMUtils.defineLazyServiceGetter(
80 "@mozilla.org/network/idn-service;1",
83 XPCOMUtils.defineLazyServiceGetter(
85 "ContentPrefService2",
86 "@mozilla.org/content-pref/service;1",
87 "nsIContentPrefService2"
89 ChromeUtils.defineLazyGetter(lazy, "gBrowserBundle", function () {
90 return Services.strings.createBundle(
91 "chrome://browser/locale/browser.properties"
95 import { SITEPERMS_ADDON_PROVIDER_PREF } from "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs";
97 XPCOMUtils.defineLazyPreferenceGetter(
99 "sitePermsAddonsProviderEnabled",
100 SITEPERMS_ADDON_PROVIDER_PREF,
105 * PermissionPrompt should be subclassed by callers that
106 * want to display prompts to the user. See each method and property
107 * below for guidance on what to override.
109 * Note that if you're creating a prompt for an
110 * nsIContentPermissionRequest, you'll want to subclass
111 * PermissionPromptForRequest instead.
113 class PermissionPrompt {
115 * Returns the associated <xul:browser> for the request. This should
116 * work for the e10s and non-e10s case.
118 * Subclasses must override this.
120 * @return {<xul:browser>}
123 throw new Error("Not implemented.");
127 * Returns the nsIPrincipal associated with the request.
129 * Subclasses must override this.
131 * @return {nsIPrincipal}
134 throw new Error("Not implemented.");
138 * Indicates the type of the permission request from content. This type might
139 * be different from the permission key used in the permissions database.
146 * If the nsIPermissionManager is being queried and written
147 * to for this permission request, set this to the key to be
148 * used. If this is undefined, no integration with temporary
149 * permissions infrastructure will be provided.
151 * Note that if a permission is set, in any follow-up
152 * prompting within the expiry window of that permission,
153 * the prompt will be skipped and the allow or deny choice
154 * will be selected automatically.
156 get permissionKey() {
161 * If true, user permissions will be read from and written to.
162 * When this is false, we still provide integration with
163 * infrastructure such as temporary permissions. permissionKey should
164 * still return a valid name in those cases for that integration to work.
166 get usePermissionManager() {
171 * Indicates what URI should be used as the scope when using temporary
172 * permissions. If undefined, it defaults to the browser.currentURI.
174 get temporaryPermissionURI() {
179 * These are the options that will be passed to the PopupNotification when it
180 * is shown. See the documentation of `PopupNotifications_show` in
181 * PopupNotifications.sys.mjs for details.
183 * Note that prompt() will automatically set displayURI to
184 * be the URI of the requesting pricipal, unless the displayURI is exactly
192 * If true, automatically denied permission requests will
193 * spawn a "post-prompt" that allows the user to correct the
194 * automatic denial by giving permanent permission access to
197 * Note that if this function returns true, the permissionKey
198 * and postPromptActions attributes must be implemented.
200 get postPromptEnabled() {
205 * If true, the prompt will be cancelled automatically unless
206 * request.hasValidTransientUserGestureActivation is true.
208 get requiresUserInput() {
213 * PopupNotification requires a unique ID to open the notification.
214 * You must return a unique ID string here, for which PopupNotification
215 * will then create a <xul:popupnotification> node with the ID
216 * "<notificationID>-notification".
218 * If there's a custom <xul:popupnotification> you're hoping to show,
219 * then you need to make sure its ID has the "-notification" suffix,
220 * and then return the prefix here.
222 * See PopupNotifications.sys.mjs for more details.
225 * The unique ID that will be used to as the
226 * "<unique ID>-notification" ID for the <xul:popupnotification>
229 get notificationID() {
230 throw new Error("Not implemented.");
234 * The ID of the element to anchor the PopupNotification to.
239 return "default-notification-icon";
243 * The message to show to the user in the PopupNotification, see
244 * `PopupNotifications_show` in PopupNotifications.sys.mjs.
246 * Subclasses must override this.
251 throw new Error("Not implemented.");
255 * Provides the preferred name to use in the permission popups,
256 * based on the principal URI (the URI.hostPort for any URI scheme
257 * besides the moz-extension one which should default to the
260 getPrincipalName(principal = this.principal) {
261 if (principal.addonPolicy) {
262 return principal.addonPolicy.name;
265 return principal.hostPort;
269 * This will be called if the request is to be cancelled.
271 * Subclasses only need to override this if they provide a
275 throw new Error("Not implemented.");
279 * This will be called if the request is to be allowed.
281 * Subclasses only need to override this if they provide a
285 throw new Error("Not implemented.");
289 * The actions that will be displayed in the PopupNotification
290 * via a dropdown menu. The first item in this array will be
291 * the default selection. Each action is an Object with the
292 * following properties:
295 * The label that will be displayed for this choice.
296 * accessKey (string):
297 * The access key character that will be used for this choice.
298 * action (SitePermissions state)
299 * The action that will be associated with this choice.
300 * This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
301 * scope (SitePermissions scope)
302 * The scope of the associated action (e.g. SitePermissions.SCOPE_PERSISTENT)
304 * callback (function, optional)
305 * A callback function that will fire if the user makes this choice, with
306 * a single parameter, state. State is an Object that contains the property
307 * checkboxChecked, which identifies whether the checkbox to remember this
308 * decision was checked.
310 get promptActions() {
315 * The actions that will be displayed in the PopupNotification
316 * for post-prompt notifications via a dropdown menu.
317 * The first item in this array will be the default selection.
318 * Each action is an Object with the following properties:
321 * The label that will be displayed for this choice.
322 * accessKey (string):
323 * The access key character that will be used for this choice.
324 * action (SitePermissions state)
325 * The action that will be associated with this choice.
326 * This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
327 * Note that the scope of this action will always be persistent.
329 * callback (function, optional)
330 * A callback function that will fire if the user makes this choice.
332 get postPromptActions() {
337 * If the prompt will be shown to the user, this callback will
338 * be called just before. Subclasses may want to override this
339 * in order to, for example, bump a counter Telemetry probe for
340 * how often a particular permission request is seen.
342 * If this returns false, it cancels the process of showing the prompt. In
343 * that case, it is the responsibility of the onBeforeShow() implementation
344 * to ensure that allow() or cancel() are called on the object appropriately.
351 * If the prompt was shown to the user, this callback will be called just
352 * after it's been shown.
357 * If the prompt was shown to the user, this callback will be called just
358 * after it's been hidden.
363 * Will determine if a prompt should be shown to the user, and if so,
366 * If a permissionKey is defined prompt() might automatically
367 * allow or cancel itself based on the user's current
368 * permission settings without displaying the prompt.
370 * If the permission is not already set and the <xul:browser> that the request
371 * is associated with does not belong to a browser window with the
372 * PopupNotifications global set, the prompt request is ignored.
375 // We ignore requests from non-nsIStandardURLs
376 let requestingURI = this.principal.URI;
377 if (!(requestingURI instanceof Ci.nsIStandardURL)) {
381 if (this.usePermissionManager && this.permissionKey) {
382 // If we're reading and setting permissions, then we need
383 // to check to see if we already have a permission setting
384 // for this particular principal.
385 let { state } = lazy.SitePermissions.getForPrincipal(
389 this.temporaryPermissionURI
392 if (state == lazy.SitePermissions.BLOCK) {
398 state == lazy.SitePermissions.ALLOW &&
399 !this.request.isRequestDelegatedToUnsafeThirdParty
404 } else if (this.permissionKey) {
405 // If we're reading a permission which already has a temporary value,
406 // see if we can use the temporary value.
407 let { state } = lazy.SitePermissions.getForPrincipal(
411 this.temporaryPermissionURI
414 if (state == lazy.SitePermissions.BLOCK) {
421 this.requiresUserInput &&
422 !this.request.hasValidTransientUserGestureActivation
424 if (this.postPromptEnabled) {
431 let chromeWin = this.browser.ownerGlobal;
432 if (!chromeWin.PopupNotifications) {
437 // Transform the PermissionPrompt actions into PopupNotification actions.
438 let popupNotificationActions = [];
439 for (let promptAction of this.promptActions) {
441 label: promptAction.label,
442 accessKey: promptAction.accessKey,
444 if (promptAction.callback) {
445 promptAction.callback();
448 if (this.usePermissionManager && this.permissionKey) {
450 (state && state.checkboxChecked && state.source != "esc-press") ||
451 promptAction.scope == lazy.SitePermissions.SCOPE_PERSISTENT
453 // Permanently store permission.
454 let scope = lazy.SitePermissions.SCOPE_PERSISTENT;
455 // Only remember permission for session if in PB mode.
456 if (lazy.PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
457 scope = lazy.SitePermissions.SCOPE_SESSION;
459 lazy.SitePermissions.setForPrincipal(
465 } else if (promptAction.action == lazy.SitePermissions.BLOCK) {
466 // Temporarily store BLOCK permissions only
467 // SitePermissions does not consider subframes when storing temporary
468 // permissions on a tab, thus storing ALLOW could be exploited.
469 lazy.SitePermissions.setForPrincipal(
473 lazy.SitePermissions.SCOPE_TEMPORARY,
478 // Grant permission if action is ALLOW.
479 if (promptAction.action == lazy.SitePermissions.ALLOW) {
484 } else if (this.permissionKey) {
485 // TODO: Add support for permitTemporaryAllow
486 if (promptAction.action == lazy.SitePermissions.BLOCK) {
487 // Temporarily store BLOCK permissions.
488 // We don't consider subframes when storing temporary
489 // permissions on a tab, thus storing ALLOW could be exploited.
490 lazy.SitePermissions.setForPrincipal(
494 lazy.SitePermissions.SCOPE_TEMPORARY,
501 if (promptAction.dismiss) {
502 action.dismiss = promptAction.dismiss;
505 popupNotificationActions.push(action);
508 this.#showNotification(popupNotificationActions);
512 let browser = this.browser;
513 let principal = this.principal;
514 let chromeWin = browser.ownerGlobal;
515 if (!chromeWin.PopupNotifications) {
519 if (!this.permissionKey) {
520 throw new Error("permissionKey is required to show a post-prompt");
523 if (!this.postPromptActions) {
524 throw new Error("postPromptActions are required to show a post-prompt");
527 // Transform the PermissionPrompt actions into PopupNotification actions.
528 let popupNotificationActions = [];
529 for (let promptAction of this.postPromptActions) {
531 label: promptAction.label,
532 accessKey: promptAction.accessKey,
534 if (promptAction.callback) {
535 promptAction.callback();
538 // Post-prompt permissions are stored permanently by default.
539 // Since we can not reply to the original permission request anymore,
540 // the page will need to listen for permission changes which are triggered
541 // by permanent entries in the permission manager.
542 let scope = lazy.SitePermissions.SCOPE_PERSISTENT;
543 // Only remember permission for session if in PB mode.
544 if (lazy.PrivateBrowsingUtils.isBrowserPrivate(browser)) {
545 scope = lazy.SitePermissions.SCOPE_SESSION;
547 lazy.SitePermissions.setForPrincipal(
555 popupNotificationActions.push(action);
558 // Post-prompt animation
559 if (!chromeWin.gReduceMotion) {
560 let anchor = chromeWin.document.getElementById(this.anchorID);
561 // Only show the animation on the first request, not after e.g. tab switching.
562 anchor.addEventListener(
564 () => anchor.removeAttribute("animate"),
567 anchor.setAttribute("animate", "true");
570 this.#showNotification(popupNotificationActions, true);
573 #showNotification(actions, postPrompt = false) {
574 let chromeWin = this.browser.ownerGlobal;
575 let mainAction = actions.length ? actions[0] : null;
576 let secondaryActions = actions.splice(1);
578 let options = this.popupOptions;
580 if (!options.hasOwnProperty("displayURI") || options.displayURI) {
581 options.displayURI = this.principal.URI;
585 // Permission prompts are always persistent; the close button is controlled by a pref.
586 options.persistent = true;
587 options.hideClose = true;
590 options.eventCallback = (topic, nextRemovalReason, isCancel) => {
591 // When the docshell of the browser is aboout to be swapped to another one,
592 // the "swapping" event is called. Returning true causes the notification
593 // to be moved to the new browser.
594 if (topic == "swapping") {
597 // The prompt has been shown, notify the PermissionUI.
598 // onShown() is currently not called for post-prompts,
599 // because there is no prompt that would make use of this.
600 // You can remove this restriction if you need it, but be
601 // mindful of other consumers.
602 if (topic == "shown" && !postPrompt) {
605 // The prompt has been removed, notify the PermissionUI.
606 // onAfterShow() is currently not called for post-prompts,
607 // because there is no prompt that would make use of this.
608 // You can remove this restriction if you need it, but be
609 // mindful of other consumers.
610 if (topic == "removed" && !postPrompt) {
619 // Post-prompts show up as dismissed.
620 options.dismissed = postPrompt;
622 // onBeforeShow() is currently not called for post-prompts,
623 // because there is no prompt that would make use of this.
624 // You can remove this restriction if you need it, but be
625 // mindful of other consumers.
626 if (postPrompt || this.onBeforeShow() !== false) {
627 chromeWin.PopupNotifications.show(
641 * A subclass of PermissionPrompt that assumes
642 * that this.request is an nsIContentPermissionRequest
643 * and fills in some of the required properties on the
644 * PermissionPrompt. For callers that are wrapping an
645 * nsIContentPermissionRequest, this should be subclassed
646 * rather than PermissionPrompt.
648 class PermissionPromptForRequest extends PermissionPrompt {
650 // In the e10s-case, the <xul:browser> will be at request.element.
651 // In the single-process case, we have to use some XPCOM incantations
652 // to resolve to the <xul:browser>.
653 if (this.request.element) {
654 return this.request.element;
656 return this.request.window.docShell.chromeEventHandler;
660 let request = this.request.QueryInterface(Ci.nsIContentPermissionRequest);
661 return request.getDelegatePrincipal(this.type);
665 this.request.cancel();
669 this.request.allow(choices);
674 * A subclass of PermissionPromptForRequest that prompts
675 * for a Synthetic SitePermsAddon addon type and starts a synthetic
676 * addon install flow.
678 class SitePermsAddonInstallRequest extends PermissionPromptForRequest {
680 // fallback to regular permission prompt for localhost,
681 // or when the SitePermsAddonProvider is not enabled.
682 if (this.principal.isLoopbackHost || !lazy.sitePermsAddonsProviderEnabled) {
687 // Otherwise, we'll use the addon install flow.
688 lazy.AddonManager.installSitePermsAddonFromWebpage(
699 // Print an error message in the console to give more information to the developer.
700 let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
702 this.getInstallErrorMessage(err) ||
703 `${this.permName} access was rejected: ${err.message}`;
705 let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
706 scriptError.initWithWindowID(
713 "content javascript",
714 this.browser.browsingContext.currentWindowGlobal.innerWindowId
716 Services.console.logMessage(scriptError);
722 * Returns an error message that will be printed to the console given a passed Component.Exception.
723 * This should be overriden by children classes.
725 * @param {Components.Exception} err
726 * @returns {String} The error message
728 getInstallErrorMessage() {
734 * Creates a PermissionPrompt for a nsIContentPermissionRequest for
735 * the GeoLocation API.
737 * @param request (nsIContentPermissionRequest)
738 * The request for a permission from content.
740 class GeolocationPermissionPrompt extends PermissionPromptForRequest {
741 constructor(request) {
743 this.request = request;
750 get permissionKey() {
755 let pref = "browser.geolocation.warning.infoURL";
757 learnMoreURL: Services.urlFormatter.formatURLPref(pref),
759 name: this.getPrincipalName(),
762 // Don't offer "always remember" action in PB mode
764 show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
765 this.browser.ownerGlobal
769 if (this.request.isRequestDelegatedToUnsafeThirdParty) {
770 // Second name should be the third party origin
771 options.secondName = this.getPrincipalName(this.request.principal);
772 options.checkbox = { show: false };
775 if (options.checkbox.show) {
776 options.checkbox.label = lazy.gBrowserBundle.GetStringFromName(
777 "geolocation.remember"
784 get notificationID() {
785 return "geolocation";
789 return "geo-notification-icon";
793 if (this.principal.schemeIs("file")) {
794 return lazy.gBrowserBundle.GetStringFromName(
795 "geolocation.shareWithFile4"
799 if (this.request.isRequestDelegatedToUnsafeThirdParty) {
800 return lazy.gBrowserBundle.formatStringFromName(
801 "geolocation.shareWithSiteUnsafeDelegation2",
806 return lazy.gBrowserBundle.formatStringFromName(
807 "geolocation.shareWithSite4",
812 get promptActions() {
815 label: lazy.gBrowserBundle.GetStringFromName("geolocation.allow"),
816 accessKey: lazy.gBrowserBundle.GetStringFromName(
817 "geolocation.allow.accesskey"
819 action: lazy.SitePermissions.ALLOW,
822 label: lazy.gBrowserBundle.GetStringFromName("geolocation.block"),
823 accessKey: lazy.gBrowserBundle.GetStringFromName(
824 "geolocation.block.accesskey"
826 action: lazy.SitePermissions.BLOCK,
831 #updateGeoSharing(state) {
832 let gBrowser = this.browser.ownerGlobal.gBrowser;
833 if (gBrowser == null) {
836 gBrowser.updateBrowserSharing(this.browser, { geo: state });
838 // Update last access timestamp
841 host = this.browser.currentURI.host;
845 if (host == null || host == "") {
848 lazy.ContentPrefService2.set(
849 this.browser.currentURI.host,
850 "permissions.geoLocation.lastAccess",
851 new Date().toString(),
852 this.browser.loadContext
857 this.#updateGeoSharing(true);
858 super.allow(...args);
862 this.#updateGeoSharing(false);
863 super.cancel(...args);
868 * Creates a PermissionPrompt for a nsIContentPermissionRequest for
871 * @param request (nsIContentPermissionRequest)
872 * The request for a permission from content.
874 class XRPermissionPrompt extends PermissionPromptForRequest {
875 constructor(request) {
877 this.request = request;
884 get permissionKey() {
889 let pref = "browser.xr.warning.infoURL";
891 learnMoreURL: Services.urlFormatter.formatURLPref(pref),
893 name: this.getPrincipalName(),
896 // Don't offer "always remember" action in PB mode
898 show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
899 this.browser.ownerGlobal
903 if (options.checkbox.show) {
904 options.checkbox.label =
905 lazy.gBrowserBundle.GetStringFromName("xr.remember");
911 get notificationID() {
916 return "xr-notification-icon";
920 if (this.principal.schemeIs("file")) {
921 return lazy.gBrowserBundle.GetStringFromName("xr.shareWithFile4");
924 return lazy.gBrowserBundle.formatStringFromName("xr.shareWithSite4", [
929 get promptActions() {
932 label: lazy.gBrowserBundle.GetStringFromName("xr.allow2"),
933 accessKey: lazy.gBrowserBundle.GetStringFromName("xr.allow2.accesskey"),
934 action: lazy.SitePermissions.ALLOW,
937 label: lazy.gBrowserBundle.GetStringFromName("xr.block"),
938 accessKey: lazy.gBrowserBundle.GetStringFromName("xr.block.accesskey"),
939 action: lazy.SitePermissions.BLOCK,
944 #updateXRSharing(state) {
945 let gBrowser = this.browser.ownerGlobal.gBrowser;
946 if (gBrowser == null) {
949 gBrowser.updateBrowserSharing(this.browser, { xr: state });
951 let devicePermOrigins = this.browser.getDevicePermissionOrigins("xr");
953 devicePermOrigins.delete(this.principal.origin);
956 devicePermOrigins.add(this.principal.origin);
960 this.#updateXRSharing(true);
961 super.allow(...args);
965 this.#updateXRSharing(false);
966 super.cancel(...args);
971 * Creates a PermissionPrompt for a nsIContentPermissionRequest for
972 * the Desktop Notification API.
974 * @param request (nsIContentPermissionRequest)
975 * The request for a permission from content.
976 * @return {PermissionPrompt} (see documentation in header)
978 class DesktopNotificationPermissionPrompt extends PermissionPromptForRequest {
979 constructor(request) {
981 this.request = request;
983 XPCOMUtils.defineLazyPreferenceGetter(
986 "dom.webnotifications.requireuserinteraction"
988 XPCOMUtils.defineLazyPreferenceGetter(
991 "permissions.desktop-notification.postPrompt.enabled"
993 XPCOMUtils.defineLazyPreferenceGetter(
996 "permissions.desktop-notification.notNow.enabled"
1001 return "desktop-notification";
1004 get permissionKey() {
1005 return "desktop-notification";
1008 get popupOptions() {
1010 Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
1015 name: this.getPrincipalName(),
1019 get notificationID() {
1020 return "web-notifications";
1024 return "web-notifications-notification-icon";
1028 return lazy.gBrowserBundle.formatStringFromName(
1029 "webNotifications.receiveFromSite3",
1034 get promptActions() {
1037 label: lazy.gBrowserBundle.GetStringFromName("webNotifications.allow2"),
1038 accessKey: lazy.gBrowserBundle.GetStringFromName(
1039 "webNotifications.allow2.accesskey"
1041 action: lazy.SitePermissions.ALLOW,
1042 scope: lazy.SitePermissions.SCOPE_PERSISTENT,
1045 if (this.notNowEnabled) {
1047 label: lazy.gBrowserBundle.GetStringFromName("webNotifications.notNow"),
1048 accessKey: lazy.gBrowserBundle.GetStringFromName(
1049 "webNotifications.notNow.accesskey"
1051 action: lazy.SitePermissions.BLOCK,
1055 let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
1059 label: isBrowserPrivate
1060 ? lazy.gBrowserBundle.GetStringFromName("webNotifications.block")
1061 : lazy.gBrowserBundle.GetStringFromName("webNotifications.alwaysBlock"),
1062 accessKey: isBrowserPrivate
1063 ? lazy.gBrowserBundle.GetStringFromName(
1064 "webNotifications.block.accesskey"
1066 : lazy.gBrowserBundle.GetStringFromName(
1067 "webNotifications.alwaysBlock.accesskey"
1069 action: lazy.SitePermissions.BLOCK,
1070 scope: isBrowserPrivate
1071 ? lazy.SitePermissions.SCOPE_SESSION
1072 : lazy.SitePermissions.SCOPE_PERSISTENT,
1077 get postPromptActions() {
1080 label: lazy.gBrowserBundle.GetStringFromName("webNotifications.allow2"),
1081 accessKey: lazy.gBrowserBundle.GetStringFromName(
1082 "webNotifications.allow2.accesskey"
1084 action: lazy.SitePermissions.ALLOW,
1088 let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
1092 label: isBrowserPrivate
1093 ? lazy.gBrowserBundle.GetStringFromName("webNotifications.block")
1094 : lazy.gBrowserBundle.GetStringFromName("webNotifications.alwaysBlock"),
1095 accessKey: isBrowserPrivate
1096 ? lazy.gBrowserBundle.GetStringFromName(
1097 "webNotifications.block.accesskey"
1099 : lazy.gBrowserBundle.GetStringFromName(
1100 "webNotifications.alwaysBlock.accesskey"
1102 action: lazy.SitePermissions.BLOCK,
1109 * Creates a PermissionPrompt for a nsIContentPermissionRequest for
1110 * the persistent-storage API.
1112 * @param request (nsIContentPermissionRequest)
1113 * The request for a permission from content.
1115 class PersistentStoragePermissionPrompt extends PermissionPromptForRequest {
1116 constructor(request) {
1118 this.request = request;
1122 return "persistent-storage";
1125 get permissionKey() {
1126 return "persistent-storage";
1129 get popupOptions() {
1131 Services.urlFormatter.formatURLPref("app.support.baseURL") +
1132 "storage-permissions";
1136 name: this.getPrincipalName(),
1140 get notificationID() {
1141 return "persistent-storage";
1145 return "persistent-storage-notification-icon";
1149 return lazy.gBrowserBundle.formatStringFromName(
1150 "persistentStorage.allowWithSite2",
1155 get promptActions() {
1158 label: lazy.gBrowserBundle.GetStringFromName("persistentStorage.allow"),
1159 accessKey: lazy.gBrowserBundle.GetStringFromName(
1160 "persistentStorage.allow.accesskey"
1162 action: Ci.nsIPermissionManager.ALLOW_ACTION,
1163 scope: lazy.SitePermissions.SCOPE_PERSISTENT,
1166 label: lazy.gBrowserBundle.GetStringFromName(
1167 "persistentStorage.block.label"
1169 accessKey: lazy.gBrowserBundle.GetStringFromName(
1170 "persistentStorage.block.accesskey"
1172 action: lazy.SitePermissions.BLOCK,
1179 * Creates a PermissionPrompt for a nsIContentPermissionRequest for
1182 * @param request (nsIContentPermissionRequest)
1183 * The request for a permission from content.
1185 class MIDIPermissionPrompt extends SitePermsAddonInstallRequest {
1186 constructor(request) {
1188 this.request = request;
1189 let types = request.types.QueryInterface(Ci.nsIArray);
1190 let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
1192 !!perm.options.length &&
1193 perm.options.queryElementAt(0, Ci.nsISupportsString) == "sysex";
1194 this.permName = "midi";
1195 if (this.isSysexPerm) {
1196 this.permName = "midi-sysex";
1204 get permissionKey() {
1205 return this.permName;
1208 get popupOptions() {
1209 // TODO (bug 1433235) We need a security/permissions explanation URL for this
1212 name: this.getPrincipalName(),
1215 // Don't offer "always remember" action in PB mode
1216 options.checkbox = {
1217 show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
1218 this.browser.ownerGlobal
1222 if (options.checkbox.show) {
1223 options.checkbox.label =
1224 lazy.gBrowserBundle.GetStringFromName("midi.remember");
1230 get notificationID() {
1235 return "midi-notification-icon";
1240 if (this.principal.schemeIs("file")) {
1241 if (this.isSysexPerm) {
1242 message = lazy.gBrowserBundle.GetStringFromName(
1243 "midi.shareSysexWithFile"
1246 message = lazy.gBrowserBundle.GetStringFromName("midi.shareWithFile");
1248 } else if (this.isSysexPerm) {
1249 message = lazy.gBrowserBundle.formatStringFromName(
1250 "midi.shareSysexWithSite",
1254 message = lazy.gBrowserBundle.formatStringFromName("midi.shareWithSite", [
1261 get promptActions() {
1264 label: lazy.gBrowserBundle.GetStringFromName("midi.allow.label"),
1265 accessKey: lazy.gBrowserBundle.GetStringFromName(
1266 "midi.allow.accesskey"
1268 action: Ci.nsIPermissionManager.ALLOW_ACTION,
1271 label: lazy.gBrowserBundle.GetStringFromName("midi.block.label"),
1272 accessKey: lazy.gBrowserBundle.GetStringFromName(
1273 "midi.block.accesskey"
1275 action: Ci.nsIPermissionManager.DENY_ACTION,
1282 * @param {Components.Exception} err
1285 getInstallErrorMessage(err) {
1286 return `WebMIDI access request was denied: ❝${err.message}❞. See https://developer.mozilla.org/docs/Web/API/Navigator/requestMIDIAccess for more information`;
1290 class StorageAccessPermissionPrompt extends PermissionPromptForRequest {
1293 constructor(request) {
1295 this.request = request;
1296 this.siteOption = null;
1297 this.#permissionKey = `3rdPartyStorage${lazy.SitePermissions.PERM_KEY_DELIMITER}${this.principal.origin}`;
1299 let types = this.request.types.QueryInterface(Ci.nsIArray);
1300 let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
1301 let options = perm.options.QueryInterface(Ci.nsIArray);
1302 // If we have an option, the permission request is different in some way.
1303 // We may be in a call from requestStorageAccessUnderSite or a frame-scoped
1304 // request, which means that the embedding principal is not the current top-level
1305 // or the permission key is different.
1306 if (options.length != 2) {
1310 let topLevelOption = options.queryElementAt(0, Ci.nsISupportsString).data;
1311 if (topLevelOption) {
1312 this.siteOption = topLevelOption;
1314 let frameOption = options.queryElementAt(1, Ci.nsISupportsString).data;
1316 // We replace the permission key with a frame-specific one that only has a site after the delimiter
1317 this.#permissionKey = `3rdPartyFrameStorage${lazy.SitePermissions.PERM_KEY_DELIMITER}${this.principal.siteOrigin}`;
1321 get usePermissionManager() {
1326 return "storage-access";
1329 get permissionKey() {
1330 // Make sure this name is unique per each third-party tracker
1331 return this.#permissionKey;
1334 get temporaryPermissionURI() {
1335 if (this.siteOption) {
1336 return Services.io.newURI(this.siteOption);
1341 prettifyHostPort(hostport) {
1342 let [host, port] = hostport.split(":");
1343 host = lazy.IDNService.convertToDisplayIDN(host, {});
1345 return `${host}:${port}`;
1350 get popupOptions() {
1352 Services.urlFormatter.formatURLPref("app.support.baseURL") +
1353 "third-party-cookies";
1354 let hostPort = this.prettifyHostPort(this.principal.hostPort);
1355 let hintText = lazy.gBrowserBundle.formatStringFromName(
1356 "storageAccess1.hintText",
1363 escAction: "secondarybuttoncommand",
1367 get notificationID() {
1368 return "storage-access";
1372 return "storage-access-notification-icon";
1376 let embeddingHost = this.topLevelPrincipal.host;
1378 if (this.siteOption) {
1379 embeddingHost = this.siteOption.split("://").at(-1);
1382 return lazy.gBrowserBundle.formatStringFromName("storageAccess4.message", [
1383 this.prettifyHostPort(this.principal.hostPort),
1384 this.prettifyHostPort(embeddingHost),
1388 get promptActions() {
1393 label: lazy.gBrowserBundle.GetStringFromName(
1394 "storageAccess1.Allow.label"
1396 accessKey: lazy.gBrowserBundle.GetStringFromName(
1397 "storageAccess1.Allow.accesskey"
1399 action: Ci.nsIPermissionManager.ALLOW_ACTION,
1401 self.allow({ "storage-access": "allow" });
1405 label: lazy.gBrowserBundle.GetStringFromName(
1406 "storageAccess1.DontAllow.label"
1408 accessKey: lazy.gBrowserBundle.GetStringFromName(
1409 "storageAccess1.DontAllow.accesskey"
1411 action: Ci.nsIPermissionManager.DENY_ACTION,
1419 get topLevelPrincipal() {
1420 return this.request.topLevelPrincipal;
1424 export const PermissionUI = {
1425 PermissionPromptForRequest,
1426 GeolocationPermissionPrompt,
1428 DesktopNotificationPermissionPrompt,
1429 PersistentStoragePermissionPrompt,
1430 MIDIPermissionPrompt,
1431 StorageAccessPermissionPrompt,