mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1432 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1432 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.importESModule(
 | 
						|
 *   "resource:///modules/PermissionUI.sys.mjs"
 | 
						|
 * );
 | 
						|
 *
 | 
						|
 * 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.defineESModuleGetters(lazy, {
 | 
						|
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
 | 
						|
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | 
						|
  SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
XPCOMUtils.defineLazyServiceGetter(
 | 
						|
  lazy,
 | 
						|
  "IDNService",
 | 
						|
  "@mozilla.org/network/idn-service;1",
 | 
						|
  "nsIIDNService"
 | 
						|
);
 | 
						|
XPCOMUtils.defineLazyServiceGetter(
 | 
						|
  lazy,
 | 
						|
  "ContentPrefService2",
 | 
						|
  "@mozilla.org/content-pref/service;1",
 | 
						|
  "nsIContentPrefService2"
 | 
						|
);
 | 
						|
ChromeUtils.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) {
 | 
						|
        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: () => {
 | 
						|
          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() {
 | 
						|
    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 {
 | 
						|
  #permissionKey;
 | 
						|
 | 
						|
  constructor(request) {
 | 
						|
    super();
 | 
						|
    this.request = request;
 | 
						|
    this.siteOption = null;
 | 
						|
    this.#permissionKey = `3rdPartyStorage${lazy.SitePermissions.PERM_KEY_DELIMITER}${this.principal.origin}`;
 | 
						|
 | 
						|
    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, the permission request is different in some way.
 | 
						|
    // We may be in a call from requestStorageAccessUnderSite or a frame-scoped
 | 
						|
    // request, which means that the embedding principal is not the current top-level
 | 
						|
    // or the permission key is different.
 | 
						|
    if (options.length != 2) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let topLevelOption = options.queryElementAt(0, Ci.nsISupportsString).data;
 | 
						|
    if (topLevelOption) {
 | 
						|
      this.siteOption = topLevelOption;
 | 
						|
    }
 | 
						|
    let frameOption = options.queryElementAt(1, Ci.nsISupportsString).data;
 | 
						|
    if (frameOption) {
 | 
						|
      // We replace the permission key with a frame-specific one that only has a site after the delimiter
 | 
						|
      this.#permissionKey = `3rdPartyFrameStorage${lazy.SitePermissions.PERM_KEY_DELIMITER}${this.principal.siteOrigin}`;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  get usePermissionManager() {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  get type() {
 | 
						|
    return "storage-access";
 | 
						|
  }
 | 
						|
 | 
						|
  get permissionKey() {
 | 
						|
    // Make sure this name is unique per each third-party tracker
 | 
						|
    return this.#permissionKey;
 | 
						|
  }
 | 
						|
 | 
						|
  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() {
 | 
						|
          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() {
 | 
						|
          self.cancel();
 | 
						|
        },
 | 
						|
      },
 | 
						|
    ];
 | 
						|
  }
 | 
						|
 | 
						|
  get topLevelPrincipal() {
 | 
						|
    return this.request.topLevelPrincipal;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export const PermissionUI = {
 | 
						|
  PermissionPromptForRequest,
 | 
						|
  GeolocationPermissionPrompt,
 | 
						|
  XRPermissionPrompt,
 | 
						|
  DesktopNotificationPermissionPrompt,
 | 
						|
  PersistentStoragePermissionPrompt,
 | 
						|
  MIDIPermissionPrompt,
 | 
						|
  StorageAccessPermissionPrompt,
 | 
						|
};
 |