Bug 1885565 - Part 1: Add mozac_ic_avatar_circle_24 to ui-icons r=android-reviewers...
[gecko.git] / toolkit / modules / ContentDOMReference.sys.mjs
blobef4896ff6747c1865bd484fe384ef61ee9be11ca
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/. */
6 /**
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).
12  *
13  * The hope is that this module can eliminate the need for passing CPOW references
14  * between processes during runtime.
15  */
17 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
19 const lazy = {};
21 XPCOMUtils.defineLazyServiceGetter(
22   lazy,
23   "finalizationService",
24   "@mozilla.org/toolkit/finalizationwitness;1",
25   "nsIFinalizationWitnessService"
28 /**
29  * @typedef {number} ElementID
30  * @typedef {Object} ElementIdentifier
31  */
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();
41 /**
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
44  * properties:
45  *
46  *   IDToElement:
47  *     A Map of IDs to WeakReference's to the elements they refer to.
48  *
49  *   elementToID:
50  *     A WeakMap from a DOM element to an ID that refers to it.
51  */
52 var gRegistry = new WeakMap();
54 export var ContentDOMReference = {
55   _init() {
56     Services.obs.addObserver(this, FINALIZATION_TOPIC);
57   },
59   observe(subject, topic, data) {
60     if (topic !== FINALIZATION_TOPIC) {
61       throw new Error("Unexpected observer topic");
62     }
64     let identifier = JSON.parse(data);
65     this._revoke(identifier);
66   },
68   /**
69    * Generate and return an identifier for a given DOM element.
70    *
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.
74    */
75   get(element) {
76     if (!element) {
77       throw new Error(
78         "Can't create a ContentDOMReference identifier for " +
79           "non-existant nodes."
80       );
81     }
83     let browsingContext = BrowsingContext.getFromWindow(element.ownerGlobal);
84     let mappings = gRegistry.get(browsingContext);
85     if (!mappings) {
86       mappings = {
87         IDToElement: new Map(),
88         elementToID: new WeakMap(),
89       };
90       gRegistry.set(browsingContext, mappings);
91     }
93     let id = mappings.elementToID.get(element);
94     if (id) {
95       // We already had this element registered, so return the pre-existing ID.
96       return { browsingContextId: browsingContext.id, id };
97     }
99     // We must be registering a new element at this point.
100     id = Math.random();
101     mappings.elementToID.set(element, id);
102     mappings.IDToElement.set(id, Cu.getWeakReference(element));
104     let identifier = { browsingContextId: browsingContext.id, id };
106     finalizerRoots.set(
107       element,
108       lazy.finalizationService.make(
109         FINALIZATION_TOPIC,
110         JSON.stringify(identifier)
111       )
112     );
114     return identifier;
115   },
117   /**
118    * Resolves an identifier back into the DOM Element that it was generated from.
119    *
120    * @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a
121    * DOM element.
122    * @return {Element} The DOM element that the identifier was generated for, or
123    * null if the element does not still exist.
124    */
125   resolve(identifier) {
126     let browsingContext = BrowsingContext.get(identifier.browsingContextId);
127     let { id } = identifier;
128     return this._resolveIDToElement(browsingContext, id);
129   },
131   /**
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.
135    *
136    * @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for
137    * a DOM element.
138    */
139   _revoke(identifier) {
140     let browsingContext = BrowsingContext.get(identifier.browsingContextId);
141     let { id } = identifier;
143     let mappings = gRegistry.get(browsingContext);
144     if (!mappings) {
145       return;
146     }
148     mappings.IDToElement.delete(id);
149   },
151   /**
152    * Private helper function that resolves a BrowsingContext and ID (the
153    * pair that makes up an identifier) to a DOM element.
154    *
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.
158    *
159    * @return {Element} The DOM element that the identifier was generated for, or
160    * null if the element does not still exist.
161    */
162   _resolveIDToElement(browsingContext, id) {
163     let mappings = gRegistry.get(browsingContext);
164     if (!mappings) {
165       return null;
166     }
168     let weakReference = mappings.IDToElement.get(id);
169     if (!weakReference) {
170       return null;
171     }
173     return weakReference.get();
174   },
177 ContentDOMReference._init();