tor-browser

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

custom-element-watcher.js (4212B)


      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 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
      8 
      9 /**
     10 * The CustomElementWatcher can be used to be notified if a custom element definition
     11 * is created for a node.
     12 *
     13 * When a custom element is defined for a monitored name, an "element-defined" event is
     14 * fired with the following Object argument:
     15 * - {String} name: name of the custom element defined
     16 * - {Set} Set of impacted node actors
     17 */
     18 class CustomElementWatcher extends EventEmitter {
     19  constructor(chromeEventHandler) {
     20    super();
     21 
     22    this.chromeEventHandler = chromeEventHandler;
     23    this._onCustomElementDefined = this._onCustomElementDefined.bind(this);
     24    this.chromeEventHandler.addEventListener(
     25      "customelementdefined",
     26      this._onCustomElementDefined
     27    );
     28 
     29    /**
     30     * Each window keeps its own custom element registry, all of them are watched
     31     * separately. The struture of the watchedRegistries is as follows
     32     *
     33     * WeakMap(
     34     *   registry -> Map (
     35     *     name -> Set(NodeActors)
     36     *   )
     37     * )
     38     */
     39    this.watchedRegistries = new WeakMap();
     40  }
     41 
     42  destroy() {
     43    this.watchedRegistries = null;
     44    this.chromeEventHandler.removeEventListener(
     45      "customelementdefined",
     46      this._onCustomElementDefined
     47    );
     48  }
     49 
     50  /**
     51   * Watch for custom element definitions matching the name of the provided NodeActor.
     52   */
     53  manageNode(nodeActor) {
     54    if (!this._isValidNode(nodeActor)) {
     55      return;
     56    }
     57 
     58    if (!this._shouldWatchDefinition(nodeActor)) {
     59      return;
     60    }
     61 
     62    const registry = nodeActor.rawNode.ownerGlobal.customElements;
     63    const registryMap = this._getMapForRegistry(registry);
     64 
     65    const name = nodeActor.rawNode.localName;
     66    const actorsSet = this._getActorsForName(name, registryMap);
     67    actorsSet.add(nodeActor);
     68  }
     69 
     70  /**
     71   * Stop watching the provided NodeActor.
     72   */
     73  unmanageNode(nodeActor) {
     74    if (!this._isValidNode(nodeActor)) {
     75      return;
     76    }
     77 
     78    const win = nodeActor.rawNode.ownerGlobal;
     79    const registry = win.customElements;
     80    const registryMap = this._getMapForRegistry(registry);
     81    const name = nodeActor.rawNode.localName;
     82    if (registryMap.has(name)) {
     83      registryMap.get(name).delete(nodeActor);
     84    }
     85  }
     86 
     87  /**
     88   * Retrieve the map of name->nodeActors for a given CustomElementsRegistry.
     89   * Will create the map if not created yet.
     90   */
     91  _getMapForRegistry(registry) {
     92    if (!this.watchedRegistries.has(registry)) {
     93      this.watchedRegistries.set(registry, new Map());
     94    }
     95    return this.watchedRegistries.get(registry);
     96  }
     97 
     98  /**
     99   * Retrieve the set of nodeActors for a given name and registry.
    100   * Will create the set if not created yet.
    101   */
    102  _getActorsForName(name, registryMap) {
    103    if (!registryMap.has(name)) {
    104      registryMap.set(name, new Set());
    105    }
    106    return registryMap.get(name);
    107  }
    108 
    109  _shouldWatchDefinition(nodeActor) {
    110    const doc = nodeActor.rawNode.ownerDocument;
    111    const namespaceURI = doc.documentElement.namespaceURI;
    112    const name = nodeActor.rawNode.localName;
    113    const isValidName = InspectorUtils.isCustomElementName(name, namespaceURI);
    114 
    115    const customElements = doc.defaultView.customElements;
    116    return isValidName && !customElements.get(name);
    117  }
    118 
    119  _onCustomElementDefined(event) {
    120    const doc = event.target;
    121    const registry = doc.defaultView.customElements;
    122    const registryMap = this._getMapForRegistry(registry);
    123 
    124    const name = event.detail;
    125    const actors = this._getActorsForName(name, registryMap);
    126    this.emit("element-defined", { name, actors });
    127    registryMap.delete(name);
    128  }
    129 
    130  /**
    131   * Some nodes (e.g. inside of <template> tags) don't have a documentElement or an
    132   * ownerGlobal and can't be watched by this helper.
    133   */
    134  _isValidNode(nodeActor) {
    135    const node = nodeActor.rawNode;
    136    return (
    137      !Cu.isDeadWrapper(node) &&
    138      node.ownerGlobal &&
    139      node.ownerDocument?.documentElement
    140    );
    141  }
    142 }
    143 
    144 exports.CustomElementWatcher = CustomElementWatcher;