/* 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 { Cu } = require("chrome"); const InspectorUtils = require("InspectorUtils"); const EventEmitter = require("devtools/shared/event-emitter"); /** * 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