blob: 1b540dba349302421b2790c50c95b603583236e1 [file] [log] [blame]
/*
* 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;
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);
}
// Static
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 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;
}
markDestroyed()
{
console.assert(!this._destroyed, this);
this._destroyed = true;
}
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) {
callback("ERROR: node is destroyed");
return;
}
let target = WI.assumingMainTarget();
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);
}
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();
// COMPATIBILITY (iOS 11.0): DOM.insertAdjacentHTML did not exist.
if (!target.hasCommand("DOM.insertAdjacentHTML")) {
WI.RemoteObject.resolveNode(this).then((object) => {
function inspectedPage_node_insertAdjacentHTML(position, html) {
this.insertAdjacentHTML(position, html);
}
object.callFunction(inspectedPage_node_insertAdjacentHTML, [position, html]);
object.release();
});
return;
}
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(callback)
{
console.assert(!this._destroyed, this);
if (this._destroyed) {
callback("ERROR: node is destroyed");
return;
}
console.assert(WI.domManager.inspectedNode === this);
let target = WI.assumingMainTarget();
target.DOMAgent.getEventListenersForNode(this.id, callback);
}
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()
{
let id = this.getAttribute("id");
if (!id)
return "";
id = id.trim();
if (!id.length)
return "";
id = CSS.escape(id);
if (/[\s'"]/.test(id))
return `[id="${id}"]`;
return `#${id}`;
}
get escapedClassSelector()
{
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}.${CSS.escape(className)}`;
}, "");
}
get displayName()
{
if (this.isPseudoElement())
return "::" + this._pseudoType;
return this.nodeNameInCorrectCase() + this.escapedIdSelector + this.escapedClassSelector;
}
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));
}
_makeUndoableCallback(callback)
{
return (...args) => {
if (!args[0]) { // error
let target = WI.assumingMainTarget();
if (target.hasCommand("DOM.markUndoableState"))
target.DOMAgent.markUndoableState();
}
if (callback)
callback.apply(null, args);
};
}
};
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",
};
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",
};