tor-browser

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

MessageHandler.sys.mjs (12395B)


      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 { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
     11  EventsDispatcher:
     12    "chrome://remote/content/shared/messagehandler/EventsDispatcher.sys.mjs",
     13  Log: "chrome://remote/content/shared/Log.sys.mjs",
     14  ModuleCache:
     15    "chrome://remote/content/shared/messagehandler/ModuleCache.sys.mjs",
     16 });
     17 
     18 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
     19 
     20 /**
     21 * A ContextDescriptor object provides information to decide if a broadcast or
     22 * a session data item should be applied to a specific MessageHandler context.
     23 *
     24 * @typedef {object} ContextDescriptor
     25 * @property {ContextDescriptorType} type
     26 *     The type of context
     27 * @property {string=} id
     28 *     Unique id of a given context for the provided type.
     29 *     For ContextDescriptorType.All, id can be omitted.
     30 *     For ContextDescriptorType.TopBrowsingContext, the id should be the
     31 *     browserId corresponding to a top-level browsing context.
     32 *     For ContextDescriptorType.UserContext, the id should be the
     33 *     platform user context id.
     34 */
     35 
     36 /**
     37 * Enum of ContextDescriptor types.
     38 *
     39 * @enum {string}
     40 */
     41 export const ContextDescriptorType = {
     42  All: "All",
     43  TopBrowsingContext: "TopBrowsingContext",
     44  UserContext: "UserContext",
     45 };
     46 
     47 /**
     48 * A ContextInfo identifies a given context that can be linked to a MessageHandler
     49 * instance. It should be used to identify events coming from this context.
     50 *
     51 * It can either be provided by the MessageHandler itself, when the event is
     52 * emitted from the context it relates to.
     53 *
     54 * Or it can be assembled manually, for instance when emitting an event which
     55 * relates to a window global from the root layer (eg browsingContext.contextCreated).
     56 *
     57 * @typedef {object} ContextInfo
     58 * @property {string} contextId
     59 *     Unique id of the MessageHandler corresponding to this context.
     60 * @property {string} type
     61 *     One of MessageHandler.type.
     62 */
     63 
     64 /**
     65 * MessageHandler instances are dedicated to handle both Commands and Events
     66 * to enable automation and introspection for remote control protocols.
     67 *
     68 * MessageHandler instances are designed to form a network, where each instance
     69 * should allow to inspect a specific context (eg. a BrowsingContext, a Worker,
     70 * etc). Those instances might live in different processes and threads but
     71 * should be linked together by the usage of a single sessionId, shared by all
     72 * the instances of a single MessageHandler network.
     73 *
     74 * MessageHandler instances will be dynamically spawned depending on which
     75 * Command or which Event needs to be processed and should therefore not be
     76 * explicitly created by consumers, nor used directly.
     77 *
     78 * The only exception is the ROOT MessageHandler. This MessageHandler will be
     79 * the entry point to send commands to the rest of the network. It will also
     80 * emit all the relevant events captured by the network.
     81 *
     82 * However, even to create this ROOT MessageHandler, consumers should use the
     83 * RootMessageHandlerRegistry. This singleton will ensure that MessageHandler
     84 * instances are properly registered and can be retrieved based on a given
     85 * session id as well as some other context information.
     86 */
     87 export class MessageHandler extends EventEmitter {
     88  #context;
     89  #contextId;
     90  #eventsDispatcher;
     91  #moduleCache;
     92  #registry;
     93  #sessionId;
     94 
     95  /**
     96   * Create a new MessageHandler instance.
     97   *
     98   * @param {string} sessionId
     99   *     ID of the session the handler is used for.
    100   * @param {object} context
    101   *     The context linked to this MessageHandler instance.
    102   * @param {MessageHandlerRegistry} registry
    103   *     The MessageHandlerRegistry which owns this MessageHandler instance.
    104   */
    105  constructor(sessionId, context, registry) {
    106    super();
    107 
    108    this.#moduleCache = new lazy.ModuleCache(this);
    109 
    110    this.#sessionId = sessionId;
    111    this.#context = context;
    112    this.#contextId = this.constructor.getIdFromContext(context);
    113    this.#eventsDispatcher = new lazy.EventsDispatcher(this);
    114    this.#registry = registry;
    115  }
    116 
    117  get context() {
    118    return this.#context;
    119  }
    120 
    121  get contextId() {
    122    return this.#contextId;
    123  }
    124 
    125  get eventsDispatcher() {
    126    return this.#eventsDispatcher;
    127  }
    128 
    129  get moduleCache() {
    130    return this.#moduleCache;
    131  }
    132 
    133  get name() {
    134    return [this.sessionId, this.constructor.type, this.contextId].join("-");
    135  }
    136 
    137  get registry() {
    138    return this.#registry;
    139  }
    140 
    141  get sessionId() {
    142    return this.#sessionId;
    143  }
    144 
    145  destroy() {
    146    lazy.logger.trace(
    147      `MessageHandler ${this.constructor.type} for session ${this.sessionId} is being destroyed`
    148    );
    149    this.#eventsDispatcher.destroy();
    150    this.#moduleCache.destroy();
    151 
    152    // At least the MessageHandlerRegistry will be expecting this event in order
    153    // to remove the instance from the registry when destroyed.
    154    this.emit("message-handler-destroyed", this);
    155  }
    156 
    157  /**
    158   * Check if the provided context matches provided contextDescriptor.
    159   *
    160   * @param {BrowsingContext} browsingContext
    161   *     The browsing context to verify.
    162   * @param {ContextDescriptor} contextDescriptor
    163   *     The context descriptor to match.
    164   *
    165   * @returns {boolean}
    166   *     Return "true" if the context matches the context descriptor,
    167   *     "false" otherwise.
    168   */
    169  contextMatchesDescriptor(browsingContext, contextDescriptor) {
    170    return (
    171      contextDescriptor.type === ContextDescriptorType.All ||
    172      (contextDescriptor.type === ContextDescriptorType.TopBrowsingContext &&
    173        contextDescriptor.id === browsingContext.browserId) ||
    174      (contextDescriptor.type === ContextDescriptorType.UserContext &&
    175        contextDescriptor.id === browsingContext.originAttributes.userContextId)
    176    );
    177  }
    178 
    179  /**
    180   * Emit a message handler event.
    181   *
    182   * Such events should bubble up to the root of a MessageHandler network.
    183   *
    184   * @param {string} name
    185   *     Name of the event. Protocol level events should be of the
    186   *     form [module name].[event name].
    187   * @param {object} data
    188   *     The event's data.
    189   * @param {ContextInfo=} contextInfo
    190   *     The event's context info, used to identify the origin of the event.
    191   *     If not provided, the context info of the current MessageHandler will be
    192   *     used.
    193   */
    194  emitEvent(name, data, contextInfo) {
    195    // If no contextInfo field is provided on the event, extract it from the
    196    // MessageHandler instance.
    197    contextInfo = contextInfo || this.#getContextInfo();
    198 
    199    // Events are emitted both under their own name for consumers listening to
    200    // a specific and as `message-handler-event` for consumers which need to
    201    // catch all events.
    202    this.emit(name, data, contextInfo);
    203    this.emit("message-handler-event", {
    204      name,
    205      contextInfo,
    206      data,
    207      sessionId: this.sessionId,
    208    });
    209  }
    210 
    211  /**
    212   * @typedef {object} CommandDestination
    213   * @property {string} type
    214   *     One of MessageHandler.type.
    215   * @property {string | number=} id
    216   *     Unique context identifier. The format depends on the type.
    217   *     For WINDOW_GLOBAL destinations, this is a browsing context id.
    218   *     Optional, should only be provided if `contextDescriptor` is missing.
    219   * @property {ContextDescriptor=} contextDescriptor
    220   *     Descriptor used to match several contexts, which will all receive the
    221   *     command.
    222   *     Optional, should only be provided if `id` is missing.
    223   */
    224 
    225  /**
    226   * @typedef {object} Command
    227   * @property {string} commandName
    228   *     The name of the command to execute.
    229   * @property {string} moduleName
    230   *     The name of the module.
    231   * @property {object} params
    232   *     Optional command parameters.
    233   * @property {CommandDestination} destination
    234   *     The destination describing a debuggable context.
    235   * @property {boolean=} retryOnAbort
    236   *     Optional. When true, commands will be retried upon AbortError, which
    237   *     can occur when the underlying JSWindowActor pair is destroyed.
    238   *     If not explicitly set, the framework will automatically retry if the
    239   *     destination is likely to be replaced (e.g. browsingContext on the
    240   *     initial document or loading a document).
    241   */
    242 
    243  /**
    244   * Retrieve all module classes matching the moduleName and destination.
    245   * See `getAllModuleClasses` (ModuleCache.sys.mjs) for more details.
    246   *
    247   * @param {string} moduleName
    248   *     The name of the module.
    249   * @param {Destination} destination
    250   *     The destination.
    251   * @returns {Array.<class<Module>|null>}
    252   *     An array of Module classes.
    253   */
    254  getAllModuleClasses(moduleName, destination) {
    255    return this.#moduleCache.getAllModuleClasses(moduleName, destination);
    256  }
    257 
    258  /**
    259   * Handle a command, either in one of the modules owned by this MessageHandler
    260   * or in a another MessageHandler after forwarding the command.
    261   *
    262   * @param {Command} command
    263   *     The command that should be either handled in this layer or forwarded to
    264   *     the next layer leading to the destination.
    265   * @returns {Promise} A Promise that will resolve with the return value of the
    266   *     command once it has been executed.
    267   */
    268  handleCommand(command) {
    269    const { moduleName, commandName, params, destination } = command;
    270    lazy.logger.trace(
    271      `Received command ${moduleName}.${commandName} for destination ${destination.type}`
    272    );
    273 
    274    if (!this.supportsCommand(moduleName, commandName, destination)) {
    275      throw new lazy.error.UnsupportedCommandError(
    276        `${moduleName}.${commandName} not supported for destination ${destination?.type}`
    277      );
    278    }
    279 
    280    const module = this.#moduleCache.getModuleInstance(moduleName, destination);
    281    if (module && module.supportsMethod(commandName)) {
    282      return module[commandName](params, destination);
    283    }
    284 
    285    return this.forwardCommand(command);
    286  }
    287 
    288  toString() {
    289    return `[object ${this.constructor.name} ${this.name}]`;
    290  }
    291 
    292  /**
    293   * Execute the required initialization steps, including apply the initial session data items
    294   * provided to this MessageHandler on startup. Implementation is specific to each MessageHandler class.
    295   *
    296   * By default the implementation is a no-op.
    297   */
    298  async initialize() {}
    299 
    300  /**
    301   * Returns the module path corresponding to this MessageHandler class.
    302   *
    303   * Needs to be implemented in the sub class.
    304   */
    305  static get modulePath() {
    306    throw new Error("Not implemented");
    307  }
    308 
    309  /**
    310   * Returns the type corresponding to this MessageHandler class.
    311   *
    312   * Needs to be implemented in the sub class.
    313   */
    314  static get type() {
    315    throw new Error("Not implemented");
    316  }
    317 
    318  /**
    319   * Returns the id corresponding to a context compatible with this
    320   * MessageHandler class.
    321   *
    322   * Needs to be implemented in the sub class.
    323   */
    324  static getIdFromContext() {
    325    throw new Error("Not implemented");
    326  }
    327 
    328  /**
    329   * Forward a command to other MessageHandlers.
    330   *
    331   * Needs to be implemented in the sub class.
    332   */
    333  forwardCommand() {
    334    throw new Error("Not implemented");
    335  }
    336 
    337  /**
    338   * Check if contextDescriptor matches the context linked
    339   * to this MessageHandler instance.
    340   *
    341   * Needs to be implemented in the sub class.
    342   */
    343  matchesContext() {
    344    throw new Error("Not implemented");
    345  }
    346 
    347  /**
    348   * Check if the given command is supported in the module
    349   * for the destination
    350   *
    351   * @param {string} moduleName
    352   *     The name of the module.
    353   * @param {string} commandName
    354   *     The name of the command.
    355   * @param {Destination} destination
    356   *     The destination.
    357   * @returns {boolean}
    358   *     True if the command is supported.
    359   */
    360  supportsCommand(moduleName, commandName, destination) {
    361    return this.getAllModuleClasses(moduleName, destination).some(cls =>
    362      cls.supportsMethod(commandName)
    363    );
    364  }
    365 
    366  /**
    367   * Return the context information for this MessageHandler instance, which
    368   * can be used to identify the origin of an event.
    369   *
    370   * @returns {ContextInfo}
    371   *     The context information for this MessageHandler.
    372   */
    373  #getContextInfo() {
    374    return {
    375      contextId: this.contextId,
    376      type: this.constructor.type,
    377    };
    378  }
    379 }