forked from mirrors/gecko-dev
Backed out changeset 7f0a872abb09 (bug 1811545) Backed out changeset 41daafb5d75a (bug 1811545) Backed out changeset 82dee6290057 (bug 1811545) Backed out changeset 3e0178b3e0dc (bug 1811545) Backed out changeset 55cec0043044 (bug 1811545) Backed out changeset 75eaaf96d039 (bug 1808894)
164 lines
4.4 KiB
JavaScript
164 lines
4.4 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
const DOCUMENT_FRAGMENT_NODE = 11;
|
|
const ELEMENT_NODE = 1;
|
|
|
|
/**
|
|
* @typedef {Object} NodeReferenceDetails
|
|
* @property {number} browserId
|
|
* @property {number} browsingContextGroupId
|
|
* @property {number} browsingContextId
|
|
* @property {boolean} isTopBrowsingContext
|
|
* @property {WeakRef} nodeWeakRef
|
|
*/
|
|
|
|
/**
|
|
* The class provides a mapping between DOM nodes and a unique node references.
|
|
*/
|
|
export class NodeCache {
|
|
#nodeIdMap;
|
|
#seenNodesMap;
|
|
|
|
constructor() {
|
|
// node => node id
|
|
this.#nodeIdMap = new WeakMap();
|
|
|
|
// Reverse map for faster lookup requests of node references. Values do
|
|
// not only contain the resolved DOM node but also further details like
|
|
// browsing context information.
|
|
//
|
|
// node id => node details
|
|
this.#seenNodesMap = new Map();
|
|
}
|
|
|
|
/**
|
|
* Get the number of nodes in the cache.
|
|
*/
|
|
get size() {
|
|
return this.#seenNodesMap.size;
|
|
}
|
|
|
|
/**
|
|
* Get or if not yet existent create a unique reference for a DOM node.
|
|
*
|
|
* @param {Node} node
|
|
* The DOM node to be added.
|
|
*
|
|
* @return {string}
|
|
* The unique node reference for the DOM node.
|
|
*/
|
|
getOrCreateNodeReference(node) {
|
|
if (![DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE].includes(node?.nodeType)) {
|
|
throw new TypeError(`Failed to create node reference for ${node}`);
|
|
}
|
|
|
|
let nodeId;
|
|
if (this.#nodeIdMap.has(node)) {
|
|
// For already known nodes return the cached node id.
|
|
nodeId = this.#nodeIdMap.get(node);
|
|
} else {
|
|
const browsingContext = node.ownerGlobal.browsingContext;
|
|
|
|
// For not yet cached nodes generate a unique id without curly braces.
|
|
nodeId = Services.uuid
|
|
.generateUUID()
|
|
.toString()
|
|
.slice(1, -1);
|
|
|
|
const details = {
|
|
browserId: browsingContext.browserId,
|
|
browsingContextGroupId: browsingContext.group.id,
|
|
browsingContextId: browsingContext.id,
|
|
isTopBrowsingContext: browsingContext.parent === null,
|
|
nodeWeakRef: Cu.getWeakReference(node),
|
|
};
|
|
|
|
this.#nodeIdMap.set(node, nodeId);
|
|
this.#seenNodesMap.set(nodeId, details);
|
|
}
|
|
|
|
return nodeId;
|
|
}
|
|
|
|
/**
|
|
* Clear known DOM nodes.
|
|
*
|
|
* @param {Object=} options
|
|
* @param {boolean=} options.all
|
|
* Clear all references from any browsing context. Defaults to false.
|
|
* @param {BrowsingContext=} browsingContext
|
|
* Clear all references living in that browsing context.
|
|
*/
|
|
clear(options = {}) {
|
|
const { all = false, browsingContext } = options;
|
|
|
|
if (all) {
|
|
this.#nodeIdMap = new WeakMap();
|
|
this.#seenNodesMap.clear();
|
|
return;
|
|
}
|
|
|
|
if (browsingContext) {
|
|
for (const [nodeId, identifier] of this.#seenNodesMap.entries()) {
|
|
const { browsingContextId, nodeWeakRef } = identifier;
|
|
const node = nodeWeakRef.get();
|
|
|
|
if (browsingContextId === browsingContext.id) {
|
|
this.#nodeIdMap.delete(node);
|
|
this.#seenNodesMap.delete(nodeId);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
throw new Error(`Requires "browsingContext" or "all" to be set.`);
|
|
}
|
|
|
|
/**
|
|
* Get a DOM node by its unique reference.
|
|
*
|
|
* @param {BrowsingContext} browsingContext
|
|
* The browsing context the node should be part of.
|
|
* @param {string} nodeId
|
|
* The unique node reference of the DOM node.
|
|
*
|
|
* @return {Node|null}
|
|
* The DOM node that the unique identifier was generated for or
|
|
* `null` if the node does not exist anymore.
|
|
*/
|
|
getNode(browsingContext, nodeId) {
|
|
const nodeDetails = this.getReferenceDetails(nodeId);
|
|
|
|
// Check that the node reference is known, and is accociated with a
|
|
// browsing context that shares the same browsing context group.
|
|
if (
|
|
nodeDetails === null ||
|
|
nodeDetails.browsingContextGroupId !== browsingContext.group.id
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
if (nodeDetails.nodeWeakRef) {
|
|
return nodeDetails.nodeWeakRef.get();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get detailed information for the node reference.
|
|
*
|
|
* @param {string} nodeId
|
|
*
|
|
* @returns {NodeReferenceDetails}
|
|
* Node details like: browsingContextId
|
|
*/
|
|
getReferenceDetails(nodeId) {
|
|
const details = this.#seenNodesMap.get(nodeId);
|
|
|
|
return details !== undefined ? details : null;
|
|
}
|
|
}
|