gecko-dev/browser/modules/PermissionUI.jsm

980 lines
31 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/. */
"use strict";
var 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.
*/
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "SitePermissions",
"resource:///modules/SitePermissions.jsm");
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
ChromeUtils.defineModuleGetter(this, "URICountListener",
"resource:///modules/BrowserUsageTelemetry.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "IDNService",
"@mozilla.org/network/idn-service;1", "nsIIDNService");
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings
.createBundle("chrome://browser/locale/browser.properties");
});
var 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.
*/
var 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.");
},
/**
* 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).
*/
get principalName() {
if (this.principal.addonPolicy) {
return this.principal.addonPolicy.name;
}
return this.principal.URI.hostPort;
},
/**
* 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;
},
/**
* 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 to the user in the PopupNotification. A string
* with "<>" as a placeholder that is usually replaced by an addon name
* or a host name, formatted in bold, to describe 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.
*
* 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} = 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"));
} 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} = SitePermissions.get(null,
this.permissionKey,
this.browser);
if (state == SitePermissions.BLOCK) {
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 == SitePermissions.SCOPE_PERSISTENT) {
// Permanently store permission.
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();
}
} else if (this.permissionKey) {
// TODO: Add support for permitTemporaryAllow
if (promptAction.action == SitePermissions.BLOCK) {
// Temporarily store BLOCK permissions.
// We don't consider subframes when storing temporary
// permissions on a tab, thus storing ALLOW could be exploited.
SitePermissions.set(null,
this.permissionKey,
promptAction.action,
SitePermissions.SCOPE_TEMPORARY,
this.browser);
}
}
},
};
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");
options.eventCallback = (topic) => {
// 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.
if (topic == "shown") {
this.onShown();
}
// The prompt has been removed, notify the PermissionUI.
if (topic == "removed") {
this.onAfterShow();
}
return false;
};
if (this.onBeforeShow() !== false) {
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.
*/
var 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.docShell.chromeEventHandler;
},
get principal() {
return this.request.principal;
},
cancel() {
this.request.cancel();
},
allow(choices) {
this.request.allow(choices);
},
};
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,
name: this.principalName,
};
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() {
if (this.principal.URI.schemeIs("file")) {
return gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
}
return gBrowserBundle.formatStringFromName("geolocation.shareWithSite3",
["<>"], 1);
},
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);
return true;
},
};
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";
return {
learnMoreURL,
displayURI: false,
name: this.principalName,
};
},
get notificationID() {
return "web-notifications";
},
get anchorID() {
return "web-notifications-notification-icon";
},
get message() {
return gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2",
["<>"], 1);
},
get promptActions() {
let actions = [
{
label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
accessKey:
gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
action: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_PERSISTENT,
},
{
label: gBrowserBundle.GetStringFromName("webNotifications.notNow"),
accessKey:
gBrowserBundle.GetStringFromName("webNotifications.notNow.accesskey"),
action: SitePermissions.BLOCK,
},
];
if (!PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
actions.push({
label: gBrowserBundle.GetStringFromName("webNotifications.never"),
accessKey:
gBrowserBundle.GetStringFromName("webNotifications.never.accesskey"),
action: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_PERSISTENT,
});
}
return actions;
},
};
PermissionUI.DesktopNotificationPermissionPrompt =
DesktopNotificationPermissionPrompt;
/**
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
* the persistent-storage API.
*
* @param request (nsIContentPermissionRequest)
* The request for a permission from content.
*/
function PersistentStoragePermissionPrompt(request) {
this.request = request;
}
PersistentStoragePermissionPrompt.prototype = {
__proto__: PermissionPromptForRequestPrototype,
get permissionKey() {
return "persistent-storage";
},
get popupOptions() {
let learnMoreURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
return {
learnMoreURL,
displayURI: false,
name: this.principalName,
};
},
get notificationID() {
return "persistent-storage";
},
get anchorID() {
return "persistent-storage-notification-icon";
},
get message() {
return gBrowserBundle.formatStringFromName("persistentStorage.allowWithSite",
["<>"], 1);
},
get promptActions() {
return [
{
label: gBrowserBundle.GetStringFromName("persistentStorage.allow"),
accessKey:
gBrowserBundle.GetStringFromName("persistentStorage.allow.accesskey"),
action: Ci.nsIPermissionManager.ALLOW_ACTION,
scope: SitePermissions.SCOPE_PERSISTENT,
},
{
label: gBrowserBundle.GetStringFromName("persistentStorage.notNow.label"),
accessKey:
gBrowserBundle.GetStringFromName("persistentStorage.notNow.accesskey"),
action: Ci.nsIPermissionManager.DENY_ACTION,
},
{
label: gBrowserBundle.GetStringFromName("persistentStorage.neverAllow.label"),
accessKey:
gBrowserBundle.GetStringFromName("persistentStorage.neverAllow.accesskey"),
action: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_PERSISTENT,
},
];
},
};
PermissionUI.PersistentStoragePermissionPrompt = PersistentStoragePermissionPrompt;
/**
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
* the WebMIDI API.
*
* @param request (nsIContentPermissionRequest)
* The request for a permission from content.
*/
function MIDIPermissionPrompt(request) {
this.request = request;
let types = request.types.QueryInterface(Ci.nsIArray);
let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
this.isSysexPerm = (perm.options.length > 0 &&
perm.options.queryElementAt(0, Ci.nsISupportsString) == "sysex");
this.permName = "midi";
if (this.isSysexPerm) {
this.permName = "midi-sysex";
}
}
MIDIPermissionPrompt.prototype = {
__proto__: PermissionPromptForRequestPrototype,
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.principalName,
};
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("midi.remember");
}
return options;
},
get notificationID() {
return "midi";
},
get anchorID() {
return "midi-notification-icon";
},
get message() {
let message;
if (this.principal.URI.schemeIs("file")) {
if (this.isSysexPerm) {
message = gBrowserBundle.formatStringFromName("midi.shareSysexWithFile.message");
} else {
message = gBrowserBundle.formatStringFromName("midi.shareWithFile.message");
}
} else if (this.isSysexPerm) {
message = gBrowserBundle.formatStringFromName("midi.shareSysexWithSite.message",
["<>"], 1);
} else {
message = gBrowserBundle.formatStringFromName("midi.shareWithSite.message",
["<>"], 1);
}
return message;
},
get promptActions() {
return [{
label: gBrowserBundle.GetStringFromName("midi.Allow.label"),
accessKey: gBrowserBundle.GetStringFromName("midi.Allow.accesskey"),
action: Ci.nsIPermissionManager.ALLOW_ACTION,
},
{
label: gBrowserBundle.GetStringFromName("midi.DontAllow.label"),
accessKey: gBrowserBundle.GetStringFromName("midi.DontAllow.accesskey"),
action: Ci.nsIPermissionManager.DENY_ACTION,
}];
},
};
PermissionUI.MIDIPermissionPrompt = MIDIPermissionPrompt;
function StorageAccessPermissionPrompt(request) {
this.request = request;
XPCOMUtils.defineLazyPreferenceGetter(this, "_autoGrants",
"dom.storage_access.auto_grants");
XPCOMUtils.defineLazyPreferenceGetter(this, "_maxConcurrentAutoGrants",
"dom.storage_access.max_concurrent_auto_grants");
}
StorageAccessPermissionPrompt.prototype = {
__proto__: PermissionPromptForRequestPrototype,
get usePermissionManager() {
return false;
},
get permissionKey() {
// Make sure this name is unique per each third-party tracker
return "storage-access-" + this.principal.origin;
},
prettifyHostPort(uri) {
try {
uri = Services.uriFixup.createExposableURI(uri);
} catch (e) {
// ignore, since we can't do anything better
}
let host = IDNService.convertToDisplayIDN(uri.host, {});
if (uri.port != -1) {
host += `:${uri.port}`;
}
return host;
},
get popupOptions() {
return {
displayURI: false,
name: this.prettifyHostPort(this.principal.URI),
secondName: this.prettifyHostPort(this.topLevelPrincipal.URI),
escAction: "buttoncommand",
};
},
onShown() {
let document = this.browser.ownerDocument;
let label =
gBrowserBundle.formatStringFromName("storageAccess.description.label",
[this.prettifyHostPort(this.request.principal.URI), "<>"], 2);
let parts = label.split("<>");
if (parts.length == 1) {
parts.push("");
}
let map = {
"storage-access-perm-label": parts[0],
"storage-access-perm-learnmore":
gBrowserBundle.GetStringFromName("storageAccess.description.learnmore"),
"storage-access-perm-endlabel": parts[1],
};
for (let id in map) {
let str = map[id];
document.getElementById(id).textContent = str;
}
let learnMoreURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "third-party-cookies";
document.getElementById("storage-access-perm-learnmore")
.href = learnMoreURL;
},
get notificationID() {
return "storage-access";
},
get anchorID() {
return "storage-access-notification-icon";
},
get message() {
return gBrowserBundle.formatStringFromName("storageAccess.message", ["<>", "<>"], 2);
},
get promptActions() {
let self = this;
return [{
label: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.label"),
accessKey: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.accesskey"),
action: Ci.nsIPermissionManager.DENY_ACTION,
callback(state) {
self.cancel();
},
},
{
label: gBrowserBundle.GetStringFromName("storageAccess.Allow.label"),
accessKey: gBrowserBundle.GetStringFromName("storageAccess.Allow.accesskey"),
action: Ci.nsIPermissionManager.ALLOW_ACTION,
callback(state) {
self.allow({"storage-access": "allow"});
},
},
{
label: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.label"),
accessKey: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.accesskey"),
action: Ci.nsIPermissionManager.ALLOW_ACTION,
callback(state) {
self.allow({"storage-access": "allow-on-any-site"});
},
}];
},
get topLevelPrincipal() {
return this.request.topLevelPrincipal;
},
get maxConcurrentAutomaticGrants() {
// one percent of the number of top-levels origins visited in the current
// session (but not to exceed 24 hours), or the value of the
// dom.storage_access.max_concurrent_auto_grants preference, whichever is
// higher.
return Math.max(Math.max(Math.floor(URICountListener.uniqueDomainsVisitedInPast24Hours / 100),
this._maxConcurrentAutoGrants), 0);
},
getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) {
let prefix = `3rdPartyStorage^${thirdPartyOrigin}`;
let perms = Services.perms.getAllWithTypePrefix(prefix);
let origins = new Set();
while (perms.length) {
let perm = perms.shift();
// Let's make sure that we're not looking at a permission for
// https://exampletracker.company when we mean to look for the
// permisison for https://exampletracker.com!
if (perm.type != prefix &&
!perm.type.startsWith(`${prefix}^`)) {
continue;
}
origins.add(perm.principal.origin);
}
return origins.size;
},
onBeforeShow() {
let thirdPartyOrigin = this.request.principal.origin;
if (this._autoGrants &&
this.getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) <
this.maxConcurrentAutomaticGrants) {
// Automatically accept the prompt
this.allow({"storage-access": "allow-auto-grant"});
return false;
}
return true;
},
};
PermissionUI.StorageAccessPermissionPrompt = StorageAccessPermissionPrompt;