Source code

Revision control

Copy as Markdown

Other Tools

/* 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 lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
/**
* @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.
* Supported types of nodes are Element and ShadowRoot.
*/
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 an Element or
* ShadowRoot node.
*
* @param {Node} node
* The node to be added.
* @param {Map<BrowsingContext, Array<string>>} seenNodeIds
* Map of browsing contexts to their seen node ids during the current
* serialization.
*
* @returns {string}
* The unique node reference for the DOM node.
*/
getOrCreateNodeReference(node, seenNodeIds) {
if (!Node.isInstance(node)) {
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 {
// Bug 1820734: For some Node types like `CDATA` no `ownerGlobal`
// property is available, and as such they cannot be deserialized
// right now.
const browsingContext = node.ownerGlobal?.browsingContext;
// For not yet cached nodes generate a unique id without curly braces.
nodeId = lazy.generateUUID();
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);
// Also add the information for the node id and its correlated browsing
// context to allow the parent process to update the seen nodes.
if (!seenNodeIds.has(browsingContext)) {
seenNodeIds.set(browsingContext, []);
}
seenNodeIds.get(browsingContext).push(nodeId);
}
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=} options.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.
*
* @returns {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 associated 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;
}
}