/* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * Copyright (C) 2013, 2016 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. */ WI.DOMNode = class DOMNode extends WI.Object { constructor(domManager, doc, isInShadowTree, payload) { super(); this._destroyed = false; this._domManager = domManager; this._isInShadowTree = isInShadowTree; this.id = payload.nodeId; this._domManager._idToDOMNode[this.id] = this; this._nodeType = payload.nodeType; this._nodeName = payload.nodeName; this._localName = payload.localName; this._nodeValue = payload.nodeValue; this._pseudoType = payload.pseudoType; this._shadowRootType = payload.shadowRootType; this._computedRole = null; this._contentSecurityPolicyHash = payload.contentSecurityPolicyHash; this._layoutFlags = []; this._layoutOverlayShowing = false; this._layoutOverlayColorSetting = null; if (this._nodeType === Node.DOCUMENT_NODE) this.ownerDocument = this; else this.ownerDocument = doc; this._frame = null; // COMPATIBILITY (iOS 12.2): DOM.Node.frameId was changed to represent the owner frame, not the content frame. // Since support can't be tested directly, check for Audit (iOS 13.0+). // FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed. if (InspectorBackend.hasDomain("Audit")) { if (payload.frameId) this._frame = WI.networkManager.frameForIdentifier(payload.frameId); } if (!this._frame && this.ownerDocument) this._frame = WI.networkManager.frameForIdentifier(this.ownerDocument.frameIdentifier); this._attributes = []; this._attributesMap = new Map; if (payload.attributes) this._setAttributesPayload(payload.attributes); this._childNodeCount = payload.childNodeCount; this._children = null; this._nextSibling = null; this._previousSibling = null; this.parentNode = null; this._enabledPseudoClasses = []; // FIXME: The logic around this._shadowRoots and this._children is very confusing. // We eventually include shadow roots at the start of _children. However we might // not have our actual children yet. So we try to defer initializing _children until // we have both shadowRoots and child nodes. this._shadowRoots = []; if (payload.shadowRoots) { for (var i = 0; i < payload.shadowRoots.length; ++i) { var root = payload.shadowRoots[i]; var node = new WI.DOMNode(this._domManager, this.ownerDocument, true, root); node.parentNode = this; this._shadowRoots.push(node); } } if (payload.children) this._setChildrenPayload(payload.children); else if (this._shadowRoots.length && !this._childNodeCount) this._children = this._shadowRoots.slice(); if (this._nodeType === Node.ELEMENT_NODE) this._customElementState = payload.customElementState || WI.DOMNode.CustomElementState.Builtin; else this._customElementState = null; if (payload.templateContent) { this._templateContent = new WI.DOMNode(this._domManager, this.ownerDocument, false, payload.templateContent); this._templateContent.parentNode = this; } this._pseudoElements = new Map; if (payload.pseudoElements) { for (var i = 0; i < payload.pseudoElements.length; ++i) { var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]); node.parentNode = this; this._pseudoElements.set(node.pseudoType(), node); } } if (payload.contentDocument) { this._contentDocument = new WI.DOMNode(this._domManager, null, false, payload.contentDocument); this._children = [this._contentDocument]; this._renumber(); } if (this._nodeType === Node.ELEMENT_NODE) { // HTML and BODY from internal iframes should not overwrite top-level ones. if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML") this.ownerDocument.documentElement = this; if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY") this.ownerDocument.body = this; if (payload.documentURL) this.documentURL = payload.documentURL; } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) { this.publicId = payload.publicId; this.systemId = payload.systemId; } else if (this._nodeType === Node.DOCUMENT_NODE) { this.documentURL = payload.documentURL; this.xmlVersion = payload.xmlVersion; } else if (this._nodeType === Node.ATTRIBUTE_NODE) { this.name = payload.name; this.value = payload.value; } this._domEvents = []; this._powerEfficientPlaybackRanges = []; if (this.isMediaElement()) WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this); // COMPATIBILITY (macOS 13.0, iOS 16.0): CSS.LayoutContextType was renamed/expanded to CSS.LayoutFlag. if (!InspectorBackend.Enum.CSS.LayoutFlag) { let layoutFlags = [WI.DOMNode.LayoutFlag.Rendered]; if (payload.layoutContextType) layoutFlags.push(payload.layoutContextType); this.layoutFlags = layoutFlags; } else this.layoutFlags = payload.layoutFlags; } // Static static resetDefaultLayoutOverlayConfiguration() { let configuration = WI.DOMNode._defaultLayoutOverlayConfiguration; configuration.nextFlexColorIndex = 0; configuration.nextGridColorIndex = 0; } static getFullscreenDOMEvents(domEvents) { return domEvents.reduce((accumulator, current) => { if (current.eventName === "webkitfullscreenchange" && current.data && (!accumulator.length || accumulator.lastValue.data.enabled !== current.data.enabled)) accumulator.push(current); return accumulator; }, []); } static isPlayEvent(eventName) { return eventName === "play" || eventName === "playing"; } static isPauseEvent(eventName) { return eventName === "pause" || eventName === "stall"; } static isStopEvent(eventName) { return eventName === "emptied" || eventName === "ended" || eventName === "suspend"; } // Public get destroyed() { return this._destroyed; } get frame() { return this._frame; } get nextSibling() { return this._nextSibling; } get previousSibling() { return this._previousSibling; } get children() { return this._children; } get domEvents() { return this._domEvents; } get powerEfficientPlaybackRanges() { return this._powerEfficientPlaybackRanges; } get layoutOverlayShowing() { return this._layoutOverlayShowing; } get attached() { if (this._destroyed) return false; for (let node = this; node; node = node.parentNode) { if (node.ownerDocument === node) return true; } return false; } get firstChild() { var children = this.children; if (children && children.length > 0) return children[0]; return null; } get lastChild() { var children = this.children; if (children && children.length > 0) return children.lastValue; return null; } get childNodeCount() { var children = this.children; if (children) return children.length; return this._childNodeCount + this._shadowRoots.length; } set childNodeCount(count) { this._childNodeCount = count; } get layoutFlags() { return this._layoutFlags; } set layoutFlags(layoutFlags) { layoutFlags ||= []; console.assert(Array.isArray(layoutFlags), layoutFlags); console.assert(layoutFlags.every((layoutFlag) => Object.values(WI.DOMNode.LayoutFlag).includes(layoutFlag)), layoutFlags); console.assert(layoutFlags.filter((layoutFlag) => WI.DOMNode._LayoutContextTypes.includes(layoutFlag)).length <= 1, layoutFlags); console.assert(!layoutFlags.length || !Array.shallowEqual(layoutFlags, this._layoutFlags), layoutFlags); let oldLayoutContextType = this.layoutContextType; this._layoutFlags = layoutFlags; this.dispatchEventToListeners(WI.DOMNode.Event.LayoutFlagsChanged); if (!this._layoutOverlayShowing) return; // The overlay is automatically hidden on the backend when the context type changes. this.dispatchEventToListeners(WI.DOMNode.Event.LayoutOverlayHidden); switch (oldLayoutContextType) { case WI.DOMNode.LayoutFlag.Flex: WI.settings.flexOverlayShowOrderNumbers.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); break; case WI.DOMNode.LayoutFlag.Grid: WI.settings.gridOverlayShowExtendedGridLines.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNames.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNumbers.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowTrackSizes.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowAreaNames.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); break; } } get layoutContextType() { return this._layoutFlags.find((layoutFlag) => WI.DOMNode._LayoutContextTypes.includes(layoutFlag)) || null; } markDestroyed() { console.assert(!this._destroyed, this); this._destroyed = true; this.layoutFlags = []; } computedRole() { return this._computedRole; } contentSecurityPolicyHash() { return this._contentSecurityPolicyHash; } hasAttributes() { return this._attributes.length > 0; } hasChildNodes() { return this.childNodeCount > 0; } hasShadowRoots() { return !!this._shadowRoots.length; } isInShadowTree() { return this._isInShadowTree; } isInUserAgentShadowTree() { return this._isInShadowTree && this.ancestorShadowRoot().isUserAgentShadowRoot(); } isCustomElement() { return this._customElementState === WI.DOMNode.CustomElementState.Custom; } customElementState() { return this._customElementState; } isShadowRoot() { return !!this._shadowRootType; } isUserAgentShadowRoot() { return this._shadowRootType === WI.DOMNode.ShadowRootType.UserAgent; } ancestorShadowRoot() { if (!this._isInShadowTree) return null; let node = this; while (node && !node.isShadowRoot()) node = node.parentNode; return node; } ancestorShadowHost() { let shadowRoot = this.ancestorShadowRoot(); return shadowRoot ? shadowRoot.parentNode : null; } isPseudoElement() { return this._pseudoType !== undefined; } nodeType() { return this._nodeType; } nodeName() { return this._nodeName; } nodeNameInCorrectCase() { return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase(); } setNodeName(name, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.setNodeName(this.id, name, this._makeUndoableCallback(callback)); } localName() { return this._localName; } templateContent() { return this._templateContent || null; } pseudoType() { return this._pseudoType; } hasPseudoElements() { return this._pseudoElements.size > 0; } pseudoElements() { return this._pseudoElements; } beforePseudoElement() { return this._pseudoElements.get(WI.DOMNode.PseudoElementType.Before) || null; } afterPseudoElement() { return this._pseudoElements.get(WI.DOMNode.PseudoElementType.After) || null; } shadowRoots() { return this._shadowRoots; } shadowRootType() { return this._shadowRootType; } nodeValue() { return this._nodeValue; } setNodeValue(value, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.setNodeValue(this.id, value, this._makeUndoableCallback(callback)); } getAttribute(name) { let attr = this._attributesMap.get(name); return attr ? attr.value : undefined; } setAttribute(name, text, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.setAttributesAsText(this.id, text, name, this._makeUndoableCallback(callback)); } setAttributeValue(name, value, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { if (!callback) return Promise.reject("ERROR: node is destroyed"); callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); if (!callback) { return target.DOMAgent.setAttributeValue(this.id, name, value).then(() => { this._markUndoableState(); }); } target.DOMAgent.setAttributeValue(this.id, name, value, this._makeUndoableCallback(callback)); } attributes() { return this._attributes; } removeAttribute(name, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } function mycallback(error, success) { if (!error) { this._attributesMap.delete(name); for (var i = 0; i < this._attributes.length; ++i) { if (this._attributes[i].name === name) { this._attributes.splice(i, 1); break; } } } this._makeUndoableCallback(callback)(error); } let target = WI.assumingMainTarget(); target.DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); } toggleClass(className, flag) { if (!className || !className.length) return; if (this.isPseudoElement()) { this.parentNode.toggleClass(className, flag); return; } if (this.nodeType() !== Node.ELEMENT_NODE) return; WI.RemoteObject.resolveNode(this).then((object) => { function inspectedPage_node_toggleClass(className, flag) { this.classList.toggle(className, flag); } object.callFunction(inspectedPage_node_toggleClass, [className, flag]); object.release(); }); } querySelector(selector, callback) { console.assert(!this._destroyed, this); let target = WI.assumingMainTarget(); if (typeof callback !== "function") { if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); return target.DOMAgent.querySelector(this.id, selector).then(({nodeId}) => nodeId); } if (this._destroyed) { callback("ERROR: node is destroyed"); return; } target.DOMAgent.querySelector(this.id, selector, WI.DOMManager.wrapClientCallback(callback)); } querySelectorAll(selector, callback) { console.assert(!this._destroyed, this); let target = WI.assumingMainTarget(); if (typeof callback !== "function") { if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); return target.DOMAgent.querySelectorAll(this.id, selector).then(({nodeIds}) => nodeIds); } if (this._destroyed) { callback("ERROR: node is destroyed"); return; } target.DOMAgent.querySelectorAll(this.id, selector, WI.DOMManager.wrapClientCallback(callback)); } highlight(mode) { if (this._destroyed) return; if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); this._hideDOMNodeHighlightTimeout = undefined; } let target = WI.assumingMainTarget(); target.DOMAgent.highlightNode(WI.DOMManager.buildHighlightConfig(mode), this.id); } showLayoutOverlay({color} = {}) { console.assert(!this._destroyed, this); if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); console.assert(Object.values(WI.DOMNode._LayoutContextTypes).includes(this.layoutContextType), this); console.assert(!color || color instanceof WI.Color, color); color ||= this.layoutOverlayColor; let target = WI.assumingMainTarget(); let agentCommandFunction = null; let agentCommandArguments = {nodeId: this.id}; switch (this.layoutContextType) { case WI.DOMNode.LayoutFlag.Grid: agentCommandArguments.gridColor = color.toProtocol(); agentCommandArguments.showLineNames = WI.settings.gridOverlayShowLineNames.value; agentCommandArguments.showLineNumbers = WI.settings.gridOverlayShowLineNumbers.value; agentCommandArguments.showExtendedGridLines = WI.settings.gridOverlayShowExtendedGridLines.value; agentCommandArguments.showTrackSizes = WI.settings.gridOverlayShowTrackSizes.value; agentCommandArguments.showAreaNames = WI.settings.gridOverlayShowAreaNames.value; agentCommandFunction = target.DOMAgent.showGridOverlay; if (!this._layoutOverlayShowing) { WI.settings.gridOverlayShowExtendedGridLines.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNames.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNumbers.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowTrackSizes.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowAreaNames.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); } break; case WI.DOMNode.LayoutFlag.Flex: agentCommandArguments.flexColor = color.toProtocol(); agentCommandArguments.showOrderNumbers = WI.settings.flexOverlayShowOrderNumbers.value; agentCommandFunction = target.DOMAgent.showFlexOverlay; if (!this._layoutOverlayShowing) WI.settings.flexOverlayShowOrderNumbers.addEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); break; } this._layoutOverlayShowing = true; this.dispatchEventToListeners(WI.DOMNode.Event.LayoutOverlayShown); console.assert(agentCommandFunction); return agentCommandFunction.invoke(agentCommandArguments); } hideLayoutOverlay() { console.assert(!this._destroyed, this); if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); console.assert(Object.values(WI.DOMNode._LayoutContextTypes).includes(this.layoutContextType), this); let target = WI.assumingMainTarget(); let agentCommandFunction; let agentCommandArguments = {nodeId: this.id}; switch (this.layoutContextType) { case WI.DOMNode.LayoutFlag.Grid: WI.settings.gridOverlayShowExtendedGridLines.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNames.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowLineNumbers.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowTrackSizes.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); WI.settings.gridOverlayShowAreaNames.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); agentCommandFunction = target.DOMAgent.hideGridOverlay; break; case WI.DOMNode.LayoutFlag.Flex: WI.settings.flexOverlayShowOrderNumbers.removeEventListener(WI.Setting.Event.Changed, this._handleLayoutOverlaySettingChanged, this); agentCommandFunction = target.DOMAgent.hideFlexOverlay; break; } console.assert(this._layoutOverlayShowing, this); this._layoutOverlayShowing = false; this.dispatchEventToListeners(WI.DOMNode.Event.LayoutOverlayHidden); console.assert(agentCommandFunction); return agentCommandFunction.invoke(agentCommandArguments); } get layoutOverlayColor() { this._createLayoutOverlayColorSettingIfNeeded(); return new WI.Color(WI.Color.Format.HSL, this._layoutOverlayColorSetting.value); } set layoutOverlayColor(color) { console.assert(color instanceof WI.Color, color); this._createLayoutOverlayColorSettingIfNeeded(); this._layoutOverlayColorSetting.value = color.hsl; if (this._layoutOverlayShowing) this.showLayoutOverlay({color}); } scrollIntoView() { WI.RemoteObject.resolveNode(this).then((object) => { function inspectedPage_node_scrollIntoView() { this.scrollIntoViewIfNeeded(true); } object.callFunction(inspectedPage_node_scrollIntoView); object.release(); }); } getChildNodes(callback) { if (this.children) { if (callback) callback(this.children); return; } if (this._destroyed) { callback(this.children); return; } function mycallback(error) { if (!error && callback) callback(this.children); } let target = WI.assumingMainTarget(); target.DOMAgent.requestChildNodes(this.id, mycallback.bind(this)); } getSubtree(depth, callback) { if (this._destroyed) { callback(this.children); return; } function mycallback(error) { if (callback) callback(error ? null : this.children); } let target = WI.assumingMainTarget(); target.DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this)); } getOuterHTML(callback) { console.assert(!this._destroyed, this); let target = WI.assumingMainTarget(); if (typeof callback !== "function") { if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); return target.DOMAgent.getOuterHTML(this.id).then(({outerHTML}) => outerHTML); } if (this._destroyed) { callback("ERROR: node is destroyed"); return; } target.DOMAgent.getOuterHTML(this.id, callback); } setOuterHTML(html, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback)); } insertAdjacentHTML(position, html) { console.assert(!this._destroyed, this); if (this._destroyed) return; if (this.nodeType() !== Node.ELEMENT_NODE) return; let target = WI.assumingMainTarget(); target.DOMAgent.insertAdjacentHTML(this.id, position, html, this._makeUndoableCallback()); } removeNode(callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback)); } getEventListeners({includeAncestors} = {}) { console.assert(!this._destroyed, this); if (this._destroyed) return Promise.reject("ERROR: node is destroyed"); includeAncestors ??= true; console.assert(WI.domManager.inspectedNode === this || !includeAncestors, this, includeAncestors); let target = WI.assumingMainTarget(); return target.DOMAgent.getEventListenersForNode.invoke({ nodeId: this.id, includeAncestors, }); } accessibilityProperties(callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback({}); return; } function accessibilityPropertiesCallback(error, accessibilityProperties) { if (!error && callback && accessibilityProperties) { this._computedRole = accessibilityProperties.role; callback({ activeDescendantNodeId: accessibilityProperties.activeDescendantNodeId, busy: accessibilityProperties.busy, checked: accessibilityProperties.checked, childNodeIds: accessibilityProperties.childNodeIds, controlledNodeIds: accessibilityProperties.controlledNodeIds, current: accessibilityProperties.current, disabled: accessibilityProperties.disabled, exists: accessibilityProperties.exists, expanded: accessibilityProperties.expanded, flowedNodeIds: accessibilityProperties.flowedNodeIds, focused: accessibilityProperties.focused, ignored: accessibilityProperties.ignored, ignoredByDefault: accessibilityProperties.ignoredByDefault, invalid: accessibilityProperties.invalid, isPopupButton: accessibilityProperties.isPopUpButton, headingLevel: accessibilityProperties.headingLevel, hierarchyLevel: accessibilityProperties.hierarchyLevel, hidden: accessibilityProperties.hidden, label: accessibilityProperties.label, liveRegionAtomic: accessibilityProperties.liveRegionAtomic, liveRegionRelevant: accessibilityProperties.liveRegionRelevant, liveRegionStatus: accessibilityProperties.liveRegionStatus, mouseEventNodeId: accessibilityProperties.mouseEventNodeId, nodeId: accessibilityProperties.nodeId, ownedNodeIds: accessibilityProperties.ownedNodeIds, parentNodeId: accessibilityProperties.parentNodeId, pressed: accessibilityProperties.pressed, readonly: accessibilityProperties.readonly, required: accessibilityProperties.required, role: accessibilityProperties.role, selected: accessibilityProperties.selected, selectedChildNodeIds: accessibilityProperties.selectedChildNodeIds }); } } let target = WI.assumingMainTarget(); target.DOMAgent.getAccessibilityPropertiesForNode(this.id, accessibilityPropertiesCallback.bind(this)); } path() { var path = []; var node = this; while (node && "index" in node && node._nodeName.length) { path.push([node.index, node._nodeName]); node = node.parentNode; } path.reverse(); return path.join(","); } get escapedIdSelector() { return this._idSelector(true); } get escapedClassSelector() { return this._classSelector(true); } get displayName() { if (this.isPseudoElement()) return "::" + this._pseudoType; return this.nodeNameInCorrectCase() + this.escapedIdSelector + this.escapedClassSelector; } get unescapedSelector() { if (this.isPseudoElement()) return "::" + this._pseudoType; const shouldEscape = false; return this.nodeNameInCorrectCase() + this._idSelector(shouldEscape) + this._classSelector(shouldEscape); } appropriateSelectorFor(justSelector) { if (this.isPseudoElement()) return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType; let lowerCaseName = this.localName() || this.nodeName().toLowerCase(); let id = this.escapedIdSelector; if (id.length) return justSelector ? id : lowerCaseName + id; let classes = this.escapedClassSelector; if (classes.length) return justSelector ? classes : lowerCaseName + classes; if (lowerCaseName === "input" && this.getAttribute("type")) return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]"; return lowerCaseName; } isAncestor(node) { if (!node) return false; var currentNode = node.parentNode; while (currentNode) { if (this === currentNode) return true; currentNode = currentNode.parentNode; } return false; } isDescendant(descendant) { return descendant !== null && descendant.isAncestor(this); } get ownerSVGElement() { if (this._nodeName === "svg") return this; if (!this.parentNode) return null; return this.parentNode.ownerSVGElement; } isSVGElement() { return !!this.ownerSVGElement; } isMediaElement() { let lowerCaseName = this.localName() || this.nodeName().toLowerCase(); return lowerCaseName === "video" || lowerCaseName === "audio"; } didFireEvent(eventName, timestamp, data) { // Called from WI.DOMManager. this._addDOMEvent({ eventName, timestamp: WI.timelineManager.computeElapsedTime(timestamp), data, }); } powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient) { // Called from WI.DOMManager. console.assert(this.canEnterPowerEfficientPlaybackState()); let lastValue = this._powerEfficientPlaybackRanges.lastValue; if (isPowerEfficient) { console.assert(!lastValue || lastValue.endTimestamp); if (!lastValue || lastValue.endTimestamp) this._powerEfficientPlaybackRanges.push({startTimestamp: timestamp}); } else { console.assert(!lastValue || lastValue.startTimestamp); if (!lastValue) this._powerEfficientPlaybackRanges.push({endTimestamp: timestamp}); else if (lastValue.startTimestamp) lastValue.endTimestamp = timestamp; } this.dispatchEventToListeners(DOMNode.Event.PowerEfficientPlaybackStateChanged, {isPowerEfficient, timestamp}); } canEnterPowerEfficientPlaybackState() { return this.localName() === "video" || this.nodeName().toLowerCase() === "video"; } _handleDOMNodeDidFireEvent(event) { if (event.target === this || !event.target.isAncestor(this)) return; let domEvent = Object.shallowCopy(event.data.domEvent); domEvent.originator = event.target; this._addDOMEvent(domEvent); } _addDOMEvent(domEvent) { this._domEvents.push(domEvent); this.dispatchEventToListeners(WI.DOMNode.Event.DidFireEvent, {domEvent}); } _setAttributesPayload(attrs) { this._attributes = []; this._attributesMap = new Map; for (var i = 0; i < attrs.length; i += 2) this._addAttribute(attrs[i], attrs[i + 1]); } _insertChild(prev, payload) { var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payload); if (!prev) { if (!this._children) { // First node this._children = this._shadowRoots.concat([node]); } else this._children.unshift(node); } else this._children.splice(this._children.indexOf(prev) + 1, 0, node); this._renumber(); return node; } _removeChild(node) { // FIXME: Handle removal if this is a shadow root. if (node.isPseudoElement()) { this._pseudoElements.delete(node.pseudoType()); node.parentNode = null; } else { this._children.splice(this._children.indexOf(node), 1); node.parentNode = null; this._renumber(); } } _setChildrenPayload(payloads) { // We set children in the constructor. if (this._contentDocument) return; this._children = this._shadowRoots.slice(); for (var i = 0; i < payloads.length; ++i) { var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payloads[i]); this._children.push(node); } this._renumber(); } _renumber() { var childNodeCount = this._children.length; if (childNodeCount === 0) return; for (var i = 0; i < childNodeCount; ++i) { var child = this._children[i]; child.index = i; child._nextSibling = i + 1 < childNodeCount ? this._children[i + 1] : null; child._previousSibling = i - 1 >= 0 ? this._children[i - 1] : null; child.parentNode = this; } } _addAttribute(name, value) { let attr = {name, value, _node: this}; this._attributesMap.set(name, attr); this._attributes.push(attr); } _setAttribute(name, value) { let attr = this._attributesMap.get(name); if (attr) attr.value = value; else this._addAttribute(name, value); } _removeAttribute(name) { let attr = this._attributesMap.get(name); if (attr) { this._attributes.remove(attr); this._attributesMap.delete(name); } } moveTo(targetNode, anchorNode, callback) { console.assert(!this._destroyed, this); if (this._destroyed) { callback("ERROR: node is destroyed"); return; } let target = WI.assumingMainTarget(); target.DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._makeUndoableCallback(callback)); } isXMLNode() { return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; } get enabledPseudoClasses() { return this._enabledPseudoClasses; } setPseudoClassEnabled(pseudoClass, enabled) { var pseudoClasses = this._enabledPseudoClasses; if (enabled) { if (pseudoClasses.includes(pseudoClass)) return; pseudoClasses.push(pseudoClass); } else { if (!pseudoClasses.includes(pseudoClass)) return; pseudoClasses.remove(pseudoClass); } function changed(error) { if (!error) this.dispatchEventToListeners(WI.DOMNode.Event.EnabledPseudoClassesChanged); } let target = WI.assumingMainTarget(); target.CSSAgent.forcePseudoState(this.id, pseudoClasses, changed.bind(this)); } _markUndoableState() { let target = WI.assumingMainTarget(); if (target.hasCommand("DOM.markUndoableState")) target.DOMAgent.markUndoableState(); } _makeUndoableCallback(callback) { return (...args) => { if (!args[0]) // error this._markUndoableState(); if (callback) callback.apply(null, args); }; } _idSelector(shouldEscape) { let id = this.getAttribute("id"); if (!id) return ""; id = id.trim(); if (!id.length) return ""; if (shouldEscape) id = CSS.escape(id); if (/[\s'"]/.test(id)) return `[id="${id}"]`; return `#${id}`; } _classSelector(shouldEscape) { let classes = this.getAttribute("class"); if (!classes) return ""; classes = classes.trim(); if (!classes.length) return ""; let foundClasses = new Set; return classes.split(/\s+/).reduce((selector, className) => { if (!className.length || foundClasses.has(className)) return selector; foundClasses.add(className); return `${selector}.${(shouldEscape ? CSS.escape(className) : className)}`; }, ""); } _createLayoutOverlayColorSettingIfNeeded() { if (this._layoutOverlayColorSetting) return; let defaultConfiguration = WI.DOMNode._defaultLayoutOverlayConfiguration; let url = this.ownerDocument.documentURL || WI.networkManager.mainFrame.url; let nextColorIndex; switch (this.layoutContextType) { case WI.DOMNode.LayoutFlag.Grid: nextColorIndex = defaultConfiguration.nextGridColorIndex; defaultConfiguration.nextGridColorIndex = (nextColorIndex + 1) % defaultConfiguration.colors.length; break; case WI.DOMNode.LayoutFlag.Flex: nextColorIndex = defaultConfiguration.nextFlexColorIndex; defaultConfiguration.nextFlexColorIndex = (nextColorIndex + 1) % defaultConfiguration.colors.length; break; } this._layoutOverlayColorSetting = new WI.Setting(`overlay-color-${url.hash}-${this.path().hash}`, defaultConfiguration.colors[nextColorIndex]); } _handleLayoutOverlaySettingChanged(event) { if (this._layoutOverlayShowing) this.showLayoutOverlay(); } }; WI.DOMNode._defaultLayoutOverlayConfiguration = { colors: [ [329, 91, 70], [207, 96, 69], [92, 90, 64], [291, 73, 68], [40, 97, 57], ], nextFlexColorIndex: 0, nextGridColorIndex: 0, }; WI.DOMNode.Event = { EnabledPseudoClassesChanged: "dom-node-enabled-pseudo-classes-did-change", AttributeModified: "dom-node-attribute-modified", AttributeRemoved: "dom-node-attribute-removed", EventListenersChanged: "dom-node-event-listeners-changed", DidFireEvent: "dom-node-did-fire-event", PowerEfficientPlaybackStateChanged: "dom-node-power-efficient-playback-state-changed", LayoutFlagsChanged: "dom-node-layout-flags-changed", LayoutOverlayShown: "dom-node-layout-overlay-shown", LayoutOverlayHidden: "dom-node-layout-overlay-hidden", }; WI.DOMNode.PseudoElementType = { Before: "before", After: "after", }; WI.DOMNode.ShadowRootType = { UserAgent: "user-agent", Closed: "closed", Open: "open", }; WI.DOMNode.CustomElementState = { Builtin: "builtin", Custom: "custom", Waiting: "waiting", Failed: "failed", }; // Corresponds to `CSS.LayoutFlag`. WI.DOMNode.LayoutFlag = { Rendered: "rendered", Event: "event", Scrollable: "scrollable", // These are mutually exclusive. Flex: "flex", Grid: "grid", }; WI.DOMNode._LayoutContextTypes = [ WI.DOMNode.LayoutFlag.Flex, WI.DOMNode.LayoutFlag.Grid, ];