summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/inspector/custom-element-watcher.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/inspector/custom-element-watcher.js')
-rw-r--r--devtools/server/actors/inspector/custom-element-watcher.js144
1 files changed, 144 insertions, 0 deletions
diff --git a/devtools/server/actors/inspector/custom-element-watcher.js b/devtools/server/actors/inspector/custom-element-watcher.js
new file mode 100644
index 0000000000..8eb57fea40
--- /dev/null
+++ b/devtools/server/actors/inspector/custom-element-watcher.js
@@ -0,0 +1,144 @@
+/* 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/. */
+
+"use strict";
+
+const EventEmitter = require("resource://devtools/shared/event-emitter.js");
+
+/**
+ * The CustomElementWatcher can be used to be notified if a custom element definition
+ * is created for a node.
+ *
+ * When a custom element is defined for a monitored name, an "element-defined" event is
+ * fired with the following Object argument:
+ * - {String} name: name of the custom element defined
+ * - {Set} Set of impacted node actors
+ */
+class CustomElementWatcher extends EventEmitter {
+ constructor(chromeEventHandler) {
+ super();
+
+ this.chromeEventHandler = chromeEventHandler;
+ this._onCustomElementDefined = this._onCustomElementDefined.bind(this);
+ this.chromeEventHandler.addEventListener(
+ "customelementdefined",
+ this._onCustomElementDefined
+ );
+
+ /**
+ * Each window keeps its own custom element registry, all of them are watched
+ * separately. The struture of the watchedRegistries is as follows
+ *
+ * WeakMap(
+ * registry -> Map (
+ * name -> Set(NodeActors)
+ * )
+ * )
+ */
+ this.watchedRegistries = new WeakMap();
+ }
+
+ destroy() {
+ this.watchedRegistries = null;
+ this.chromeEventHandler.removeEventListener(
+ "customelementdefined",
+ this._onCustomElementDefined
+ );
+ }
+
+ /**
+ * Watch for custom element definitions matching the name of the provided NodeActor.
+ */
+ manageNode(nodeActor) {
+ if (!this._isValidNode(nodeActor)) {
+ return;
+ }
+
+ if (!this._shouldWatchDefinition(nodeActor)) {
+ return;
+ }
+
+ const registry = nodeActor.rawNode.ownerGlobal.customElements;
+ const registryMap = this._getMapForRegistry(registry);
+
+ const name = nodeActor.rawNode.localName;
+ const actorsSet = this._getActorsForName(name, registryMap);
+ actorsSet.add(nodeActor);
+ }
+
+ /**
+ * Stop watching the provided NodeActor.
+ */
+ unmanageNode(nodeActor) {
+ if (!this._isValidNode(nodeActor)) {
+ return;
+ }
+
+ const win = nodeActor.rawNode.ownerGlobal;
+ const registry = win.customElements;
+ const registryMap = this._getMapForRegistry(registry);
+ const name = nodeActor.rawNode.localName;
+ if (registryMap.has(name)) {
+ registryMap.get(name).delete(nodeActor);
+ }
+ }
+
+ /**
+ * Retrieve the map of name->nodeActors for a given CustomElementsRegistry.
+ * Will create the map if not created yet.
+ */
+ _getMapForRegistry(registry) {
+ if (!this.watchedRegistries.has(registry)) {
+ this.watchedRegistries.set(registry, new Map());
+ }
+ return this.watchedRegistries.get(registry);
+ }
+
+ /**
+ * Retrieve the set of nodeActors for a given name and registry.
+ * Will create the set if not created yet.
+ */
+ _getActorsForName(name, registryMap) {
+ if (!registryMap.has(name)) {
+ registryMap.set(name, new Set());
+ }
+ return registryMap.get(name);
+ }
+
+ _shouldWatchDefinition(nodeActor) {
+ const doc = nodeActor.rawNode.ownerDocument;
+ const namespaceURI = doc.documentElement.namespaceURI;
+ const name = nodeActor.rawNode.localName;
+ const isValidName = InspectorUtils.isCustomElementName(name, namespaceURI);
+
+ const customElements = doc.defaultView.customElements;
+ return isValidName && !customElements.get(name);
+ }
+
+ _onCustomElementDefined(event) {
+ const doc = event.target;
+ const registry = doc.defaultView.customElements;
+ const registryMap = this._getMapForRegistry(registry);
+
+ const name = event.detail;
+ const actors = this._getActorsForName(name, registryMap);
+ this.emit("element-defined", { name, actors });
+ registryMap.delete(name);
+ }
+
+ /**
+ * Some nodes (e.g. inside of <template> tags) don't have a documentElement or an
+ * ownerGlobal and can't be watched by this helper.
+ */
+ _isValidNode(nodeActor) {
+ const node = nodeActor.rawNode;
+ return (
+ !Cu.isDeadWrapper(node) &&
+ node.ownerGlobal &&
+ node.ownerDocument?.documentElement
+ );
+ }
+}
+
+exports.CustomElementWatcher = CustomElementWatcher;