/* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // FIXME: DOMManager lacks advanced multi-target support. (DOMNodes per-target) WI.DOMManager = class DOMManager extends WI.Object { constructor() { super(); this._idToDOMNode = {}; this._document = null; this._documentPromise = null; this._attributeLoadNodeIds = {}; this._restoreSelectedNodeIsAllowed = true; this._loadNodeAttributesTimeout = 0; this._inspectedNode = null; this._breakpointsForEventListeners = new Map; this._hasRequestedDocument = false; this._pendingDocumentRequestCallbacks = null; WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleEventBreakpointDisabledStateChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleEventBreakpointEditablePropertyChanged, this); WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleEventBreakpointActionsChanged, this); WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); } // Target initializeTarget(target) { // FIXME: This should be improved when adding better DOM multi-target support since it is really per-target. // This currently uses a setTimeout since it doesn't need to happen immediately, and DOMManager uses the // global DOMAgent to request the document, so we want to make sure we've transitioned the global agents // to this target if necessary. if (target.hasDomain("DOM")) { setTimeout(() => { this.ensureDocument(); }); if (WI.engineeringSettingsAllowed()) { if (DOMManager.supportsEditingUserAgentShadowTrees({target})) target.DOMAgent.setAllowEditingUserAgentShadowTrees(WI.settings.engineeringAllowEditingUserAgentShadowTrees.value); } } } transitionPageTarget() { this._documentUpdated(); } // Static static buildHighlightConfig(mode) { mode = mode || "all"; let highlightConfig = {showInfo: mode === "all"}; if (mode === "all" || mode === "content") highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66}; if (mode === "all" || mode === "padding") highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66}; if (mode === "all" || mode === "border") highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66}; if (mode === "all" || mode === "margin") highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66}; return highlightConfig; } static wrapClientCallback(callback) { if (!callback) return null; return function(error, result) { if (error) console.error("Error during DOMAgent operation: " + error); callback(error ? null : result); }; } static supportsEventListenerBreakpoints() { return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener") && InspectorBackend.hasCommand("DOM.removeBreakpointForEventListener"); } static supportsEventListenerBreakpointConfiguration() { // COMPATIBILITY (iOS 14): DOM.setBreakpointForEventListener did not have an "options" parameter yet. return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener", "options"); } static supportsEditingUserAgentShadowTrees({frontendOnly, target} = {}) { target = target || InspectorBackend; return WI.settings.engineeringAllowEditingUserAgentShadowTrees.value && (frontendOnly || target.hasCommand("DOM.setAllowEditingUserAgentShadowTrees")); } // Public get inspectedNode() { return this._inspectedNode; } get eventListenerBreakpoints() { return Array.from(this._breakpointsForEventListeners.values()); } *attachedNodes({filter} = {}) { if (!this._document) return; filter ??= (node) => true; // Traverse the node tree in the same order items would appear if the entire tree were expanded in order to // provide a predictable order for the results. let currentBranch = [this._document]; while (currentBranch.length) { let currentNode = currentBranch.at(-1); if (filter(currentNode)) yield currentNode; // The `::before` pseudo element is the first child of any node. let beforePseudoElement = currentNode.beforePseudoElement(); if (beforePseudoElement && filter(beforePseudoElement)) yield beforePseudoElement; let firstChild = currentNode.children?.[0]; if (firstChild) { currentBranch.push(firstChild); continue; } while (currentBranch.length) { let parent = currentBranch.pop(); // The `::after` pseudo element is the last child of any node. let parentAfterPseudoElement = parent.afterPseudoElement(); if (parentAfterPseudoElement && filter(parentAfterPseudoElement)) yield parentAfterPseudoElement; if (parent.nextSibling) { currentBranch.push(parent.nextSibling); break; } } } } requestDocument(callback) { if (typeof callback !== "function") return this._requestDocumentWithPromise(); this._requestDocumentWithCallback(callback); } ensureDocument() { this.requestDocument(function(){}); } pushNodeToFrontend(objectId, callback) { let target = WI.assumingMainTarget(); this._dispatchWhenDocumentAvailable((callbackWrapper) => { target.DOMAgent.requestNode(objectId, callbackWrapper); }, callback); } pushNodeByPathToFrontend(path, callback) { let target = WI.assumingMainTarget(); this._dispatchWhenDocumentAvailable((callbackWrapper) => { target.DOMAgent.pushNodeByPathToFrontend(path, callbackWrapper); }, callback); } // DOMObserver willDestroyDOMNode(nodeId) { let node = this._idToDOMNode[nodeId]; node.markDestroyed(); delete this._idToDOMNode[nodeId]; this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node}); } didAddEventListener(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged); } willRemoveEventListener(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged); } didFireEvent(nodeId, eventName, timestamp, data) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.didFireEvent(eventName, timestamp, data); } powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient); } // CSSObserver nodeLayoutFlagsChanged(nodeId, layoutFlags) { let domNode = this._idToDOMNode[nodeId]; console.assert(domNode instanceof WI.DOMNode, domNode, nodeId); if (!domNode) return; domNode.layoutFlags = layoutFlags; } // Private _dispatchWhenDocumentAvailable(func, callback) { var callbackWrapper = DOMManager.wrapClientCallback(callback); function onDocumentAvailable() { if (this._document) func(callbackWrapper); else { if (callbackWrapper) callbackWrapper("No document"); } } this.requestDocument(onDocumentAvailable.bind(this)); } _requestDocumentWithPromise() { if (this._documentPromise) return this._documentPromise.promise; this._documentPromise = new WI.WrappedPromise; if (this._document) this._documentPromise.resolve(this._document); else { this._requestDocumentWithCallback((doc) => { this._documentPromise.resolve(doc); }); } return this._documentPromise.promise; } _requestDocumentWithCallback(callback) { if (this._document) { callback(this._document); return; } if (this._pendingDocumentRequestCallbacks) this._pendingDocumentRequestCallbacks.push(callback); else this._pendingDocumentRequestCallbacks = [callback]; if (this._hasRequestedDocument) return; if (!WI.pageTarget) return; if (!WI.pageTarget.hasDomain("DOM")) return; this._hasRequestedDocument = true; WI.pageTarget.DOMAgent.getDocument((error, root) => { if (!error) this._setDocument(root); for (let callback of this._pendingDocumentRequestCallbacks) callback(this._document); this._pendingDocumentRequestCallbacks = null; }); } _attributeModified(nodeId, name, value) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._setAttribute(name, value); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name}); } _attributeRemoved(nodeId, name) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._removeAttribute(name); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeRemoved, {node, name}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeRemoved, {name}); } _inlineStyleInvalidated(nodeIds) { for (var nodeId of nodeIds) this._attributeLoadNodeIds[nodeId] = true; if (this._loadNodeAttributesTimeout) return; this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0); } _loadNodeAttributes() { function callback(nodeId, error, attributes) { if (error) { console.error("Error during DOMAgent operation: " + error); return; } var node = this._idToDOMNode[nodeId]; if (node) { node._setAttributesPayload(attributes); this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name: "style"}); node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name: "style"}); } } this._loadNodeAttributesTimeout = 0; let target = WI.assumingMainTarget(); for (var nodeId in this._attributeLoadNodeIds) { if (!(nodeId in this._idToDOMNode)) continue; var nodeIdAsNumber = parseInt(nodeId); target.DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber)); } this._attributeLoadNodeIds = {}; } _characterDataModified(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node._nodeValue = newValue; this.dispatchEventToListeners(WI.DOMManager.Event.CharacterDataModified, {node}); } nodeForId(nodeId) { return this._idToDOMNode[nodeId] || null; } _documentUpdated() { this._setDocument(null); } _setDocument(payload) { for (let node of Object.values(this._idToDOMNode)) node.markDestroyed(); this._idToDOMNode = {}; for (let breakpoint of this._breakpointsForEventListeners.values()) WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); this._breakpointsForEventListeners.clear(); let newDocument = null; if (payload && "nodeId" in payload) newDocument = new WI.DOMNode(this, null, false, payload); if (this._document === newDocument) return; this._document = newDocument; // Force the promise to be recreated so that it resolves to the new document. this._documentPromise = null; if (!this._document) this._hasRequestedDocument = false; this.dispatchEventToListeners(WI.DOMManager.Event.DocumentUpdated, {document: this._document}); } _setDetachedRoot(payload) { new WI.DOMNode(this, null, false, payload); } _setChildNodes(parentId, payloads) { if (!parentId && payloads.length) { this._setDetachedRoot(payloads[0]); return; } var parent = this._idToDOMNode[parentId]; if (parent.children) { for (let node of parent.children) this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent}); } parent._setChildrenPayload(payloads); for (let node of parent.children) this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _childNodeCountUpdated(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node.childNodeCount = newValue; this.dispatchEventToListeners(WI.DOMManager.Event.ChildNodeCountUpdated, node); } _childNodeInserted(parentId, prevId, payload) { var parent = this._idToDOMNode[parentId]; var prev = this._idToDOMNode[prevId]; var node = parent._insertChild(prev, payload); this._idToDOMNode[node.id] = node; this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _childNodeRemoved(parentId, nodeId) { var parent = this._idToDOMNode[parentId]; var node = this._idToDOMNode[nodeId]; parent._removeChild(node); this._unbind(node); this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent}); } _customElementStateChanged(elementId, newState) { const node = this._idToDOMNode[elementId]; node._customElementState = newState; this.dispatchEventToListeners(WI.DOMManager.Event.CustomElementStateChanged, {node}); } _pseudoElementAdded(parentId, pseudoElement) { var parent = this._idToDOMNode[parentId]; if (!parent) return; var node = new WI.DOMNode(this, parent.ownerDocument, false, pseudoElement); node.parentNode = parent; this._idToDOMNode[node.id] = node; console.assert(!parent.pseudoElements().get(node.pseudoType())); parent.pseudoElements().set(node.pseudoType(), node); this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent}); } _pseudoElementRemoved(parentId, pseudoElementId) { var pseudoElement = this._idToDOMNode[pseudoElementId]; if (!pseudoElement) return; var parent = pseudoElement.parentNode; console.assert(parent); console.assert(parent.id === parentId); if (!parent) return; parent._removeChild(pseudoElement); this._unbind(pseudoElement); this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node: pseudoElement, parent}); } _unbind(node) { node.markDestroyed(); delete this._idToDOMNode[node.id]; for (let i = 0; node.children && i < node.children.length; ++i) this._unbind(node.children[i]); let templateContent = node.templateContent(); if (templateContent) this._unbind(templateContent); for (let pseudoElement of node.pseudoElements().values()) this._unbind(pseudoElement); // FIXME: Handle shadow roots. } get restoreSelectedNodeIsAllowed() { return this._restoreSelectedNodeIsAllowed; } inspectElement(nodeId, options = {}) { var node = this._idToDOMNode[nodeId]; if (!node || !node.ownerDocument) return; // This code path is hit by "Reveal in DOM Tree" and clicking element links/console widgets. // Unless overridden by callers, assume that this is navigation is initiated by a Inspect mode. let initiatorHint = options.initiatorHint || WI.TabBrowser.TabNavigationInitiator.Inspect; this.dispatchEventToListeners(WI.DOMManager.Event.DOMNodeWasInspected, {node, initiatorHint}); this._inspectModeEnabled = false; this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged); } inspectNodeObject(remoteObject) { this._restoreSelectedNodeIsAllowed = false; function nodeAvailable(nodeId) { remoteObject.release(); console.assert(nodeId); if (!nodeId) return; this.inspectElement(nodeId); // Re-resolve the node in the console's object group when adding to the console. let domNode = this.nodeForId(nodeId); WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => { WI.consoleLogViewController.appendImmediateExecutionWithResult(WI.UIString("Selected Element"), remoteObject, {addSpecialUserLogClass: true}); }); } remoteObject.pushNodeToFrontend(nodeAvailable.bind(this)); } highlightDOMNodeList(nodes, mode) { if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); this._hideDOMNodeHighlightTimeout = undefined; } let nodeIds = []; for (let node of nodes) { console.assert(node instanceof WI.DOMNode, node); console.assert(!node.destroyed, node); if (node.destroyed) continue; nodeIds.push(node.id); } let target = WI.assumingMainTarget(); target.DOMAgent.highlightNodeList(nodeIds, DOMManager.buildHighlightConfig(mode)); } highlightSelector(selectorText, frameId, mode) { if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); this._hideDOMNodeHighlightTimeout = undefined; } let target = WI.assumingMainTarget(); target.DOMAgent.highlightSelector(DOMManager.buildHighlightConfig(mode), selectorText, frameId); } highlightRect(rect, usePageCoordinates) { let target = WI.assumingMainTarget(); target.DOMAgent.highlightRect.invoke({ x: rect.x, y: rect.y, width: rect.width, height: rect.height, color: {r: 111, g: 168, b: 220, a: 0.66}, outlineColor: {r: 255, g: 229, b: 153, a: 0.66}, usePageCoordinates }); } hideDOMNodeHighlight() { for (let target of WI.targets) { if (target.hasCommand("DOM.hideHighlight")) target.DOMAgent.hideHighlight(); } } highlightDOMNodeForTwoSeconds(nodeId) { let node = this._idToDOMNode[nodeId]; if (!node) return; node.highlight(); this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000); } get inspectModeEnabled() { return this._inspectModeEnabled; } set inspectModeEnabled(enabled) { if (enabled === this._inspectModeEnabled) return; let target = WI.assumingMainTarget(); let commandArguments = { enabled, highlightConfig: DOMManager.buildHighlightConfig(), showRulers: WI.settings.showRulersDuringElementSelection.value, }; target.DOMAgent.setInspectModeEnabled.invoke(commandArguments, (error) => { if (error) { WI.reportInternalError(error); return; } this._inspectModeEnabled = enabled; this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged); }); } setInspectedNode(node) { console.assert(node instanceof WI.DOMNode); if (node === this._inspectedNode) return; console.assert(!node.destroyed, node); if (node.destroyed) return; let callback = (error) => { console.assert(!error, error); if (error) return; let lastInspectedNode = this._inspectedNode; this._inspectedNode = node; this.dispatchEventToListeners(WI.DOMManager.Event.InspectedNodeChanged, {lastInspectedNode}); }; let target = WI.assumingMainTarget(); target.DOMAgent.setInspectedNode(node.id, callback); } getSupportedEventNames(callback) { let target = WI.assumingMainTarget(); if (!target.hasCommand("DOM.getSupportedEventNames")) return Promise.resolve(new Set); if (!this._getSupportedEventNamesPromise) { this._getSupportedEventNamesPromise = target.DOMAgent.getSupportedEventNames() .then(({eventNames}) => new Set(eventNames)); } return this._getSupportedEventNamesPromise; } setEventListenerDisabled(eventListener, disabled) { let target = WI.assumingMainTarget(); target.DOMAgent.setEventListenerDisabled(eventListener.eventListenerId, disabled); } setBreakpointForEventListener(eventListener) { let breakpoint = this._breakpointsForEventListeners.get(eventListener.eventListenerId); if (breakpoint) { console.assert(breakpoint.disabled); breakpoint.disabled = false; return; } breakpoint = new WI.EventBreakpoint(WI.EventBreakpoint.Type.Listener, {eventName: eventListener.type, eventListener}); console.assert(!breakpoint.disabled); this._breakpointsForEventListeners.set(eventListener.eventListenerId, breakpoint); for (let target of WI.targets) { if (target.hasDomain("DOM")) this._setEventBreakpoint(breakpoint, target); } WI.debuggerManager.addProbesForBreakpoint(breakpoint); WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint}); } removeBreakpointForEventListener(eventListener) { let breakpoint = this._breakpointsForEventListeners.take(eventListener.eventListenerId); if (!breakpoint) return; // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint. breakpoint.disabled = true; breakpoint.clearActions(); WI.debuggerManager.removeProbesForBreakpoint(breakpoint); WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint}); } removeEventListenerBreakpointsForNode(domNode) { for (let breakpoint of Array.from(this._breakpointsForEventListeners.values())) { let eventListener = breakpoint.eventListener; if (eventListener.nodeId === domNode.id) this.removeBreakpointForEventListener(eventListener); } } breakpointForEventListenerId(eventListenerId) { return this._breakpointsForEventListeners.get(eventListenerId) || null; } // Private _setEventBreakpoint(breakpoint, target) { console.assert(!breakpoint.disabled, breakpoint); let eventListener = breakpoint.eventListener; console.assert(eventListener); if (!WI.debuggerManager.breakpointsDisabledTemporarily) WI.debuggerManager.breakpointsEnabled = true; target.DOMAgent.setBreakpointForEventListener.invoke({ eventListenerId: eventListener.eventListenerId, options: breakpoint.optionsToProtocol(), }); } _removeEventBreakpoint(breakpoint, target) { let eventListener = breakpoint.eventListener; console.assert(eventListener); target.DOMAgent.removeBreakpointForEventListener(eventListener.eventListenerId); } _handleEventBreakpointDisabledStateChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; for (let target of WI.targets) { if (!target.hasDomain("DOM")) continue; if (breakpoint.disabled) this._removeEventBreakpoint(breakpoint, target); else this._setEventBreakpoint(breakpoint, target); } } _handleEventBreakpointEditablePropertyChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; if (breakpoint.disabled) return; for (let target of WI.targets) { // Clear the old breakpoint from the backend before setting the new one. this._removeEventBreakpoint(breakpoint, target); this._setEventBreakpoint(breakpoint, target); } } _handleEventBreakpointActionsChanged(event) { let breakpoint = event.target; // Non-specific event listener breakpoints are handled by `DOMDebuggerManager`. if (!breakpoint.eventListener) return; this._handleEventBreakpointEditablePropertyChanged(event); WI.debuggerManager.updateProbesForBreakpoint(breakpoint); } _mainResourceDidChange(event) { if (!event.target.isMainFrame()) return; this._restoreSelectedNodeIsAllowed = true; this.ensureDocument(); WI.DOMNode.resetDefaultLayoutOverlayConfiguration(); } }; WI.DOMManager.Event = { AttributeModified: "dom-manager-attribute-modified", AttributeRemoved: "dom-manager-attribute-removed", CharacterDataModified: "dom-manager-character-data-modified", NodeInserted: "dom-manager-node-inserted", NodeRemoved: "dom-manager-node-removed", CustomElementStateChanged: "dom-manager-custom-element-state-changed", DocumentUpdated: "dom-manager-document-updated", ChildNodeCountUpdated: "dom-manager-child-node-count-updated", DOMNodeWasInspected: "dom-manager-dom-node-was-inspected", InspectModeStateChanged: "dom-manager-inspect-mode-state-changed", InspectedNodeChanged: "dom-manager-inspected-node-changed", };