tor-browser

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

actor-registry.js (13696B)


      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 "use strict";
      6 
      7 var gRegisteredModules = Object.create(null);
      8 
      9 const ActorRegistry = {
     10  // Map of global actor names to actor constructors.
     11  globalActorFactories: {},
     12  // Map of target-scoped actor names to actor constructors.
     13  targetScopedActorFactories: {},
     14  init(connections) {
     15    this._connections = connections;
     16  },
     17 
     18  /**
     19   * Register a CommonJS module with the devtools server.
     20   *
     21   * @param id string
     22   *        The ID of a CommonJS module.
     23   *        The actor is going to be registered immediately, but loaded only
     24   *        when a client starts sending packets to an actor with the same id.
     25   *
     26   * @param options object
     27   *        An object with 3 mandatory attributes:
     28   *        - prefix (string):
     29   *          The prefix of an actor is used to compute:
     30   *          - the `actorID` of each new actor instance (ex: prefix1). (See Pool.manage)
     31   *          - the actor name in the listTabs request. Sending a listTabs
     32   *            request to the root actor returns actor IDs. IDs are in
     33   *            dictionaries, with actor names as keys and actor IDs as values.
     34   *            The actor name is the prefix to which the "Actor" string is
     35   *            appended. So for an actor with the `console` prefix, the actor
     36   *            name will be `consoleActor`.
     37   *        - constructor (string):
     38   *          the name of the exported symbol to be used as the actor
     39   *          constructor.
     40   *        - type (a dictionary of booleans with following attribute names):
     41   *          - "global"
     42   *            registers a global actor instance, if true.
     43   *            A global actor has the root actor as its parent.
     44   *          - "target"
     45   *            registers a target-scoped actor instance, if true.
     46   *            A new actor will be created for each target, such as a tab.
     47   */
     48  registerModule(id, options) {
     49    if (id in gRegisteredModules) {
     50      return;
     51    }
     52 
     53    if (!options) {
     54      throw new Error(
     55        "ActorRegistry.registerModule requires an options argument"
     56      );
     57    }
     58    const { prefix, constructor, type } = options;
     59    if (typeof prefix !== "string") {
     60      throw new Error(
     61        `Lazy actor definition for '${id}' requires a string ` +
     62          `'prefix' option.`
     63      );
     64    }
     65    if (typeof constructor !== "string") {
     66      throw new Error(
     67        `Lazy actor definition for '${id}' requires a string ` +
     68          `'constructor' option.`
     69      );
     70    }
     71    if (!("global" in type) && !("target" in type)) {
     72      throw new Error(
     73        `Lazy actor definition for '${id}' requires a dictionary ` +
     74          `'type' option whose attributes can be 'global' or 'target'.`
     75      );
     76    }
     77    const name = prefix + "Actor";
     78    const mod = {
     79      id,
     80      prefix,
     81      constructorName: constructor,
     82      type,
     83      globalActor: type.global,
     84      targetScopedActor: type.target,
     85    };
     86    gRegisteredModules[id] = mod;
     87    if (mod.targetScopedActor) {
     88      this.addTargetScopedActor(mod, name);
     89    }
     90    if (mod.globalActor) {
     91      this.addGlobalActor(mod, name);
     92    }
     93  },
     94 
     95  /**
     96   * Unregister a previously-loaded CommonJS module from the devtools server.
     97   */
     98  unregisterModule(id) {
     99    const mod = gRegisteredModules[id];
    100    if (!mod) {
    101      throw new Error(
    102        "Tried to unregister a module that was not previously registered."
    103      );
    104    }
    105 
    106    // Lazy actors
    107    if (mod.targetScopedActor) {
    108      this.removeTargetScopedActor(mod);
    109    }
    110    if (mod.globalActor) {
    111      this.removeGlobalActor(mod);
    112    }
    113 
    114    delete gRegisteredModules[id];
    115  },
    116 
    117  /**
    118   * Install Firefox-specific actors.
    119   *
    120   * /!\ Be careful when adding a new actor, especially global actors.
    121   * Any new global actor will be exposed and returned by the root actor.
    122   */
    123  addBrowserActors() {
    124    this.registerModule("devtools/server/actors/preference", {
    125      prefix: "preference",
    126      constructor: "PreferenceActor",
    127      type: { global: true },
    128    });
    129    this.registerModule("devtools/server/actors/addon/addons", {
    130      prefix: "addons",
    131      constructor: "AddonsActor",
    132      type: { global: true },
    133    });
    134    this.registerModule("devtools/server/actors/device", {
    135      prefix: "device",
    136      constructor: "DeviceActor",
    137      type: { global: true },
    138    });
    139    this.registerModule("devtools/server/actors/heap-snapshot-file", {
    140      prefix: "heapSnapshotFile",
    141      constructor: "HeapSnapshotFileActor",
    142      type: { global: true },
    143    });
    144    // Always register this as a global module, even while there is a pref turning
    145    // on and off the other performance actor. This actor shouldn't conflict with
    146    // the other one. These are also lazily loaded so there shouldn't be a performance
    147    // impact.
    148    this.registerModule("devtools/server/actors/perf", {
    149      prefix: "perf",
    150      constructor: "PerfActor",
    151      type: { global: true },
    152    });
    153    /**
    154     * Always register parent accessibility actor as a global module. This
    155     * actor is responsible for all top level accessibility actor functionality
    156     * that relies on the parent process.
    157     */
    158    this.registerModule(
    159      "devtools/server/actors/accessibility/parent-accessibility",
    160      {
    161        prefix: "parentAccessibility",
    162        constructor: "ParentAccessibilityActor",
    163        type: { global: true },
    164      }
    165    );
    166 
    167    this.registerModule("devtools/server/actors/screenshot", {
    168      prefix: "screenshot",
    169      constructor: "ScreenshotActor",
    170      type: { global: true },
    171    });
    172  },
    173 
    174  /**
    175   * Install target-scoped actors.
    176   */
    177  addTargetScopedActors() {
    178    this.registerModule("devtools/server/actors/webconsole", {
    179      prefix: "console",
    180      constructor: "WebConsoleActor",
    181      type: { target: true },
    182    });
    183    this.registerModule("devtools/server/actors/inspector/inspector", {
    184      prefix: "inspector",
    185      constructor: "InspectorActor",
    186      type: { target: true },
    187    });
    188    this.registerModule("devtools/server/actors/style-sheets", {
    189      prefix: "styleSheets",
    190      constructor: "StyleSheetsActor",
    191      type: { target: true },
    192    });
    193    this.registerModule("devtools/server/actors/memory", {
    194      prefix: "memory",
    195      constructor: "MemoryActor",
    196      type: { target: true },
    197    });
    198    this.registerModule("devtools/server/actors/reflow", {
    199      prefix: "reflow",
    200      constructor: "ReflowActor",
    201      type: { target: true },
    202    });
    203    this.registerModule("devtools/server/actors/css-properties", {
    204      prefix: "cssProperties",
    205      constructor: "CssPropertiesActor",
    206      type: { target: true },
    207    });
    208    this.registerModule("devtools/server/actors/animation", {
    209      prefix: "animations",
    210      constructor: "AnimationsActor",
    211      type: { target: true },
    212    });
    213    this.registerModule("devtools/server/actors/emulation/responsive", {
    214      prefix: "responsive",
    215      constructor: "ResponsiveActor",
    216      type: { target: true },
    217    });
    218    this.registerModule(
    219      "devtools/server/actors/addon/webextension-inspected-window",
    220      {
    221        prefix: "webExtensionInspectedWindow",
    222        constructor: "WebExtensionInspectedWindowActor",
    223        type: { target: true },
    224      }
    225    );
    226    this.registerModule("devtools/server/actors/accessibility/accessibility", {
    227      prefix: "accessibility",
    228      constructor: "AccessibilityActor",
    229      type: { target: true },
    230    });
    231    this.registerModule("devtools/server/actors/manifest", {
    232      prefix: "manifest",
    233      constructor: "ManifestActor",
    234      type: { target: true },
    235    });
    236    this.registerModule(
    237      "devtools/server/actors/network-monitor/network-content",
    238      {
    239        prefix: "networkContent",
    240        constructor: "NetworkContentActor",
    241        type: { target: true },
    242      }
    243    );
    244    this.registerModule("devtools/server/actors/screenshot-content", {
    245      prefix: "screenshotContent",
    246      constructor: "ScreenshotContentActor",
    247      type: { target: true },
    248    });
    249    this.registerModule("devtools/server/actors/tracer", {
    250      prefix: "tracer",
    251      constructor: "TracerActor",
    252      type: { target: true },
    253    });
    254    this.registerModule("devtools/server/actors/objects-manager", {
    255      prefix: "objectsManager",
    256      constructor: "ObjectsManagerActor",
    257      type: { target: true },
    258    });
    259  },
    260 
    261  /**
    262   * Registers handlers for new target-scoped request types defined dynamically.
    263   *
    264   * Note that the name of the request type is not allowed to clash with existing protocol
    265   * packet properties, like 'title', 'url' or 'actor', since that would break the protocol.
    266   *
    267   * @param options object
    268   *        - constructorName: (required)
    269   *          name of actor constructor, which is also used when removing the actor.
    270   *        One of the following:
    271   *          - id:
    272   *            module ID that contains the actor
    273   *          - constructorFun:
    274   *            a function to construct the actor
    275   * @param name string
    276   *        The name of the new request type.
    277   */
    278  addTargetScopedActor(options, name) {
    279    if (!name) {
    280      throw Error("addTargetScopedActor requires the `name` argument");
    281    }
    282    if (["title", "url", "actor"].includes(name)) {
    283      throw Error(name + " is not allowed");
    284    }
    285    if (this.targetScopedActorFactories.hasOwnProperty(name)) {
    286      throw Error(name + " already exists");
    287    }
    288    this.targetScopedActorFactories[name] = { options, name };
    289  },
    290 
    291  /**
    292   * Unregisters the handler for the specified target-scoped request type.
    293   *
    294   * When unregistering an existing target-scoped actor, we remove the actor factory as
    295   * well as all existing instances of the actor.
    296   *
    297   * @param actor object, string
    298   *        In case of object:
    299   *          The `actor` object being given to related addTargetScopedActor call.
    300   *        In case of string:
    301   *          The `name` string being given to related addTargetScopedActor call.
    302   */
    303  removeTargetScopedActor(actorOrName) {
    304    let name;
    305    if (typeof actorOrName == "string") {
    306      name = actorOrName;
    307    } else {
    308      const actor = actorOrName;
    309      for (const factoryName in this.targetScopedActorFactories) {
    310        const handler = this.targetScopedActorFactories[factoryName];
    311        if (
    312          handler.options.constructorName == actor.name ||
    313          handler.options.id == actor.id
    314        ) {
    315          name = factoryName;
    316          break;
    317        }
    318      }
    319    }
    320    if (!name) {
    321      return;
    322    }
    323    delete this.targetScopedActorFactories[name];
    324    for (const connID of Object.getOwnPropertyNames(this._connections)) {
    325      // DevToolsServerConnection in child process don't have rootActor
    326      if (this._connections[connID].rootActor) {
    327        this._connections[connID].rootActor.removeActorByName(name);
    328      }
    329    }
    330  },
    331 
    332  /**
    333   * Registers handlers for new browser-scoped request types defined dynamically.
    334   *
    335   * Note that the name of the request type is not allowed to clash with existing protocol
    336   * packet properties, like 'from', 'tabs' or 'selected', since that would break the protocol.
    337   *
    338   * @param options object
    339   *        - constructorName: (required)
    340   *          name of actor constructor, which is also used when removing the actor.
    341   *        One of the following:
    342   *          - id:
    343   *            module ID that contains the actor
    344   *          - constructorFun:
    345   *            a function to construct the actor
    346   * @param name string
    347   *        The name of the new request type.
    348   */
    349  addGlobalActor(options, name) {
    350    if (!name) {
    351      throw Error("addGlobalActor requires the `name` argument");
    352    }
    353    if (["from", "tabs", "selected"].includes(name)) {
    354      throw Error(name + " is not allowed");
    355    }
    356    if (this.globalActorFactories.hasOwnProperty(name)) {
    357      throw Error(name + " already exists");
    358    }
    359    this.globalActorFactories[name] = { options, name };
    360  },
    361 
    362  /**
    363   * Unregisters the handler for the specified browser-scoped request type.
    364   *
    365   * When unregistering an existing global actor, we remove the actor factory as well as
    366   * all existing instances of the actor.
    367   *
    368   * @param actor object, string
    369   *        In case of object:
    370   *          The `actor` object being given to related addGlobalActor call.
    371   *        In case of string:
    372   *          The `name` string being given to related addGlobalActor call.
    373   */
    374  removeGlobalActor(actorOrName) {
    375    let name;
    376    if (typeof actorOrName == "string") {
    377      name = actorOrName;
    378    } else {
    379      const actor = actorOrName;
    380      for (const factoryName in this.globalActorFactories) {
    381        const handler = this.globalActorFactories[factoryName];
    382        if (
    383          handler.options.constructorName == actor.name ||
    384          handler.options.id == actor.id
    385        ) {
    386          name = factoryName;
    387          break;
    388        }
    389      }
    390    }
    391    if (!name) {
    392      return;
    393    }
    394    delete this.globalActorFactories[name];
    395    for (const connID of Object.getOwnPropertyNames(this._connections)) {
    396      // DevToolsServerConnection in child process don't have rootActor
    397      if (this._connections[connID].rootActor) {
    398        this._connections[connID].rootActor.removeActorByName(name);
    399      }
    400    }
    401  },
    402 
    403  destroy() {
    404    for (const id of Object.getOwnPropertyNames(gRegisteredModules)) {
    405      this.unregisterModule(id);
    406    }
    407    gRegisteredModules = Object.create(null);
    408 
    409    this.globalActorFactories = {};
    410    this.targetScopedActorFactories = {};
    411  },
    412 };
    413 
    414 exports.ActorRegistry = ActorRegistry;