/* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.DOMNode = class DOMNode extends WebInspector.Object { constructor(domTreeManager, doc, isInShadowTree, payload) { super(); this._domTreeManager = domTreeManager; this._isInShadowTree = isInShadowTree; this.id = payload.nodeId; this._domTreeManager._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._computedRole = payload.role; if (this._nodeType === Node.DOCUMENT_NODE) this.ownerDocument = this; else this.ownerDocument = doc; this._attributes = []; this._attributesMap = {}; if (payload.attributes) this._setAttributesPayload(payload.attributes); this._childNodeCount = payload.childNodeCount; this._children = null; this._filteredChildren = null; this._filteredChildrenNeedsUpdating = true; this._nextSibling = null; this._previousSibling = null; this.parentNode = null; this._enabledPseudoClasses = []; this._shadowRoots = []; if (payload.shadowRoots) { for (var i = 0; i < payload.shadowRoots.length; ++i) { var root = payload.shadowRoots[i]; var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, true, root); this._shadowRoots.push(node); } } if (payload.templateContent) { this._templateContent = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, true, payload.templateContent); this._templateContent.parentNode = this; } if (payload.children) this._setChildrenPayload(payload.children); this._pseudoElements = new Map; if (payload.pseudoElements) { for (var i = 0; i < payload.pseudoElements.length; ++i) { var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]); node.parentNode = this; this._pseudoElements.set(node.pseudoType(), node); } } if (payload.contentDocument) { this._contentDocument = new WebInspector.DOMNode(this._domTreeManager, null, false, payload.contentDocument); this._children = [this._contentDocument]; this._renumber(); } if (payload.frameId) this._frameIdentifier = payload.frameId; 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; this.internalSubset = payload.internalSubset; } 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; } } // Public get frameIdentifier() { return this._frameIdentifier || this.ownerDocument.frameIdentifier; } get frame() { if (!this._frame) this._frame = WebInspector.frameResourceManager.frameForIdentifier(this.frameIdentifier); return this._frame; } get children() { if (!this._children) return null; if (WebInspector.showShadowDOMSetting.value) return this._children; if (this._filteredChildrenNeedsUpdating) { this._filteredChildrenNeedsUpdating = false; this._filteredChildren = this._children.filter(function(node) { return !node._isInShadowTree; }); } return this._filteredChildren; } 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 nextSibling() { if (WebInspector.showShadowDOMSetting.value) return this._nextSibling; var node = this._nextSibling; while (node) { if (!node._isInShadowTree) return node; node = node._nextSibling; } return null; } get previousSibling() { if (WebInspector.showShadowDOMSetting.value) return this._previousSibling; var node = this._previousSibling; while (node) { if (!node._isInShadowTree) return node; node = node._previousSibling; } return null; } get childNodeCount() { var children = this.children; if (children) return children.length; if (WebInspector.showShadowDOMSetting.value) return this._childNodeCount + this._shadowRoots.length; return this._childNodeCount; } set childNodeCount(count) { this._childNodeCount = count; } computedRole() { return this._computedRole; } hasAttributes() { return this._attributes.length > 0; } hasChildNodes() { return this.childNodeCount > 0; } hasShadowRoots() { return !!this._shadowRoots.length; } isInShadowTree() { return this._isInShadowTree; } 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) { 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(WebInspector.DOMNode.PseudoElementType.Before) || null; } afterPseudoElement() { return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementType.After) || null; } nodeValue() { return this._nodeValue; } setNodeValue(value, callback) { DOMAgent.setNodeValue(this.id, value, this._makeUndoableCallback(callback)); } getAttribute(name) { var attr = this._attributesMap[name]; return attr ? attr.value : undefined; } setAttribute(name, text, callback) { DOMAgent.setAttributesAsText(this.id, text, name, this._makeUndoableCallback(callback)); } setAttributeValue(name, value, callback) { DOMAgent.setAttributeValue(this.id, name, value, this._makeUndoableCallback(callback)); } attributes() { return this._attributes; } removeAttribute(name, callback) { function mycallback(error, success) { if (!error) { delete this._attributesMap[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); } DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); } getChildNodes(callback) { if (this.children) { if (callback) callback(this.children); return; } function mycallback(error) { if (!error && callback) callback(this.children); } DOMAgent.requestChildNodes(this.id, mycallback.bind(this)); } getSubtree(depth, callback) { function mycallback(error) { if (callback) callback(error ? null : this.children); } DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this)); } getOuterHTML(callback) { DOMAgent.getOuterHTML(this.id, callback); } setOuterHTML(html, callback) { DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback)); } removeNode(callback) { DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback)); } copyNode() { function copy(error, text) { if (!error) InspectorFrontendHost.copyText(text); } DOMAgent.getOuterHTML(this.id, copy); } eventListeners(callback) { DOMAgent.getEventListenersForNode(this.id, callback); } accessibilityProperties(callback) { function accessibilityPropertiesCallback(error, accessibilityProperties) { if (!error && callback && accessibilityProperties) { 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, 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 }); } } 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(","); } appropriateSelectorFor(justSelector) { if (this.isPseudoElement()) return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType; var lowerCaseName = this.localName() || this.nodeName().toLowerCase(); var id = this.getAttribute("id"); if (id) { if (/[\s'"]/.test(id)) { id = id.replace(/\\/g, "\\\\").replace(/\"/g, "\\\""); selector = lowerCaseName + "[id=\"" + id + "\"]"; } else selector = "#" + id; return (justSelector ? selector : lowerCaseName + selector); } var className = this.getAttribute("class"); if (className) { var selector = "." + className.trim().replace(/\s+/g, "."); return (justSelector ? selector : lowerCaseName + selector); } 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); } _setAttributesPayload(attrs) { this._attributes = []; this._attributesMap = {}; for (var i = 0; i < attrs.length; i += 2) this._addAttribute(attrs[i], attrs[i + 1]); } _insertChild(prev, payload) { var node = new WebInspector.DOMNode(this._domTreeManager, 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 WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payloads[i]); this._children.push(node); } this._renumber(); } _renumber() { this._filteredChildrenNeedsUpdating = true; 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) { var attr = {name, value, _node: this}; this._attributesMap[name] = attr; this._attributes.push(attr); } _setAttribute(name, value) { var attr = this._attributesMap[name]; if (attr) attr.value = value; else this._addAttribute(name, value); } _removeAttribute(name) { var attr = this._attributesMap[name]; if (attr) { this._attributes.remove(attr); delete this._attributesMap[name]; } } moveTo(targetNode, anchorNode, callback) { 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(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged); } CSSAgent.forcePseudoState(this.id, pseudoClasses, changed.bind(this)); } _makeUndoableCallback(callback) { return function(error) { if (!error) DOMAgent.markUndoableState(); if (callback) callback.apply(null, arguments); }; } }; WebInspector.DOMNode.Event = { EnabledPseudoClassesChanged: "dom-node-enabled-pseudo-classes-did-change", AttributeModified: "dom-node-attribute-modified", AttributeRemoved: "dom-node-attribute-removed" }; WebInspector.DOMNode.PseudoElementType = { Before: "before", After: "after", };