зеркало из https://github.com/mozilla/gecko-dev.git
178 строки
5.4 KiB
JavaScript
178 строки
5.4 KiB
JavaScript
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
|
/* 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/. */
|
|
|
|
/**
|
|
* This module holds weak references to DOM elements that exist within the
|
|
* current content process, and converts them to a unique identifier that can be
|
|
* passed between processes. The identifer, if received by the same content process
|
|
* that issued it, can then be converted back into the DOM element (presuming the
|
|
* element hasn't had all of its other references dropped).
|
|
*
|
|
* The hope is that this module can eliminate the need for passing CPOW references
|
|
* between processes during runtime.
|
|
*/
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"finalizationService",
|
|
"@mozilla.org/toolkit/finalizationwitness;1",
|
|
"nsIFinalizationWitnessService"
|
|
);
|
|
|
|
/**
|
|
* @typedef {number} ElementID
|
|
* @typedef {Object} ElementIdentifier
|
|
*/
|
|
|
|
const FINALIZATION_TOPIC = "content-dom-reference-finalized";
|
|
|
|
// A WeakMap which ties finalization witness objects to the lifetime of the DOM
|
|
// nodes they're meant to witness. When the DOM node in the map key is
|
|
// finalized, the WeakMap stops holding the finalization witness in its value
|
|
// alive, which alerts our observer that the element has been destroyed.
|
|
const finalizerRoots = new WeakMap();
|
|
|
|
/**
|
|
* An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
|
|
* ID and a numeric ID. gRegistry maps BrowsingContext's to an object with the following
|
|
* properties:
|
|
*
|
|
* IDToElement:
|
|
* A Map of IDs to WeakReference's to the elements they refer to.
|
|
*
|
|
* elementToID:
|
|
* A WeakMap from a DOM element to an ID that refers to it.
|
|
*/
|
|
var gRegistry = new WeakMap();
|
|
|
|
export var ContentDOMReference = {
|
|
_init() {
|
|
Services.obs.addObserver(this, FINALIZATION_TOPIC);
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
if (topic !== FINALIZATION_TOPIC) {
|
|
throw new Error("Unexpected observer topic");
|
|
}
|
|
|
|
let identifier = JSON.parse(data);
|
|
this._revoke(identifier);
|
|
},
|
|
|
|
/**
|
|
* Generate and return an identifier for a given DOM element.
|
|
*
|
|
* @param {Element} element The DOM element to generate the identifier for.
|
|
* @return {ElementIdentifier} The identifier for the DOM element that can be passed between
|
|
* processes as a message.
|
|
*/
|
|
get(element) {
|
|
if (!element) {
|
|
throw new Error(
|
|
"Can't create a ContentDOMReference identifier for " +
|
|
"non-existant nodes."
|
|
);
|
|
}
|
|
|
|
let browsingContext = BrowsingContext.getFromWindow(element.ownerGlobal);
|
|
let mappings = gRegistry.get(browsingContext);
|
|
if (!mappings) {
|
|
mappings = {
|
|
IDToElement: new Map(),
|
|
elementToID: new WeakMap(),
|
|
};
|
|
gRegistry.set(browsingContext, mappings);
|
|
}
|
|
|
|
let id = mappings.elementToID.get(element);
|
|
if (id) {
|
|
// We already had this element registered, so return the pre-existing ID.
|
|
return { browsingContextId: browsingContext.id, id };
|
|
}
|
|
|
|
// We must be registering a new element at this point.
|
|
id = Math.random();
|
|
mappings.elementToID.set(element, id);
|
|
mappings.IDToElement.set(id, Cu.getWeakReference(element));
|
|
|
|
let identifier = { browsingContextId: browsingContext.id, id };
|
|
|
|
finalizerRoots.set(
|
|
element,
|
|
lazy.finalizationService.make(
|
|
FINALIZATION_TOPIC,
|
|
JSON.stringify(identifier)
|
|
)
|
|
);
|
|
|
|
return identifier;
|
|
},
|
|
|
|
/**
|
|
* Resolves an identifier back into the DOM Element that it was generated from.
|
|
*
|
|
* @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a
|
|
* DOM element.
|
|
* @return {Element} The DOM element that the identifier was generated for, or
|
|
* null if the element does not still exist.
|
|
*/
|
|
resolve(identifier) {
|
|
let browsingContext = BrowsingContext.get(identifier.browsingContextId);
|
|
let { id } = identifier;
|
|
return this._resolveIDToElement(browsingContext, id);
|
|
},
|
|
|
|
/**
|
|
* Removes an identifier from the registry so that subsequent attempts
|
|
* to resolve it will result in null. This is done automatically when the
|
|
* target node is GCed.
|
|
*
|
|
* @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for
|
|
* a DOM element.
|
|
*/
|
|
_revoke(identifier) {
|
|
let browsingContext = BrowsingContext.get(identifier.browsingContextId);
|
|
let { id } = identifier;
|
|
|
|
let mappings = gRegistry.get(browsingContext);
|
|
if (!mappings) {
|
|
return;
|
|
}
|
|
|
|
mappings.IDToElement.delete(id);
|
|
},
|
|
|
|
/**
|
|
* Private helper function that resolves a BrowsingContext and ID (the
|
|
* pair that makes up an identifier) to a DOM element.
|
|
*
|
|
* @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
|
|
* the DOM element at the time that the identifier was generated.
|
|
* @param {ElementID} id The ID generated for the DOM element.
|
|
*
|
|
* @return {Element} The DOM element that the identifier was generated for, or
|
|
* null if the element does not still exist.
|
|
*/
|
|
_resolveIDToElement(browsingContext, id) {
|
|
let mappings = gRegistry.get(browsingContext);
|
|
if (!mappings) {
|
|
return null;
|
|
}
|
|
|
|
let weakReference = mappings.IDToElement.get(id);
|
|
if (!weakReference) {
|
|
return null;
|
|
}
|
|
|
|
return weakReference.get();
|
|
},
|
|
};
|
|
|
|
ContentDOMReference._init();
|