forked from mirrors/gecko-dev
		
	 f8d9b2c8b2
			
		
	
	
		f8d9b2c8b2
		
	
	
	
	
		
			
			nsIPermissionManager already keys by principal origin so updating SitePermissions to do the same will make our permission handling more consistent. Differential Revision: https://phabricator.services.mozilla.com/D167988
		
			
				
	
	
		
			1439 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1439 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * PermissionUI is responsible for exposing both a prototype
 | |
|  * PermissionPrompt that can be used by arbitrary browser
 | |
|  * components and add-ons, but also hosts the implementations of
 | |
|  * built-in permission prompts.
 | |
|  *
 | |
|  * If you're developing a feature that requires web content to ask
 | |
|  * for special permissions from the user, this module is for you.
 | |
|  *
 | |
|  * Suppose a system add-on wants to add a new prompt for a new request
 | |
|  * for getting more low-level access to the user's sound card, and the
 | |
|  * permission request is coming up from content by way of the
 | |
|  * nsContentPermissionHelper. The system add-on could then do the following:
 | |
|  *
 | |
|  * const { Integration } = ChromeUtils.importESModule(
 | |
|  *   "resource://gre/modules/Integration.sys.mjs"
 | |
|  * );
 | |
|  * const { PermissionUI } = ChromeUtils.import(
 | |
|  *   "resource:///modules/PermissionUI.jsm"
 | |
|  * );
 | |
|  *
 | |
|  * const SoundCardIntegration = base => {
 | |
|  *   let soundCardObj = {
 | |
|  *     createPermissionPrompt(type, request) {
 | |
|  *       if (type != "sound-api") {
 | |
|  *         return super.createPermissionPrompt(...arguments);
 | |
|  *       }
 | |
|  *
 | |
|  *       let permissionPrompt = {
 | |
|  *         get permissionKey() {
 | |
|  *           return "sound-permission";
 | |
|  *         }
 | |
|  *         // etc - see the documentation for PermissionPrompt for
 | |
|  *         // a better idea of what things one can and should override.
 | |
|  *       };
 | |
|  *       Object.setPrototypeOf(
 | |
|  *         permissionPrompt,
 | |
|  *         PermissionUI.PermissionPromptForRequest
 | |
|  *       );
 | |
|  *       return permissionPrompt;
 | |
|  *     },
 | |
|  *   };
 | |
|  *   Object.setPrototypeOf(soundCardObj, base);
 | |
|  *   return soundCardObj;
 | |
|  * };
 | |
|  *
 | |
|  * // Add-on startup:
 | |
|  * Integration.contentPermission.register(SoundCardIntegration);
 | |
|  * // ...
 | |
|  * // Add-on shutdown:
 | |
|  * Integration.contentPermission.unregister(SoundCardIntegration);
 | |
|  *
 | |
|  * Note that PermissionPromptForRequest must be used as the
 | |
|  * prototype, since the prompt is wrapping an nsIContentPermissionRequest,
 | |
|  * and going through nsIContentPermissionPrompt.
 | |
|  *
 | |
|  * It is, however, possible to take advantage of PermissionPrompt without
 | |
|  * having to go through nsIContentPermissionPrompt or with a
 | |
|  * nsIContentPermissionRequest. The PermissionPrompt can be
 | |
|  * imported, subclassed, and have prompt() called directly, without
 | |
|  * the caller having called into createPermissionPrompt.
 | |
|  */
 | |
| import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   lazy,
 | |
|   "AddonManager",
 | |
|   "resource://gre/modules/AddonManager.jsm"
 | |
| );
 | |
| ChromeUtils.defineModuleGetter(
 | |
|   lazy,
 | |
|   "SitePermissions",
 | |
|   "resource:///modules/SitePermissions.jsm"
 | |
| );
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   lazy,
 | |
|   "IDNService",
 | |
|   "@mozilla.org/network/idn-service;1",
 | |
|   "nsIIDNService"
 | |
| );
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   lazy,
 | |
|   "ContentPrefService2",
 | |
|   "@mozilla.org/content-pref/service;1",
 | |
|   "nsIContentPrefService2"
 | |
| );
 | |
| XPCOMUtils.defineLazyGetter(lazy, "gBrowserBundle", function() {
 | |
|   return Services.strings.createBundle(
 | |
|     "chrome://browser/locale/browser.properties"
 | |
|   );
 | |
| });
 | |
| 
 | |
| import { SITEPERMS_ADDON_PROVIDER_PREF } from "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs";
 | |
| 
 | |
| XPCOMUtils.defineLazyPreferenceGetter(
 | |
|   lazy,
 | |
|   "sitePermsAddonsProviderEnabled",
 | |
|   SITEPERMS_ADDON_PROVIDER_PREF,
 | |
|   false
 | |
| );
 | |
| 
 | |
| /**
 | |
|  * PermissionPrompt should be subclassed by callers that
 | |
|  * want to display prompts to the user. See each method and property
 | |
|  * below for guidance on what to override.
 | |
|  *
 | |
|  * Note that if you're creating a prompt for an
 | |
|  * nsIContentPermissionRequest, you'll want to subclass
 | |
|  * PermissionPromptForRequest instead.
 | |
|  */
 | |
| class PermissionPrompt {
 | |
|   /**
 | |
|    * Returns the associated <xul:browser> for the request. This should
 | |
|    * work for the e10s and non-e10s case.
 | |
|    *
 | |
|    * Subclasses must override this.
 | |
|    *
 | |
|    * @return {<xul:browser>}
 | |
|    */
 | |
|   get browser() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the nsIPrincipal associated with the request.
 | |
|    *
 | |
|    * Subclasses must override this.
 | |
|    *
 | |
|    * @return {nsIPrincipal}
 | |
|    */
 | |
|   get principal() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Indicates the type of the permission request from content. This type might
 | |
|    * be different from the permission key used in the permissions database.
 | |
|    */
 | |
|   get type() {
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If the nsIPermissionManager is being queried and written
 | |
|    * to for this permission request, set this to the key to be
 | |
|    * used. If this is undefined, no integration with temporary
 | |
|    * permissions infrastructure will be provided.
 | |
|    *
 | |
|    * Note that if a permission is set, in any follow-up
 | |
|    * prompting within the expiry window of that permission,
 | |
|    * the prompt will be skipped and the allow or deny choice
 | |
|    * will be selected automatically.
 | |
|    */
 | |
|   get permissionKey() {
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If true, user permissions will be read from and written to.
 | |
|    * When this is false, we still provide integration with
 | |
|    * infrastructure such as temporary permissions. permissionKey should
 | |
|    * still return a valid name in those cases for that integration to work.
 | |
|    */
 | |
|   get usePermissionManager() {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Indicates what URI should be used as the scope when using temporary
 | |
|    * permissions. If undefined, it defaults to the browser.currentURI.
 | |
|    */
 | |
|   get temporaryPermissionURI() {
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * These are the options that will be passed to the PopupNotification when it
 | |
|    * is shown. See the documentation of `PopupNotifications_show` in
 | |
|    * PopupNotifications.sys.mjs for details.
 | |
|    *
 | |
|    * Note that prompt() will automatically set displayURI to
 | |
|    * be the URI of the requesting pricipal, unless the displayURI is exactly
 | |
|    * set to false.
 | |
|    */
 | |
|   get popupOptions() {
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If true, automatically denied permission requests will
 | |
|    * spawn a "post-prompt" that allows the user to correct the
 | |
|    * automatic denial by giving permanent permission access to
 | |
|    * the site.
 | |
|    *
 | |
|    * Note that if this function returns true, the permissionKey
 | |
|    * and postPromptActions attributes must be implemented.
 | |
|    */
 | |
|   get postPromptEnabled() {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If true, the prompt will be cancelled automatically unless
 | |
|    * request.hasValidTransientUserGestureActivation is true.
 | |
|    */
 | |
|   get requiresUserInput() {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * PopupNotification requires a unique ID to open the notification.
 | |
|    * You must return a unique ID string here, for which PopupNotification
 | |
|    * will then create a <xul:popupnotification> node with the ID
 | |
|    * "<notificationID>-notification".
 | |
|    *
 | |
|    * If there's a custom <xul:popupnotification> you're hoping to show,
 | |
|    * then you need to make sure its ID has the "-notification" suffix,
 | |
|    * and then return the prefix here.
 | |
|    *
 | |
|    * See PopupNotifications.sys.mjs for more details.
 | |
|    *
 | |
|    * @return {string}
 | |
|    *         The unique ID that will be used to as the
 | |
|    *         "<unique ID>-notification" ID for the <xul:popupnotification>
 | |
|    *         to use or create.
 | |
|    */
 | |
|   get notificationID() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The ID of the element to anchor the PopupNotification to.
 | |
|    *
 | |
|    * @return {string}
 | |
|    */
 | |
|   get anchorID() {
 | |
|     return "default-notification-icon";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The message to show to the user in the PopupNotification, see
 | |
|    * `PopupNotifications_show` in PopupNotifications.sys.mjs.
 | |
|    *
 | |
|    * Subclasses must override this.
 | |
|    *
 | |
|    * @return {string}
 | |
|    */
 | |
|   get message() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Provides the preferred name to use in the permission popups,
 | |
|    * based on the principal URI (the URI.hostPort for any URI scheme
 | |
|    * besides the moz-extension one which should default to the
 | |
|    * extension name).
 | |
|    */
 | |
|   getPrincipalName(principal = this.principal) {
 | |
|     if (principal.addonPolicy) {
 | |
|       return principal.addonPolicy.name;
 | |
|     }
 | |
| 
 | |
|     return principal.hostPort;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This will be called if the request is to be cancelled.
 | |
|    *
 | |
|    * Subclasses only need to override this if they provide a
 | |
|    * permissionKey.
 | |
|    */
 | |
|   cancel() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This will be called if the request is to be allowed.
 | |
|    *
 | |
|    * Subclasses only need to override this if they provide a
 | |
|    * permissionKey.
 | |
|    */
 | |
|   allow() {
 | |
|     throw new Error("Not implemented.");
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The actions that will be displayed in the PopupNotification
 | |
|    * via a dropdown menu. The first item in this array will be
 | |
|    * the default selection. Each action is an Object with the
 | |
|    * following properties:
 | |
|    *
 | |
|    *  label (string):
 | |
|    *    The label that will be displayed for this choice.
 | |
|    *  accessKey (string):
 | |
|    *    The access key character that will be used for this choice.
 | |
|    *  action (SitePermissions state)
 | |
|    *    The action that will be associated with this choice.
 | |
|    *    This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
 | |
|    *  scope (SitePermissions scope)
 | |
|    *    The scope of the associated action (e.g. SitePermissions.SCOPE_PERSISTENT)
 | |
|    *
 | |
|    *  callback (function, optional)
 | |
|    *    A callback function that will fire if the user makes this choice, with
 | |
|    *    a single parameter, state. State is an Object that contains the property
 | |
|    *    checkboxChecked, which identifies whether the checkbox to remember this
 | |
|    *    decision was checked.
 | |
|    */
 | |
|   get promptActions() {
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The actions that will be displayed in the PopupNotification
 | |
|    * for post-prompt notifications via a dropdown menu.
 | |
|    * The first item in this array will be the default selection.
 | |
|    * Each action is an Object with the following properties:
 | |
|    *
 | |
|    *  label (string):
 | |
|    *    The label that will be displayed for this choice.
 | |
|    *  accessKey (string):
 | |
|    *    The access key character that will be used for this choice.
 | |
|    *  action (SitePermissions state)
 | |
|    *    The action that will be associated with this choice.
 | |
|    *    This should be either SitePermissions.ALLOW or SitePermissions.BLOCK.
 | |
|    *    Note that the scope of this action will always be persistent.
 | |
|    *
 | |
|    *  callback (function, optional)
 | |
|    *    A callback function that will fire if the user makes this choice.
 | |
|    */
 | |
|   get postPromptActions() {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If the prompt will be shown to the user, this callback will
 | |
|    * be called just before. Subclasses may want to override this
 | |
|    * in order to, for example, bump a counter Telemetry probe for
 | |
|    * how often a particular permission request is seen.
 | |
|    *
 | |
|    * If this returns false, it cancels the process of showing the prompt.  In
 | |
|    * that case, it is the responsibility of the onBeforeShow() implementation
 | |
|    * to ensure that allow() or cancel() are called on the object appropriately.
 | |
|    */
 | |
|   onBeforeShow() {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * If the prompt was shown to the user, this callback will be called just
 | |
|    * after it's been shown.
 | |
|    */
 | |
|   onShown() {}
 | |
| 
 | |
|   /**
 | |
|    * If the prompt was shown to the user, this callback will be called just
 | |
|    * after it's been hidden.
 | |
|    */
 | |
|   onAfterShow() {}
 | |
| 
 | |
|   /**
 | |
|    * Will determine if a prompt should be shown to the user, and if so,
 | |
|    * will show it.
 | |
|    *
 | |
|    * If a permissionKey is defined prompt() might automatically
 | |
|    * allow or cancel itself based on the user's current
 | |
|    * permission settings without displaying the prompt.
 | |
|    *
 | |
|    * If the permission is not already set and the <xul:browser> that the request
 | |
|    * is associated with does not belong to a browser window with the
 | |
|    * PopupNotifications global set, the prompt request is ignored.
 | |
|    */
 | |
|   prompt() {
 | |
|     // We ignore requests from non-nsIStandardURLs
 | |
|     let requestingURI = this.principal.URI;
 | |
|     if (!(requestingURI instanceof Ci.nsIStandardURL)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this.usePermissionManager && this.permissionKey) {
 | |
|       // If we're reading and setting permissions, then we need
 | |
|       // to check to see if we already have a permission setting
 | |
|       // for this particular principal.
 | |
|       let { state } = lazy.SitePermissions.getForPrincipal(
 | |
|         this.principal,
 | |
|         this.permissionKey,
 | |
|         this.browser,
 | |
|         this.temporaryPermissionURI
 | |
|       );
 | |
| 
 | |
|       if (state == lazy.SitePermissions.BLOCK) {
 | |
|         // If this block was done based on a global user setting, we want to show
 | |
|         // a post prompt to give the user some more granular control without
 | |
|         // annoying them too much.
 | |
|         if (
 | |
|           this.postPromptEnabled &&
 | |
|           lazy.SitePermissions.getDefault(this.permissionKey) ==
 | |
|             lazy.SitePermissions.BLOCK
 | |
|         ) {
 | |
|           this.postPrompt();
 | |
|         }
 | |
|         this.cancel();
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       if (
 | |
|         state == lazy.SitePermissions.ALLOW &&
 | |
|         !this.request.isRequestDelegatedToUnsafeThirdParty
 | |
|       ) {
 | |
|         this.allow();
 | |
|         return;
 | |
|       }
 | |
|     } else if (this.permissionKey) {
 | |
|       // If we're reading a permission which already has a temporary value,
 | |
|       // see if we can use the temporary value.
 | |
|       let { state } = lazy.SitePermissions.getForPrincipal(
 | |
|         null,
 | |
|         this.permissionKey,
 | |
|         this.browser,
 | |
|         this.temporaryPermissionURI
 | |
|       );
 | |
| 
 | |
|       if (state == lazy.SitePermissions.BLOCK) {
 | |
|         this.cancel();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       this.requiresUserInput &&
 | |
|       !this.request.hasValidTransientUserGestureActivation
 | |
|     ) {
 | |
|       if (this.postPromptEnabled) {
 | |
|         this.postPrompt();
 | |
|       }
 | |
|       this.cancel();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let chromeWin = this.browser.ownerGlobal;
 | |
|     if (!chromeWin.PopupNotifications) {
 | |
|       this.cancel();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Transform the PermissionPrompt actions into PopupNotification actions.
 | |
|     let popupNotificationActions = [];
 | |
|     for (let promptAction of this.promptActions) {
 | |
|       let action = {
 | |
|         label: promptAction.label,
 | |
|         accessKey: promptAction.accessKey,
 | |
|         callback: state => {
 | |
|           if (promptAction.callback) {
 | |
|             promptAction.callback();
 | |
|           }
 | |
| 
 | |
|           if (this.usePermissionManager && this.permissionKey) {
 | |
|             if (
 | |
|               (state && state.checkboxChecked && state.source != "esc-press") ||
 | |
|               promptAction.scope == lazy.SitePermissions.SCOPE_PERSISTENT
 | |
|             ) {
 | |
|               // Permanently store permission.
 | |
|               let scope = lazy.SitePermissions.SCOPE_PERSISTENT;
 | |
|               // Only remember permission for session if in PB mode.
 | |
|               if (lazy.PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
 | |
|                 scope = lazy.SitePermissions.SCOPE_SESSION;
 | |
|               }
 | |
|               lazy.SitePermissions.setForPrincipal(
 | |
|                 this.principal,
 | |
|                 this.permissionKey,
 | |
|                 promptAction.action,
 | |
|                 scope
 | |
|               );
 | |
|             } else if (promptAction.action == lazy.SitePermissions.BLOCK) {
 | |
|               // Temporarily store BLOCK permissions only
 | |
|               // SitePermissions does not consider subframes when storing temporary
 | |
|               // permissions on a tab, thus storing ALLOW could be exploited.
 | |
|               lazy.SitePermissions.setForPrincipal(
 | |
|                 this.principal,
 | |
|                 this.permissionKey,
 | |
|                 promptAction.action,
 | |
|                 lazy.SitePermissions.SCOPE_TEMPORARY,
 | |
|                 this.browser
 | |
|               );
 | |
|             }
 | |
| 
 | |
|             // Grant permission if action is ALLOW.
 | |
|             if (promptAction.action == lazy.SitePermissions.ALLOW) {
 | |
|               this.allow();
 | |
|             } else {
 | |
|               this.cancel();
 | |
|             }
 | |
|           } else if (this.permissionKey) {
 | |
|             // TODO: Add support for permitTemporaryAllow
 | |
|             if (promptAction.action == lazy.SitePermissions.BLOCK) {
 | |
|               // Temporarily store BLOCK permissions.
 | |
|               // We don't consider subframes when storing temporary
 | |
|               // permissions on a tab, thus storing ALLOW could be exploited.
 | |
|               lazy.SitePermissions.setForPrincipal(
 | |
|                 null,
 | |
|                 this.permissionKey,
 | |
|                 promptAction.action,
 | |
|                 lazy.SitePermissions.SCOPE_TEMPORARY,
 | |
|                 this.browser
 | |
|               );
 | |
|             }
 | |
|           }
 | |
|         },
 | |
|       };
 | |
|       if (promptAction.dismiss) {
 | |
|         action.dismiss = promptAction.dismiss;
 | |
|       }
 | |
| 
 | |
|       popupNotificationActions.push(action);
 | |
|     }
 | |
| 
 | |
|     this.#showNotification(popupNotificationActions);
 | |
|   }
 | |
| 
 | |
|   postPrompt() {
 | |
|     let browser = this.browser;
 | |
|     let principal = this.principal;
 | |
|     let chromeWin = browser.ownerGlobal;
 | |
|     if (!chromeWin.PopupNotifications) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!this.permissionKey) {
 | |
|       throw new Error("permissionKey is required to show a post-prompt");
 | |
|     }
 | |
| 
 | |
|     if (!this.postPromptActions) {
 | |
|       throw new Error("postPromptActions are required to show a post-prompt");
 | |
|     }
 | |
| 
 | |
|     // Transform the PermissionPrompt actions into PopupNotification actions.
 | |
|     let popupNotificationActions = [];
 | |
|     for (let promptAction of this.postPromptActions) {
 | |
|       let action = {
 | |
|         label: promptAction.label,
 | |
|         accessKey: promptAction.accessKey,
 | |
|         callback: state => {
 | |
|           if (promptAction.callback) {
 | |
|             promptAction.callback();
 | |
|           }
 | |
| 
 | |
|           // Post-prompt permissions are stored permanently by default.
 | |
|           // Since we can not reply to the original permission request anymore,
 | |
|           // the page will need to listen for permission changes which are triggered
 | |
|           // by permanent entries in the permission manager.
 | |
|           let scope = lazy.SitePermissions.SCOPE_PERSISTENT;
 | |
|           // Only remember permission for session if in PB mode.
 | |
|           if (lazy.PrivateBrowsingUtils.isBrowserPrivate(browser)) {
 | |
|             scope = lazy.SitePermissions.SCOPE_SESSION;
 | |
|           }
 | |
|           lazy.SitePermissions.setForPrincipal(
 | |
|             principal,
 | |
|             this.permissionKey,
 | |
|             promptAction.action,
 | |
|             scope
 | |
|           );
 | |
|         },
 | |
|       };
 | |
|       popupNotificationActions.push(action);
 | |
|     }
 | |
| 
 | |
|     // Post-prompt animation
 | |
|     if (!chromeWin.gReduceMotion) {
 | |
|       let anchor = chromeWin.document.getElementById(this.anchorID);
 | |
|       // Only show the animation on the first request, not after e.g. tab switching.
 | |
|       anchor.addEventListener(
 | |
|         "animationend",
 | |
|         () => anchor.removeAttribute("animate"),
 | |
|         { once: true }
 | |
|       );
 | |
|       anchor.setAttribute("animate", "true");
 | |
|     }
 | |
| 
 | |
|     this.#showNotification(popupNotificationActions, true);
 | |
|   }
 | |
| 
 | |
|   #showNotification(actions, postPrompt = false) {
 | |
|     let chromeWin = this.browser.ownerGlobal;
 | |
|     let mainAction = actions.length ? actions[0] : null;
 | |
|     let secondaryActions = actions.splice(1);
 | |
| 
 | |
|     let options = this.popupOptions;
 | |
| 
 | |
|     if (!options.hasOwnProperty("displayURI") || options.displayURI) {
 | |
|       options.displayURI = this.principal.URI;
 | |
|     }
 | |
| 
 | |
|     if (!postPrompt) {
 | |
|       // Permission prompts are always persistent; the close button is controlled by a pref.
 | |
|       options.persistent = true;
 | |
|       options.hideClose = true;
 | |
|     }
 | |
| 
 | |
|     options.eventCallback = (topic, nextRemovalReason, isCancel) => {
 | |
|       // When the docshell of the browser is aboout to be swapped to another one,
 | |
|       // the "swapping" event is called. Returning true causes the notification
 | |
|       // to be moved to the new browser.
 | |
|       if (topic == "swapping") {
 | |
|         return true;
 | |
|       }
 | |
|       // The prompt has been shown, notify the PermissionUI.
 | |
|       // onShown() is currently not called for post-prompts,
 | |
|       // because there is no prompt that would make use of this.
 | |
|       // You can remove this restriction if you need it, but be
 | |
|       // mindful of other consumers.
 | |
|       if (topic == "shown" && !postPrompt) {
 | |
|         this.onShown();
 | |
|       }
 | |
|       // The prompt has been removed, notify the PermissionUI.
 | |
|       // onAfterShow() is currently not called for post-prompts,
 | |
|       // because there is no prompt that would make use of this.
 | |
|       // You can remove this restriction if you need it, but be
 | |
|       // mindful of other consumers.
 | |
|       if (topic == "removed" && !postPrompt) {
 | |
|         if (isCancel) {
 | |
|           this.cancel();
 | |
|         }
 | |
|         this.onAfterShow();
 | |
|       }
 | |
|       return false;
 | |
|     };
 | |
| 
 | |
|     // Post-prompts show up as dismissed.
 | |
|     options.dismissed = postPrompt;
 | |
| 
 | |
|     // onBeforeShow() is currently not called for post-prompts,
 | |
|     // because there is no prompt that would make use of this.
 | |
|     // You can remove this restriction if you need it, but be
 | |
|     // mindful of other consumers.
 | |
|     if (postPrompt || this.onBeforeShow() !== false) {
 | |
|       chromeWin.PopupNotifications.show(
 | |
|         this.browser,
 | |
|         this.notificationID,
 | |
|         this.message,
 | |
|         this.anchorID,
 | |
|         mainAction,
 | |
|         secondaryActions,
 | |
|         options
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A subclass of PermissionPrompt that assumes
 | |
|  * that this.request is an nsIContentPermissionRequest
 | |
|  * and fills in some of the required properties on the
 | |
|  * PermissionPrompt. For callers that are wrapping an
 | |
|  * nsIContentPermissionRequest, this should be subclassed
 | |
|  * rather than PermissionPrompt.
 | |
|  */
 | |
| class PermissionPromptForRequest extends PermissionPrompt {
 | |
|   get browser() {
 | |
|     // In the e10s-case, the <xul:browser> will be at request.element.
 | |
|     // In the single-process case, we have to use some XPCOM incantations
 | |
|     // to resolve to the <xul:browser>.
 | |
|     if (this.request.element) {
 | |
|       return this.request.element;
 | |
|     }
 | |
|     return this.request.window.docShell.chromeEventHandler;
 | |
|   }
 | |
| 
 | |
|   get principal() {
 | |
|     let request = this.request.QueryInterface(Ci.nsIContentPermissionRequest);
 | |
|     return request.getDelegatePrincipal(this.type);
 | |
|   }
 | |
| 
 | |
|   cancel() {
 | |
|     this.request.cancel();
 | |
|   }
 | |
| 
 | |
|   allow(choices) {
 | |
|     this.request.allow(choices);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A subclass of PermissionPromptForRequest that prompts
 | |
|  * for a Synthetic SitePermsAddon addon type and starts a synthetic
 | |
|  * addon install flow.
 | |
|  */
 | |
| class SitePermsAddonInstallRequest extends PermissionPromptForRequest {
 | |
|   prompt() {
 | |
|     // fallback to regular permission prompt for localhost,
 | |
|     // or when the SitePermsAddonProvider is not enabled.
 | |
|     if (this.principal.isLoopbackHost || !lazy.sitePermsAddonsProviderEnabled) {
 | |
|       super.prompt();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Otherwise, we'll use the addon install flow.
 | |
|     lazy.AddonManager.installSitePermsAddonFromWebpage(
 | |
|       this.browser,
 | |
|       this.principal,
 | |
|       this.permName
 | |
|     ).then(
 | |
|       () => {
 | |
|         this.allow();
 | |
|       },
 | |
|       err => {
 | |
|         this.cancel();
 | |
| 
 | |
|         // Print an error message in the console to give more information to the developer.
 | |
|         let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
 | |
|         let errorMessage =
 | |
|           this.getInstallErrorMessage(err) ||
 | |
|           `${this.permName} access was rejected: ${err.message}`;
 | |
| 
 | |
|         let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
 | |
|         scriptError.initWithWindowID(
 | |
|           errorMessage,
 | |
|           null,
 | |
|           null,
 | |
|           0,
 | |
|           0,
 | |
|           0,
 | |
|           "content javascript",
 | |
|           this.browser.browsingContext.currentWindowGlobal.innerWindowId
 | |
|         );
 | |
|         Services.console.logMessage(scriptError);
 | |
|       }
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns an error message that will be printed to the console given a passed Component.Exception.
 | |
|    * This should be overriden by children classes.
 | |
|    *
 | |
|    * @param {Components.Exception} err
 | |
|    * @returns {String} The error message
 | |
|    */
 | |
|   getInstallErrorMessage(err) {
 | |
|     return null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
 | |
|  * the GeoLocation API.
 | |
|  *
 | |
|  * @param request (nsIContentPermissionRequest)
 | |
|  *        The request for a permission from content.
 | |
|  */
 | |
| class GeolocationPermissionPrompt extends PermissionPromptForRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "geo";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     return "geo";
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     let pref = "browser.geolocation.warning.infoURL";
 | |
|     let options = {
 | |
|       learnMoreURL: Services.urlFormatter.formatURLPref(pref),
 | |
|       displayURI: false,
 | |
|       name: this.getPrincipalName(),
 | |
|     };
 | |
| 
 | |
|     // Don't offer "always remember" action in PB mode
 | |
|     options.checkbox = {
 | |
|       show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
 | |
|         this.browser.ownerGlobal
 | |
|       ),
 | |
|     };
 | |
| 
 | |
|     if (this.request.isRequestDelegatedToUnsafeThirdParty) {
 | |
|       // Second name should be the third party origin
 | |
|       options.secondName = this.getPrincipalName(this.request.principal);
 | |
|       options.checkbox = { show: false };
 | |
|     }
 | |
| 
 | |
|     if (options.checkbox.show) {
 | |
|       options.checkbox.label = lazy.gBrowserBundle.GetStringFromName(
 | |
|         "geolocation.remember"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return options;
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "geolocation";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "geo-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     if (this.principal.schemeIs("file")) {
 | |
|       return lazy.gBrowserBundle.GetStringFromName(
 | |
|         "geolocation.shareWithFile4"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (this.request.isRequestDelegatedToUnsafeThirdParty) {
 | |
|       return lazy.gBrowserBundle.formatStringFromName(
 | |
|         "geolocation.shareWithSiteUnsafeDelegation2",
 | |
|         ["<>", "{}"]
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return lazy.gBrowserBundle.formatStringFromName(
 | |
|       "geolocation.shareWithSite4",
 | |
|       ["<>"]
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     return [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("geolocation.allow"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "geolocation.allow.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.ALLOW,
 | |
|       },
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("geolocation.block"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "geolocation.block.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.BLOCK,
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   #updateGeoSharing(state) {
 | |
|     let gBrowser = this.browser.ownerGlobal.gBrowser;
 | |
|     if (gBrowser == null) {
 | |
|       return;
 | |
|     }
 | |
|     gBrowser.updateBrowserSharing(this.browser, { geo: state });
 | |
| 
 | |
|     // Update last access timestamp
 | |
|     let host;
 | |
|     try {
 | |
|       host = this.browser.currentURI.host;
 | |
|     } catch (e) {
 | |
|       return;
 | |
|     }
 | |
|     if (host == null || host == "") {
 | |
|       return;
 | |
|     }
 | |
|     lazy.ContentPrefService2.set(
 | |
|       this.browser.currentURI.host,
 | |
|       "permissions.geoLocation.lastAccess",
 | |
|       new Date().toString(),
 | |
|       this.browser.loadContext
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   allow(...args) {
 | |
|     this.#updateGeoSharing(true);
 | |
|     super.allow(...args);
 | |
|   }
 | |
| 
 | |
|   cancel(...args) {
 | |
|     this.#updateGeoSharing(false);
 | |
|     super.cancel(...args);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
 | |
|  * the WebXR API.
 | |
|  *
 | |
|  * @param request (nsIContentPermissionRequest)
 | |
|  *        The request for a permission from content.
 | |
|  */
 | |
| class XRPermissionPrompt extends PermissionPromptForRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "xr";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     return "xr";
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     let pref = "browser.xr.warning.infoURL";
 | |
|     let options = {
 | |
|       learnMoreURL: Services.urlFormatter.formatURLPref(pref),
 | |
|       displayURI: false,
 | |
|       name: this.getPrincipalName(),
 | |
|     };
 | |
| 
 | |
|     // Don't offer "always remember" action in PB mode
 | |
|     options.checkbox = {
 | |
|       show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
 | |
|         this.browser.ownerGlobal
 | |
|       ),
 | |
|     };
 | |
| 
 | |
|     if (options.checkbox.show) {
 | |
|       options.checkbox.label = lazy.gBrowserBundle.GetStringFromName(
 | |
|         "xr.remember"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return options;
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "xr";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "xr-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     if (this.principal.schemeIs("file")) {
 | |
|       return lazy.gBrowserBundle.GetStringFromName("xr.shareWithFile4");
 | |
|     }
 | |
| 
 | |
|     return lazy.gBrowserBundle.formatStringFromName("xr.shareWithSite4", [
 | |
|       "<>",
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     return [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("xr.allow2"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName("xr.allow2.accesskey"),
 | |
|         action: lazy.SitePermissions.ALLOW,
 | |
|       },
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("xr.block"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName("xr.block.accesskey"),
 | |
|         action: lazy.SitePermissions.BLOCK,
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   #updateXRSharing(state) {
 | |
|     let gBrowser = this.browser.ownerGlobal.gBrowser;
 | |
|     if (gBrowser == null) {
 | |
|       return;
 | |
|     }
 | |
|     gBrowser.updateBrowserSharing(this.browser, { xr: state });
 | |
| 
 | |
|     let devicePermOrigins = this.browser.getDevicePermissionOrigins("xr");
 | |
|     if (!state) {
 | |
|       devicePermOrigins.delete(this.principal.origin);
 | |
|       return;
 | |
|     }
 | |
|     devicePermOrigins.add(this.principal.origin);
 | |
|   }
 | |
| 
 | |
|   allow(...args) {
 | |
|     this.#updateXRSharing(true);
 | |
|     super.allow(...args);
 | |
|   }
 | |
| 
 | |
|   cancel(...args) {
 | |
|     this.#updateXRSharing(false);
 | |
|     super.cancel(...args);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
 | |
|  * the Desktop Notification API.
 | |
|  *
 | |
|  * @param request (nsIContentPermissionRequest)
 | |
|  *        The request for a permission from content.
 | |
|  * @return {PermissionPrompt} (see documentation in header)
 | |
|  */
 | |
| class DesktopNotificationPermissionPrompt extends PermissionPromptForRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
| 
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "requiresUserInput",
 | |
|       "dom.webnotifications.requireuserinteraction"
 | |
|     );
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "postPromptEnabled",
 | |
|       "permissions.desktop-notification.postPrompt.enabled"
 | |
|     );
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "notNowEnabled",
 | |
|       "permissions.desktop-notification.notNow.enabled"
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "desktop-notification";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     return "desktop-notification";
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     let learnMoreURL =
 | |
|       Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
 | |
| 
 | |
|     return {
 | |
|       learnMoreURL,
 | |
|       displayURI: false,
 | |
|       name: this.getPrincipalName(),
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "web-notifications";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "web-notifications-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     return lazy.gBrowserBundle.formatStringFromName(
 | |
|       "webNotifications.receiveFromSite3",
 | |
|       ["<>"]
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     let actions = [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("webNotifications.allow2"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "webNotifications.allow2.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.ALLOW,
 | |
|         scope: lazy.SitePermissions.SCOPE_PERSISTENT,
 | |
|       },
 | |
|     ];
 | |
|     if (this.notNowEnabled) {
 | |
|       actions.push({
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("webNotifications.notNow"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "webNotifications.notNow.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.BLOCK,
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
 | |
|       this.browser
 | |
|     );
 | |
|     actions.push({
 | |
|       label: isBrowserPrivate
 | |
|         ? lazy.gBrowserBundle.GetStringFromName("webNotifications.block")
 | |
|         : lazy.gBrowserBundle.GetStringFromName("webNotifications.alwaysBlock"),
 | |
|       accessKey: isBrowserPrivate
 | |
|         ? lazy.gBrowserBundle.GetStringFromName(
 | |
|             "webNotifications.block.accesskey"
 | |
|           )
 | |
|         : lazy.gBrowserBundle.GetStringFromName(
 | |
|             "webNotifications.alwaysBlock.accesskey"
 | |
|           ),
 | |
|       action: lazy.SitePermissions.BLOCK,
 | |
|       scope: isBrowserPrivate
 | |
|         ? lazy.SitePermissions.SCOPE_SESSION
 | |
|         : lazy.SitePermissions.SCOPE_PERSISTENT,
 | |
|     });
 | |
|     return actions;
 | |
|   }
 | |
| 
 | |
|   get postPromptActions() {
 | |
|     let actions = [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("webNotifications.allow2"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "webNotifications.allow2.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.ALLOW,
 | |
|       },
 | |
|     ];
 | |
| 
 | |
|     let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
 | |
|       this.browser
 | |
|     );
 | |
|     actions.push({
 | |
|       label: isBrowserPrivate
 | |
|         ? lazy.gBrowserBundle.GetStringFromName("webNotifications.block")
 | |
|         : lazy.gBrowserBundle.GetStringFromName("webNotifications.alwaysBlock"),
 | |
|       accessKey: isBrowserPrivate
 | |
|         ? lazy.gBrowserBundle.GetStringFromName(
 | |
|             "webNotifications.block.accesskey"
 | |
|           )
 | |
|         : lazy.gBrowserBundle.GetStringFromName(
 | |
|             "webNotifications.alwaysBlock.accesskey"
 | |
|           ),
 | |
|       action: lazy.SitePermissions.BLOCK,
 | |
|     });
 | |
|     return actions;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
 | |
|  * the persistent-storage API.
 | |
|  *
 | |
|  * @param request (nsIContentPermissionRequest)
 | |
|  *        The request for a permission from content.
 | |
|  */
 | |
| class PersistentStoragePermissionPrompt extends PermissionPromptForRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "persistent-storage";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     return "persistent-storage";
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     let learnMoreURL =
 | |
|       Services.urlFormatter.formatURLPref("app.support.baseURL") +
 | |
|       "storage-permissions";
 | |
|     return {
 | |
|       learnMoreURL,
 | |
|       displayURI: false,
 | |
|       name: this.getPrincipalName(),
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "persistent-storage";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "persistent-storage-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     return lazy.gBrowserBundle.formatStringFromName(
 | |
|       "persistentStorage.allowWithSite2",
 | |
|       ["<>"]
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     return [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("persistentStorage.allow"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "persistentStorage.allow.accesskey"
 | |
|         ),
 | |
|         action: Ci.nsIPermissionManager.ALLOW_ACTION,
 | |
|         scope: lazy.SitePermissions.SCOPE_PERSISTENT,
 | |
|       },
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "persistentStorage.block.label"
 | |
|         ),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "persistentStorage.block.accesskey"
 | |
|         ),
 | |
|         action: lazy.SitePermissions.BLOCK,
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a PermissionPrompt for a nsIContentPermissionRequest for
 | |
|  * the WebMIDI API.
 | |
|  *
 | |
|  * @param request (nsIContentPermissionRequest)
 | |
|  *        The request for a permission from content.
 | |
|  */
 | |
| class MIDIPermissionPrompt extends SitePermsAddonInstallRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
|     let types = request.types.QueryInterface(Ci.nsIArray);
 | |
|     let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
 | |
|     this.isSysexPerm =
 | |
|       !!perm.options.length &&
 | |
|       perm.options.queryElementAt(0, Ci.nsISupportsString) == "sysex";
 | |
|     this.permName = "midi";
 | |
|     if (this.isSysexPerm) {
 | |
|       this.permName = "midi-sysex";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "midi";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     return this.permName;
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     // TODO (bug 1433235) We need a security/permissions explanation URL for this
 | |
|     let options = {
 | |
|       displayURI: false,
 | |
|       name: this.getPrincipalName(),
 | |
|     };
 | |
| 
 | |
|     // Don't offer "always remember" action in PB mode
 | |
|     options.checkbox = {
 | |
|       show: !lazy.PrivateBrowsingUtils.isWindowPrivate(
 | |
|         this.browser.ownerGlobal
 | |
|       ),
 | |
|     };
 | |
| 
 | |
|     if (options.checkbox.show) {
 | |
|       options.checkbox.label = lazy.gBrowserBundle.GetStringFromName(
 | |
|         "midi.remember"
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return options;
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "midi";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "midi-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     let message;
 | |
|     if (this.principal.schemeIs("file")) {
 | |
|       if (this.isSysexPerm) {
 | |
|         message = lazy.gBrowserBundle.GetStringFromName(
 | |
|           "midi.shareSysexWithFile"
 | |
|         );
 | |
|       } else {
 | |
|         message = lazy.gBrowserBundle.GetStringFromName("midi.shareWithFile");
 | |
|       }
 | |
|     } else if (this.isSysexPerm) {
 | |
|       message = lazy.gBrowserBundle.formatStringFromName(
 | |
|         "midi.shareSysexWithSite",
 | |
|         ["<>"]
 | |
|       );
 | |
|     } else {
 | |
|       message = lazy.gBrowserBundle.formatStringFromName("midi.shareWithSite", [
 | |
|         "<>",
 | |
|       ]);
 | |
|     }
 | |
|     return message;
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     return [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("midi.allow.label"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "midi.allow.accesskey"
 | |
|         ),
 | |
|         action: Ci.nsIPermissionManager.ALLOW_ACTION,
 | |
|       },
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName("midi.block.label"),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "midi.block.accesskey"
 | |
|         ),
 | |
|         action: Ci.nsIPermissionManager.DENY_ACTION,
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @override
 | |
|    * @param {Components.Exception} err
 | |
|    * @returns {String}
 | |
|    */
 | |
|   getInstallErrorMessage(err) {
 | |
|     return `WebMIDI access request was denied: ❝${err.message}❞. See https://developer.mozilla.org/docs/Web/API/Navigator/requestMIDIAccess for more information`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class StorageAccessPermissionPrompt extends PermissionPromptForRequest {
 | |
|   constructor(request) {
 | |
|     super();
 | |
|     this.request = request;
 | |
|     this.siteOption = null;
 | |
| 
 | |
|     let types = this.request.types.QueryInterface(Ci.nsIArray);
 | |
|     let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
 | |
|     let options = perm.options.QueryInterface(Ci.nsIArray);
 | |
|     // If we have an option, we are in a call from requestStorageAccessUnderSite
 | |
|     // which means that the embedding principal is not the current top-level.
 | |
|     // Instead we have to grab the Site string out of the option and use that
 | |
|     // in the UI.
 | |
|     if (options.length) {
 | |
|       this.siteOption = options.queryElementAt(0, Ci.nsISupportsString).data;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   get usePermissionManager() {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   get type() {
 | |
|     return "storage-access";
 | |
|   }
 | |
| 
 | |
|   get permissionKey() {
 | |
|     // Make sure this name is unique per each third-party tracker
 | |
|     return `3rdPartyStorage${lazy.SitePermissions.PERM_KEY_DELIMITER}${this.principal.origin}`;
 | |
|   }
 | |
| 
 | |
|   get temporaryPermissionURI() {
 | |
|     if (this.siteOption) {
 | |
|       return Services.io.newURI(this.siteOption);
 | |
|     }
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   prettifyHostPort(hostport) {
 | |
|     let [host, port] = hostport.split(":");
 | |
|     host = lazy.IDNService.convertToDisplayIDN(host, {});
 | |
|     if (port) {
 | |
|       return `${host}:${port}`;
 | |
|     }
 | |
|     return host;
 | |
|   }
 | |
| 
 | |
|   get popupOptions() {
 | |
|     let learnMoreURL =
 | |
|       Services.urlFormatter.formatURLPref("app.support.baseURL") +
 | |
|       "third-party-cookies";
 | |
|     let hostPort = this.prettifyHostPort(this.principal.hostPort);
 | |
|     let hintText = lazy.gBrowserBundle.formatStringFromName(
 | |
|       "storageAccess1.hintText",
 | |
|       [hostPort]
 | |
|     );
 | |
|     return {
 | |
|       learnMoreURL,
 | |
|       displayURI: false,
 | |
|       hintText,
 | |
|       escAction: "secondarybuttoncommand",
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   get notificationID() {
 | |
|     return "storage-access";
 | |
|   }
 | |
| 
 | |
|   get anchorID() {
 | |
|     return "storage-access-notification-icon";
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     let embeddingHost = this.topLevelPrincipal.host;
 | |
| 
 | |
|     if (this.siteOption) {
 | |
|       embeddingHost = this.siteOption.split("://").at(-1);
 | |
|     }
 | |
| 
 | |
|     return lazy.gBrowserBundle.formatStringFromName("storageAccess4.message", [
 | |
|       this.prettifyHostPort(this.principal.hostPort),
 | |
|       this.prettifyHostPort(embeddingHost),
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   get promptActions() {
 | |
|     let self = this;
 | |
| 
 | |
|     return [
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "storageAccess1.Allow.label"
 | |
|         ),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "storageAccess1.Allow.accesskey"
 | |
|         ),
 | |
|         action: Ci.nsIPermissionManager.ALLOW_ACTION,
 | |
|         callback(state) {
 | |
|           self.allow({ "storage-access": "allow" });
 | |
|         },
 | |
|       },
 | |
|       {
 | |
|         label: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "storageAccess1.DontAllow.label"
 | |
|         ),
 | |
|         accessKey: lazy.gBrowserBundle.GetStringFromName(
 | |
|           "storageAccess1.DontAllow.accesskey"
 | |
|         ),
 | |
|         action: Ci.nsIPermissionManager.DENY_ACTION,
 | |
|         callback(state) {
 | |
|           self.cancel();
 | |
|         },
 | |
|       },
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   get topLevelPrincipal() {
 | |
|     return this.request.topLevelPrincipal;
 | |
|   }
 | |
| }
 | |
| 
 | |
| export const PermissionUI = {
 | |
|   PermissionPromptForRequest,
 | |
|   GeolocationPermissionPrompt,
 | |
|   XRPermissionPrompt,
 | |
|   DesktopNotificationPermissionPrompt,
 | |
|   PersistentStoragePermissionPrompt,
 | |
|   MIDIPermissionPrompt,
 | |
|   StorageAccessPermissionPrompt,
 | |
| };
 |