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/. */
4
5var EXPORTED_SYMBOLS = [ "SitePermissions" ];
6
7const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
9
10var gStringBundle =
11  Services.strings.createBundle("chrome://communicator/locale/sitePermissions.properties");
12
13/**
14 * A helper module to manage temporarily blocked permissions.
15 *
16 * Permissions are keyed by browser, so methods take a Browser
17 * element to identify the corresponding permission set.
18 *
19 * This uses a WeakMap to key browsers, so that entries are
20 * automatically cleared once the browser stops existing
21 * (once there are no other references to the browser object);
22 */
23var TemporaryBlockedPermissions = {
24  // This is a three level deep map with the following structure:
25  //
26  // Browser => {
27  //   <prePath>: {
28  //     <permissionID>: {Number} <timeStamp>
29  //   }
30  // }
31  //
32  // Only the top level browser elements are stored via WeakMap. The WeakMap
33  // value is an object with URI prePaths as keys. The keys of that object
34  // are ids that identify permissions that were set for the specific URI.
35  // The final value is an object containing the timestamp of when the permission
36  // was set (in order to invalidate after a certain amount of time has passed).
37  _stateByBrowser: new WeakMap(),
38
39  // Private helper method that bundles some shared behavior for
40  // get() and getAll(), e.g. deleting permissions when they have expired.
41  _get(entry, prePath, id, timeStamp) {
42    if (timeStamp == null) {
43      delete entry[prePath][id];
44      return null;
45    }
46    if (timeStamp + SitePermissions.temporaryPermissionExpireTime < Date.now()) {
47      delete entry[prePath][id];
48      return null;
49    }
50    return {id, state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_TEMPORARY};
51  },
52
53  // Sets a new permission for the specified browser.
54  set(browser, id) {
55    if (!browser) {
56      return;
57    }
58    if (!this._stateByBrowser.has(browser)) {
59      this._stateByBrowser.set(browser, {});
60    }
61    let entry = this._stateByBrowser.get(browser);
62    let prePath = browser.currentURI.prePath;
63    if (!entry[prePath]) {
64      entry[prePath] = {};
65    }
66    entry[prePath][id] = Date.now();
67  },
68
69  // Removes a permission with the specified id for the specified browser.
70  remove(browser, id) {
71    if (!browser) {
72      return;
73    }
74    let entry = this._stateByBrowser.get(browser);
75    let prePath = browser.currentURI.prePath;
76    if (entry && entry[prePath]) {
77      delete entry[prePath][id];
78    }
79  },
80
81  // Gets a permission with the specified id for the specified browser.
82  get(browser, id) {
83    if (!browser || !browser.currentURI) {
84      return null;
85    }
86    let entry = this._stateByBrowser.get(browser);
87    let prePath = browser.currentURI.prePath;
88    if (entry && entry[prePath]) {
89      let permission = entry[prePath][id];
90      return this._get(entry, prePath, id, permission);
91    }
92    return null;
93  },
94
95  // Gets all permissions for the specified browser.
96  // Note that only permissions that apply to the current URI
97  // of the passed browser element will be returned.
98  getAll(browser) {
99    let permissions = [];
100    let entry = this._stateByBrowser.get(browser);
101    let prePath = browser.currentURI.prePath;
102    if (entry && entry[prePath]) {
103      let timeStamps = entry[prePath];
104      for (let id of Object.keys(timeStamps)) {
105        let permission = this._get(entry, prePath, id, timeStamps[id]);
106        // _get() returns null when the permission has expired.
107        if (permission) {
108          permissions.push(permission);
109        }
110      }
111    }
112    return permissions;
113  },
114
115  // Clears all permissions for the specified browser.
116  // Unlike other methods, this does NOT clear only for
117  // the currentURI but the whole browser state.
118  clear(browser) {
119    this._stateByBrowser.delete(browser);
120  },
121
122  // Copies the temporary permission state of one browser
123  // into a new entry for the other browser.
124  copy(browser, newBrowser) {
125    let entry = this._stateByBrowser.get(browser);
126    if (entry) {
127      this._stateByBrowser.set(newBrowser, entry);
128    }
129  },
130};
131
132/**
133 * A module to manage permanent and temporary permissions
134 * by URI and browser.
135 *
136 * Some methods have the side effect of dispatching a "PermissionStateChange"
137 * event on changes to temporary permissions, as mentioned in the respective docs.
138 */
139var SitePermissions = {
140  // Permission states.
141  // PROMPT_HIDE state is only used to show the "Hide Prompt" state in the
142  // identity panel for the "plugin:flash" permission and not in pageinfo.
143  UNKNOWN: Services.perms.UNKNOWN_ACTION,
144  ALLOW: Services.perms.ALLOW_ACTION,
145  BLOCK: Services.perms.DENY_ACTION,
146  PROMPT: Services.perms.PROMPT_ACTION,
147  ALLOW_COOKIES_FOR_SESSION: Ci.nsICookiePermission.ACCESS_SESSION,
148  PROMPT_HIDE: Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET,
149
150  // Permission scopes.
151  SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
152  SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
153  SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
154  SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
155
156  _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
157
158  /**
159   * Deprecated! Please use getAllByPrincipal(principal) instead.
160   * Gets all custom permissions for a given URI.
161   * Install addon permission is excluded, check bug 1303108.
162   *
163   * @return {Array} a list of objects with the keys:
164   *          - id: the permissionId of the permission
165   *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
166   *          - state: a constant representing the current permission state
167   *            (e.g. SitePermissions.ALLOW)
168   */
169  getAllByURI(uri) {
170    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
171    return this.getAllByPrincipal(principal);
172  },
173
174  /**
175   * Gets all custom permissions for a given principal.
176   * Install addon permission is excluded, check bug 1303108.
177   *
178   * @return {Array} a list of objects with the keys:
179   *          - id: the permissionId of the permission
180   *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
181   *          - state: a constant representing the current permission state
182   *            (e.g. SitePermissions.ALLOW)
183   */
184  getAllByPrincipal(principal) {
185    let result = [];
186    if (!this.isSupportedPrincipal(principal)) {
187      return result;
188    }
189
190    let permissions = Services.perms.getAllForPrincipal(principal);
191    while (permissions.hasMoreElements()) {
192      let permission = permissions.getNext();
193
194      // filter out unknown permissions
195      if (gPermissionObject[permission.type]) {
196        // XXX Bug 1303108 - Control Center should only show non-default permissions
197        if (permission.type == "install") {
198          continue;
199        }
200        let scope = this.SCOPE_PERSISTENT;
201        if (permission.expireType == Services.perms.EXPIRE_SESSION) {
202          scope = this.SCOPE_SESSION;
203        }
204        result.push({
205          id: permission.type,
206          scope,
207          state: permission.capability,
208        });
209      }
210    }
211
212    return result;
213  },
214
215  /**
216   * Returns all custom permissions for a given browser.
217   *
218   * To receive a more detailed, albeit less performant listing see
219   * SitePermissions.getAllPermissionDetailsForBrowser().
220   *
221   * @param {Browser} browser
222   *        The browser to fetch permission for.
223   *
224   * @return {Array} a list of objects with the keys:
225   *         - id: the permissionId of the permission
226   *         - state: a constant representing the current permission state
227   *           (e.g. SitePermissions.ALLOW)
228   *         - scope: a constant representing how long the permission will
229   *           be kept.
230   */
231  getAllForBrowser(browser) {
232    let permissions = {};
233
234    for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
235      permission.scope = this.SCOPE_TEMPORARY;
236      permissions[permission.id] = permission;
237    }
238
239    for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
240      permissions[permission.id] = permission;
241    }
242
243    return Object.values(permissions);
244  },
245
246  /**
247   * Returns a list of objects with detailed information on all permissions
248   * that are currently set for the given browser.
249   *
250   * @param {Browser} browser
251   *        The browser to fetch permission for.
252   *
253   * @return {Array<Object>} a list of objects with the keys:
254   *           - id: the permissionID of the permission
255   *           - state: a constant representing the current permission state
256   *             (e.g. SitePermissions.ALLOW)
257   *           - scope: a constant representing how long the permission will
258   *             be kept.
259   *           - label: the localized label
260   */
261  getAllPermissionDetailsForBrowser(browser) {
262    return this.getAllForBrowser(browser).map(({id, scope, state}) =>
263      ({id, scope, state, label: this.getPermissionLabel(id)}));
264  },
265
266  /**
267   * Deprecated! Please use isSupportedPrincipal(principal) instead.
268   * Checks whether a UI for managing permissions should be exposed for a given
269   * URI.
270   *
271   * @param {nsIURI} uri
272   *        The URI to check.
273   *
274   * @return {boolean} if the URI is supported.
275   */
276  isSupportedURI(uri) {
277    return uri && ["file", "http", "https", "moz-extension"].includes(uri.scheme);
278  },
279
280  /**
281   * Checks whether a UI for managing permissions should be exposed for a given
282   * principal.
283   *
284   * @param {nsIPrincipal} principal
285   *        The principal to check.
286   *
287   * @return {boolean} if the principal is supported.
288   */
289  isSupportedPrincipal(principal) {
290    return principal && principal.URI &&
291      ["file", "http", "https", "moz-extension"].includes(principal.URI.scheme);
292  },
293
294 /**
295   * Gets an array of all permission IDs.
296   *
297   * @return {Array<String>} an array of all permission IDs.
298   */
299  listPermissions() {
300    return Object.keys(gPermissionObject);
301  },
302
303  /**
304   * Returns an array of permission states to be exposed to the user for a
305   * permission with the given ID.
306   *
307   * @param {string} permissionID
308   *        The ID to get permission states for.
309   *
310   * @return {Array<SitePermissions state>} an array of all permission states.
311   */
312  getAvailableStates(permissionID) {
313    if (permissionID in gPermissionObject &&
314        gPermissionObject[permissionID].states)
315      return gPermissionObject[permissionID].states;
316
317    /* Since the permissions we are dealing with have adopted the convention
318     * of treating UNKNOWN == PROMPT, we only include one of either UNKNOWN
319     * or PROMPT in this list, to avoid duplicating states. */
320    if (this.getDefault(permissionID) == this.UNKNOWN)
321      return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
322
323    return [ SitePermissions.PROMPT, SitePermissions.ALLOW, SitePermissions.BLOCK ];
324  },
325
326  /**
327   * Returns the default state of a particular permission.
328   *
329   * @param {string} permissionID
330   *        The ID to get the default for.
331   *
332   * @return {SitePermissions.state} the default state.
333   */
334  getDefault(permissionID) {
335    // If the permission has custom logic for getting its default value,
336    // try that first.
337    if (permissionID in gPermissionObject &&
338        gPermissionObject[permissionID].getDefault)
339      return gPermissionObject[permissionID].getDefault();
340
341    // Otherwise try to get the default preference for that permission.
342    return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
343  },
344
345  /**
346   * Set the default state of a particular permission.
347   *
348   * @param {string} permissionID
349   *        The ID to set the default for.
350   *
351   * @param {string} state
352   *        The state to set.
353   */
354  setDefault(permissionID, state) {
355    if (permissionID in gPermissionObject &&
356        gPermissionObject[permissionID].setDefault) {
357      return gPermissionObject[permissionID].setDefault(state);
358    }
359    let key = "permissions.default." + permissionID;
360    return Services.prefs.setIntPref(key, state);
361  },
362  /**
363   * Returns the state and scope of a particular permission for a given URI.
364   *
365   * This method will NOT dispatch a "PermissionStateChange" event on the specified
366   * browser if a temporary permission was removed because it has expired.
367   *
368   * @param {nsIURI} uri
369   *        The URI to check.
370   * @param {String} permissionID
371   *        The id of the permission.
372   * @param {Browser} browser (optional)
373   *        The browser object to check for temporary permissions.
374   *
375   * @return {Object} an object with the keys:
376   *           - state: The current state of the permission
377   *             (e.g. SitePermissions.ALLOW)
378   *           - scope: The scope of the permission
379   *             (e.g. SitePermissions.SCOPE_PERSISTENT)
380   */
381  get(uri, permissionID, browser) {
382    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
383    return this.getForPrincipal(principal, permissionID, browser);
384  },
385
386 /**
387   * Returns the state and scope of a particular permission for a given
388   * principal.
389   *
390   * This method will NOT dispatch a "PermissionStateChange" event on the
391   * specified browser if a temporary permission was removed because it has
392   * expired.
393   *
394   * @param {nsIPrincipal} principal
395   *        The principal to check.
396   * @param {String} permissionID
397   *        The id of the permission.
398   * @param {Browser} browser (optional)
399   *        The browser object to check for temporary permissions.
400   *
401   * @return {Object} an object with the keys:
402   *           - state: The current state of the permission
403   *             (e.g. SitePermissions.ALLOW)
404   *           - scope: The scope of the permission
405   *             (e.g. SitePermissions.SCOPE_PERSISTENT)
406   */
407  getForPrincipal(principal, permissionID, browser) {
408    let defaultState = this.getDefault(permissionID);
409    let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
410    if (this.isSupportedPrincipal(principal)) {
411      let permission = null;
412      if (permissionID in gPermissionObject &&
413        gPermissionObject[permissionID].exactHostMatch) {
414        permission = Services.perms.getPermissionObject(principal, permissionID, true);
415      } else {
416        permission = Services.perms.getPermissionObject(principal, permissionID, false);
417      }
418
419      if (permission) {
420        result.state = permission.capability;
421        if (permission.expireType == Services.perms.EXPIRE_SESSION) {
422          result.scope = this.SCOPE_SESSION;
423        }
424      }
425    }
426
427    if (result.state == defaultState) {
428      // If there's no persistent permission saved, check if we have something
429      // set temporarily.
430      let value = TemporaryBlockedPermissions.get(browser, permissionID);
431
432      if (value) {
433        result.state = value.state;
434        result.scope = this.SCOPE_TEMPORARY;
435      }
436    }
437
438    return result;
439  },
440
441  /**
442   * Deprecated! Use setForPrincipal(...) instead.
443   * Sets the state of a particular permission for a given URI or browser.
444   * This method will dispatch a "PermissionStateChange" event on the specified
445   * browser if a temporary permission was set
446   *
447   * @param {nsIURI} uri
448   *        The URI to set the permission for.
449   *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
450   * @param {String} permissionID
451   *        The id of the permission.
452   * @param {SitePermissions state} state
453   *        The state of the permission.
454   * @param {SitePermissions scope} scope (optional)
455   *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
456   * @param {Browser} browser (optional)
457   *        The browser object to set temporary permissions on.
458   *        This needs to be provided if the scope is SCOPE_TEMPORARY!
459   */
460  set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
461    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
462    return this.setForPrincipal(principal, permissionID, state, scope, browser);
463  },
464
465  /**
466   * Sets the state of a particular permission for a given principal or browser.
467   * This method will dispatch a "PermissionStateChange" event on the specified
468   * browser if a temporary permission was set
469   *
470   * @param {nsIPrincipal} principal
471   *        The principal to set the permission for.
472   *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
473   * @param {String} permissionID
474   *        The id of the permission.
475   * @param {SitePermissions state} state
476   *        The state of the permission.
477   * @param {SitePermissions scope} scope (optional)
478   *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
479   * @param {Browser} browser (optional)
480   *        The browser object to set temporary permissions on.
481   *        This needs to be provided if the scope is SCOPE_TEMPORARY!
482   */
483  setForPrincipal(principal, permissionID, state,
484                  scope = this.SCOPE_PERSISTENT, browser = null) {
485    if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
486      // Because they are controlled by two prefs with many states that do not
487      // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
488      // allow the user to add exceptions to their cookie rules without
489      // removing them.
490      if (permissionID != "cookie") {
491        this.removeFromPrincipal(principal, permissionID, browser);
492        return;
493      }
494    }
495
496    if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
497      throw "ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission";
498    }
499
500    // Save temporary permissions.
501    if (scope == this.SCOPE_TEMPORARY) {
502      // We do not support setting temp ALLOW for security reasons.
503      // In its current state, this permission could be exploited by subframes
504      // on the same page. This is because for BLOCK we ignore the request
505      // principal and only consider the current browser principal, to avoid
506      // notification spamming.
507      //
508      // If you ever consider removing this line, you likely want to implement
509      // a more fine-grained TemporaryBlockedPermissions that temporarily blocks for the
510      // entire browser, but temporarily allows only for specific frames.
511      if (state != this.BLOCK) {
512        throw "'Block' is the only permission we can save temporarily on a browser";
513      }
514
515      if (!browser) {
516        throw "TEMPORARY scoped permissions require a browser object";
517      }
518
519      TemporaryBlockedPermissions.set(browser, permissionID);
520
521      browser.dispatchEvent(new browser.ownerGlobal
522                                       .CustomEvent("PermissionStateChange"));
523    } else if (this.isSupportedPrincipal(principal)) {
524      let perms_scope = Services.perms.EXPIRE_NEVER;
525      if (scope == this.SCOPE_SESSION) {
526        perms_scope = Services.perms.EXPIRE_SESSION;
527      }
528
529      Services.perms.addFromPrincipal(principal, permissionID, state, perms_scope);
530    }
531  },
532
533  /**
534   * Deprecated! Please use removeFromPrincipal(principal, permissionID, browser).
535   * Removes the saved state of a particular permission for a given URI and/or browser.
536   * This method will dispatch a "PermissionStateChange" event on the specified
537   * browser if a temporary permission was removed.
538   *
539   * @param {nsIURI} uri
540   *        The URI to remove the permission for.
541   * @param {String} permissionID
542   *        The id of the permission.
543   * @param {Browser} browser (optional)
544   *        The browser object to remove temporary permissions on.
545   */
546  remove(uri, permissionID, browser) {
547    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
548    return this.removeFromPrincipal(principal, permissionID, browser);
549  },
550
551  /**
552   * Removes the saved state of a particular permission for a given principal
553   * and/or browser.
554   * This method will dispatch a "PermissionStateChange" event on the specified
555   * browser if a temporary permission was removed.
556   *
557   * @param {nsIPrincipal} principal
558   *        The principal to remove the permission for.
559   * @param {String} permissionID
560   *        The id of the permission.
561   * @param {Browser} browser (optional)
562   *        The browser object to remove temporary permissions on.
563   */
564  removeFromPrincipal(principal, permissionID, browser) {
565    if (this.isSupportedPrincipal(principal))
566      Services.perms.removeFromPrincipal(principal, permissionID);
567
568    // TemporaryBlockedPermissions.get() deletes expired permissions automatically,
569    if (TemporaryBlockedPermissions.get(browser, permissionID)) {
570      // If it exists but has not expired, remove it explicitly.
571      TemporaryBlockedPermissions.remove(browser, permissionID);
572      // Send a PermissionStateChange event only if the permission hasn't expired.
573      browser.dispatchEvent(new browser.ownerGlobal
574                                       .CustomEvent("PermissionStateChange"));
575    }
576  },
577
578  /**
579   * Clears all permissions that were temporarily saved.
580   *
581   * @param {Browser} browser
582   *        The browser object to clear.
583   */
584  clearTemporaryPermissions(browser) {
585    TemporaryBlockedPermissions.clear(browser);
586  },
587
588  /**
589   * Copy all permissions that were temporarily saved on one
590   * browser object to a new browser.
591   *
592   * @param {Browser} browser
593   *        The browser object to copy from.
594   * @param {Browser} newBrowser
595   *        The browser object to copy to.
596   */
597  copyTemporaryPermissions(browser, newBrowser) {
598    TemporaryBlockedPermissions.copy(browser, newBrowser);
599  },
600
601  /**
602   * Returns the localized label for the permission with the given ID, to be
603   * used in a UI for managing permissions.
604   *
605   * @param {string} permissionID
606   *        The permission to get the label for.
607   *
608   * @return {String} the localized label.
609   */
610  getPermissionLabel(permissionID) {
611    let labelID = gPermissionObject[permissionID].labelID || permissionID;
612    return gStringBundle.GetStringFromName("permission." + labelID + ".label");
613  },
614
615  /**
616   * Returns the localized label for the given permission state, to be used in
617   * a UI for managing permissions.
618   *
619   * @param {string} permissionID
620   *        The permission to get the label for.
621   *
622   * @param {SitePermissions state} state
623   *        The state to get the label for.
624   *
625   * @return {String|null} the localized label or null if an
626   *         unknown state was passed.
627   */
628  getMultichoiceStateLabel(permissionID, state) {
629    // If the permission has custom logic for getting its default value,
630    // try that first.
631    if (permissionID in gPermissionObject &&
632        gPermissionObject[permissionID].getMultichoiceStateLabel) {
633      return gPermissionObject[permissionID].getMultichoiceStateLabel(state);
634    }
635
636    switch (state) {
637      case this.UNKNOWN:
638      case this.PROMPT:
639        return gStringBundle.GetStringFromName("state.multichoice.alwaysAsk");
640      case this.ALLOW:
641        return gStringBundle.GetStringFromName("state.multichoice.allow");
642      case this.ALLOW_COOKIES_FOR_SESSION:
643        return gStringBundle.GetStringFromName("state.multichoice.allowForSession");
644      case this.BLOCK:
645        return gStringBundle.GetStringFromName("state.multichoice.block");
646      default:
647        return null;
648    }
649  },
650
651  /**
652   * Returns the localized label for a permission's current state.
653   *
654   * @param {SitePermissions state} state
655   *        The state to get the label for.
656   * @param {string} id
657   *        The permission to get the state label for.
658   * @param {SitePermissions scope} scope (optional)
659   *        The scope to get the label for.
660   *
661   * @return {String|null} the localized label or null if an
662   *         unknown state was passed.
663   */
664  getCurrentStateLabel(state, id, scope = null) {
665    // We try to avoid a collision between SitePermissions.PROMPT_HIDE and
666    // SitePermissions.ALLOW_COOKIES_FOR_SESSION which share the same const
667    // value.
668    if (id.startsWith("plugin") && state == SitePermissions.PROMPT_HIDE) {
669      return gStringBundle.GetStringFromName("state.current.hide");
670    }
671    switch (state) {
672      case this.PROMPT:
673        return gStringBundle.GetStringFromName("state.current.prompt");
674      case this.ALLOW:
675        if (scope && scope != this.SCOPE_PERSISTENT)
676          return gStringBundle.GetStringFromName("state.current.allowedTemporarily");
677        return gStringBundle.GetStringFromName("state.current.allowed");
678      case this.ALLOW_COOKIES_FOR_SESSION:
679        return gStringBundle.GetStringFromName("state.current.allowedForSession");
680      case this.BLOCK:
681        if (scope && scope != this.SCOPE_PERSISTENT)
682          return gStringBundle.GetStringFromName("state.current.blockedTemporarily");
683        return gStringBundle.GetStringFromName("state.current.blocked");
684      default:
685        return null;
686    }
687  },
688};
689
690var gPermissionObject = {
691  /* Holds permission ID => options pairs.
692   *
693   * Supported options:
694   *
695   *  - exactHostMatch
696   *    Allows sub domains to have their own permissions.
697   *    Defaults to false.
698   *
699   *  - getDefault
700   *    Called to get the permission's default state.
701   *    Defaults to UNKNOWN, indicating that the user will be asked each time
702   *    a page asks for that permissions.
703   *
704   *  - labelID
705   *    Use the given ID instead of the permission name for looking up strings.
706   *    e.g. "desktop-notification2" to use permission.desktop-notification2.label
707   *
708   *  - states
709   *    Array of permission states to be exposed to the user.
710   *    Defaults to ALLOW, BLOCK and the default state (see getDefault).
711   *    The PROMPT_HIDE state is deliberately excluded from "plugin:flash"
712   *    since we don't want to expose a "Hide Prompt" button to the user
713   *    through pageinfo.
714   *
715   *  - getMultichoiceStateLabel
716   *    Allows for custom logic for getting its default value
717   */
718
719  "image": {
720    states: [
721      SitePermissions.ALLOW,
722      SitePermissions.PROMPT,
723      SitePermissions.BLOCK
724    ],
725    getMultichoiceStateLabel(state) {
726      switch (state) {
727        case SitePermissions.ALLOW:
728          return gStringBundle.GetStringFromName("state.multichoice.allow");
729        // Equates to BEHAVIOR_NOFOREIGN from nsContentBlocker.cpp
730        case SitePermissions.PROMPT:
731          return gStringBundle.GetStringFromName("state.multichoice.allowForSameDomain");
732        case SitePermissions.BLOCK:
733          return gStringBundle.GetStringFromName("state.multichoice.block");
734      }
735      throw new Error(`Unknown state: ${state}`);
736    },
737  },
738
739  "cookie": {
740    states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
741    getDefault() {
742      if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == Ci.nsICookieService.BEHAVIOR_REJECT)
743        return SitePermissions.BLOCK;
744
745      if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == Ci.nsICookieService.ACCEPT_SESSION)
746        return SitePermissions.ALLOW_COOKIES_FOR_SESSION;
747
748      return SitePermissions.ALLOW;
749    }
750  },
751
752  "desktop-notification": {
753    exactHostMatch: true,
754    labelID: "desktop-notification2",
755  },
756
757  "camera": {
758    exactHostMatch: true,
759  },
760
761  "microphone": {
762    exactHostMatch: true,
763  },
764
765  "screen": {
766    exactHostMatch: true,
767    states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
768  },
769
770  "popup": {
771    getDefault() {
772      return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
773               SitePermissions.BLOCK : SitePermissions.ALLOW;
774    },
775    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
776  },
777
778  "install": {
779    getDefault() {
780      return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
781               SitePermissions.BLOCK : SitePermissions.ALLOW;
782    },
783    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
784  },
785
786  "geo": {
787    exactHostMatch: true
788  },
789
790  "focus-tab-by-prompt": {
791    exactHostMatch: true,
792    states: [ SitePermissions.UNKNOWN, SitePermissions.ALLOW ],
793  },
794
795  "persistent-storage": {
796    exactHostMatch: true
797  },
798
799  "plugin:flash": {
800    labelID: "flash-plugin",
801    states: [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ],
802  }
803};
804
805// Delete this entry while being pre-off
806// or the persistent-storage permission would appear in Page info's Permission section
807if (!Services.prefs.getBoolPref("browser.storageManager.enabled")) {
808  delete gPermissionObject["persistent-storage"];
809}
810
811XPCOMUtils.defineLazyPreferenceGetter(SitePermissions, "temporaryPermissionExpireTime",
812                                      "privacy.temporary_permission_expire_time_ms", 3600 * 1000);
813