custom-element-watcher.js (4212B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 8 9 /** 10 * The CustomElementWatcher can be used to be notified if a custom element definition 11 * is created for a node. 12 * 13 * When a custom element is defined for a monitored name, an "element-defined" event is 14 * fired with the following Object argument: 15 * - {String} name: name of the custom element defined 16 * - {Set} Set of impacted node actors 17 */ 18 class CustomElementWatcher extends EventEmitter { 19 constructor(chromeEventHandler) { 20 super(); 21 22 this.chromeEventHandler = chromeEventHandler; 23 this._onCustomElementDefined = this._onCustomElementDefined.bind(this); 24 this.chromeEventHandler.addEventListener( 25 "customelementdefined", 26 this._onCustomElementDefined 27 ); 28 29 /** 30 * Each window keeps its own custom element registry, all of them are watched 31 * separately. The struture of the watchedRegistries is as follows 32 * 33 * WeakMap( 34 * registry -> Map ( 35 * name -> Set(NodeActors) 36 * ) 37 * ) 38 */ 39 this.watchedRegistries = new WeakMap(); 40 } 41 42 destroy() { 43 this.watchedRegistries = null; 44 this.chromeEventHandler.removeEventListener( 45 "customelementdefined", 46 this._onCustomElementDefined 47 ); 48 } 49 50 /** 51 * Watch for custom element definitions matching the name of the provided NodeActor. 52 */ 53 manageNode(nodeActor) { 54 if (!this._isValidNode(nodeActor)) { 55 return; 56 } 57 58 if (!this._shouldWatchDefinition(nodeActor)) { 59 return; 60 } 61 62 const registry = nodeActor.rawNode.ownerGlobal.customElements; 63 const registryMap = this._getMapForRegistry(registry); 64 65 const name = nodeActor.rawNode.localName; 66 const actorsSet = this._getActorsForName(name, registryMap); 67 actorsSet.add(nodeActor); 68 } 69 70 /** 71 * Stop watching the provided NodeActor. 72 */ 73 unmanageNode(nodeActor) { 74 if (!this._isValidNode(nodeActor)) { 75 return; 76 } 77 78 const win = nodeActor.rawNode.ownerGlobal; 79 const registry = win.customElements; 80 const registryMap = this._getMapForRegistry(registry); 81 const name = nodeActor.rawNode.localName; 82 if (registryMap.has(name)) { 83 registryMap.get(name).delete(nodeActor); 84 } 85 } 86 87 /** 88 * Retrieve the map of name->nodeActors for a given CustomElementsRegistry. 89 * Will create the map if not created yet. 90 */ 91 _getMapForRegistry(registry) { 92 if (!this.watchedRegistries.has(registry)) { 93 this.watchedRegistries.set(registry, new Map()); 94 } 95 return this.watchedRegistries.get(registry); 96 } 97 98 /** 99 * Retrieve the set of nodeActors for a given name and registry. 100 * Will create the set if not created yet. 101 */ 102 _getActorsForName(name, registryMap) { 103 if (!registryMap.has(name)) { 104 registryMap.set(name, new Set()); 105 } 106 return registryMap.get(name); 107 } 108 109 _shouldWatchDefinition(nodeActor) { 110 const doc = nodeActor.rawNode.ownerDocument; 111 const namespaceURI = doc.documentElement.namespaceURI; 112 const name = nodeActor.rawNode.localName; 113 const isValidName = InspectorUtils.isCustomElementName(name, namespaceURI); 114 115 const customElements = doc.defaultView.customElements; 116 return isValidName && !customElements.get(name); 117 } 118 119 _onCustomElementDefined(event) { 120 const doc = event.target; 121 const registry = doc.defaultView.customElements; 122 const registryMap = this._getMapForRegistry(registry); 123 124 const name = event.detail; 125 const actors = this._getActorsForName(name, registryMap); 126 this.emit("element-defined", { name, actors }); 127 registryMap.delete(name); 128 } 129 130 /** 131 * Some nodes (e.g. inside of <template> tags) don't have a documentElement or an 132 * ownerGlobal and can't be watched by this helper. 133 */ 134 _isValidNode(nodeActor) { 135 const node = nodeActor.rawNode; 136 return ( 137 !Cu.isDeadWrapper(node) && 138 node.ownerGlobal && 139 node.ownerDocument?.documentElement 140 ); 141 } 142 } 143 144 exports.CustomElementWatcher = CustomElementWatcher;