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 }