tor-browser

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

NodeCache.sys.mjs (5219B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
      9 });
     10 
     11 /**
     12 * @typedef {object} NodeReferenceDetails
     13 * @property {number} browserId
     14 * @property {number} browsingContextGroupId
     15 * @property {number} browsingContextId
     16 * @property {boolean} isTopBrowsingContext
     17 * @property {WeakRef} nodeWeakRef
     18 */
     19 
     20 /**
     21 * The class provides a mapping between DOM nodes and a unique node references.
     22 * Supported types of nodes are Element and ShadowRoot.
     23 */
     24 export class NodeCache {
     25  #nodeIdMap;
     26  #seenNodesMap;
     27 
     28  constructor() {
     29    // node => node id
     30    this.#nodeIdMap = new WeakMap();
     31 
     32    // Reverse map for faster lookup requests of node references. Values do
     33    // not only contain the resolved DOM node but also further details like
     34    // browsing context information.
     35    //
     36    // node id => node details
     37    this.#seenNodesMap = new Map();
     38  }
     39 
     40  /**
     41   * Get the number of nodes in the cache.
     42   */
     43  get size() {
     44    return this.#seenNodesMap.size;
     45  }
     46 
     47  /**
     48   * Get or if not yet existent create a unique reference for an Element or
     49   * ShadowRoot node.
     50   *
     51   * @param {Node} node
     52   *    The node to be added.
     53   * @param {Map<BrowsingContext, Array<string>>} seenNodeIds
     54   *     Map of browsing contexts to their seen node ids during the current
     55   *     serialization.
     56   *
     57   * @returns {string}
     58   *     The unique node reference for the DOM node.
     59   */
     60  getOrCreateNodeReference(node, seenNodeIds) {
     61    if (!Node.isInstance(node)) {
     62      throw new TypeError(`Failed to create node reference for ${node}`);
     63    }
     64 
     65    let nodeId;
     66    if (this.#nodeIdMap.has(node)) {
     67      // For already known nodes return the cached node id.
     68      nodeId = this.#nodeIdMap.get(node);
     69    } else {
     70      // Bug 1820734: For some Node types like `CDATA` no `ownerGlobal`
     71      // property is available, and as such they cannot be deserialized
     72      // right now.
     73      const browsingContext = node.ownerGlobal?.browsingContext;
     74 
     75      // For not yet cached nodes generate a unique id without curly braces.
     76      nodeId = lazy.generateUUID();
     77 
     78      const details = {
     79        browserId: browsingContext?.browserId,
     80        browsingContextGroupId: browsingContext?.group.id,
     81        browsingContextId: browsingContext?.id,
     82        isTopBrowsingContext: browsingContext?.parent === null,
     83        nodeWeakRef: Cu.getWeakReference(node),
     84      };
     85 
     86      this.#nodeIdMap.set(node, nodeId);
     87      this.#seenNodesMap.set(nodeId, details);
     88 
     89      // Also add the information for the node id and its correlated browsing
     90      // context to allow the parent process to update the seen nodes.
     91      if (!seenNodeIds.has(browsingContext)) {
     92        seenNodeIds.set(browsingContext, []);
     93      }
     94      seenNodeIds.get(browsingContext).push(nodeId);
     95    }
     96 
     97    return nodeId;
     98  }
     99 
    100  /**
    101   * Clear known DOM nodes.
    102   *
    103   * @param {object=} options
    104   * @param {boolean=} options.all
    105   *     Clear all references from any browsing context. Defaults to false.
    106   * @param {BrowsingContext=} options.browsingContext
    107   *     Clear all references living in that browsing context.
    108   */
    109  clear(options = {}) {
    110    const { all = false, browsingContext } = options;
    111 
    112    if (all) {
    113      this.#nodeIdMap = new WeakMap();
    114      this.#seenNodesMap.clear();
    115      return;
    116    }
    117 
    118    if (browsingContext) {
    119      for (const [nodeId, identifier] of this.#seenNodesMap.entries()) {
    120        const { browsingContextId, nodeWeakRef } = identifier;
    121        const node = nodeWeakRef.get();
    122 
    123        if (browsingContextId === browsingContext.id) {
    124          this.#nodeIdMap.delete(node);
    125          this.#seenNodesMap.delete(nodeId);
    126        }
    127      }
    128 
    129      return;
    130    }
    131 
    132    throw new Error(`Requires "browsingContext" or "all" to be set.`);
    133  }
    134 
    135  /**
    136   * Get a DOM node by its unique reference.
    137   *
    138   * @param {BrowsingContext} browsingContext
    139   *     The browsing context the node should be part of.
    140   * @param {string} nodeId
    141   *     The unique node reference of the DOM node.
    142   *
    143   * @returns {Node|null}
    144   *     The DOM node that the unique identifier was generated for or
    145   *     `null` if the node does not exist anymore.
    146   */
    147  getNode(browsingContext, nodeId) {
    148    const nodeDetails = this.getReferenceDetails(nodeId);
    149 
    150    // Check that the node reference is known, and is associated with a
    151    // browsing context that shares the same browsing context group.
    152    if (
    153      nodeDetails === null ||
    154      nodeDetails.browsingContextGroupId !== browsingContext.group.id
    155    ) {
    156      return null;
    157    }
    158 
    159    if (nodeDetails.nodeWeakRef) {
    160      return nodeDetails.nodeWeakRef.get();
    161    }
    162 
    163    return null;
    164  }
    165 
    166  /**
    167   * Get detailed information for the node reference.
    168   *
    169   * @param {string} nodeId
    170   *
    171   * @returns {NodeReferenceDetails}
    172   *     Node details like: browsingContextId
    173   */
    174  getReferenceDetails(nodeId) {
    175    const details = this.#seenNodesMap.get(nodeId);
    176 
    177    return details !== undefined ? details : null;
    178  }
    179 }