tor-browser

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

WindowGlobalMessageHandler.sys.mjs (7470B)


      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 import { MessageHandler } from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     11  getMessageHandlerFrameChildActor:
     12    "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs",
     13  RootMessageHandler:
     14    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
     15  WindowRealm: "chrome://remote/content/shared/Realm.sys.mjs",
     16 });
     17 
     18 /**
     19 * A WindowGlobalMessageHandler is dedicated to debugging a single window
     20 * global. It follows the lifecycle of the corresponding window global and will
     21 * therefore not survive any navigation. This MessageHandler cannot forward
     22 * commands further to other MessageHandlers and represents a leaf node in a
     23 * MessageHandler network.
     24 */
     25 export class WindowGlobalMessageHandler extends MessageHandler {
     26  #innerWindowId;
     27  #realms;
     28 
     29  constructor() {
     30    super(...arguments);
     31 
     32    this.#innerWindowId = this.context.window.windowGlobalChild.innerWindowId;
     33 
     34    // Maps sandbox names to instances of window realms.
     35    this.#realms = new Map();
     36  }
     37 
     38  initialize(sessionDataItems) {
     39    // Create the default realm, it is mapped to an empty string sandbox name.
     40    this.#realms.set("", this.#createRealm());
     41 
     42    // This method, even though being async, is not awaited on purpose,
     43    // since for now the sessionDataItems are passed in response to an event in a for loop.
     44    this.#applyInitialSessionDataItems(sessionDataItems);
     45 
     46    // With the session data applied the handler is now ready to be used.
     47    this.emitEvent("window-global-handler-created", {
     48      contextId: this.contextId,
     49      innerWindowId: this.#innerWindowId,
     50    });
     51  }
     52 
     53  destroy() {
     54    for (const realm of this.#realms.values()) {
     55      realm.destroy();
     56    }
     57    this.emitEvent("windowglobal-pagehide", {
     58      context: this.context,
     59      innerWindowId: this.innerWindowId,
     60    });
     61    this.#realms = null;
     62 
     63    super.destroy();
     64  }
     65 
     66  /**
     67   * Returns the WindowGlobalMessageHandler module path.
     68   *
     69   * @returns {string}
     70   */
     71  static get modulePath() {
     72    return "windowglobal";
     73  }
     74 
     75  /**
     76   * Returns the WindowGlobalMessageHandler type.
     77   *
     78   * @returns {string}
     79   */
     80  static get type() {
     81    return "WINDOW_GLOBAL";
     82  }
     83 
     84  /**
     85   * For WINDOW_GLOBAL MessageHandlers, `context` is a BrowsingContext,
     86   * and BrowsingContext.id can be used as the context id.
     87   *
     88   * @param {BrowsingContext} context
     89   *     WindowGlobalMessageHandler contexts are expected to be
     90   *     BrowsingContexts.
     91   * @returns {string}
     92   *     The browsing context id.
     93   */
     94  static getIdFromContext(context) {
     95    return context.id;
     96  }
     97 
     98  get innerWindowId() {
     99    return this.#innerWindowId;
    100  }
    101 
    102  get realms() {
    103    return this.#realms;
    104  }
    105 
    106  get window() {
    107    return this.context.window;
    108  }
    109 
    110  #createRealm(sandboxName = null) {
    111    const realm = new lazy.WindowRealm(this.context.window, {
    112      sandboxName,
    113    });
    114 
    115    this.emitEvent("realm-created", {
    116      realmInfo: realm.getInfo(),
    117      innerWindowId: this.innerWindowId,
    118    });
    119 
    120    return realm;
    121  }
    122 
    123  #getRealmFromSandboxName(sandboxName = null) {
    124    if (sandboxName === null || sandboxName === "") {
    125      return this.#realms.get("");
    126    }
    127 
    128    if (this.#realms.has(sandboxName)) {
    129      return this.#realms.get(sandboxName);
    130    }
    131 
    132    const realm = this.#createRealm(sandboxName);
    133 
    134    this.#realms.set(sandboxName, realm);
    135 
    136    return realm;
    137  }
    138 
    139  async #applyInitialSessionDataItems(sessionDataItems) {
    140    if (!Array.isArray(sessionDataItems)) {
    141      return;
    142    }
    143 
    144    const destination = {
    145      type: WindowGlobalMessageHandler.type,
    146    };
    147 
    148    // Create a Map with the structure moduleName -> category -> relevant session data items.
    149    const structuredUpdates = new Map();
    150    for (const sessionDataItem of sessionDataItems) {
    151      const { category, contextDescriptor, moduleName } = sessionDataItem;
    152 
    153      if (!this.matchesContext(contextDescriptor)) {
    154        continue;
    155      }
    156      if (!structuredUpdates.has(moduleName)) {
    157        // Skip session data item if the module is not present
    158        // for the destination.
    159        if (!this.moduleCache.hasModuleClass(moduleName, destination)) {
    160          continue;
    161        }
    162        structuredUpdates.set(moduleName, new Map());
    163      }
    164 
    165      if (!structuredUpdates.get(moduleName).has(category)) {
    166        structuredUpdates.get(moduleName).set(category, new Set());
    167      }
    168 
    169      structuredUpdates.get(moduleName).get(category).add(sessionDataItem);
    170    }
    171 
    172    const sessionDataPromises = [];
    173 
    174    for (const [moduleName, categories] of structuredUpdates.entries()) {
    175      for (const [category, relevantSessionData] of categories.entries()) {
    176        sessionDataPromises.push(
    177          this.handleCommand({
    178            moduleName,
    179            commandName: "_applySessionData",
    180            params: {
    181              category,
    182              sessionData: Array.from(relevantSessionData),
    183              initial: true,
    184            },
    185            destination,
    186          })
    187        );
    188      }
    189    }
    190 
    191    await Promise.all(sessionDataPromises);
    192  }
    193 
    194  forwardCommand(command) {
    195    switch (command.destination.type) {
    196      case lazy.RootMessageHandler.type:
    197        return lazy
    198          .getMessageHandlerFrameChildActor(this)
    199          .sendCommand(command, this.sessionId);
    200      default:
    201        throw new Error(
    202          `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".`
    203        );
    204    }
    205  }
    206 
    207  /**
    208   * If <var>realmId</var> is null or not provided get the realm for
    209   * a given <var>sandboxName</var>, otherwise find the realm
    210   * in the cache with the realm id equal given <var>realmId</var>.
    211   *
    212   * @param {object} options
    213   * @param {string|null=} options.realmId
    214   *     The realm id.
    215   * @param {string=} options.sandboxName
    216   *     The name of sandbox
    217   *
    218   * @returns {Realm}
    219   *     The realm object.
    220   */
    221  getRealm(options = {}) {
    222    const { realmId = null, sandboxName } = options;
    223    if (realmId === null) {
    224      return this.#getRealmFromSandboxName(sandboxName);
    225    }
    226 
    227    const realm = Array.from(this.#realms.values()).find(
    228      realm => realm.id === realmId
    229    );
    230 
    231    if (realm) {
    232      return realm;
    233    }
    234 
    235    throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`);
    236  }
    237 
    238  /**
    239   * Check if the context matches a provided context descriptor.
    240   *
    241   * @param {object} contextDescriptor
    242   *     A context descriptor.
    243   * @returns {boolean}
    244   *     Return true if the context matches a provided context descriptor,
    245   *     false otherwise.
    246   */
    247  matchesContext(contextDescriptor) {
    248    return this.contextMatchesDescriptor(this.context, contextDescriptor);
    249  }
    250 
    251  /**
    252   * Send a command to the root MessageHandler.
    253   *
    254   * @param {Command} command
    255   *     The command to send to the root MessageHandler.
    256   * @returns {Promise}
    257   *     A promise which resolves with the return value of the command.
    258   */
    259  sendRootCommand(command) {
    260    return this.handleCommand({
    261      ...command,
    262      destination: {
    263        type: lazy.RootMessageHandler.type,
    264      },
    265    });
    266  }
    267 }