1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * This module holds weak references to DOM elements that exist within the
8 * current content process, and converts them to a unique identifier that can be
9 * passed between processes. The identifer, if received by the same content process
10 * that issued it, can then be converted back into the DOM element (presuming the
11 * element hasn't had all of its other references dropped).
13 * The hope is that this module can eliminate the need for passing CPOW references
14 * between processes during runtime.
17 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
21 XPCOMUtils.defineLazyServiceGetter(
23 "finalizationService",
24 "@mozilla.org/toolkit/finalizationwitness;1",
25 "nsIFinalizationWitnessService"
29 * @typedef {number} ElementID
30 * @typedef {Object} ElementIdentifier
33 const FINALIZATION_TOPIC = "content-dom-reference-finalized";
35 // A WeakMap which ties finalization witness objects to the lifetime of the DOM
36 // nodes they're meant to witness. When the DOM node in the map key is
37 // finalized, the WeakMap stops holding the finalization witness in its value
38 // alive, which alerts our observer that the element has been destroyed.
39 const finalizerRoots = new WeakMap();
42 * An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
43 * ID and a numeric ID. gRegistry maps BrowsingContext's to an object with the following
47 * A Map of IDs to WeakReference's to the elements they refer to.
50 * A WeakMap from a DOM element to an ID that refers to it.
52 var gRegistry = new WeakMap();
54 export var ContentDOMReference = {
56 Services.obs.addObserver(this, FINALIZATION_TOPIC);
59 observe(subject, topic, data) {
60 if (topic !== FINALIZATION_TOPIC) {
61 throw new Error("Unexpected observer topic");
64 let identifier = JSON.parse(data);
65 this._revoke(identifier);
69 * Generate and return an identifier for a given DOM element.
71 * @param {Element} element The DOM element to generate the identifier for.
72 * @return {ElementIdentifier} The identifier for the DOM element that can be passed between
73 * processes as a message.
78 "Can't create a ContentDOMReference identifier for " +
83 let browsingContext = BrowsingContext.getFromWindow(element.ownerGlobal);
84 let mappings = gRegistry.get(browsingContext);
87 IDToElement: new Map(),
88 elementToID: new WeakMap(),
90 gRegistry.set(browsingContext, mappings);
93 let id = mappings.elementToID.get(element);
95 // We already had this element registered, so return the pre-existing ID.
96 return { browsingContextId: browsingContext.id, id };
99 // We must be registering a new element at this point.
101 mappings.elementToID.set(element, id);
102 mappings.IDToElement.set(id, Cu.getWeakReference(element));
104 let identifier = { browsingContextId: browsingContext.id, id };
108 lazy.finalizationService.make(
110 JSON.stringify(identifier)
118 * Resolves an identifier back into the DOM Element that it was generated from.
120 * @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a
122 * @return {Element} The DOM element that the identifier was generated for, or
123 * null if the element does not still exist.
125 resolve(identifier) {
126 let browsingContext = BrowsingContext.get(identifier.browsingContextId);
127 let { id } = identifier;
128 return this._resolveIDToElement(browsingContext, id);
132 * Removes an identifier from the registry so that subsequent attempts
133 * to resolve it will result in null. This is done automatically when the
134 * target node is GCed.
136 * @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for
139 _revoke(identifier) {
140 let browsingContext = BrowsingContext.get(identifier.browsingContextId);
141 let { id } = identifier;
143 let mappings = gRegistry.get(browsingContext);
148 mappings.IDToElement.delete(id);
152 * Private helper function that resolves a BrowsingContext and ID (the
153 * pair that makes up an identifier) to a DOM element.
155 * @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
156 * the DOM element at the time that the identifier was generated.
157 * @param {ElementID} id The ID generated for the DOM element.
159 * @return {Element} The DOM element that the identifier was generated for, or
160 * null if the element does not still exist.
162 _resolveIDToElement(browsingContext, id) {
163 let mappings = gRegistry.get(browsingContext);
168 let weakReference = mappings.IDToElement.get(id);
169 if (!weakReference) {
173 return weakReference.get();
177 ContentDOMReference._init();