1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 var EXPORTED_SYMBOLS = ["SitePermissions"];
7 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 const { XPCOMUtils } = ChromeUtils.import(
9 "resource://gre/modules/XPCOMUtils.jsm"
12 XPCOMUtils.defineLazyModuleGetters(this, {
13 clearTimeout: "resource://gre/modules/Timer.jsm",
14 setTimeout: "resource://gre/modules/Timer.jsm",
17 var gStringBundle = Services.strings.createBundle(
18 "chrome://browser/locale/sitePermissions.properties"
22 * A helper module to manage temporary permissions.
24 * Permissions are keyed by browser, so methods take a Browser
25 * element to identify the corresponding permission set.
27 * This uses a WeakMap to key browsers, so that entries are
28 * automatically cleared once the browser stops existing
29 * (once there are no other references to the browser object);
31 const TemporaryPermissions = {
32 // This is a three level deep map with the following structure:
35 // <baseDomain|prePath>: {
36 // <permissionID>: {state: Number, expireTimeout: Number}
40 // Only the top level browser elements are stored via WeakMap. The WeakMap
41 // value is an object with URI baseDomains or prePaths as keys. The keys of
42 // that object are ids that identify permissions that were set for the
43 // specific URI. The final value is an object containing the permission state
44 // and the id of the timeout which will cause permission expiry.
45 // BLOCK permissions are keyed under baseDomain to prevent bypassing the block
46 // (see Bug 1492668). Any other permissions are keyed under URI prePath.
47 _stateByBrowser: new WeakMap(),
49 // Extract baseDomain from uri. Fallback to hostname on conversion error.
50 _uriToBaseDomain(uri) {
52 return Services.eTLD.getBaseDomain(uri);
55 error.result !== Cr.NS_ERROR_HOST_IS_IP_ADDRESS &&
56 error.result !== Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
65 * Generate keys to store temporary permissions under. The strict key is
66 * nsIURI.prePath, non-strict is URI baseDomain.
67 * @param {nsIURI} uri - URI to derive keys from.
68 * @returns {Object} keys - Object containing the generated permission keys.
69 * @returns {string} keys.strict - Key to be used for strict matching.
70 * @returns {string} keys.nonStrict - Key to be used for non-strict matching.
71 * @throws {Error} - Throws if URI is undefined or no valid permission key can
74 _getKeysFromURI(uri) {
75 return { strict: uri.prePath, nonStrict: this._uriToBaseDomain(uri) };
78 // Sets a new permission for the specified browser.
79 set(browser, id, state, expireTimeMS, browserURI, expireCallback) {
83 !SitePermissions.isSupportedScheme(browserURI.scheme)
87 let entry = this._stateByBrowser.get(browser);
89 entry = { browser: Cu.getWeakReference(browser), uriToPerm: {} };
90 this._stateByBrowser.set(browser, entry);
92 let { uriToPerm } = entry;
93 // We store blocked permissions by baseDomain. Other states by URI prePath.
94 let { strict, nonStrict } = this._getKeysFromURI(browserURI);
97 // Differenciate between block and non-block permissions. If we store a
98 // block permission we need to delete old entries which may be set under URI
99 // prePath before setting the new permission for baseDomain. For non-block
100 // permissions this is swapped.
101 if (state == SitePermissions.BLOCK) {
106 deleteKey = nonStrict;
109 if (!uriToPerm[setKey]) {
110 uriToPerm[setKey] = {};
113 let expireTimeout = uriToPerm[setKey][id]?.expireTimeout;
114 // If overwriting a permission state. We need to cancel the old timeout.
116 clearTimeout(expireTimeout);
118 // Construct the new timeout to remove the permission once it has expired.
119 expireTimeout = setTimeout(() => {
120 let entryBrowser = entry.browser.get();
121 // Exit early if the browser is no longer alive when we get the timeout
123 if (!entryBrowser || !uriToPerm[setKey]) {
126 delete uriToPerm[setKey][id];
127 // Notify SitePermissions that a temporary permission has expired.
128 // Get the browser the permission is currently set for. If this.copy was
129 // used this browser is different from the original one passed above.
130 expireCallback(entryBrowser);
132 uriToPerm[setKey][id] = {
137 // If we set a permission state for a prePath we need to reset the old state
138 // which may be set for baseDomain and vice versa. An individual permission
139 // must only ever be keyed by either prePath or baseDomain.
140 let permissions = uriToPerm[deleteKey];
144 expireTimeout = permissions[id]?.expireTimeout;
146 clearTimeout(expireTimeout);
148 delete permissions[id];
151 // Removes a permission with the specified id for the specified browser.
152 remove(browser, id) {
155 !SitePermissions.isSupportedScheme(browser.currentURI.scheme) ||
156 !this._stateByBrowser.has(browser)
160 // Permission can be stored by any of the two keys (strict and non-strict).
161 // getKeysFromURI can throw. We let the caller handle the exception.
162 let { strict, nonStrict } = this._getKeysFromURI(browser.currentURI);
163 let { uriToPerm } = this._stateByBrowser.get(browser);
164 for (let key of [nonStrict, strict]) {
165 if (uriToPerm[key]?.[id] != null) {
166 let { expireTimeout } = uriToPerm[key][id];
168 clearTimeout(expireTimeout);
170 delete uriToPerm[key][id];
171 // Individual permissions can only ever be keyed either strict or
172 // non-strict. If we find the permission via the first key run we can
179 // Gets a permission with the specified id for the specified browser.
183 !browser.currentURI ||
184 !SitePermissions.isSupportedScheme(browser.currentURI.scheme) ||
185 !this._stateByBrowser.has(browser)
189 let { uriToPerm } = this._stateByBrowser.get(browser);
191 let { strict, nonStrict } = this._getKeysFromURI(browser.currentURI);
192 for (let key of [nonStrict, strict]) {
193 if (uriToPerm[key]) {
194 let permission = uriToPerm[key][id];
198 state: permission.state,
199 scope: SitePermissions.SCOPE_TEMPORARY,
207 // Gets all permissions for the specified browser.
208 // Note that only permissions that apply to the current URI
209 // of the passed browser element will be returned.
211 let permissions = [];
213 !SitePermissions.isSupportedScheme(browser.currentURI.scheme) ||
214 !this._stateByBrowser.has(browser)
218 let { uriToPerm } = this._stateByBrowser.get(browser);
220 let { strict, nonStrict } = this._getKeysFromURI(browser.currentURI);
221 for (let key of [nonStrict, strict]) {
222 if (uriToPerm[key]) {
223 let perms = uriToPerm[key];
224 for (let id of Object.keys(perms)) {
225 let permission = perms[id];
229 state: permission.state,
230 scope: SitePermissions.SCOPE_TEMPORARY,
240 // Clears all permissions for the specified browser.
241 // Unlike other methods, this does NOT clear only for
242 // the currentURI but the whole browser state.
245 * Clear temporary permissions for the specified browser. Unlike other
246 * methods, this does NOT clear only for the currentURI but the whole browser
248 * @param {Browser} browser - Browser to clear permissions for.
249 * @param {Number} [filterState] - Only clear permissions with the given state
250 * value. Defaults to all permissions.
252 clear(browser, filterState = null) {
253 let entry = this._stateByBrowser.get(browser);
254 if (!entry?.uriToPerm) {
258 let { uriToPerm } = entry;
259 Object.entries(uriToPerm).forEach(([uriKey, permissions]) => {
260 Object.entries(permissions).forEach(
261 ([permId, { state, expireTimeout }]) => {
262 // We need to explicitly check for null or undefined here, because the
263 // permission state may be 0.
264 if (filterState != null) {
265 if (state != filterState) {
266 // Skip permission entry if it doesn't match the filter.
269 delete permissions[permId];
271 // For the clear-all case we remove the entire browser entry, so we
272 // only need to clear the timeouts.
273 if (!expireTimeout) {
276 clearTimeout(expireTimeout);
279 // If there are no more permissions, remove the entry from the URI map.
280 if (filterState != null && !Object.keys(permissions).length) {
281 delete uriToPerm[uriKey];
285 // We're either clearing all permissions or only the permissions with state
286 // == filterState. If we have a filter, we can only clean up the browser if
287 // there are no permission entries left in the map.
288 if (filterState == null || !Object.keys(uriToPerm).length) {
289 this._stateByBrowser.delete(browser);
293 // Copies the temporary permission state of one browser
294 // into a new entry for the other browser.
295 copy(browser, newBrowser) {
296 let entry = this._stateByBrowser.get(browser);
298 entry.browser = Cu.getWeakReference(newBrowser);
299 this._stateByBrowser.set(newBrowser, entry);
304 // This hold a flag per browser to indicate whether we should show the
305 // user a notification as a permission has been requested that has been
306 // blocked globally. We only want to notify the user in the case that
307 // they actually requested the permission within the current page load
308 // so will clear the flag on navigation.
309 const GloballyBlockedPermissions = {
310 _stateByBrowser: new WeakMap(),
313 if (!this._stateByBrowser.has(browser)) {
314 this._stateByBrowser.set(browser, {});
316 let entry = this._stateByBrowser.get(browser);
317 let prePath = browser.currentURI.prePath;
318 if (!entry[prePath]) {
322 if (entry[prePath][id]) {
325 entry[prePath][id] = true;
327 // Clear the flag and remove the listener once the user has navigated.
328 // WebProgress will report various things including hashchanges to us, the
329 // navigation we care about is either leaving the current page or reloading.
330 browser.addProgressListener(
332 QueryInterface: ChromeUtils.generateQI([
333 "nsIWebProgressListener",
334 "nsISupportsWeakReference",
336 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
338 aLocation.prePath != prePath ||
339 !(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
341 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD
344 if (aWebProgress.isTopLevel && (hasLeftPage || isReload)) {
345 GloballyBlockedPermissions.remove(browser, id, prePath);
346 browser.removeProgressListener(this);
350 Ci.nsIWebProgress.NOTIFY_LOCATION
354 // Removes a permission with the specified id for the specified browser.
355 remove(browser, id, prePath = null) {
356 let entry = this._stateByBrowser.get(browser);
358 prePath = browser.currentURI.prePath;
360 if (entry && entry[prePath]) {
361 delete entry[prePath][id];
365 // Gets all permissions for the specified browser.
366 // Note that only permissions that apply to the current URI
367 // of the passed browser element will be returned.
369 let permissions = [];
370 let entry = this._stateByBrowser.get(browser);
371 let prePath = browser.currentURI.prePath;
372 if (entry && entry[prePath]) {
373 let timeStamps = entry[prePath];
374 for (let id of Object.keys(timeStamps)) {
377 state: gPermissions.get(id).getDefault(),
378 scope: SitePermissions.SCOPE_GLOBAL,
385 // Copies the globally blocked permission state of one browser
386 // into a new entry for the other browser.
387 copy(browser, newBrowser) {
388 let entry = this._stateByBrowser.get(browser);
390 this._stateByBrowser.set(newBrowser, entry);
396 * A module to manage permanent and temporary permissions
397 * by URI and browser.
399 * Some methods have the side effect of dispatching a "PermissionStateChange"
400 * event on changes to temporary permissions, as mentioned in the respective docs.
402 var SitePermissions = {
403 // Permission states.
404 UNKNOWN: Services.perms.UNKNOWN_ACTION,
405 ALLOW: Services.perms.ALLOW_ACTION,
406 BLOCK: Services.perms.DENY_ACTION,
407 PROMPT: Services.perms.PROMPT_ACTION,
408 ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
409 AUTOPLAY_BLOCKED_ALL: Ci.nsIAutoplay.BLOCKED_ALL,
411 // Permission scopes.
412 SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
413 SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
414 SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
415 SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
416 SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
417 SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
419 // The delimiter used for double keyed permissions.
420 // For example: open-protocol-handler^irc
421 PERM_KEY_DELIMITER: "^",
423 _permissionsArray: null,
424 _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
426 // For testing use only.
427 _temporaryPermissions: TemporaryPermissions,
430 * Gets all custom permissions for a given principal.
431 * Install addon permission is excluded, check bug 1303108.
433 * @return {Array} a list of objects with the keys:
434 * - id: the permissionId of the permission
435 * - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
436 * - state: a constant representing the current permission state
437 * (e.g. SitePermissions.ALLOW)
439 getAllByPrincipal(principal) {
441 throw new Error("principal argument cannot be null.");
443 if (!this.isSupportedPrincipal(principal)) {
447 // Get all permissions from the permission manager by principal, excluding
448 // the ones set to be disabled.
449 let permissions = Services.perms
450 .getAllForPrincipal(principal)
451 .filter(permission => {
452 let entry = gPermissions.get(permission.type);
453 if (!entry || entry.disabled) {
458 /* Hide persistent storage permission when extension principal
459 * have WebExtensions-unlimitedStorage permission. */
461 type == "persistent-storage" &&
462 SitePermissions.getForPrincipal(
464 "WebExtensions-unlimitedStorage"
465 ).state == SitePermissions.ALLOW
473 return permissions.map(permission => {
474 let scope = this.SCOPE_PERSISTENT;
475 if (permission.expireType == Services.perms.EXPIRE_SESSION) {
476 scope = this.SCOPE_SESSION;
477 } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
478 scope = this.SCOPE_POLICY;
484 state: permission.capability,
490 * Returns all custom permissions for a given browser.
492 * To receive a more detailed, albeit less performant listing see
493 * SitePermissions.getAllPermissionDetailsForBrowser().
495 * @param {Browser} browser
496 * The browser to fetch permission for.
498 * @return {Array} a list of objects with the keys:
499 * - id: the permissionId of the permission
500 * - state: a constant representing the current permission state
501 * (e.g. SitePermissions.ALLOW)
502 * - scope: a constant representing how long the permission will
505 getAllForBrowser(browser) {
506 let permissions = {};
508 for (let permission of TemporaryPermissions.getAll(browser)) {
509 permission.scope = this.SCOPE_TEMPORARY;
510 permissions[permission.id] = permission;
513 for (let permission of GloballyBlockedPermissions.getAll(browser)) {
514 permissions[permission.id] = permission;
517 for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
518 permissions[permission.id] = permission;
521 return Object.values(permissions);
525 * Returns a list of objects with detailed information on all permissions
526 * that are currently set for the given browser.
528 * @param {Browser} browser
529 * The browser to fetch permission for.
531 * @return {Array<Object>} a list of objects with the keys:
532 * - id: the permissionID of the permission
533 * - state: a constant representing the current permission state
534 * (e.g. SitePermissions.ALLOW)
535 * - scope: a constant representing how long the permission will
537 * - label: the localized label, or null if none is available.
539 getAllPermissionDetailsForBrowser(browser) {
540 return this.getAllForBrowser(browser).map(({ id, scope, state }) => ({
544 label: this.getPermissionLabel(id),
549 * Checks whether a UI for managing permissions should be exposed for a given
552 * @param {nsIPrincipal} principal
553 * The principal to check.
555 * @return {boolean} if the principal is supported.
557 isSupportedPrincipal(principal) {
561 if (!(principal instanceof Ci.nsIPrincipal)) {
563 "Argument passed as principal is not an instance of Ci.nsIPrincipal"
566 return this.isSupportedScheme(principal.scheme);
570 * Checks whether we support managing permissions for a specific scheme.
571 * @param {string} scheme - Scheme to test.
572 * @returns {boolean} Whether the scheme is supported.
574 isSupportedScheme(scheme) {
575 return ["http", "https", "moz-extension", "file"].includes(scheme);
579 * Gets an array of all permission IDs.
581 * @return {Array<String>} an array of all permission IDs.
584 if (this._permissionsArray === null) {
585 this._permissionsArray = gPermissions.getEnabledPermissions();
587 return this._permissionsArray;
591 * Test whether a permission is managed by SitePermissions.
592 * @param {string} type - Permission type.
595 isSitePermission(type) {
596 return gPermissions.has(type);
600 * Called when a preference changes its value.
602 * @param {string} data
603 * The last argument passed to the preference change observer
604 * @param {string} previous
605 * The previous value of the preference
606 * @param {string} latest
607 * The latest value of the preference
609 invalidatePermissionList(data, previous, latest) {
610 // Ensure that listPermissions() will reconstruct its return value the next
612 this._permissionsArray = null;
616 * Returns an array of permission states to be exposed to the user for a
617 * permission with the given ID.
619 * @param {string} permissionID
620 * The ID to get permission states for.
622 * @return {Array<SitePermissions state>} an array of all permission states.
624 getAvailableStates(permissionID) {
626 gPermissions.has(permissionID) &&
627 gPermissions.get(permissionID).states
629 return gPermissions.get(permissionID).states;
632 /* Since the permissions we are dealing with have adopted the convention
633 * of treating UNKNOWN == PROMPT, we only include one of either UNKNOWN
634 * or PROMPT in this list, to avoid duplicating states. */
635 if (this.getDefault(permissionID) == this.UNKNOWN) {
637 SitePermissions.UNKNOWN,
638 SitePermissions.ALLOW,
639 SitePermissions.BLOCK,
644 SitePermissions.PROMPT,
645 SitePermissions.ALLOW,
646 SitePermissions.BLOCK,
651 * Returns the default state of a particular permission.
653 * @param {string} permissionID
654 * The ID to get the default for.
656 * @return {SitePermissions.state} the default state.
658 getDefault(permissionID) {
659 // If the permission has custom logic for getting its default value,
662 gPermissions.has(permissionID) &&
663 gPermissions.get(permissionID).getDefault
665 return gPermissions.get(permissionID).getDefault();
668 // Otherwise try to get the default preference for that permission.
669 return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
673 * Set the default state of a particular permission.
675 * @param {string} permissionID
676 * The ID to set the default for.
678 * @param {string} state
681 setDefault(permissionID, state) {
683 gPermissions.has(permissionID) &&
684 gPermissions.get(permissionID).setDefault
686 return gPermissions.get(permissionID).setDefault(state);
688 let key = "permissions.default." + permissionID;
689 return Services.prefs.setIntPref(key, state);
693 * Returns the state and scope of a particular permission for a given principal.
695 * This method will NOT dispatch a "PermissionStateChange" event on the specified
696 * browser if a temporary permission was removed because it has expired.
698 * @param {nsIPrincipal} principal
699 * The principal to check.
700 * @param {String} permissionID
701 * The id of the permission.
702 * @param {Browser} browser (optional)
703 * The browser object to check for temporary permissions.
705 * @return {Object} an object with the keys:
706 * - state: The current state of the permission
707 * (e.g. SitePermissions.ALLOW)
708 * - scope: The scope of the permission
709 * (e.g. SitePermissions.SCOPE_PERSISTENT)
711 getForPrincipal(principal, permissionID, browser) {
712 if (!principal && !browser) {
714 "Atleast one of the arguments, either principal or browser should not be null."
717 let defaultState = this.getDefault(permissionID);
718 let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
719 if (this.isSupportedPrincipal(principal)) {
720 let permission = null;
722 gPermissions.has(permissionID) &&
723 gPermissions.get(permissionID).exactHostMatch
725 permission = Services.perms.getPermissionObject(
731 permission = Services.perms.getPermissionObject(
739 result.state = permission.capability;
740 if (permission.expireType == Services.perms.EXPIRE_SESSION) {
741 result.scope = this.SCOPE_SESSION;
742 } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
743 result.scope = this.SCOPE_POLICY;
748 if (result.state == defaultState) {
749 // If there's no persistent permission saved, check if we have something
751 let value = TemporaryPermissions.get(browser, permissionID);
754 result.state = value.state;
755 result.scope = this.SCOPE_TEMPORARY;
763 * Sets the state of a particular permission for a given principal or browser.
764 * This method will dispatch a "PermissionStateChange" event on the specified
765 * browser if a temporary permission was set
767 * @param {nsIPrincipal} principal
768 * The principal to set the permission for.
769 * Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
770 * @param {String} permissionID
771 * The id of the permission.
772 * @param {SitePermissions state} state
773 * The state of the permission.
774 * @param {SitePermissions scope} scope (optional)
775 * The scope of the permission. Defaults to SCOPE_PERSISTENT.
776 * @param {Browser} browser (optional)
777 * The browser object to set temporary permissions on.
778 * This needs to be provided if the scope is SCOPE_TEMPORARY!
779 * @param {number} expireTimeMS (optional) If setting a temporary permission,
780 * how many milliseconds it should be valid for.
781 * @param {nsIURI} browserURI (optional) Pass a custom URI for the
782 * temporary permission scope. This defaults to the current URI of the
789 scope = this.SCOPE_PERSISTENT,
791 expireTimeMS = SitePermissions.temporaryPermissionExpireTime,
792 browserURI = browser?.currentURI
794 if (!principal && !browser) {
796 "Atleast one of the arguments, either principal or browser should not be null."
799 if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
800 GloballyBlockedPermissions.set(browser, permissionID);
801 browser.dispatchEvent(
802 new browser.ownerGlobal.CustomEvent("PermissionStateChange")
807 if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
808 // Because they are controlled by two prefs with many states that do not
809 // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
810 // allow the user to add exceptions to their cookie rules without removing them.
811 if (permissionID != "cookie") {
812 this.removeFromPrincipal(principal, permissionID, browser);
817 if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
819 "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission"
823 // Save temporary permissions.
824 if (scope == this.SCOPE_TEMPORARY) {
827 "TEMPORARY scoped permissions require a browser object"
830 if (!Number.isInteger(expireTimeMS) || expireTimeMS <= 0) {
831 throw new Error("expireTime must be a positive integer");
834 TemporaryPermissions.set(
840 // On permission expiry
842 if (!origBrowser.ownerGlobal) {
845 origBrowser.dispatchEvent(
846 new origBrowser.ownerGlobal.CustomEvent("PermissionStateChange")
851 browser.dispatchEvent(
852 new browser.ownerGlobal.CustomEvent("PermissionStateChange")
854 } else if (this.isSupportedPrincipal(principal)) {
855 let perms_scope = Services.perms.EXPIRE_NEVER;
856 if (scope == this.SCOPE_SESSION) {
857 perms_scope = Services.perms.EXPIRE_SESSION;
858 } else if (scope == this.SCOPE_POLICY) {
859 perms_scope = Services.perms.EXPIRE_POLICY;
862 Services.perms.addFromPrincipal(
872 * Removes the saved state of a particular permission for a given principal and/or browser.
873 * This method will dispatch a "PermissionStateChange" event on the specified
874 * browser if a temporary permission was removed.
876 * @param {nsIPrincipal} principal
877 * The principal to remove the permission for.
878 * @param {String} permissionID
879 * The id of the permission.
880 * @param {Browser} browser (optional)
881 * The browser object to remove temporary permissions on.
883 removeFromPrincipal(principal, permissionID, browser) {
884 if (!principal && !browser) {
886 "Atleast one of the arguments, either principal or browser should not be null."
889 if (this.isSupportedPrincipal(principal)) {
890 Services.perms.removeFromPrincipal(principal, permissionID);
893 // TemporaryPermissions.get() deletes expired permissions automatically,
894 if (TemporaryPermissions.get(browser, permissionID)) {
895 // If it exists but has not expired, remove it explicitly.
896 TemporaryPermissions.remove(browser, permissionID);
897 // Send a PermissionStateChange event only if the permission hasn't expired.
898 browser.dispatchEvent(
899 new browser.ownerGlobal.CustomEvent("PermissionStateChange")
905 * Clears all block permissions that were temporarily saved.
907 * @param {Browser} browser
908 * The browser object to clear.
910 clearTemporaryBlockPermissions(browser) {
911 TemporaryPermissions.clear(browser, SitePermissions.BLOCK);
915 * Copy all permissions that were temporarily saved on one
916 * browser object to a new browser.
918 * @param {Browser} browser
919 * The browser object to copy from.
920 * @param {Browser} newBrowser
921 * The browser object to copy to.
923 copyTemporaryPermissions(browser, newBrowser) {
924 TemporaryPermissions.copy(browser, newBrowser);
925 GloballyBlockedPermissions.copy(browser, newBrowser);
929 * Returns the localized label for the permission with the given ID, to be
930 * used in a UI for managing permissions.
931 * If a permission is double keyed (has an additional key in the ID), the
932 * second key is split off and supplied to the string formatter as a variable.
934 * @param {string} permissionID
935 * The permission to get the label for. May include second key.
937 * @return {String} the localized label or null if none is available.
939 getPermissionLabel(permissionID) {
940 let [id, key] = permissionID.split(this.PERM_KEY_DELIMITER);
941 if (!gPermissions.has(id)) {
942 // Permission can't be found.
946 "labelID" in gPermissions.get(id) &&
947 gPermissions.get(id).labelID === null
949 // Permission doesn't support having a label.
952 if (id == "3rdPartyStorage") {
953 // The key is the 3rd party origin, which we use for the label.
956 let labelID = gPermissions.get(id).labelID || id;
957 return gStringBundle.formatStringFromName(`permission.${labelID}.label`, [
963 * Returns the localized label for the given permission state, to be used in
964 * a UI for managing permissions.
966 * @param {string} permissionID
967 * The permission to get the label for.
969 * @param {SitePermissions state} state
970 * The state to get the label for.
972 * @return {String|null} the localized label or null if an
973 * unknown state was passed.
975 getMultichoiceStateLabel(permissionID, state) {
976 // If the permission has custom logic for getting its default value,
979 gPermissions.has(permissionID) &&
980 gPermissions.get(permissionID).getMultichoiceStateLabel
982 return gPermissions.get(permissionID).getMultichoiceStateLabel(state);
988 return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
990 return gStringBundle.GetStringFromName("state.multichoice.allow");
991 case this.ALLOW_COOKIES_FOR_SESSION:
992 return gStringBundle.GetStringFromName(
993 "state.multichoice.allowForSession"
996 return gStringBundle.GetStringFromName("state.multichoice.block");
1003 * Returns the localized label for a permission's current state.
1005 * @param {SitePermissions state} state
1006 * The state to get the label for.
1007 * @param {string} id
1008 * The permission to get the state label for.
1009 * @param {SitePermissions scope} scope (optional)
1010 * The scope to get the label for.
1012 * @return {String|null} the localized label or null if an
1013 * unknown state was passed.
1015 getCurrentStateLabel(state, id, scope = null) {
1018 return gStringBundle.GetStringFromName("state.current.prompt");
1022 scope != this.SCOPE_PERSISTENT &&
1023 scope != this.SCOPE_POLICY
1025 return gStringBundle.GetStringFromName(
1026 "state.current.allowedTemporarily"
1029 return gStringBundle.GetStringFromName("state.current.allowed");
1030 case this.ALLOW_COOKIES_FOR_SESSION:
1031 return gStringBundle.GetStringFromName(
1032 "state.current.allowedForSession"
1037 scope != this.SCOPE_PERSISTENT &&
1038 scope != this.SCOPE_POLICY &&
1039 scope != this.SCOPE_GLOBAL
1041 return gStringBundle.GetStringFromName(
1042 "state.current.blockedTemporarily"
1045 return gStringBundle.GetStringFromName("state.current.blocked");
1052 let gPermissions = {
1054 // Split off second key (if it exists).
1055 let [id] = type.split(SitePermissions.PERM_KEY_DELIMITER);
1060 return this._getId(type) in this._permissions;
1064 let id = this._getId(type);
1065 let perm = this._permissions[id];
1072 getEnabledPermissions() {
1073 return Object.keys(this._permissions).filter(
1074 id => !this._permissions[id].disabled
1078 /* Holds permission ID => options pairs.
1080 * Supported options:
1083 * Allows sub domains to have their own permissions.
1084 * Defaults to false.
1087 * Called to get the permission's default state.
1088 * Defaults to UNKNOWN, indicating that the user will be asked each time
1089 * a page asks for that permissions.
1092 * Use the given ID instead of the permission name for looking up strings.
1093 * e.g. "desktop-notification2" to use permission.desktop-notification2.label
1096 * Array of permission states to be exposed to the user.
1097 * Defaults to ALLOW, BLOCK and the default state (see getDefault).
1099 * - getMultichoiceStateLabel
1100 * Optional method to overwrite SitePermissions#getMultichoiceStateLabel with custom label logic.
1104 exactHostMatch: true,
1106 let pref = Services.prefs.getIntPref(
1107 "media.autoplay.default",
1108 Ci.nsIAutoplay.BLOCKED
1110 if (pref == Ci.nsIAutoplay.ALLOWED) {
1111 return SitePermissions.ALLOW;
1113 if (pref == Ci.nsIAutoplay.BLOCKED_ALL) {
1114 return SitePermissions.AUTOPLAY_BLOCKED_ALL;
1116 return SitePermissions.BLOCK;
1119 let prefValue = Ci.nsIAutoplay.BLOCKED;
1120 if (value == SitePermissions.ALLOW) {
1121 prefValue = Ci.nsIAutoplay.ALLOWED;
1122 } else if (value == SitePermissions.AUTOPLAY_BLOCKED_ALL) {
1123 prefValue = Ci.nsIAutoplay.BLOCKED_ALL;
1125 Services.prefs.setIntPref("media.autoplay.default", prefValue);
1127 labelID: "autoplay",
1129 SitePermissions.ALLOW,
1130 SitePermissions.BLOCK,
1131 SitePermissions.AUTOPLAY_BLOCKED_ALL,
1133 getMultichoiceStateLabel(state) {
1135 case SitePermissions.AUTOPLAY_BLOCKED_ALL:
1136 return gStringBundle.GetStringFromName(
1137 "state.multichoice.autoplayblockall"
1139 case SitePermissions.BLOCK:
1140 return gStringBundle.GetStringFromName(
1141 "state.multichoice.autoplayblock"
1143 case SitePermissions.ALLOW:
1144 return gStringBundle.GetStringFromName(
1145 "state.multichoice.autoplayallow"
1148 throw new Error(`Unknown state: ${state}`);
1154 SitePermissions.ALLOW,
1155 SitePermissions.ALLOW_COOKIES_FOR_SESSION,
1156 SitePermissions.BLOCK,
1160 Services.cookies.getCookieBehavior(false) ==
1161 Ci.nsICookieService.BEHAVIOR_REJECT
1163 return SitePermissions.BLOCK;
1167 Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
1168 Ci.nsICookieService.ACCEPT_SESSION
1170 return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
1173 return SitePermissions.ALLOW;
1177 "desktop-notification": {
1178 exactHostMatch: true,
1179 labelID: "desktop-notification3",
1183 exactHostMatch: true,
1187 exactHostMatch: true,
1191 exactHostMatch: true,
1192 states: [SitePermissions.UNKNOWN, SitePermissions.BLOCK],
1197 return Services.prefs.getBoolPref("dom.disable_open_during_load")
1198 ? SitePermissions.BLOCK
1199 : SitePermissions.ALLOW;
1201 states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
1206 return Services.prefs.getBoolPref("xpinstall.whitelist.required")
1207 ? SitePermissions.UNKNOWN
1208 : SitePermissions.ALLOW;
1213 exactHostMatch: true,
1216 "open-protocol-handler": {
1217 labelID: "open-protocol-handler",
1218 exactHostMatch: true,
1219 states: [SitePermissions.UNKNOWN, SitePermissions.ALLOW],
1221 return !SitePermissions.openProtoPermissionEnabled;
1226 exactHostMatch: true,
1229 "focus-tab-by-prompt": {
1230 exactHostMatch: true,
1231 states: [SitePermissions.UNKNOWN, SitePermissions.ALLOW],
1233 "persistent-storage": {
1234 exactHostMatch: true,
1238 states: [SitePermissions.ALLOW, SitePermissions.BLOCK],
1243 return !SitePermissions.resistFingerprinting;
1248 exactHostMatch: true,
1250 return !SitePermissions.midiPermissionEnabled;
1255 exactHostMatch: true,
1257 return !SitePermissions.midiPermissionEnabled;
1264 return SitePermissions.UNKNOWN;
1268 "3rdPartyStorage": {
1270 return !SitePermissions.statePartitioningPermissionsEnabled;
1276 SitePermissions.midiPermissionEnabled = Services.prefs.getBoolPref(
1277 "dom.webmidi.enabled"
1280 XPCOMUtils.defineLazyPreferenceGetter(
1282 "temporaryPermissionExpireTime",
1283 "privacy.temporary_permission_expire_time_ms",
1286 XPCOMUtils.defineLazyPreferenceGetter(
1288 "resistFingerprinting",
1289 "privacy.resistFingerprinting",
1291 SitePermissions.invalidatePermissionList.bind(SitePermissions)
1293 XPCOMUtils.defineLazyPreferenceGetter(
1295 "openProtoPermissionEnabled",
1296 "security.external_protocol_requires_permission",
1298 SitePermissions.invalidatePermissionList.bind(SitePermissions)
1300 XPCOMUtils.defineLazyPreferenceGetter(
1302 "statePartitioningPermissionsEnabled",
1303 "browser.contentblocking.state-partitioning.mvp.ui.enabled",
1305 SitePermissions.invalidatePermissionList.bind(SitePermissions)