tor-browser

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

RootTransport.sys.mjs (8551B)


      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  ContextDescriptorType:
      9    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
     10  error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
     11  isBrowsingContextCompatible:
     12    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs",
     13  isInitialDocument:
     14    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs",
     15  Log: "chrome://remote/content/shared/Log.sys.mjs",
     16  MessageHandlerFrameActor:
     17    "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameActor.sys.mjs",
     18  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
     19  waitForCurrentWindowGlobal:
     20    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs",
     21 });
     22 
     23 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
     24 
     25 ChromeUtils.defineLazyGetter(lazy, "prefRetryOnAbort", () => {
     26  return Services.prefs.getBoolPref("remote.retry-on-abort", false);
     27 });
     28 
     29 const MAX_RETRY_ATTEMPTS = 10;
     30 
     31 /**
     32 * RootTransport is intended to be used from a ROOT MessageHandler to communicate
     33 * with WINDOW_GLOBAL MessageHandlers via the MessageHandlerFrame JSWindow
     34 * actors.
     35 */
     36 export class RootTransport {
     37  /**
     38   * @param {MessageHandler} messageHandler
     39   *     The MessageHandler instance which owns this RootTransport instance.
     40   */
     41  constructor(messageHandler) {
     42    this._messageHandler = messageHandler;
     43 
     44    // RootTransport will rely on the MessageHandlerFrame JSWindow actors.
     45    // Make sure they are registered when instantiating a RootTransport.
     46    lazy.MessageHandlerFrameActor.register();
     47  }
     48 
     49  /**
     50   * Forward the provided command to WINDOW_GLOBAL MessageHandlers via the
     51   * MessageHandlerFrame actors.
     52   *
     53   * @param {Command} command
     54   *     The command to forward. See type definition in MessageHandler.js
     55   * @returns {Promise}
     56   *     Returns a promise that resolves with the result of the command after
     57   *     being processed by WINDOW_GLOBAL MessageHandlers.
     58   */
     59  forwardCommand(command) {
     60    if (command.destination.id && command.destination.contextDescriptor) {
     61      throw new Error(
     62        "Invalid command destination with both 'id' and 'contextDescriptor' properties"
     63      );
     64    }
     65 
     66    // With an id given forward the command to only this specific destination.
     67    if (command.destination.id) {
     68      const browsingContext = BrowsingContext.get(command.destination.id);
     69      if (!browsingContext) {
     70        throw new lazy.error.DiscardedBrowsingContextError(
     71          `Unable to find a BrowsingContext for id "${command.destination.id}"`
     72        );
     73      }
     74      return this._sendCommandToBrowsingContext(command, browsingContext);
     75    }
     76 
     77    // ... otherwise broadcast to destinations matching the contextDescriptor.
     78    if (command.destination.contextDescriptor) {
     79      return this._broadcastCommand(command);
     80    }
     81 
     82    throw new Error(
     83      "Unrecognized command destination, missing 'id' or 'contextDescriptor' properties"
     84    );
     85  }
     86 
     87  _broadcastCommand(command) {
     88    const { contextDescriptor } = command.destination;
     89    const browsingContexts =
     90      this._getBrowsingContextsForDescriptor(contextDescriptor);
     91 
     92    return Promise.all(
     93      browsingContexts.map(async browsingContext => {
     94        try {
     95          return await this._sendCommandToBrowsingContext(
     96            command,
     97            browsingContext
     98          );
     99        } catch (e) {
    100          console.error(
    101            `Failed to broadcast a command to browsingContext ${browsingContext.id}`,
    102            e
    103          );
    104          return null;
    105        }
    106      })
    107    );
    108  }
    109 
    110  async _sendCommandToBrowsingContext(command, browsingContext) {
    111    const name = `${command.moduleName}.${command.commandName}`;
    112 
    113    let retryOnAbort = true;
    114    if (command.retryOnAbort !== undefined) {
    115      // The caller should always be able to force a value.
    116      retryOnAbort = command.retryOnAbort;
    117    } else if (!lazy.prefRetryOnAbort) {
    118      // If we don't retry by default do it at least for the initial document.
    119      retryOnAbort = lazy.isInitialDocument(browsingContext);
    120    }
    121 
    122    // If a top-level content browsing context was replaced and
    123    // retrying is allowed, retrieve the new one for the current browser.
    124    if (
    125      browsingContext &&
    126      browsingContext.isContent &&
    127      browsingContext.isReplaced &&
    128      browsingContext.top === browsingContext &&
    129      retryOnAbort
    130    ) {
    131      browsingContext = BrowsingContext.getCurrentTopByBrowserId(
    132        browsingContext.browserId
    133      );
    134    }
    135 
    136    if (!browsingContext || browsingContext.isDiscarded) {
    137      throw new lazy.error.DiscardedBrowsingContextError(
    138        `BrowsingContext does no longer exist`
    139      );
    140    }
    141 
    142    // For content browsing contexts keep a reference to the
    143    // webProgress, which will persist, and always use it to
    144    // retrieve the currently valid browsing context.
    145    const webProgress = browsingContext.webProgress;
    146 
    147    let attempts = 0;
    148    while (true) {
    149      try {
    150        if (browsingContext.isContent) {
    151          browsingContext = webProgress.browsingContext;
    152        }
    153 
    154        if (!browsingContext.currentWindowGlobal) {
    155          await lazy.waitForCurrentWindowGlobal(browsingContext);
    156        }
    157 
    158        return await browsingContext.currentWindowGlobal
    159          .getActor("MessageHandlerFrame")
    160          .sendCommand(command, this._messageHandler.sessionId);
    161      } catch (e) {
    162        // Re-throw the error in case it is not an AbortError.
    163        if (e.name != "AbortError") {
    164          throw e;
    165        }
    166 
    167        // Only retry if the command supports retryOnAbort and when the
    168        // JSWindowActor pair gets destroyed.
    169        if (!retryOnAbort) {
    170          throw new lazy.error.DiscardedBrowsingContextError(e.message);
    171        }
    172 
    173        if (++attempts > MAX_RETRY_ATTEMPTS) {
    174          lazy.logger.trace(
    175            `RootTransport reached the limit of retry attempts (${MAX_RETRY_ATTEMPTS})` +
    176              ` for command ${name} and browsing context ${browsingContext.id}.`
    177          );
    178          throw new lazy.error.DiscardedBrowsingContextError(e.message);
    179        }
    180 
    181        lazy.logger.trace(
    182          `RootTransport retrying command ${name} for ` +
    183            `browsing context ${browsingContext.id}, attempt: ${attempts}.`
    184        );
    185        await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
    186      }
    187    }
    188  }
    189 
    190  toString() {
    191    return `[object ${this.constructor.name} ${this._messageHandler.name}]`;
    192  }
    193 
    194  _getBrowsingContextsForDescriptor(contextDescriptor) {
    195    const { id, type } = contextDescriptor;
    196 
    197    if (type === lazy.ContextDescriptorType.All) {
    198      return this._getBrowsingContexts();
    199    }
    200 
    201    if (type === lazy.ContextDescriptorType.TopBrowsingContext) {
    202      return this._getBrowsingContexts({ browserId: id });
    203    }
    204 
    205    if (type === lazy.ContextDescriptorType.UserContext) {
    206      return this._getBrowsingContexts({ userContext: id });
    207    }
    208 
    209    // TODO: Handle other types of context descriptors.
    210    throw new Error(
    211      `Unsupported contextDescriptor type for broadcasting: ${type}`
    212    );
    213  }
    214 
    215  /**
    216   * Get all browsing contexts, optionally matching the provided options.
    217   *
    218   * @param {object} options
    219   * @param {string=} options.browserId
    220   *    The id of the browser to filter the browsing contexts by (optional).
    221   * @param {string=} options.userContext
    222   *    The id of the user context to filter the browsing contexts by (optional).
    223   * @returns {Array<BrowsingContext>}
    224   *    The browsing contexts matching the provided options or all browsing contexts
    225   *    if no options are provided.
    226   */
    227  _getBrowsingContexts(options = {}) {
    228    // extract browserId from options
    229    const { browserId, userContext } = options;
    230    let browsingContexts = [];
    231 
    232    // Fetch all tab related browsing contexts for top-level windows.
    233    for (const { browsingContext } of lazy.TabManager.getBrowsers()) {
    234      if (
    235        lazy.isBrowsingContextCompatible(browsingContext, {
    236          browserId,
    237          userContext,
    238        })
    239      ) {
    240        browsingContexts = browsingContexts.concat(
    241          browsingContext.getAllBrowsingContextsInSubtree()
    242        );
    243      }
    244    }
    245 
    246    return browsingContexts;
    247  }
    248 }