forked from mirrors/gecko-dev
		
	 2de58add22
			
		
	
	
		2de58add22
		
	
	
	
	
		
			
			MozReview-Commit-ID: JlnH2f1KW3U --HG-- extra : rebase_source : 13fb179fd85a47f1842b7715e82e92a30c4c4784
		
			
				
	
	
		
			829 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			829 lines
		
	
	
	
		
			28 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/. */
 | |
| 
 | |
| var EXPORTED_SYMBOLS = [ "SitePermissions" ];
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| 
 | |
| var gStringBundle =
 | |
|   Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
 | |
| 
 | |
| /**
 | |
|  * A helper module to manage temporary permissions.
 | |
|  *
 | |
|  * Permissions are keyed by browser, so methods take a Browser
 | |
|  * element to identify the corresponding permission set.
 | |
|  *
 | |
|  * This uses a WeakMap to key browsers, so that entries are
 | |
|  * automatically cleared once the browser stops existing
 | |
|  * (once there are no other references to the browser object);
 | |
|  */
 | |
| const TemporaryPermissions = {
 | |
|   // This is a three level deep map with the following structure:
 | |
|   //
 | |
|   // Browser => {
 | |
|   //   <prePath>: {
 | |
|   //     <permissionID>: {Number} <timeStamp>
 | |
|   //   }
 | |
|   // }
 | |
|   //
 | |
|   // Only the top level browser elements are stored via WeakMap. The WeakMap
 | |
|   // value is an object with URI prePaths as keys. The keys of that object
 | |
|   // are ids that identify permissions that were set for the specific URI.
 | |
|   // The final value is an object containing the timestamp of when the permission
 | |
|   // was set (in order to invalidate after a certain amount of time has passed).
 | |
|   _stateByBrowser: new WeakMap(),
 | |
| 
 | |
|   // Private helper method that bundles some shared behavior for
 | |
|   // get() and getAll(), e.g. deleting permissions when they have expired.
 | |
|   _get(entry, prePath, id, permission) {
 | |
|     if (permission == null || permission.timeStamp == null) {
 | |
|       delete entry[prePath][id];
 | |
|       return null;
 | |
|     }
 | |
|     if (permission.timeStamp + SitePermissions.temporaryPermissionExpireTime < Date.now()) {
 | |
|       delete entry[prePath][id];
 | |
|       return null;
 | |
|     }
 | |
|     return {id, state: permission.state, scope: SitePermissions.SCOPE_TEMPORARY};
 | |
|   },
 | |
| 
 | |
|   // Sets a new permission for the specified browser.
 | |
|   set(browser, id, state) {
 | |
|     if (!browser) {
 | |
|       return;
 | |
|     }
 | |
|     if (!this._stateByBrowser.has(browser)) {
 | |
|       this._stateByBrowser.set(browser, {});
 | |
|     }
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (!entry[prePath]) {
 | |
|       entry[prePath] = {};
 | |
|     }
 | |
|     entry[prePath][id] = {timeStamp: Date.now(), state};
 | |
|   },
 | |
| 
 | |
|   // Removes a permission with the specified id for the specified browser.
 | |
|   remove(browser, id) {
 | |
|     if (!browser) {
 | |
|       return;
 | |
|     }
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (entry && entry[prePath]) {
 | |
|       delete entry[prePath][id];
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Gets a permission with the specified id for the specified browser.
 | |
|   get(browser, id) {
 | |
|     if (!browser || !browser.currentURI) {
 | |
|       return null;
 | |
|     }
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (entry && entry[prePath]) {
 | |
|       let permission = entry[prePath][id];
 | |
|       return this._get(entry, prePath, id, permission);
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   // Gets all permissions for the specified browser.
 | |
|   // Note that only permissions that apply to the current URI
 | |
|   // of the passed browser element will be returned.
 | |
|   getAll(browser) {
 | |
|     let permissions = [];
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (entry && entry[prePath]) {
 | |
|       let timeStamps = entry[prePath];
 | |
|       for (let id of Object.keys(timeStamps)) {
 | |
|         let permission = this._get(entry, prePath, id, timeStamps[id]);
 | |
|         // _get() returns null when the permission has expired.
 | |
|         if (permission) {
 | |
|           permissions.push(permission);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return permissions;
 | |
|   },
 | |
| 
 | |
|   // Clears all permissions for the specified browser.
 | |
|   // Unlike other methods, this does NOT clear only for
 | |
|   // the currentURI but the whole browser state.
 | |
|   clear(browser) {
 | |
|     this._stateByBrowser.delete(browser);
 | |
|   },
 | |
| 
 | |
|   // Copies the temporary permission state of one browser
 | |
|   // into a new entry for the other browser.
 | |
|   copy(browser, newBrowser) {
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     if (entry) {
 | |
|       this._stateByBrowser.set(newBrowser, entry);
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| // This hold a flag per browser to indicate whether we should show the
 | |
| // user a notification as a permission has been requested that has been
 | |
| // blocked globally. We only want to notify the user in the case that
 | |
| // they actually requested the permission within the current page load
 | |
| // so will clear the flag on navigation.
 | |
| const GloballyBlockedPermissions = {
 | |
| 
 | |
|   _stateByBrowser: new WeakMap(),
 | |
| 
 | |
|   set(browser, id) {
 | |
|     if (!this._stateByBrowser.has(browser)) {
 | |
|       this._stateByBrowser.set(browser, {});
 | |
|     }
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (!entry[prePath]) {
 | |
|       entry[prePath] = {};
 | |
|     }
 | |
| 
 | |
|     entry[prePath][id] = true;
 | |
| 
 | |
|     // Listen to any top level navigations, once we see one clear the flag
 | |
|     // and remove the listener.
 | |
|     browser.addProgressListener({
 | |
|       QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
 | |
|                                               Ci.nsISupportsWeakReference]),
 | |
|       onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
 | |
|         if (aWebProgress.isTopLevel) {
 | |
|           GloballyBlockedPermissions.remove(browser, id);
 | |
|           browser.removeProgressListener(this);
 | |
|         }
 | |
|       },
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   // Removes a permission with the specified id for the specified browser.
 | |
|   remove(browser, id) {
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (entry && entry[prePath]) {
 | |
|       delete entry[prePath][id];
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Gets all permissions for the specified browser.
 | |
|   // Note that only permissions that apply to the current URI
 | |
|   // of the passed browser element will be returned.
 | |
|   getAll(browser) {
 | |
|     let permissions = [];
 | |
|     let entry = this._stateByBrowser.get(browser);
 | |
|     let prePath = browser.currentURI.prePath;
 | |
|     if (entry && entry[prePath]) {
 | |
|       let timeStamps = entry[prePath];
 | |
|       for (let id of Object.keys(timeStamps)) {
 | |
|         permissions.push({
 | |
|           id,
 | |
|           state: SitePermissions.BLOCK,
 | |
|           scope: SitePermissions.SCOPE_GLOBAL
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|     return permissions;
 | |
|   },
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * A module to manage permanent and temporary permissions
 | |
|  * by URI and browser.
 | |
|  *
 | |
|  * Some methods have the side effect of dispatching a "PermissionStateChange"
 | |
|  * event on changes to temporary permissions, as mentioned in the respective docs.
 | |
|  */
 | |
| var SitePermissions = {
 | |
|   // Permission states.
 | |
|   // PROMPT_HIDE state is only used to show the "Hide Prompt" state in the identity panel
 | |
|   // for the "plugin:flash" permission and not in pageinfo.
 | |
|   UNKNOWN: Services.perms.UNKNOWN_ACTION,
 | |
|   ALLOW: Services.perms.ALLOW_ACTION,
 | |
|   BLOCK: Services.perms.DENY_ACTION,
 | |
|   PROMPT: Services.perms.PROMPT_ACTION,
 | |
|   ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
 | |
|   PROMPT_HIDE: Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET,
 | |
| 
 | |
|   // Permission scopes.
 | |
|   SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
 | |
|   SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
 | |
|   SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
 | |
|   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
 | |
|   SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
 | |
|   SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
 | |
| 
 | |
|   _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
 | |
| 
 | |
|   /**
 | |
|    * Gets all custom permissions for a given URI.
 | |
|    * Install addon permission is excluded, check bug 1303108.
 | |
|    *
 | |
|    * @return {Array} a list of objects with the keys:
 | |
|    *          - id: the permissionId of the permission
 | |
|    *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
 | |
|    *          - state: a constant representing the current permission state
 | |
|    *            (e.g. SitePermissions.ALLOW)
 | |
|    */
 | |
|   getAllByURI(uri) {
 | |
|     let result = [];
 | |
|     if (!this.isSupportedURI(uri)) {
 | |
|       return result;
 | |
|     }
 | |
| 
 | |
|     let permissions = Services.perms.getAllForURI(uri);
 | |
|     while (permissions.hasMoreElements()) {
 | |
|       let permission = permissions.getNext();
 | |
| 
 | |
|       // filter out unknown permissions
 | |
|       if (gPermissionObject[permission.type]) {
 | |
|         // XXX Bug 1303108 - Control Center should only show non-default permissions
 | |
|         if (permission.type == "install") {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         // Hide canvas permission when privacy.resistFingerprinting is false.
 | |
|         if ((permission.type == "canvas") &&
 | |
|             !Services.prefs.getBoolPref("privacy.resistFingerprinting")) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         let scope = this.SCOPE_PERSISTENT;
 | |
|         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
 | |
|           scope = this.SCOPE_SESSION;
 | |
|         } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
 | |
|           scope = this.SCOPE_POLICY;
 | |
|         }
 | |
| 
 | |
|         result.push({
 | |
|           id: permission.type,
 | |
|           scope,
 | |
|           state: permission.capability,
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns all custom permissions for a given browser.
 | |
|    *
 | |
|    * To receive a more detailed, albeit less performant listing see
 | |
|    * SitePermissions.getAllPermissionDetailsForBrowser().
 | |
|    *
 | |
|    * @param {Browser} browser
 | |
|    *        The browser to fetch permission for.
 | |
|    *
 | |
|    * @return {Array} a list of objects with the keys:
 | |
|    *         - id: the permissionId of the permission
 | |
|    *         - state: a constant representing the current permission state
 | |
|    *           (e.g. SitePermissions.ALLOW)
 | |
|    *         - scope: a constant representing how long the permission will
 | |
|    *           be kept.
 | |
|    */
 | |
|   getAllForBrowser(browser) {
 | |
|     let permissions = {};
 | |
| 
 | |
|     for (let permission of TemporaryPermissions.getAll(browser)) {
 | |
|       permission.scope = this.SCOPE_TEMPORARY;
 | |
|       permissions[permission.id] = permission;
 | |
|     }
 | |
| 
 | |
|     for (let permission of GloballyBlockedPermissions.getAll(browser)) {
 | |
|       permissions[permission.id] = permission;
 | |
|     }
 | |
| 
 | |
|     for (let permission of this.getAllByURI(browser.currentURI)) {
 | |
|       permissions[permission.id] = permission;
 | |
|     }
 | |
| 
 | |
|     return Object.values(permissions);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns a list of objects with detailed information on all permissions
 | |
|    * that are currently set for the given browser.
 | |
|    *
 | |
|    * @param {Browser} browser
 | |
|    *        The browser to fetch permission for.
 | |
|    *
 | |
|    * @return {Array<Object>} a list of objects with the keys:
 | |
|    *           - id: the permissionID of the permission
 | |
|    *           - state: a constant representing the current permission state
 | |
|    *             (e.g. SitePermissions.ALLOW)
 | |
|    *           - scope: a constant representing how long the permission will
 | |
|    *             be kept.
 | |
|    *           - label: the localized label
 | |
|    */
 | |
|   getAllPermissionDetailsForBrowser(browser) {
 | |
|     return this.getAllForBrowser(browser).map(({id, scope, state}) =>
 | |
|       ({id, scope, state, label: this.getPermissionLabel(id)}));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Checks whether a UI for managing permissions should be exposed for a given
 | |
|    * URI. This excludes file URIs, for instance, as they don't have a host,
 | |
|    * even though nsIPermissionManager can still handle them.
 | |
|    *
 | |
|    * @param {nsIURI} uri
 | |
|    *        The URI to check.
 | |
|    *
 | |
|    * @return {boolean} if the URI is supported.
 | |
|    */
 | |
|   isSupportedURI(uri) {
 | |
|     return uri && ["http", "https", "moz-extension"].includes(uri.scheme);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Gets an array of all permission IDs.
 | |
|    *
 | |
|    * @return {Array<String>} an array of all permission IDs.
 | |
|    */
 | |
|   listPermissions() {
 | |
|     let permissions = Object.keys(gPermissionObject);
 | |
| 
 | |
|     // Hide canvas permission when privacy.resistFingerprinting is false.
 | |
|     if (!Services.prefs.getBoolPref("privacy.resistFingerprinting")) {
 | |
|       permissions = permissions.filter(permission => permission !== "canvas");
 | |
|     }
 | |
| 
 | |
|     return permissions;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns an array of permission states to be exposed to the user for a
 | |
|    * permission with the given ID.
 | |
|    *
 | |
|    * @param {string} permissionID
 | |
|    *        The ID to get permission states for.
 | |
|    *
 | |
|    * @return {Array<SitePermissions state>} an array of all permission states.
 | |
|    */
 | |
|   getAvailableStates(permissionID) {
 | |
|     if (permissionID in gPermissionObject &&
 | |
|         gPermissionObject[permissionID].states)
 | |
|       return gPermissionObject[permissionID].states;
 | |
| 
 | |
|     /* Since the permissions we are dealing with have adopted the convention
 | |
|      * of treating UNKNOWN == PROMPT, we only include one of either UNKNOWN
 | |
|      * or PROMPT in this list, to avoid duplicating states. */
 | |
|     if (this.getDefault(permissionID) == this.UNKNOWN)
 | |
|       return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
 | |
| 
 | |
|     return [ SitePermissions.PROMPT, SitePermissions.ALLOW, SitePermissions.BLOCK ];
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the default state of a particular permission.
 | |
|    *
 | |
|    * @param {string} permissionID
 | |
|    *        The ID to get the default for.
 | |
|    *
 | |
|    * @return {SitePermissions.state} the default state.
 | |
|    */
 | |
|   getDefault(permissionID) {
 | |
|     // If the permission has custom logic for getting its default value,
 | |
|     // try that first.
 | |
|     if (permissionID in gPermissionObject &&
 | |
|         gPermissionObject[permissionID].getDefault)
 | |
|       return gPermissionObject[permissionID].getDefault();
 | |
| 
 | |
|     // Otherwise try to get the default preference for that permission.
 | |
|     return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Return whether the browser should notify the user if a permission was
 | |
|    * globally blocked due to a preference.
 | |
|    *
 | |
|    * @param {string} permissionID
 | |
|    *        The ID to get the state for.
 | |
|    *
 | |
|    * @return boolean Whether to show notification for globally blocked permissions.
 | |
|    */
 | |
|   showGloballyBlocked(permissionID) {
 | |
|     if (permissionID in gPermissionObject &&
 | |
|         gPermissionObject[permissionID].showGloballyBlocked)
 | |
|       return gPermissionObject[permissionID].showGloballyBlocked;
 | |
| 
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   /*
 | |
|    * Return whether SitePermissions is permitted to store a TEMPORARY ALLOW
 | |
|    * state for a particular permission.
 | |
|    *
 | |
|    * @param {string} permissionID
 | |
|    *        The ID to get the state for.
 | |
|    *
 | |
|    * @return boolean Whether storing TEMPORARY ALLOW is permitted.
 | |
|    */
 | |
|   permitTemporaryAllow(permissionID) {
 | |
|     if (permissionID in gPermissionObject &&
 | |
|         gPermissionObject[permissionID].permitTemporaryAllow)
 | |
|       return gPermissionObject[permissionID].permitTemporaryAllow;
 | |
| 
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the state and scope of a particular permission for a given URI.
 | |
|    *
 | |
|    * This method will NOT dispatch a "PermissionStateChange" event on the specified
 | |
|    * browser if a temporary permission was removed because it has expired.
 | |
|    *
 | |
|    * @param {nsIURI} uri
 | |
|    *        The URI to check.
 | |
|    * @param {String} permissionID
 | |
|    *        The id of the permission.
 | |
|    * @param {Browser} browser (optional)
 | |
|    *        The browser object to check for temporary permissions.
 | |
|    *
 | |
|    * @return {Object} an object with the keys:
 | |
|    *           - state: The current state of the permission
 | |
|    *             (e.g. SitePermissions.ALLOW)
 | |
|    *           - scope: The scope of the permission
 | |
|    *             (e.g. SitePermissions.SCOPE_PERSISTENT)
 | |
|    */
 | |
|   get(uri, permissionID, browser) {
 | |
|     let defaultState = this.getDefault(permissionID);
 | |
|     let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
 | |
|     if (this.isSupportedURI(uri)) {
 | |
|       let permission = null;
 | |
|       if (permissionID in gPermissionObject &&
 | |
|         gPermissionObject[permissionID].exactHostMatch) {
 | |
|         permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
 | |
|       } else {
 | |
|         permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
 | |
|       }
 | |
| 
 | |
|       if (permission) {
 | |
|         result.state = permission.capability;
 | |
|         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
 | |
|           result.scope = this.SCOPE_SESSION;
 | |
|         } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
 | |
|           result.scope = this.SCOPE_POLICY;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (result.state == defaultState) {
 | |
|       // If there's no persistent permission saved, check if we have something
 | |
|       // set temporarily.
 | |
|       let value = TemporaryPermissions.get(browser, permissionID);
 | |
| 
 | |
|       if (value) {
 | |
|         result.state = value.state;
 | |
|         result.scope = this.SCOPE_TEMPORARY;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Sets the state of a particular permission for a given URI or browser.
 | |
|    * This method will dispatch a "PermissionStateChange" event on the specified
 | |
|    * browser if a temporary permission was set
 | |
|    *
 | |
|    * @param {nsIURI} uri
 | |
|    *        The URI to set the permission for.
 | |
|    *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
 | |
|    * @param {String} permissionID
 | |
|    *        The id of the permission.
 | |
|    * @param {SitePermissions state} state
 | |
|    *        The state of the permission.
 | |
|    * @param {SitePermissions scope} scope (optional)
 | |
|    *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
 | |
|    * @param {Browser} browser (optional)
 | |
|    *        The browser object to set temporary permissions on.
 | |
|    *        This needs to be provided if the scope is SCOPE_TEMPORARY!
 | |
|    */
 | |
|   set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
 | |
| 
 | |
|     if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
 | |
|       GloballyBlockedPermissions.set(browser, permissionID);
 | |
|       browser.dispatchEvent(new browser.ownerGlobal.CustomEvent("PermissionStateChange"));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
 | |
|       // Because they are controlled by two prefs with many states that do not
 | |
|       // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
 | |
|       // allow the user to add exceptions to their cookie rules without removing them.
 | |
|       if (permissionID != "cookie") {
 | |
|         this.remove(uri, permissionID, browser);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
 | |
|       throw "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission";
 | |
|     }
 | |
| 
 | |
|     // Save temporary permissions.
 | |
|     if (scope == this.SCOPE_TEMPORARY) {
 | |
|       // We do not support setting temp ALLOW for security reasons.
 | |
|       // In its current state, this permission could be exploited by subframes
 | |
|       // on the same page. This is because for BLOCK we ignore the request
 | |
|       // URI and only consider the current browser URI, to avoid notification spamming.
 | |
|       //
 | |
|       // If you ever consider removing this line, you likely want to implement
 | |
|       // a more fine-grained TemporaryPermissions that temporarily blocks for the
 | |
|       // entire browser, but temporarily allows only for specific frames.
 | |
|       if (state != this.BLOCK && !this.permitTemporaryAllow(permissionID)) {
 | |
|         throw "'Block' is the only permission we can save temporarily on a browser";
 | |
|       }
 | |
| 
 | |
|       if (!browser) {
 | |
|         throw "TEMPORARY scoped permissions require a browser object";
 | |
|       }
 | |
| 
 | |
|       TemporaryPermissions.set(browser, permissionID, state);
 | |
| 
 | |
|       browser.dispatchEvent(new browser.ownerGlobal
 | |
|                                        .CustomEvent("PermissionStateChange"));
 | |
|     } else if (this.isSupportedURI(uri)) {
 | |
|       let perms_scope = Services.perms.EXPIRE_NEVER;
 | |
|       if (scope == this.SCOPE_SESSION) {
 | |
|         perms_scope = Services.perms.EXPIRE_SESSION;
 | |
|       } else if (scope == this.SCOPE_POLICY) {
 | |
|         perms_scope = Services.perms.EXPIRE_POLICY;
 | |
|       }
 | |
| 
 | |
|       Services.perms.add(uri, permissionID, state, perms_scope);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes the saved state of a particular permission for a given URI and/or browser.
 | |
|    * This method will dispatch a "PermissionStateChange" event on the specified
 | |
|    * browser if a temporary permission was removed.
 | |
|    *
 | |
|    * @param {nsIURI} uri
 | |
|    *        The URI to remove the permission for.
 | |
|    * @param {String} permissionID
 | |
|    *        The id of the permission.
 | |
|    * @param {Browser} browser (optional)
 | |
|    *        The browser object to remove temporary permissions on.
 | |
|    */
 | |
|   remove(uri, permissionID, browser) {
 | |
|     if (this.isSupportedURI(uri))
 | |
|       Services.perms.remove(uri, permissionID);
 | |
| 
 | |
|     // TemporaryPermissions.get() deletes expired permissions automatically,
 | |
|     if (TemporaryPermissions.get(browser, permissionID)) {
 | |
|       // If it exists but has not expired, remove it explicitly.
 | |
|       TemporaryPermissions.remove(browser, permissionID);
 | |
|       // Send a PermissionStateChange event only if the permission hasn't expired.
 | |
|       browser.dispatchEvent(new browser.ownerGlobal
 | |
|                                        .CustomEvent("PermissionStateChange"));
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Clears all permissions that were temporarily saved.
 | |
|    *
 | |
|    * @param {Browser} browser
 | |
|    *        The browser object to clear.
 | |
|    */
 | |
|   clearTemporaryPermissions(browser) {
 | |
|     TemporaryPermissions.clear(browser);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Copy all permissions that were temporarily saved on one
 | |
|    * browser object to a new browser.
 | |
|    *
 | |
|    * @param {Browser} browser
 | |
|    *        The browser object to copy from.
 | |
|    * @param {Browser} newBrowser
 | |
|    *        The browser object to copy to.
 | |
|    */
 | |
|   copyTemporaryPermissions(browser, newBrowser) {
 | |
|     TemporaryPermissions.copy(browser, newBrowser);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the localized label for the permission with the given ID, to be
 | |
|    * used in a UI for managing permissions.
 | |
|    *
 | |
|    * @param {string} permissionID
 | |
|    *        The permission to get the label for.
 | |
|    *
 | |
|    * @return {String} the localized label.
 | |
|    */
 | |
|   getPermissionLabel(permissionID) {
 | |
|     let labelID = gPermissionObject[permissionID].labelID || permissionID;
 | |
|     return gStringBundle.GetStringFromName("permission." + labelID + ".label");
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the localized label for the given permission state, to be used in
 | |
|    * a UI for managing permissions.
 | |
|    *
 | |
|    * @param {SitePermissions state} state
 | |
|    *        The state to get the label for.
 | |
|    *
 | |
|    * @return {String|null} the localized label or null if an
 | |
|    *         unknown state was passed.
 | |
|    */
 | |
|   getMultichoiceStateLabel(state) {
 | |
|     switch (state) {
 | |
|       case this.UNKNOWN:
 | |
|       case this.PROMPT:
 | |
|         return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
 | |
|       case this.ALLOW:
 | |
|         return gStringBundle.GetStringFromName("state.multichoice.allow");
 | |
|       case this.ALLOW_COOKIES_FOR_SESSION:
 | |
|         return gStringBundle.GetStringFromName("state.multichoice.allowForSession");
 | |
|       case this.BLOCK:
 | |
|         return gStringBundle.GetStringFromName("state.multichoice.block");
 | |
|       default:
 | |
|         return null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the localized label for a permission's current state.
 | |
|    *
 | |
|    * @param {SitePermissions state} state
 | |
|    *        The state to get the label for.
 | |
|    * @param {string} id
 | |
|    *        The permission to get the state label for.
 | |
|    * @param {SitePermissions scope} scope (optional)
 | |
|    *        The scope to get the label for.
 | |
|    *
 | |
|    * @return {String|null} the localized label or null if an
 | |
|    *         unknown state was passed.
 | |
|    */
 | |
|   getCurrentStateLabel(state, id, scope = null) {
 | |
|     // We try to avoid a collision between SitePermissions.PROMPT_HIDE
 | |
|     // and SitePermissions.ALLOW_COOKIES_FOR_SESSION which share the same const value.
 | |
|     if (id.startsWith("plugin") && state == SitePermissions.PROMPT_HIDE) {
 | |
|       return gStringBundle.GetStringFromName("state.current.hide");
 | |
|     }
 | |
| 
 | |
|     switch (state) {
 | |
|       case this.PROMPT:
 | |
|         return gStringBundle.GetStringFromName("state.current.prompt");
 | |
|       case this.ALLOW:
 | |
|         if (scope && scope != this.SCOPE_PERSISTENT && scope != this.SCOPE_POLICY)
 | |
|           return gStringBundle.GetStringFromName("state.current.allowedTemporarily");
 | |
|         return gStringBundle.GetStringFromName("state.current.allowed");
 | |
|       case this.ALLOW_COOKIES_FOR_SESSION:
 | |
|         return gStringBundle.GetStringFromName("state.current.allowedForSession");
 | |
|       case this.BLOCK:
 | |
|         if (scope && scope != this.SCOPE_PERSISTENT && scope != this.SCOPE_POLICY)
 | |
|           return gStringBundle.GetStringFromName("state.current.blockedTemporarily");
 | |
|         return gStringBundle.GetStringFromName("state.current.blocked");
 | |
|       default:
 | |
|         return null;
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| var gPermissionObject = {
 | |
|   /* Holds permission ID => options pairs.
 | |
|    *
 | |
|    * Supported options:
 | |
|    *
 | |
|    *  - exactHostMatch
 | |
|    *    Allows sub domains to have their own permissions.
 | |
|    *    Defaults to false.
 | |
|    *
 | |
|    *  - getDefault
 | |
|    *    Called to get the permission's default state.
 | |
|    *    Defaults to UNKNOWN, indicating that the user will be asked each time
 | |
|    *    a page asks for that permissions.
 | |
|    *
 | |
|    *  - labelID
 | |
|    *    Use the given ID instead of the permission name for looking up strings.
 | |
|    *    e.g. "desktop-notification2" to use permission.desktop-notification2.label
 | |
|    *
 | |
|    *  - states
 | |
|    *    Array of permission states to be exposed to the user.
 | |
|    *    Defaults to ALLOW, BLOCK and the default state (see getDefault).
 | |
|    *    The PROMPT_HIDE state is deliberately excluded from "plugin:flash" since we
 | |
|    *    don't want to expose a "Hide Prompt" button to the user through pageinfo.
 | |
|    */
 | |
| 
 | |
|   "autoplay-media": {
 | |
|     exactHostMatch: true,
 | |
|     showGloballyBlocked: true,
 | |
|     permitTemporaryAllow: true,
 | |
|     getDefault() {
 | |
|       let state = Services.prefs.getIntPref("media.autoplay.default",
 | |
|                                             Ci.nsIAutoplay.PROMPT);
 | |
|       if (state == Ci.nsIAutoplay.ALLOWED) {
 | |
|         return SitePermissions.ALLOW;
 | |
|       } if (state == Ci.nsIAutoplay.BLOCKED) {
 | |
|         return SitePermissions.BLOCK;
 | |
|       }
 | |
|       return SitePermissions.UNKNOWN;
 | |
|     },
 | |
|     labelID: "autoplay-media"
 | |
|   },
 | |
| 
 | |
|   "image": {
 | |
|     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "cookie": {
 | |
|     states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
 | |
|     getDefault() {
 | |
|       if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == Ci.nsICookieService.BEHAVIOR_REJECT)
 | |
|         return SitePermissions.BLOCK;
 | |
| 
 | |
|       if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == Ci.nsICookieService.ACCEPT_SESSION)
 | |
|         return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
 | |
| 
 | |
|       return SitePermissions.ALLOW;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   "desktop-notification": {
 | |
|     exactHostMatch: true,
 | |
|     labelID: "desktop-notification2",
 | |
|   },
 | |
| 
 | |
|   "camera": {
 | |
|     exactHostMatch: true,
 | |
|   },
 | |
| 
 | |
|   "microphone": {
 | |
|     exactHostMatch: true,
 | |
|   },
 | |
| 
 | |
|   "screen": {
 | |
|     exactHostMatch: true,
 | |
|     states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "popup": {
 | |
|     getDefault() {
 | |
|       return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
 | |
|                SitePermissions.BLOCK : SitePermissions.ALLOW;
 | |
|     },
 | |
|     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "install": {
 | |
|     getDefault() {
 | |
|       return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
 | |
|                SitePermissions.BLOCK : SitePermissions.ALLOW;
 | |
|     },
 | |
|     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "geo": {
 | |
|     exactHostMatch: true
 | |
|   },
 | |
| 
 | |
|   "focus-tab-by-prompt": {
 | |
|     exactHostMatch: true,
 | |
|     states: [ SitePermissions.UNKNOWN, SitePermissions.ALLOW ],
 | |
|   },
 | |
|   "persistent-storage": {
 | |
|     exactHostMatch: true
 | |
|   },
 | |
| 
 | |
|   "shortcuts": {
 | |
|     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "canvas": {
 | |
|   },
 | |
| 
 | |
|   "plugin:flash": {
 | |
|     labelID: "flash-plugin",
 | |
|     states: [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ],
 | |
|   },
 | |
| 
 | |
|   "midi": {
 | |
|     exactHostMatch: true
 | |
|   },
 | |
| 
 | |
|   "midi-sysex": {
 | |
|     exactHostMatch: true
 | |
|   }
 | |
| };
 | |
| 
 | |
| if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
 | |
|   // ESLint gets angry about array versus dot notation here, but some permission
 | |
|   // names use hyphens. Disabling rule for line to keep things consistent.
 | |
|   // eslint-disable-next-line dot-notation
 | |
|   delete gPermissionObject["midi"];
 | |
|   delete gPermissionObject["midi-sysex"];
 | |
| }
 | |
| 
 | |
| XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
 | |
|                                       "privacy.temporary_permission_expire_time_ms", 3600 * 1000);
 |