tor-browser

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

MessageHandlerFrameParent.sys.mjs (7097B)


      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  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
      9  setTimeout: "resource://gre/modules/Timer.sys.mjs",
     10 
     11  error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
     12  Log: "chrome://remote/content/shared/Log.sys.mjs",
     13  RootMessageHandlerRegistry:
     14    "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
     15  WindowGlobalMessageHandler:
     16    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
     17 });
     18 
     19 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
     20 
     21 ChromeUtils.defineLazyGetter(lazy, "WebDriverError", () => {
     22  return ChromeUtils.importESModule(
     23    "chrome://remote/content/shared/webdriver/Errors.sys.mjs"
     24  ).error.WebDriverError;
     25 });
     26 
     27 // Set the timeout delay before a command is considered as potentially timing
     28 // out. This can be customized by a preference mostly for tests. Regular
     29 // implementation should use DEFAULT_COMMAND_DELAY;
     30 const DEFAULT_COMMAND_DELAY = 10000;
     31 const PREF_REMOTE_COMMAND_DELAY = "remote.messagehandler.test.command.delay";
     32 
     33 ChromeUtils.defineLazyGetter(lazy, "commandDelay", () =>
     34  Services.prefs.getIntPref(PREF_REMOTE_COMMAND_DELAY, DEFAULT_COMMAND_DELAY)
     35 );
     36 
     37 const PING_DELAY = 1000;
     38 const PING_TIMEOUT = Symbol();
     39 
     40 /**
     41 * Parent actor for the MessageHandlerFrame JSWindowActor. The
     42 * MessageHandlerFrame actor is used by RootTransport to communicate between
     43 * ROOT MessageHandlers and WINDOW_GLOBAL MessageHandlers.
     44 */
     45 export class MessageHandlerFrameParent extends JSWindowActorParent {
     46  #destroyed;
     47 
     48  constructor() {
     49    super();
     50    this.#destroyed = false;
     51  }
     52 
     53  didDestroy() {
     54    this.#destroyed = true;
     55  }
     56 
     57  async receiveMessage(message) {
     58    switch (message.name) {
     59      case "MessageHandlerFrameChild:sendCommand": {
     60        return this.#handleSendCommandMessage(message.data);
     61      }
     62      case "MessageHandlerFrameChild:messageHandlerEvent": {
     63        return this.#handleMessageHandlerEventMessage(message.data);
     64      }
     65      default:
     66        throw new Error("Unsupported message:" + message.name);
     67    }
     68  }
     69 
     70  /**
     71   * Send a command to the corresponding MessageHandlerFrameChild actor via a
     72   * JSWindowActor query.
     73   *
     74   * @param {Command} command
     75   *     The command to forward. See type definition in MessageHandler.js
     76   * @param {string} sessionId
     77   *     ID of the session that sent the command.
     78   * @returns {Promise}
     79   *     Promise that will resolve with the result of query sent to the
     80   *     MessageHandlerFrameChild actor.
     81   */
     82  async sendCommand(command, sessionId) {
     83    const timer = lazy.setTimeout(
     84      () => this.#sendPing(command),
     85      lazy.commandDelay
     86    );
     87 
     88    const result = await this.sendQuery(
     89      "MessageHandlerFrameParent:sendCommand",
     90      {
     91        command,
     92        sessionId,
     93      }
     94    );
     95 
     96    lazy.clearTimeout(timer);
     97 
     98    if (result?.error) {
     99      if (result.isMessageHandlerError) {
    100        throw lazy.error.MessageHandlerError.fromJSON(result.error);
    101      }
    102 
    103      // TODO: Do not assume WebDriver is the session protocol, see Bug 1779026.
    104      throw lazy.WebDriverError.fromJSON(result.error);
    105    }
    106 
    107    return result;
    108  }
    109 
    110  async #handleMessageHandlerEventMessage(messageData) {
    111    const { name, contextInfo, data, sessionId } = messageData;
    112    const [moduleName] = name.split(".");
    113 
    114    // Re-emit the event on the RootMessageHandler.
    115    const messageHandler =
    116      lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
    117 
    118    if (!messageHandler) {
    119      // If there is no message handler for the provided session id, this is a
    120      // late event for an already destroyed session, bail out.
    121      // Bug 1730913: A trace could be added here once Bug 1730913 is resolved.
    122      // Until that, this would lead to too much log pollution, because content
    123      // process modules will not be destroyed until the corresponding window
    124      // is destroyed (either closed or navigates).
    125      return;
    126    }
    127 
    128    // TODO: getModuleInstance expects a CommandDestination in theory,
    129    // but only uses the MessageHandler type in practice, see Bug 1776389.
    130    const module = messageHandler.moduleCache.getModuleInstance(moduleName, {
    131      type: lazy.WindowGlobalMessageHandler.type,
    132    });
    133    let eventPayload = data;
    134 
    135    // Modify an event payload if there is a special method in the targeted module.
    136    // If present it can be found in windowglobal-in-root module.
    137    if (module?.interceptEvent) {
    138      eventPayload = await module.interceptEvent(name, data);
    139 
    140      if (eventPayload === null) {
    141        lazy.logger.trace(
    142          `${moduleName}.interceptEvent returned null, skipping event: ${name}, data: ${data}`
    143        );
    144        return;
    145      }
    146      // Make sure that an event payload is returned.
    147      if (!eventPayload) {
    148        throw new Error(
    149          `${moduleName}.interceptEvent doesn't return the event payload`
    150        );
    151      }
    152    }
    153    messageHandler.emitEvent(name, eventPayload, contextInfo);
    154  }
    155 
    156  async #handleSendCommandMessage(messageData) {
    157    const { sessionId, command } = messageData;
    158    const messageHandler =
    159      lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
    160    try {
    161      return await messageHandler.handleCommand(command);
    162    } catch (e) {
    163      if (e?.isRemoteError) {
    164        return {
    165          error: e.toJSON(),
    166          isMessageHandlerError: e.isMessageHandlerError,
    167        };
    168      }
    169      throw e;
    170    }
    171  }
    172 
    173  async #sendPing(command) {
    174    const commandName = `${command.moduleName}.${command.commandName}`;
    175    const destination = command.destination.id;
    176 
    177    if (this.#destroyed) {
    178      // If the JSWindowActor was destroyed already, no need to send a ping.
    179      return;
    180    }
    181 
    182    lazy.logger.trace(
    183      `MessageHandlerFrameParent command ${commandName} to ${destination} ` +
    184        `takes more than ${lazy.commandDelay / 1000} seconds to resolve, sending ping`
    185    );
    186 
    187    try {
    188      const result = await Promise.race([
    189        this.sendQuery("MessageHandlerFrameParent:sendPing"),
    190        new Promise(r => lazy.setTimeout(() => r(PING_TIMEOUT), PING_DELAY)),
    191      ]);
    192 
    193      if (result === PING_TIMEOUT) {
    194        lazy.logger.warn(
    195          `MessageHandlerFrameParent ping for command ${commandName} to ${destination} timed out`
    196        );
    197      } else {
    198        lazy.logger.trace(
    199          `MessageHandlerFrameParent ping for command ${commandName} to ${destination} was successful`
    200        );
    201      }
    202    } catch (e) {
    203      if (!this.#destroyed) {
    204        // Only swallow errors if the JSWindowActor pair was destroyed while
    205        // waiting for the ping response.
    206        throw e;
    207      }
    208 
    209      lazy.logger.trace(
    210        `MessageHandlerFrameParent ping for command ${commandName} to ${destination}` +
    211          ` lost after JSWindowActor was destroyed`
    212      );
    213    }
    214  }
    215 }