UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

1,305 lines 56.9 kB
// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * 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. */ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; import * as Platform from '../platform/platform.js'; import * as Root from '../root/root.js'; import { CSSModel } from './CSSModel.js'; import { FrameManager } from './FrameManager.js'; import { OverlayModel } from './OverlayModel.js'; import { RuntimeModel } from './RuntimeModel.js'; import { Capability } from './Target.js'; import { SDKModel } from './SDKModel.js'; import { TargetManager } from './TargetManager.js'; import { ResourceTreeModel } from './ResourceTreeModel.js'; export class DOMNode { #domModelInternal; #agent; ownerDocument; #isInShadowTreeInternal; id; index; #backendNodeIdInternal; #nodeTypeInternal; #nodeNameInternal; #localNameInternal; nodeValueInternal; #pseudoTypeInternal; #pseudoIdentifier; #shadowRootTypeInternal; #frameOwnerFrameIdInternal; #xmlVersion; #isSVGNodeInternal; #creationStackTraceInternal; #pseudoElements; #distributedNodesInternal; assignedSlot; shadowRootsInternal; #attributesInternal; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any #markers; #subtreeMarkerCount; childNodeCountInternal; childrenInternal; nextSibling; previousSibling; firstChild; lastChild; parentNode; templateContentInternal; contentDocumentInternal; childDocumentPromiseForTesting; #importedDocumentInternal; publicId; systemId; internalSubset; name; value; constructor(domModel) { this.#domModelInternal = domModel; this.#agent = this.#domModelInternal.getAgent(); this.index = undefined; this.#creationStackTraceInternal = null; this.#pseudoElements = new Map(); this.#distributedNodesInternal = []; this.assignedSlot = null; this.shadowRootsInternal = []; this.#attributesInternal = new Map(); this.#markers = new Map(); this.#subtreeMarkerCount = 0; this.childrenInternal = null; this.nextSibling = null; this.previousSibling = null; this.firstChild = null; this.lastChild = null; this.parentNode = null; } static create(domModel, doc, isInShadowTree, payload) { const node = new DOMNode(domModel); node.init(doc, isInShadowTree, payload); return node; } init(doc, isInShadowTree, payload) { this.#agent = this.#domModelInternal.getAgent(); this.ownerDocument = doc; this.#isInShadowTreeInternal = isInShadowTree; this.id = payload.nodeId; this.#backendNodeIdInternal = payload.backendNodeId; this.#domModelInternal.registerNode(this); this.#nodeTypeInternal = payload.nodeType; this.#nodeNameInternal = payload.nodeName; this.#localNameInternal = payload.localName; this.nodeValueInternal = payload.nodeValue; this.#pseudoTypeInternal = payload.pseudoType; this.#pseudoIdentifier = payload.pseudoIdentifier; this.#shadowRootTypeInternal = payload.shadowRootType; this.#frameOwnerFrameIdInternal = payload.frameId || null; this.#xmlVersion = payload.xmlVersion; this.#isSVGNodeInternal = Boolean(payload.isSVG); if (payload.attributes) { this.setAttributesPayload(payload.attributes); } this.childNodeCountInternal = payload.childNodeCount || 0; if (payload.shadowRoots) { for (let i = 0; i < payload.shadowRoots.length; ++i) { const root = payload.shadowRoots[i]; const node = DOMNode.create(this.#domModelInternal, this.ownerDocument, true, root); this.shadowRootsInternal.push(node); node.parentNode = this; } } if (payload.templateContent) { this.templateContentInternal = DOMNode.create(this.#domModelInternal, this.ownerDocument, true, payload.templateContent); this.templateContentInternal.parentNode = this; this.childrenInternal = []; } const frameOwnerTags = new Set(['EMBED', 'IFRAME', 'OBJECT', 'PORTAL', 'FENCEDFRAME']); if (payload.contentDocument) { this.contentDocumentInternal = new DOMDocument(this.#domModelInternal, payload.contentDocument); this.contentDocumentInternal.parentNode = this; this.childrenInternal = []; } else if (payload.frameId && frameOwnerTags.has(payload.nodeName)) { // At this point we know we are in an OOPIF, otherwise `payload.contentDocument` would have been set. this.childDocumentPromiseForTesting = this.requestChildDocument(payload.frameId, this.#domModelInternal.target()); this.childrenInternal = []; } if (payload.importedDocument) { this.#importedDocumentInternal = DOMNode.create(this.#domModelInternal, this.ownerDocument, true, payload.importedDocument); this.#importedDocumentInternal.parentNode = this; this.childrenInternal = []; } if (payload.distributedNodes) { this.setDistributedNodePayloads(payload.distributedNodes); } if (payload.assignedSlot) { this.setAssignedSlot(payload.assignedSlot); } if (payload.children) { this.setChildrenPayload(payload.children); } this.setPseudoElements(payload.pseudoElements); if (this.#nodeTypeInternal === Node.ELEMENT_NODE) { // HTML and BODY from internal iframes should not overwrite top-level ones. if (this.ownerDocument && !this.ownerDocument.documentElement && this.#nodeNameInternal === 'HTML') { this.ownerDocument.documentElement = this; } if (this.ownerDocument && !this.ownerDocument.body && this.#nodeNameInternal === 'BODY') { this.ownerDocument.body = this; } } else if (this.#nodeTypeInternal === Node.DOCUMENT_TYPE_NODE) { this.publicId = payload.publicId; this.systemId = payload.systemId; this.internalSubset = payload.internalSubset; } else if (this.#nodeTypeInternal === Node.ATTRIBUTE_NODE) { this.name = payload.name; this.value = payload.value; } } async requestChildDocument(frameId, notInTarget) { const frame = await FrameManager.instance().getOrWaitForFrame(frameId, notInTarget); const childModel = frame.resourceTreeModel()?.target().model(DOMModel); return childModel?.requestDocument() || null; } isAdFrameNode() { if (this.isIframe() && this.#frameOwnerFrameIdInternal) { const frame = FrameManager.instance().getFrame(this.#frameOwnerFrameIdInternal); if (!frame) { return false; } return frame.adFrameType() !== "none" /* Protocol.Page.AdFrameType.None */; } return false; } isSVGNode() { return this.#isSVGNodeInternal; } creationStackTrace() { if (this.#creationStackTraceInternal) { return this.#creationStackTraceInternal; } const stackTracesPromise = this.#agent.invoke_getNodeStackTraces({ nodeId: this.id }); this.#creationStackTraceInternal = stackTracesPromise.then(res => res.creation || null); return this.#creationStackTraceInternal; } get subtreeMarkerCount() { return this.#subtreeMarkerCount; } domModel() { return this.#domModelInternal; } backendNodeId() { return this.#backendNodeIdInternal; } children() { return this.childrenInternal ? this.childrenInternal.slice() : null; } setChildren(children) { this.childrenInternal = children; } hasAttributes() { return this.#attributesInternal.size > 0; } childNodeCount() { return this.childNodeCountInternal; } setChildNodeCount(childNodeCount) { this.childNodeCountInternal = childNodeCount; } hasShadowRoots() { return Boolean(this.shadowRootsInternal.length); } shadowRoots() { return this.shadowRootsInternal.slice(); } templateContent() { return this.templateContentInternal || null; } contentDocument() { return this.contentDocumentInternal || null; } setContentDocument(node) { this.contentDocumentInternal = node; } isIframe() { return this.#nodeNameInternal === 'IFRAME'; } isPortal() { return this.#nodeNameInternal === 'PORTAL'; } importedDocument() { return this.#importedDocumentInternal || null; } nodeType() { return this.#nodeTypeInternal; } nodeName() { return this.#nodeNameInternal; } pseudoType() { return this.#pseudoTypeInternal; } pseudoIdentifier() { return this.#pseudoIdentifier; } hasPseudoElements() { return this.#pseudoElements.size > 0; } pseudoElements() { return this.#pseudoElements; } beforePseudoElement() { return this.#pseudoElements.get("before" /* Protocol.DOM.PseudoType.Before */)?.at(-1); } afterPseudoElement() { return this.#pseudoElements.get("after" /* Protocol.DOM.PseudoType.After */)?.at(-1); } markerPseudoElement() { return this.#pseudoElements.get("marker" /* Protocol.DOM.PseudoType.Marker */)?.at(-1); } backdropPseudoElement() { return this.#pseudoElements.get("backdrop" /* Protocol.DOM.PseudoType.Backdrop */)?.at(-1); } viewTransitionPseudoElements() { return [ ...this.#pseudoElements.get("view-transition" /* Protocol.DOM.PseudoType.ViewTransition */) || [], ...this.#pseudoElements.get("view-transition-group" /* Protocol.DOM.PseudoType.ViewTransitionGroup */) || [], ...this.#pseudoElements.get("view-transition-image-pair" /* Protocol.DOM.PseudoType.ViewTransitionImagePair */) || [], ...this.#pseudoElements.get("view-transition-old" /* Protocol.DOM.PseudoType.ViewTransitionOld */) || [], ...this.#pseudoElements.get("view-transition-new" /* Protocol.DOM.PseudoType.ViewTransitionNew */) || [], ]; } hasAssignedSlot() { return this.assignedSlot !== null; } isInsertionPoint() { return !this.isXMLNode() && (this.#nodeNameInternal === 'SHADOW' || this.#nodeNameInternal === 'CONTENT' || this.#nodeNameInternal === 'SLOT'); } distributedNodes() { return this.#distributedNodesInternal; } isInShadowTree() { return this.#isInShadowTreeInternal; } ancestorShadowHost() { const ancestorShadowRoot = this.ancestorShadowRoot(); return ancestorShadowRoot ? ancestorShadowRoot.parentNode : null; } ancestorShadowRoot() { if (!this.#isInShadowTreeInternal) { return null; } let current = this; while (current && !current.isShadowRoot()) { current = current.parentNode; } return current; } ancestorUserAgentShadowRoot() { const ancestorShadowRoot = this.ancestorShadowRoot(); if (!ancestorShadowRoot) { return null; } return ancestorShadowRoot.shadowRootType() === DOMNode.ShadowRootTypes.UserAgent ? ancestorShadowRoot : null; } isShadowRoot() { return Boolean(this.#shadowRootTypeInternal); } shadowRootType() { return this.#shadowRootTypeInternal || null; } nodeNameInCorrectCase() { const shadowRootType = this.shadowRootType(); if (shadowRootType) { return '#shadow-root (' + shadowRootType + ')'; } // If there is no local #name, it's case sensitive if (!this.localName()) { return this.nodeName(); } // If the names are different lengths, there is a prefix and it's case sensitive if (this.localName().length !== this.nodeName().length) { return this.nodeName(); } // Return the localname, which will be case insensitive if its an html node return this.localName(); } setNodeName(name, callback) { void this.#agent.invoke_setNodeName({ nodeId: this.id, name }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null, this.#domModelInternal.nodeForId(response.nodeId)); } }); } localName() { return this.#localNameInternal; } nodeValue() { return this.nodeValueInternal; } setNodeValueInternal(nodeValue) { this.nodeValueInternal = nodeValue; } setNodeValue(value, callback) { void this.#agent.invoke_setNodeValue({ nodeId: this.id, value }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null); } }); } getAttribute(name) { const attr = this.#attributesInternal.get(name); return attr ? attr.value : undefined; } setAttribute(name, text, callback) { void this.#agent.invoke_setAttributesAsText({ nodeId: this.id, text, name }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null); } }); } setAttributeValue(name, value, callback) { void this.#agent.invoke_setAttributeValue({ nodeId: this.id, name, value }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null); } }); } setAttributeValuePromise(name, value) { return new Promise(fulfill => this.setAttributeValue(name, value, fulfill)); } attributes() { return [...this.#attributesInternal.values()]; } async removeAttribute(name) { const response = await this.#agent.invoke_removeAttribute({ nodeId: this.id, name }); if (response.getError()) { return; } this.#attributesInternal.delete(name); this.#domModelInternal.markUndoableState(); } getChildNodes(callback) { if (this.childrenInternal) { callback(this.children()); return; } void this.#agent.invoke_requestChildNodes({ nodeId: this.id }).then(response => { callback(response.getError() ? null : this.children()); }); } async getSubtree(depth, pierce) { const response = await this.#agent.invoke_requestChildNodes({ nodeId: this.id, depth: depth, pierce: pierce }); return response.getError() ? null : this.childrenInternal; } async getOuterHTML() { const { outerHTML } = await this.#agent.invoke_getOuterHTML({ nodeId: this.id }); return outerHTML; } setOuterHTML(html, callback) { void this.#agent.invoke_setOuterHTML({ nodeId: this.id, outerHTML: html }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null); } }); } removeNode(callback) { return this.#agent.invoke_removeNode({ nodeId: this.id }).then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null); } }); } async copyNode() { const { outerHTML } = await this.#agent.invoke_getOuterHTML({ nodeId: this.id }); if (outerHTML !== null) { Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(outerHTML); } return outerHTML; } path() { function canPush(node) { return (node.index !== undefined || (node.isShadowRoot() && node.parentNode)) && node.#nodeNameInternal.length; } const path = []; let node = this; while (node && canPush(node)) { const index = typeof node.index === 'number' ? node.index : (node.shadowRootType() === DOMNode.ShadowRootTypes.UserAgent ? 'u' : 'a'); path.push([index, node.#nodeNameInternal]); node = node.parentNode; } path.reverse(); return path.join(','); } isAncestor(node) { if (!node) { return false; } let currentNode = node.parentNode; while (currentNode) { if (this === currentNode) { return true; } currentNode = currentNode.parentNode; } return false; } isDescendant(descendant) { return descendant !== null && descendant.isAncestor(this); } frameOwnerFrameId() { return this.#frameOwnerFrameIdInternal; } frameId() { let node = this.parentNode || this; while (!node.#frameOwnerFrameIdInternal && node.parentNode) { node = node.parentNode; } return node.#frameOwnerFrameIdInternal; } setAttributesPayload(attrs) { let attributesChanged = !this.#attributesInternal || attrs.length !== this.#attributesInternal.size * 2; const oldAttributesMap = this.#attributesInternal || new Map(); this.#attributesInternal = new Map(); for (let i = 0; i < attrs.length; i += 2) { const name = attrs[i]; const value = attrs[i + 1]; this.addAttribute(name, value); if (attributesChanged) { continue; } const oldAttribute = oldAttributesMap.get(name); if (!oldAttribute || oldAttribute.value !== value) { attributesChanged = true; } } return attributesChanged; } insertChild(prev, payload) { if (!this.childrenInternal) { throw new Error('DOMNode._children is expected to not be null.'); } const node = DOMNode.create(this.#domModelInternal, this.ownerDocument, this.#isInShadowTreeInternal, payload); this.childrenInternal.splice(prev ? this.childrenInternal.indexOf(prev) + 1 : 0, 0, node); this.renumber(); return node; } removeChild(node) { const pseudoType = node.pseudoType(); if (pseudoType) { const updatedPseudoElements = this.#pseudoElements.get(pseudoType)?.filter(element => element !== node); if (updatedPseudoElements && updatedPseudoElements.length > 0) { this.#pseudoElements.set(pseudoType, updatedPseudoElements); } else { this.#pseudoElements.delete(pseudoType); } } else { const shadowRootIndex = this.shadowRootsInternal.indexOf(node); if (shadowRootIndex !== -1) { this.shadowRootsInternal.splice(shadowRootIndex, 1); } else { if (!this.childrenInternal) { throw new Error('DOMNode._children is expected to not be null.'); } if (this.childrenInternal.indexOf(node) === -1) { throw new Error('DOMNode._children is expected to contain the node to be removed.'); } this.childrenInternal.splice(this.childrenInternal.indexOf(node), 1); } } node.parentNode = null; this.#subtreeMarkerCount -= node.#subtreeMarkerCount; if (node.#subtreeMarkerCount) { this.#domModelInternal.dispatchEventToListeners(Events.MarkersChanged, this); } this.renumber(); } setChildrenPayload(payloads) { this.childrenInternal = []; for (let i = 0; i < payloads.length; ++i) { const payload = payloads[i]; const node = DOMNode.create(this.#domModelInternal, this.ownerDocument, this.#isInShadowTreeInternal, payload); this.childrenInternal.push(node); } this.renumber(); } setPseudoElements(payloads) { if (!payloads) { return; } for (let i = 0; i < payloads.length; ++i) { const node = DOMNode.create(this.#domModelInternal, this.ownerDocument, this.#isInShadowTreeInternal, payloads[i]); node.parentNode = this; const pseudoType = node.pseudoType(); if (!pseudoType) { throw new Error('DOMNode.pseudoType() is expected to be defined.'); } const currentPseudoElements = this.#pseudoElements.get(pseudoType); if (currentPseudoElements) { currentPseudoElements.push(node); } else { this.#pseudoElements.set(pseudoType, [node]); } } } setDistributedNodePayloads(payloads) { this.#distributedNodesInternal = []; for (const payload of payloads) { this.#distributedNodesInternal.push(new DOMNodeShortcut(this.#domModelInternal.target(), payload.backendNodeId, payload.nodeType, payload.nodeName)); } } setAssignedSlot(payload) { this.assignedSlot = new DOMNodeShortcut(this.#domModelInternal.target(), payload.backendNodeId, payload.nodeType, payload.nodeName); } renumber() { if (!this.childrenInternal) { throw new Error('DOMNode._children is expected to not be null.'); } this.childNodeCountInternal = this.childrenInternal.length; if (this.childNodeCountInternal === 0) { this.firstChild = null; this.lastChild = null; return; } this.firstChild = this.childrenInternal[0]; this.lastChild = this.childrenInternal[this.childNodeCountInternal - 1]; for (let i = 0; i < this.childNodeCountInternal; ++i) { const child = this.childrenInternal[i]; child.index = i; child.nextSibling = i + 1 < this.childNodeCountInternal ? this.childrenInternal[i + 1] : null; child.previousSibling = i - 1 >= 0 ? this.childrenInternal[i - 1] : null; child.parentNode = this; } } addAttribute(name, value) { const attr = { name: name, value: value, _node: this }; this.#attributesInternal.set(name, attr); } setAttributeInternal(name, value) { const attr = this.#attributesInternal.get(name); if (attr) { attr.value = value; } else { this.addAttribute(name, value); } } removeAttributeInternal(name) { this.#attributesInternal.delete(name); } copyTo(targetNode, anchorNode, callback) { void this.#agent .invoke_copyTo({ nodeId: this.id, targetNodeId: targetNode.id, insertBeforeNodeId: anchorNode ? anchorNode.id : undefined }) .then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null, this.#domModelInternal.nodeForId(response.nodeId)); } }); } moveTo(targetNode, anchorNode, callback) { void this.#agent .invoke_moveTo({ nodeId: this.id, targetNodeId: targetNode.id, insertBeforeNodeId: anchorNode ? anchorNode.id : undefined }) .then(response => { if (!response.getError()) { this.#domModelInternal.markUndoableState(); } if (callback) { callback(response.getError() || null, this.#domModelInternal.nodeForId(response.nodeId)); } }); } isXMLNode() { return Boolean(this.#xmlVersion); } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any setMarker(name, value) { if (value === null) { if (!this.#markers.has(name)) { return; } this.#markers.delete(name); for (let node = this; node; node = node.parentNode) { --node.#subtreeMarkerCount; } for (let node = this; node; node = node.parentNode) { this.#domModelInternal.dispatchEventToListeners(Events.MarkersChanged, node); } return; } if (this.parentNode && !this.#markers.has(name)) { for (let node = this; node; node = node.parentNode) { ++node.#subtreeMarkerCount; } } this.#markers.set(name, value); for (let node = this; node; node = node.parentNode) { this.#domModelInternal.dispatchEventToListeners(Events.MarkersChanged, node); } } marker(name) { return this.#markers.get(name) || null; } getMarkerKeysForTest() { return [...this.#markers.keys()]; } traverseMarkers(visitor) { function traverse(node) { if (!node.#subtreeMarkerCount) { return; } for (const marker of node.#markers.keys()) { visitor(node, marker); } if (!node.childrenInternal) { return; } for (const child of node.childrenInternal) { traverse(child); } } traverse(this); } resolveURL(url) { if (!url) { return url; } for (let frameOwnerCandidate = this; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { if (frameOwnerCandidate instanceof DOMDocument && frameOwnerCandidate.baseURL) { return Common.ParsedURL.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url); } } return null; } highlight(mode) { this.#domModelInternal.overlayModel().highlightInOverlay({ node: this, selectorList: undefined }, mode); } highlightForTwoSeconds() { this.#domModelInternal.overlayModel().highlightInOverlayForTwoSeconds({ node: this, selectorList: undefined }); } async resolveToObject(objectGroup) { const { object } = await this.#agent.invoke_resolveNode({ nodeId: this.id, backendNodeId: undefined, objectGroup }); return object && this.#domModelInternal.runtimeModelInternal.createRemoteObject(object) || null; } async boxModel() { const { model } = await this.#agent.invoke_getBoxModel({ nodeId: this.id }); return model; } async setAsInspectedNode() { let node = this; if (node && node.pseudoType()) { node = node.parentNode; } while (node) { let ancestor = node.ancestorUserAgentShadowRoot(); if (!ancestor) { break; } ancestor = node.ancestorShadowHost(); if (!ancestor) { break; } // User #agent shadow root, keep climbing up. node = ancestor; } if (!node) { throw new Error('In DOMNode.setAsInspectedNode: node is expected to not be null.'); } await this.#agent.invoke_setInspectedNode({ nodeId: node.id }); } enclosingElementOrSelf() { let node = this; if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode) { node = node.parentNode; } if (node && node.nodeType() !== Node.ELEMENT_NODE) { node = null; } return node; } async scrollIntoView() { const node = this.enclosingElementOrSelf(); if (!node) { return; } const object = await node.resolveToObject(); if (!object) { return; } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // @ts-expect-error void object.callFunction(scrollIntoView); object.release(); node.highlightForTwoSeconds(); function scrollIntoView() { this.scrollIntoViewIfNeeded(true); } } async focus() { const node = this.enclosingElementOrSelf(); if (!node) { throw new Error('DOMNode.focus expects node to not be null.'); } const object = await node.resolveToObject(); if (!object) { return; } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // @ts-expect-error await object.callFunction(focusInPage); object.release(); node.highlightForTwoSeconds(); await this.#domModelInternal.target().pageAgent().invoke_bringToFront(); function focusInPage() { this.focus(); } } simpleSelector() { const lowerCaseName = this.localName() || this.nodeName().toLowerCase(); if (this.nodeType() !== Node.ELEMENT_NODE) { return lowerCaseName; } const type = this.getAttribute('type'); const id = this.getAttribute('id'); const classes = this.getAttribute('class'); if (lowerCaseName === 'input' && type && !id && !classes) { return lowerCaseName + '[type="' + CSS.escape(type) + '"]'; } if (id) { return lowerCaseName + '#' + CSS.escape(id); } if (classes) { const classList = classes.trim().split(/\s+/g); return (lowerCaseName === 'div' ? '' : lowerCaseName) + '.' + classList.map(cls => CSS.escape(cls)).join('.'); } return lowerCaseName; } } (function (DOMNode) { // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum let ShadowRootTypes; (function (ShadowRootTypes) { ShadowRootTypes["UserAgent"] = "user-agent"; ShadowRootTypes["Open"] = "open"; ShadowRootTypes["Closed"] = "closed"; })(ShadowRootTypes = DOMNode.ShadowRootTypes || (DOMNode.ShadowRootTypes = {})); })(DOMNode || (DOMNode = {})); export class DeferredDOMNode { #domModelInternal; #backendNodeIdInternal; constructor(target, backendNodeId) { this.#domModelInternal = target.model(DOMModel); this.#backendNodeIdInternal = backendNodeId; } resolve(callback) { void this.resolvePromise().then(callback); } async resolvePromise() { const nodeIds = await this.#domModelInternal.pushNodesByBackendIdsToFrontend(new Set([this.#backendNodeIdInternal])); return nodeIds && nodeIds.get(this.#backendNodeIdInternal) || null; } backendNodeId() { return this.#backendNodeIdInternal; } domModel() { return this.#domModelInternal; } highlight() { this.#domModelInternal.overlayModel().highlightInOverlay({ deferredNode: this, selectorList: undefined }); } } export class DOMNodeShortcut { nodeType; nodeName; deferredNode; constructor(target, backendNodeId, nodeType, nodeName) { this.nodeType = nodeType; this.nodeName = nodeName; this.deferredNode = new DeferredDOMNode(target, backendNodeId); } } export class DOMDocument extends DOMNode { body; documentElement; documentURL; baseURL; constructor(domModel, payload) { super(domModel); this.body = null; this.documentElement = null; this.init(this, false, payload); this.documentURL = (payload.documentURL || ''); this.baseURL = (payload.baseURL || ''); } } export class DOMModel extends SDKModel { agent; idToDOMNode = new Map(); #document; #attributeLoadNodeIds; runtimeModelInternal; #lastMutationId; #pendingDocumentRequestPromise; #frameOwnerNode; #loadNodeAttributesTimeout; #searchId; constructor(target) { super(target); this.agent = target.domAgent(); this.#document = null; this.#attributeLoadNodeIds = new Set(); target.registerDOMDispatcher(new DOMDispatcher(this)); this.runtimeModelInternal = target.model(RuntimeModel); this.#pendingDocumentRequestPromise = null; if (!target.suspended()) { void this.agent.invoke_enable({}); } if (Root.Runtime.experiments.isEnabled('captureNodeCreationStacks')) { void this.agent.invoke_setNodeStackTracesEnabled({ enable: true }); } } runtimeModel() { return this.runtimeModelInternal; } cssModel() { return this.target().model(CSSModel); } overlayModel() { return this.target().model(OverlayModel); } static cancelSearch() { for (const domModel of TargetManager.instance().models(DOMModel)) { domModel.cancelSearch(); } } scheduleMutationEvent(node) { if (!this.hasEventListeners(Events.DOMMutated)) { return; } this.#lastMutationId = (this.#lastMutationId || 0) + 1; void Promise.resolve().then(callObserve.bind(this, node, this.#lastMutationId)); function callObserve(node, mutationId) { if (!this.hasEventListeners(Events.DOMMutated) || this.#lastMutationId !== mutationId) { return; } this.dispatchEventToListeners(Events.DOMMutated, node); } } requestDocument() { if (this.#document) { return Promise.resolve(this.#document); } if (!this.#pendingDocumentRequestPromise) { this.#pendingDocumentRequestPromise = this.requestDocumentInternal(); } return this.#pendingDocumentRequestPromise; } async getOwnerNodeForFrame(frameId) { // Returns an error if the frameId does not belong to the current target. const response = await this.agent.invoke_getFrameOwner({ frameId }); if (response.getError()) { return null; } return new DeferredDOMNode(this.target(), response.backendNodeId); } async requestDocumentInternal() { const response = await this.agent.invoke_getDocument({}); if (response.getError()) { return null; } const { root: documentPayload } = response; this.#pendingDocumentRequestPromise = null; if (documentPayload) { this.setDocument(documentPayload); } if (!this.#document) { console.error('No document'); return null; } const parentModel = this.parentModel(); if (parentModel && !this.#frameOwnerNode) { await parentModel.requestDocument(); const mainFrame = this.target().model(ResourceTreeModel)?.mainFrame; if (mainFrame) { const response = await parentModel.agent.invoke_getFrameOwner({ frameId: mainFrame.id }); if (!response.getError() && response.nodeId) { this.#frameOwnerNode = parentModel.nodeForId(response.nodeId); } } } // Document could have been cleared by now. if (this.#frameOwnerNode) { const oldDocument = this.#frameOwnerNode.contentDocument(); this.#frameOwnerNode.setContentDocument(this.#document); this.#frameOwnerNode.setChildren([]); if (this.#document) { this.#document.parentNode = this.#frameOwnerNode; this.dispatchEventToListeners(Events.NodeInserted, this.#document); } else if (oldDocument) { this.dispatchEventToListeners(Events.NodeRemoved, { node: oldDocument, parent: this.#frameOwnerNode }); } } return this.#document; } existingDocument() { return this.#document; } async pushNodeToFrontend(objectId) { await this.requestDocument(); const { nodeId } = await this.agent.invoke_requestNode({ objectId }); return nodeId ? this.nodeForId(nodeId) : null; } pushNodeByPathToFrontend(path) { return this.requestDocument() .then(() => this.agent.invoke_pushNodeByPathToFrontend({ path })) .then(({ nodeId }) => nodeId); } async pushNodesByBackendIdsToFrontend(backendNodeIds) { await this.requestDocument(); const backendNodeIdsArray = [...backendNodeIds]; const { nodeIds } = await this.agent.invoke_pushNodesByBackendIdsToFrontend({ backendNodeIds: backendNodeIdsArray }); if (!nodeIds) { return null; } const map = new Map(); for (let i = 0; i < nodeIds.length; ++i) { if (nodeIds[i]) { map.set(backendNodeIdsArray[i], this.nodeForId(nodeIds[i])); } } return map; } attributeModified(nodeId, name, value) { const node = this.idToDOMNode.get(nodeId); if (!node) { return; } node.setAttributeInternal(name, value); this.dispatchEventToListeners(Events.AttrModified, { node: node, name: name }); this.scheduleMutationEvent(node); } attributeRemoved(nodeId, name) { const node = this.idToDOMNode.get(nodeId); if (!node) { return; } node.removeAttributeInternal(name); this.dispatchEventToListeners(Events.AttrRemoved, { node: node, name: name }); this.scheduleMutationEvent(node); } inlineStyleInvalidated(nodeIds) { Platform.SetUtilities.addAll(this.#attributeLoadNodeIds, nodeIds); if (!this.#loadNodeAttributesTimeout) { this.#loadNodeAttributesTimeout = window.setTimeout(this.loadNodeAttributes.bind(this), 20); } } loadNodeAttributes() { this.#loadNodeAttributesTimeout = undefined; for (const nodeId of this.#attributeLoadNodeIds) { void this.agent.invoke_getAttributes({ nodeId }).then(({ attributes }) => { if (!attributes) { // We are calling loadNodeAttributes asynchronously, it is ok if node is not found. return; } const node = this.idToDOMNode.get(nodeId); if (!node) { return; } if (node.setAttributesPayload(attributes)) { this.dispatchEventToListeners(Events.AttrModified, { node: node, name: 'style' }); this.scheduleMutationEvent(node); } }); } this.#attributeLoadNodeIds.clear(); } characterDataModified(nodeId, newValue) { const node = this.idToDOMNode.get(nodeId); if (!node) { console.error('nodeId could not be resolved to a node'); return; } node.setNodeValueInternal(newValue); this.dispatchEventToListeners(Events.CharacterDataModified, node); this.scheduleMutationEvent(node); } nodeForId(nodeId) { return nodeId ? this.idToDOMNode.get(nodeId) || null : null; } documentUpdated() { // If we have this.#pendingDocumentRequestPromise in flight, // it will contain most recent result. const documentWasRequested = this.#pendingDocumentRequestPromise; this.setDocument(null); if (this.parentModel() && !documentWasRequested) { void this.requestDocument(); } } setDocument(payload) { this.idToDOMNode = new Map(); if (payload && 'nodeId' in payload) { this.#document = new DOMDocument(this, payload); } else { this.#document = null; } DOMModelUndoStack.instance().dispose(this); if (!this.parentModel()) { this.dispatchEventToListeners(Events.DocumentUpdated, this); } } setDetachedRoot(payload) { if (payload.nodeName === '#document') { new DOMDocument(this, payload); } else { DOMNode.create(this, null, false, payload); } } setChildNodes(parentId, payloads) { if (!parentId && payloads.length) { this.setDetachedRoot(payloads[0]); return; } const parent = this.idToDOMNode.get(parentId); parent?.setChildrenPayload(payloads); } childNodeCountUpdated(nodeId, newValue) { const node = this.idToDOMNode.get(nodeId); if (!node) { console.error('nodeId could not be resolved to a node'); return; } node.setChildNodeCount(newValue); this.dispatchEventToListeners(Events.ChildNodeCountUpdated, node); this.scheduleMutationEvent(node); } childNodeInserted(parentId, prevId, payload) { const parent = this.idToDOMNode.get(parentId); const prev = this.idToDOMNode.get(prevId); if (!parent) { console.error('parentId could not be resolved to a node'); return; } const node = parent.insertChild(prev, payload); this.idToDOMNode.set(node.id, node); this.dispatchEventToListeners(Events.NodeInserted, node); this.scheduleMutationEvent(node); } childNodeRemoved(parentId, nodeId) { const parent = this.idToDOMNode.get(parentId); const node = this.idToDOMNode.get(nodeId); if (!parent || !node) { console.error('parentId or nodeId could not be resolved to a node'); return; } parent.removeChild(node); this.unbind(node); this.dispatchEventToListeners(Events.NodeRemoved, { node: node, parent: parent }); this.scheduleMutationEvent(node); } shadowRootPushed(hostId, root) { const host = this.idToDOMNode.get(hostId); if (!host) { return; } const node = DOMNode.create(this, host.ownerDocument, true, root); node.parentNode = host; this.idToDOMNode.set(node.id, node); host.shadowRootsInternal.unshift(node); this.dispatchEventToListeners(Events.NodeInserted, node); this.scheduleMutationEvent(node); } shadowRootPopped(hostId, rootId) { const host = this.idToDOMNode.get(hostId); if (!host) { return; } const root = this.idToDOMNode.get(rootId); if (!root) { return; } host.removeChild(root); this.unbind(root); this.dispatchEventToListeners(Events.NodeRemoved, { node: root, parent: host }); this.scheduleMutationEvent(root); } pseudoElementAdded(parentId, pseudoElement) { const parent = this.idToDOMNode.get(parentId); if (!parent) { return; } const node = DOMNode.create(this, parent.ownerDocument, false, pseudoElement); node.parentNode = parent; this.idToDOMNode.set(node.id, node); const pseudoType = node.pseudoType(); if (!pseudoType) { throw new Error('DOMModel._pseudoElementAdded expects pseudoType to be defined.'); } const currentPseudoElements = parent.pseudoElements().get(pseudoType); if (currentPseudoElements) { Platform.DCHECK(() => pseudoType.startsWith('view-transition'), 'DOMModel.pseudoElementAdded expects parent to not already have this pseudo type added; only view-transition* pseudo elements can coexist under the same parent.'); currentPseudoElements.push(node); } else { parent.pseudoElements().set(pseudoType, [node]); } this.dispatchEventToListeners(Events.NodeInserted, node); this.scheduleMutationEvent(node); } topLayerElementsUpdated() { this.dispatchEventToListeners(Events.TopLayerElementsChanged); } pseudoElementRemoved(parentId, pseudoElementId) { const parent = this.idToDOMNode.get(parentId); if (!parent) { return; } const pseudoElement = this.idToDOMNode.get(pseudoElementId); if (!pseudoElement) { return; } parent.removeChild(pseudoElement); this.unbind(pseudoElement); this.dispatchEventToListeners(Events.NodeRemoved, { node: pseudoElement, parent: parent }); this.scheduleMutationEvent(pseudoElement); } distributedNodesUpdated(insertionPointId, distributedNodes) { const insertionPoint = this.idToDOMNode.get(insertionPointId); if (!insertionPoint) { return; } insertionPoint.setDistributedNodePayloads(distributedNodes); this.dispatchEventToListeners(Events.DistributedNodesChanged, insertionPoint); this.scheduleMutationEvent(insertionPoint); } unbind(node) { this.idToDOMNode.delete(node.id); const children = node.children(); for (let i = 0; children && i < children.length; ++i) { this.unbind(children[i]); } for (let i = 0; i < node.shadowRootsInternal.length; ++i) { this.unbind(node.shadowRootsInternal[i]); } const pseudoElements = node.pseudoElements(); for (const value of pseudoElements.values()) { for (const pseudoElement of value) { this.unbind(pseudoElement); } } const templateContent = node.templateContent(); if (templateContent) { this.unbind(templateContent); } } async getNodesByStyle(computedStyles, pierce = false) { await this.requestDocument(); if (!this.#document) { throw new Error('DOMModel.getNodesByStyle expects to have a document.'); } const response = await this.agent.invoke_getNodesForSubtreeByStyle({ nodeId: this.#document.id, computedStyles, pierce }); if (response.getError()) { throw response.getError(); } return response.nodeIds; } async performSearch(query, includeUserAgentShadowDOM) { const response = await this.agent.invoke_performSearch({ query, includeUserAgentShadowDOM }); if (!response.getError()) { this.#searchId = response.searchId; } return response.getError() ? 0 : response.resultCount; } async searchResult(index) { if (!this.#searchId) { return null; } const { nodeIds } = await this.agent.invoke_getSearchResults({ searchId: this.#searchId, fromIndex: index, toIndex: index + 1 }); return nodeIds && nodeIds.length === 1 ? this.nodeForId(nodeIds[0]) : null; } cancelSearch() { if (!this.#searchId) { return; } void this.agent.invoke_discardSearchResults({ searchId: this.#searchId }); this.#searchId = undefined; } classNamesPromise(nodeId) { return this.agent.invoke_collectClassNamesFromSubtree({ nodeId }).then(({ classNames }) => classNames || []); } querySelector(nodeId, selector) { return this.agent.invoke_querySelector({ nodeId, selector }).then(({ nodeId }) => nodeId); } querySelectorAll(nodeId, selector) { return this.agent.invoke_querySelectorAll({ nodeId, selector }).then(({ nodeIds }) => nodeIds); } getTopLayer