summaryrefslogtreecommitdiff
path: root/application/basilisk/modules/PermissionUI.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/modules/PermissionUI.jsm')
-rw-r--r--application/basilisk/modules/PermissionUI.jsm601
1 files changed, 601 insertions, 0 deletions
diff --git a/application/basilisk/modules/PermissionUI.jsm b/application/basilisk/modules/PermissionUI.jsm
new file mode 100644
index 0000000000..b4856a007c
--- /dev/null
+++ b/application/basilisk/modules/PermissionUI.jsm
@@ -0,0 +1,601 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "PermissionUI",
+];
+
+/**
+ * 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:
+ *
+ * Cu.import("resource://gre/modules/Integration.jsm");
+ * Cu.import("resource:///modules/PermissionUI.jsm");
+ *
+ * const SoundCardIntegration = (base) => ({
+ * __proto__: base,
+ * createPermissionPrompt(type, request) {
+ * if (type != "sound-api") {
+ * return super.createPermissionPrompt(...arguments);
+ * }
+ *
+ * return {
+ * __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ * get permissionKey() {
+ * return "sound-permission";
+ * }
+ * // etc - see the documentation for PermissionPrompt for
+ * // a better idea of what things one can and should override.
+ * }
+ * },
+ * });
+ *
+ * // Add-on startup:
+ * Integration.contentPermission.register(SoundCardIntegration);
+ * // ...
+ * // Add-on shutdown:
+ * Integration.contentPermission.unregister(SoundCardIntegration);
+ *
+ * Note that PermissionPromptForRequestPrototype 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 PermissionPromptPrototype can be
+ * imported, subclassed, and have prompt() called directly, without
+ * the caller having called into createPermissionPrompt.
+ */
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
+ "resource:///modules/SitePermissions.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ return Services.strings
+ .createBundle("chrome://branding/locale/brand.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings
+ .createBundle("chrome://browser/locale/browser.properties");
+});
+
+this.PermissionUI = {};
+
+/**
+ * PermissionPromptPrototype 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
+ * PermissionPromptForRequestPrototype instead.
+ */
+this.PermissionPromptPrototype = {
+ /**
+ * 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.");
+ },
+
+ /**
+ * 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, user permissions will not be
+ * read from or written to.
+ *
+ * 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;
+ },
+
+ /**
+ * These are the options that will be passed to the
+ * PopupNotification when it is shown. See the documentation
+ * for PopupNotification for more 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 {};
+ },
+
+ /**
+ * 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 PopupNotification.jsm 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 the user in the PopupNotification. This
+ * is usually a string describing the permission that is being
+ * requested.
+ *
+ * Subclasses must override this.
+ *
+ * @return {string}
+ */
+ get message() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * 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.
+ *
+ * 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 [];
+ },
+
+ /**
+ * 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.
+ */
+ onBeforeShow() {},
+
+ /**
+ * 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 <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() {
+ let chromeWin = this.browser.ownerGlobal;
+ if (!chromeWin.PopupNotifications) {
+ return;
+ }
+
+ // We ignore requests from non-nsIStandardURLs
+ let requestingURI = this.principal.URI;
+ if (!(requestingURI instanceof Ci.nsIStandardURL)) {
+ return;
+ }
+
+ if (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} = SitePermissions.get(requestingURI,
+ this.permissionKey,
+ this.browser);
+
+ if (state == SitePermissions.BLOCK) {
+ this.cancel();
+ return;
+ }
+
+ if (state == SitePermissions.ALLOW) {
+ this.allow();
+ return;
+ }
+
+ // Tell the browser to refresh the identity block display in case there
+ // are expired permission states.
+ this.browser.dispatchEvent(new this.browser.ownerGlobal
+ .CustomEvent("PermissionStateChange"));
+ }
+
+ // 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.permissionKey) {
+
+ // Permanently store permission.
+ if (state && state.checkboxChecked) {
+ let scope = SitePermissions.SCOPE_PERSISTENT;
+ // Only remember permission for session if in PB mode.
+ if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
+ scope = SitePermissions.SCOPE_SESSION;
+ }
+ SitePermissions.set(this.principal.URI,
+ this.permissionKey,
+ promptAction.action,
+ scope);
+ } else if (promptAction.action == 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.
+ SitePermissions.set(this.principal.URI,
+ this.permissionKey,
+ promptAction.action,
+ SitePermissions.SCOPE_TEMPORARY,
+ this.browser);
+ }
+
+ // Grant permission if action is ALLOW.
+ if (promptAction.action == SitePermissions.ALLOW) {
+ this.allow();
+ } else {
+ this.cancel();
+ }
+ }
+ },
+ };
+ if (promptAction.dismiss) {
+ action.dismiss = promptAction.dismiss
+ }
+
+ popupNotificationActions.push(action);
+ }
+
+ let mainAction = popupNotificationActions.length ?
+ popupNotificationActions[0] : null;
+ let secondaryActions = popupNotificationActions.splice(1);
+
+ let options = this.popupOptions;
+
+ if (!options.hasOwnProperty("displayURI") || options.displayURI) {
+ options.displayURI = this.principal.URI;
+ }
+ // Permission prompts are always persistent; the close button is controlled by a pref.
+ options.persistent = true;
+ options.hideClose = !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton");
+ // 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.
+ options.eventCallback = topic => topic == "swapping";
+
+ this.onBeforeShow();
+ chromeWin.PopupNotifications.show(this.browser,
+ this.notificationID,
+ this.message,
+ this.anchorID,
+ mainAction,
+ secondaryActions,
+ options);
+ },
+};
+
+PermissionUI.PermissionPromptPrototype = PermissionPromptPrototype;
+
+/**
+ * A subclass of PermissionPromptPrototype 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 PermissionPromptPrototype.
+ */
+this.PermissionPromptForRequestPrototype = {
+ __proto__: PermissionPromptPrototype,
+
+ 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
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ },
+
+ get principal() {
+ return this.request.principal;
+ },
+
+ cancel() {
+ this.request.cancel();
+ },
+
+ allow() {
+ this.request.allow();
+ },
+};
+
+PermissionUI.PermissionPromptForRequestPrototype =
+ PermissionPromptForRequestPrototype;
+
+/**
+ * Creates a PermissionPrompt for a nsIContentPermissionRequest for
+ * the GeoLocation API.
+ *
+ * @param request (nsIContentPermissionRequest)
+ * The request for a permission from content.
+ */
+function GeolocationPermissionPrompt(request) {
+ this.request = request;
+}
+
+GeolocationPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get permissionKey() {
+ return "geo";
+ },
+
+ get popupOptions() {
+ let pref = "browser.geolocation.warning.infoURL";
+ let options = {
+ learnMoreURL: Services.urlFormatter.formatURLPref(pref),
+ displayURI: false
+ };
+
+ if (this.principal.URI.schemeIs("file")) {
+ options.checkbox = { show: false };
+ } else {
+ // Don't offer "always remember" action in PB mode
+ options.checkbox = {
+ show: !PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal)
+ };
+ }
+
+ if (options.checkbox.show) {
+ options.checkbox.label = gBrowserBundle.GetStringFromName("geolocation.remember");
+ }
+
+ return options;
+ },
+
+ get notificationID() {
+ return "geolocation";
+ },
+
+ get anchorID() {
+ return "geo-notification-icon";
+ },
+
+ get message() {
+ let message;
+ if (this.principal.URI.schemeIs("file")) {
+ message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
+ } else {
+ let hostPort = "<>";
+ try {
+ hostPort = this.principal.URI.hostPort;
+ } catch (ex) { }
+ message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite3",
+ [hostPort], 1);
+ }
+ return message;
+ },
+
+ get promptActions() {
+ // We collect Telemetry data on Geolocation prompts and how users
+ // respond to them. The probe keys are a bit verbose, so let's alias them.
+ const SHARE_LOCATION =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION;
+ const ALWAYS_SHARE =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE;
+ const NEVER_SHARE =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
+
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+
+ return [{
+ label: gBrowserBundle.GetStringFromName("geolocation.allowLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.allowLocation.accesskey"),
+ action: SitePermissions.ALLOW,
+ callback(state) {
+ if (state && state.checkboxChecked) {
+ secHistogram.add(ALWAYS_SHARE);
+ } else {
+ secHistogram.add(SHARE_LOCATION);
+ }
+ },
+ }, {
+ label: gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation.accesskey"),
+ action: SitePermissions.BLOCK,
+ callback(state) {
+ if (state && state.checkboxChecked) {
+ secHistogram.add(NEVER_SHARE);
+ }
+ },
+ }];
+ },
+
+ onBeforeShow() {
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
+ secHistogram.add(SHOW_REQUEST);
+ },
+};
+
+PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
+
+/**
+ * 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)
+ */
+function DesktopNotificationPermissionPrompt(request) {
+ this.request = request;
+}
+
+DesktopNotificationPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get permissionKey() {
+ return "desktop-notification";
+ },
+
+ get popupOptions() {
+ let learnMoreURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
+
+ let checkbox = {
+ show: true,
+ checked: true,
+ label: gBrowserBundle.GetStringFromName("webNotifications.remember")
+ };
+
+ // In PB mode, the "always remember" checkbox should only remember for the
+ // session.
+ if (PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal)) {
+ checkbox.label =
+ gBrowserBundle.GetStringFromName("webNotifications.rememberForSession");
+ }
+
+ return {
+ learnMoreURL,
+ checkbox,
+ displayURI: false
+ };
+ },
+
+ get notificationID() {
+ return "web-notifications";
+ },
+
+ get anchorID() {
+ return "web-notifications-notification-icon";
+ },
+
+ get message() {
+ let hostPort = "<>";
+ try {
+ hostPort = this.principal.URI.hostPort;
+ } catch (ex) { }
+ return gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2",
+ [hostPort], 1);
+ },
+
+ get promptActions() {
+ return [
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
+ action: SitePermissions.ALLOW,
+ },
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.dontAllow"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.dontAllow.accesskey"),
+ action: SitePermissions.BLOCK,
+ },
+ ];
+ },
+};
+
+PermissionUI.DesktopNotificationPermissionPrompt =
+ DesktopNotificationPermissionPrompt;