tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UserContextManager.sys.mjs (7612B)


      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 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  ContextualIdentityService:
      9    "resource://gre/modules/ContextualIdentityService.sys.mjs",
     10  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
     11 
     12  ContextualIdentityListener:
     13    "chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs",
     14  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
     15  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
     16 });
     17 
     18 const DEFAULT_CONTEXT_ID = "default";
     19 const DEFAULT_INTERNAL_ID = 0;
     20 
     21 /**
     22 * A UserContextManager instance keeps track of all public user contexts and
     23 * maps their internal platform.
     24 *
     25 * This class is exported for test purposes. Otherwise the UserContextManager
     26 * singleton should be used.
     27 *
     28 * @fires UserContextManagerClass#"user-context-created"
     29 *      - {string} userContextId
     30 *            The UUID of the user context which was just created.
     31 * @fires UserContextManagerClass#"user-context-deleted"
     32 *      - {string} userContextId
     33 *            The UUID of the user context which was just deleted.
     34 */
     35 export class UserContextManagerClass {
     36  #contextualIdentityListener;
     37  #userContextIds;
     38 
     39  constructor() {
     40    lazy.EventEmitter.decorate(this);
     41 
     42    // Map from internal ids (numbers) from the ContextualIdentityService to
     43    // opaque UUIDs (string).
     44    this.#userContextIds = new Map();
     45 
     46    // The default user context is always using 0 as internal user context id
     47    // and should be exposed as "default" instead of a randomly generated id.
     48    this.#userContextIds.set(DEFAULT_INTERNAL_ID, DEFAULT_CONTEXT_ID);
     49 
     50    // Register other (non-default) public contexts.
     51    lazy.ContextualIdentityService.getPublicIdentities().forEach(identity =>
     52      this.#registerIdentity(identity)
     53    );
     54 
     55    this.#contextualIdentityListener = new lazy.ContextualIdentityListener();
     56    this.#contextualIdentityListener.on("created", this.#onIdentityCreated);
     57    this.#contextualIdentityListener.on("deleted", this.#onIdentityDeleted);
     58    this.#contextualIdentityListener.startListening();
     59  }
     60 
     61  destroy() {
     62    this.#contextualIdentityListener.off("created", this.#onIdentityCreated);
     63    this.#contextualIdentityListener.off("deleted", this.#onIdentityDeleted);
     64    this.#contextualIdentityListener.destroy();
     65 
     66    this.#userContextIds = null;
     67  }
     68 
     69  /**
     70   * Retrieve the user context id corresponding to the default user context.
     71   *
     72   * @returns {string}
     73   *     The default user context id.
     74   */
     75  get defaultUserContextId() {
     76    return DEFAULT_CONTEXT_ID;
     77  }
     78 
     79  /**
     80   * Creates a new user context.
     81   *
     82   * @param {string} prefix
     83   *     The prefix to use for the name of the user context.
     84   *
     85   * @returns {string}
     86   *     The user context id of the new user context.
     87   */
     88  createContext(prefix = "remote") {
     89    // Prepare a unique name.
     90    const name = `${prefix}-${lazy.generateUUID()}`;
     91 
     92    // Create the user context.
     93    const identity = lazy.ContextualIdentityService.create(name);
     94 
     95    // An id has been set already by the contextual-identity-created observer.
     96    return this.#userContextIds.get(identity.userContextId);
     97  }
     98 
     99  /**
    100   * Retrieve the user context id corresponding to the provided browsing context.
    101   *
    102   * @param {BrowsingContext} browsingContext
    103   *     The browsing context to get the user context id from.
    104   *
    105   * @returns {string}
    106   *     The corresponding user context id.
    107   *
    108   * @throws {TypeError}
    109   *     If `browsingContext` is not a CanonicalBrowsingContext instance.
    110   */
    111  getIdByBrowsingContext(browsingContext) {
    112    if (!CanonicalBrowsingContext.isInstance(browsingContext)) {
    113      throw new TypeError(
    114        `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}`
    115      );
    116    }
    117 
    118    return this.getIdByInternalId(
    119      browsingContext.originAttributes.userContextId
    120    );
    121  }
    122 
    123  /**
    124   * Retrieve the user context id corresponding to the provided internal id.
    125   *
    126   * @param {number} internalId
    127   *     The internal user context id.
    128   *
    129   * @returns {string|null}
    130   *     The corresponding user context id or null if the user context does not
    131   *     exist.
    132   */
    133  getIdByInternalId(internalId) {
    134    if (this.#userContextIds.has(internalId)) {
    135      return this.#userContextIds.get(internalId);
    136    }
    137    return null;
    138  }
    139 
    140  /**
    141   * Retrieve the internal id corresponding to the provided user
    142   * context id.
    143   *
    144   * @param {string} userContextId
    145   *     The user context id.
    146   *
    147   * @returns {number|null}
    148   *     The internal user context id or null if the user context does not
    149   *     exist.
    150   */
    151  getInternalIdById(userContextId) {
    152    for (const [internalId, id] of this.#userContextIds) {
    153      if (userContextId == id) {
    154        return internalId;
    155      }
    156    }
    157    return null;
    158  }
    159 
    160  /**
    161   * Returns an array of tabs related
    162   * to the provided internal user context id.
    163   *
    164   * @param {string} internalId
    165   *     The internal user context id.
    166   *
    167   * @returns {Array<Tab>}
    168   *     The array of tabs.
    169   */
    170  getTabsForUserContext(internalId) {
    171    return lazy.TabManager.allTabs.filter(tab => {
    172      return (
    173        (tab.hasAttribute("usercontextid") &&
    174          parseInt(tab.getAttribute("usercontextid"), 10) == internalId) ||
    175        (!tab.hasAttribute("usercontextid") && internalId === 0)
    176      );
    177    });
    178  }
    179 
    180  /**
    181   * Returns an array of all known user context ids.
    182   *
    183   * @returns {Array<string>}
    184   *     The array of user context ids.
    185   */
    186  getUserContextIds() {
    187    return Array.from(this.#userContextIds.values());
    188  }
    189 
    190  /**
    191   * Checks if the provided user context id is known by this UserContextManager.
    192   *
    193   * @param {string} userContextId
    194   *     The id of the user context to check.
    195   */
    196  hasUserContextId(userContextId) {
    197    return this.getUserContextIds().includes(userContextId);
    198  }
    199 
    200  /**
    201   * Removes a user context and closes all related container tabs.
    202   *
    203   * Note: When closing the related container tabs possible "beforeunload"
    204   * prompts will be ignored.
    205   *
    206   * @param {string} userContextId
    207   *     The id of the user context to remove.
    208   * @param {object=} options
    209   * @param {boolean=} options.closeContextTabs
    210   *     Pass true if the tabs owned by the user context should also be closed.
    211   *     Defaults to false.
    212   */
    213  removeUserContext(userContextId, options = {}) {
    214    const { closeContextTabs = false } = options;
    215 
    216    if (!this.hasUserContextId(userContextId)) {
    217      return;
    218    }
    219 
    220    const internalId = this.getInternalIdById(userContextId);
    221    if (closeContextTabs) {
    222      lazy.ContextualIdentityService.closeContainerTabs(internalId, {
    223        skipPermitUnload: true,
    224      });
    225    }
    226    lazy.ContextualIdentityService.remove(internalId);
    227  }
    228 
    229  #onIdentityCreated = (eventName, data) => {
    230    this.#registerIdentity(data.identity);
    231  };
    232 
    233  #onIdentityDeleted = (eventName, data) => {
    234    const userContextId = this.#userContextIds.get(data.identity.userContextId);
    235    this.#userContextIds.delete(data.identity.userContextId);
    236    this.emit("user-context-deleted", { userContextId });
    237  };
    238 
    239  #registerIdentity(identity) {
    240    const userContextId = lazy.generateUUID();
    241    this.#userContextIds.set(identity.userContextId, userContextId);
    242    this.emit("user-context-created", { userContextId });
    243  }
    244 }
    245 
    246 // Expose a shared singleton.
    247 export const UserContextManager = new UserContextManagerClass();