| /* |
| * 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. |
| */ |
| |
| /** |
| * @constructor |
| * @param {WebInspector.DOMAgent} domAgent |
| * @param {?WebInspector.DOMDocument} doc |
| * @param {boolean} isInShadowTree |
| * @param {DOMAgent.Node} payload |
| */ |
| WebInspector.DOMNode = function(domAgent, doc, isInShadowTree, payload) { |
| this._domAgent = domAgent; |
| this.ownerDocument = doc; |
| this._isInShadowTree = isInShadowTree; |
| |
| this.id = payload.nodeId; |
| domAgent._idToDOMNode[this.id] = this; |
| this._nodeType = payload.nodeType; |
| this._nodeName = payload.nodeName; |
| this._localName = payload.localName; |
| this._nodeValue = payload.nodeValue; |
| |
| this._shadowRoots = []; |
| |
| this._attributes = []; |
| this._attributesMap = {}; |
| if (payload.attributes) |
| this._setAttributesPayload(payload.attributes); |
| |
| this._userProperties = {}; |
| this._descendantUserPropertyCounters = {}; |
| |
| this._childNodeCount = payload.childNodeCount; |
| this.children = null; |
| |
| this.nextSibling = null; |
| this.previousSibling = null; |
| this.firstChild = null; |
| this.lastChild = null; |
| this.parentNode = null; |
| |
| if (payload.children) |
| this._setChildrenPayload(payload.children); |
| |
| if (payload.contentDocument) { |
| this._contentDocument = new WebInspector.DOMDocument(domAgent, payload.contentDocument); |
| this.children = [this._contentDocument]; |
| this._renumber(); |
| } |
| |
| if (payload.shadowRoots && WebInspector.experimentsSettings.showShadowDOM.isEnabled()) { |
| for (var i = 0; i < payload.shadowRoots.length; ++i) { |
| var root = payload.shadowRoots[i]; |
| var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, true, root); |
| this._shadowRoots.push(node); |
| } |
| } |
| |
| 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; |
| } 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.ATTRIBUTE_NODE) { |
| this.name = payload.name; |
| this.value = payload.value; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {string} value |
| * @param {boolean} optimized |
| */ |
| WebInspector.DOMNode.XPathStep = function(value, optimized) |
| { |
| this.value = value; |
| this.optimized = optimized; |
| } |
| |
| WebInspector.DOMNode.XPathStep.prototype = { |
| toString: function() |
| { |
| return this.value; |
| } |
| } |
| |
| WebInspector.DOMNode.prototype = { |
| /** |
| * @return {boolean} |
| */ |
| hasAttributes: function() |
| { |
| return this._attributes.length > 0; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| hasChildNodes: function() |
| { |
| return this._childNodeCount > 0 || !!this._shadowRoots.length; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| nodeType: function() |
| { |
| return this._nodeType; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| nodeName: function() |
| { |
| return this._nodeName; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| isInShadowTree: function() |
| { |
| return this._isInShadowTree; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| nodeNameInCorrectCase: function() |
| { |
| return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase(); |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setNodeName: function(name, callback) |
| { |
| DOMAgent.setNodeName(this.id, name, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| localName: function() |
| { |
| return this._localName; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| nodeValue: function() |
| { |
| return this._nodeValue; |
| }, |
| |
| /** |
| * @param {string} value |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setNodeValue: function(value, callback) |
| { |
| DOMAgent.setNodeValue(this.id, value, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @param {string} name |
| * @return {string} |
| */ |
| getAttribute: function(name) |
| { |
| var attr = this._attributesMap[name]; |
| return attr ? attr.value : undefined; |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {string} text |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setAttribute: function(name, text, callback) |
| { |
| DOMAgent.setAttributesAsText(this.id, text, name, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {string} value |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setAttributeValue: function(name, value, callback) |
| { |
| DOMAgent.setAttributeValue(this.id, name, value, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @return {Object} |
| */ |
| attributes: function() |
| { |
| return this._attributes; |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| removeAttribute: function(name, callback) |
| { |
| /** |
| * @param {?Protocol.Error} error |
| */ |
| function mycallback(error) |
| { |
| 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; |
| } |
| } |
| } |
| |
| WebInspector.domAgent._markRevision(this, callback)(error); |
| } |
| DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); |
| }, |
| |
| /** |
| * @param {function(Array.<WebInspector.DOMNode>)=} callback |
| */ |
| getChildNodes: function(callback) |
| { |
| if (this.children) { |
| if (callback) |
| callback(this.children); |
| return; |
| } |
| |
| /** |
| * @this {WebInspector.DOMNode} |
| * @param {?Protocol.Error} error |
| */ |
| function mycallback(error) |
| { |
| if (!error && callback) |
| callback(this.children); |
| } |
| |
| DOMAgent.requestChildNodes(this.id, mycallback.bind(this)); |
| }, |
| |
| /** |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| getOuterHTML: function(callback) |
| { |
| DOMAgent.getOuterHTML(this.id, callback); |
| }, |
| |
| /** |
| * @param {string} html |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setOuterHTML: function(html, callback) |
| { |
| DOMAgent.setOuterHTML(this.id, html, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| removeNode: function(callback) |
| { |
| DOMAgent.removeNode(this.id, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| copyNode: function() |
| { |
| function copy(error, text) |
| { |
| if (!error) |
| InspectorFrontendHost.copyText(text); |
| } |
| DOMAgent.getOuterHTML(this.id, copy); |
| }, |
| |
| /** |
| * @param {boolean} optimized |
| */ |
| copyXPath: function(optimized) |
| { |
| InspectorFrontendHost.copyText(this.xPath(optimized)); |
| }, |
| |
| /** |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| eventListeners: function(callback) |
| { |
| DOMAgent.getEventListenersForNode(this.id, callback); |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| path: function() |
| { |
| 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(","); |
| }, |
| |
| /** |
| * @param {boolean} justSelector |
| * @return {string} |
| */ |
| appropriateSelectorFor: function(justSelector) |
| { |
| var lowerCaseName = this.localName() || this.nodeName().toLowerCase(); |
| |
| var id = this.getAttribute("id"); |
| if (id) { |
| var 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; |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} node |
| * @return {boolean} |
| */ |
| isAncestor: function(node) |
| { |
| if (!node) |
| return false; |
| |
| var currentNode = node.parentNode; |
| while (currentNode) { |
| if (this === currentNode) |
| return true; |
| currentNode = currentNode.parentNode; |
| } |
| return false; |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} descendant |
| * @return {boolean} |
| */ |
| isDescendant: function(descendant) |
| { |
| return descendant !== null && descendant.isAncestor(this); |
| }, |
| |
| /** |
| * @param {Array.<string>} attrs |
| * @return {boolean} |
| */ |
| _setAttributesPayload: function(attrs) |
| { |
| var attributesChanged = !this._attributes || attrs.length !== this._attributes.length * 2; |
| var oldAttributesMap = this._attributesMap || {}; |
| |
| this._attributes = []; |
| this._attributesMap = {}; |
| |
| for (var i = 0; i < attrs.length; i += 2) { |
| var name = attrs[i]; |
| var value = attrs[i + 1]; |
| this._addAttribute(name, value); |
| |
| if (attributesChanged) |
| continue; |
| |
| if (!oldAttributesMap[name] || oldAttributesMap[name].value !== value) |
| attributesChanged = true; |
| } |
| return attributesChanged; |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} prev |
| * @param {DOMAgent.Node} payload |
| * @return {WebInspector.DOMNode} |
| */ |
| _insertChild: function(prev, payload) |
| { |
| var node = new WebInspector.DOMNode(this._domAgent, 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; |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} node |
| */ |
| _removeChild: function(node) |
| { |
| this.children.splice(this.children.indexOf(node), 1); |
| node.parentNode = null; |
| node._updateChildUserPropertyCountsOnRemoval(this); |
| this._renumber(); |
| }, |
| |
| /** |
| * @param {Array.<DOMAgent.Node>} payloads |
| */ |
| _setChildrenPayload: function(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 payload = payloads[i]; |
| var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payload); |
| this.children.push(node); |
| } |
| this._renumber(); |
| }, |
| |
| _renumber: function() |
| { |
| this._childNodeCount = this.children.length; |
| if (this._childNodeCount == 0) { |
| this.firstChild = null; |
| this.lastChild = null; |
| return; |
| } |
| this.firstChild = this.children[0]; |
| this.lastChild = this.children[this._childNodeCount - 1]; |
| for (var i = 0; i < this._childNodeCount; ++i) { |
| var child = this.children[i]; |
| child.index = i; |
| child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null; |
| child.previousSibling = i - 1 >= 0 ? this.children[i - 1] : null; |
| child.parentNode = this; |
| } |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {string} value |
| */ |
| _addAttribute: function(name, value) |
| { |
| var attr = { |
| name: name, |
| value: value, |
| _node: this |
| }; |
| this._attributesMap[name] = attr; |
| this._attributes.push(attr); |
| }, |
| |
| /** |
| * @param {string} name |
| * @param {string} value |
| */ |
| _setAttribute: function(name, value) |
| { |
| var attr = this._attributesMap[name]; |
| if (attr) |
| attr.value = value; |
| else |
| this._addAttribute(name, value); |
| }, |
| |
| /** |
| * @param {string} name |
| */ |
| _removeAttribute: function(name) |
| { |
| var attr = this._attributesMap[name]; |
| if (attr) { |
| this._attributes.remove(attr); |
| delete this._attributesMap[name]; |
| } |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} targetNode |
| * @param {?WebInspector.DOMNode} anchorNode |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| moveTo: function(targetNode, anchorNode, callback) |
| { |
| DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, WebInspector.domAgent._markRevision(this, callback)); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| isXMLNode: function() |
| { |
| return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; |
| }, |
| |
| /** |
| * @param {boolean} optimized |
| * @return {string} |
| */ |
| xPath: function(optimized) |
| { |
| if (this._nodeType === Node.DOCUMENT_NODE) |
| return "/"; |
| |
| var steps = []; |
| var contextNode = this; |
| while (contextNode) { |
| var step = contextNode._xPathValue(optimized); |
| if (!step) |
| break; // Error - bail out early. |
| steps.push(step); |
| if (step.optimized) |
| break; |
| contextNode = contextNode.parentNode; |
| } |
| |
| steps.reverse(); |
| return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/"); |
| }, |
| |
| /** |
| * @param {boolean} optimized |
| * @return {WebInspector.DOMNode.XPathStep} |
| */ |
| _xPathValue: function(optimized) |
| { |
| var ownValue; |
| var ownIndex = this._xPathIndex(); |
| if (ownIndex === -1) |
| return null; // Error. |
| |
| switch (this._nodeType) { |
| case Node.ELEMENT_NODE: |
| if (optimized && this.getAttribute("id")) |
| return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true); |
| ownValue = this._localName; |
| break; |
| case Node.ATTRIBUTE_NODE: |
| ownValue = "@" + this._nodeName; |
| break; |
| case Node.TEXT_NODE: |
| case Node.CDATA_SECTION_NODE: |
| ownValue = "text()"; |
| break; |
| case Node.PROCESSING_INSTRUCTION_NODE: |
| ownValue = "processing-instruction()"; |
| break; |
| case Node.COMMENT_NODE: |
| ownValue = "comment()"; |
| break; |
| case Node.DOCUMENT_NODE: |
| ownValue = ""; |
| break; |
| default: |
| ownValue = ""; |
| break; |
| } |
| |
| if (ownIndex > 0) |
| ownValue += "[" + ownIndex + "]"; |
| |
| return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE); |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| _xPathIndex: function() |
| { |
| // Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise. |
| function areNodesSimilar(left, right) |
| { |
| if (left === right) |
| return true; |
| |
| if (left._nodeType === Node.ELEMENT_NODE && right._nodeType === Node.ELEMENT_NODE) |
| return left._localName === right._localName; |
| |
| if (left._nodeType === right._nodeType) |
| return true; |
| |
| // XPath treats CDATA as text nodes. |
| var leftType = left._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left._nodeType; |
| var rightType = right._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right._nodeType; |
| return leftType === rightType; |
| } |
| |
| var siblings = this.parentNode ? this.parentNode.children : null; |
| if (!siblings) |
| return 0; // Root node - no siblings. |
| var hasSameNamedElements; |
| for (var i = 0; i < siblings.length; ++i) { |
| if (areNodesSimilar(this, siblings[i]) && siblings[i] !== this) { |
| hasSameNamedElements = true; |
| break; |
| } |
| } |
| if (!hasSameNamedElements) |
| return 0; |
| var ownIndex = 1; // XPath indices start with 1. |
| for (var i = 0; i < siblings.length; ++i) { |
| if (areNodesSimilar(this, siblings[i])) { |
| if (siblings[i] === this) |
| return ownIndex; |
| ++ownIndex; |
| } |
| } |
| return -1; // An error occurred: |this| not found in parent's children. |
| }, |
| |
| _updateChildUserPropertyCountsOnRemoval: function(parentNode) |
| { |
| var result = {}; |
| if (this._userProperties) { |
| for (var name in this._userProperties) |
| result[name] = (result[name] || 0) + 1; |
| } |
| |
| if (this._descendantUserPropertyCounters) { |
| for (var name in this._descendantUserPropertyCounters) { |
| var counter = this._descendantUserPropertyCounters[name]; |
| result[name] = (result[name] || 0) + counter; |
| } |
| } |
| |
| for (var name in result) |
| parentNode._updateDescendantUserPropertyCount(name, -result[name]); |
| }, |
| |
| _updateDescendantUserPropertyCount: function(name, delta) |
| { |
| if (!this._descendantUserPropertyCounters.hasOwnProperty(name)) |
| this._descendantUserPropertyCounters[name] = 0; |
| this._descendantUserPropertyCounters[name] += delta; |
| if (!this._descendantUserPropertyCounters[name]) |
| delete this._descendantUserPropertyCounters[name]; |
| if (this.parentNode) |
| this.parentNode._updateDescendantUserPropertyCount(name, delta); |
| }, |
| |
| setUserProperty: function(name, value) |
| { |
| if (value === null) { |
| this.removeUserProperty(name); |
| return; |
| } |
| |
| if (this.parentNode && !this._userProperties.hasOwnProperty(name)) |
| this.parentNode._updateDescendantUserPropertyCount(name, 1); |
| |
| this._userProperties[name] = value; |
| }, |
| |
| removeUserProperty: function(name) |
| { |
| if (!this._userProperties.hasOwnProperty(name)) |
| return; |
| |
| delete this._userProperties[name]; |
| if (this.parentNode) |
| this.parentNode._updateDescendantUserPropertyCount(name, -1); |
| }, |
| |
| getUserProperty: function(name) |
| { |
| return this._userProperties ? this._userProperties[name] : null; |
| }, |
| |
| descendantUserPropertyCount: function(name) |
| { |
| return this._descendantUserPropertyCounters && this._descendantUserPropertyCounters[name] ? this._descendantUserPropertyCounters[name] : 0; |
| } |
| } |
| |
| /** |
| * @extends {WebInspector.DOMNode} |
| * @constructor |
| * @param {WebInspector.DOMAgent} domAgent |
| * @param {DOMAgent.Node} payload |
| */ |
| WebInspector.DOMDocument = function(domAgent, payload) |
| { |
| WebInspector.DOMNode.call(this, domAgent, this, false, payload); |
| this.documentURL = payload.documentURL || ""; |
| this.xmlVersion = payload.xmlVersion; |
| this._listeners = {}; |
| } |
| |
| WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype; |
| |
| /** |
| * @extends {WebInspector.Object} |
| * @constructor |
| */ |
| WebInspector.DOMAgent = function() { |
| /** @type {Object|undefined} */ |
| this._idToDOMNode = {}; |
| this._document = null; |
| this._attributeLoadNodeIds = {}; |
| InspectorBackend.registerDOMDispatcher(new WebInspector.DOMDispatcher(this)); |
| if (WebInspector.settings.emulateTouchEvents.get()) |
| this._emulateTouchEventsChanged(); |
| WebInspector.settings.emulateTouchEvents.addChangeListener(this._emulateTouchEventsChanged, this); |
| } |
| |
| WebInspector.DOMAgent.Events = { |
| AttrModified: "AttrModified", |
| AttrRemoved: "AttrRemoved", |
| CharacterDataModified: "CharacterDataModified", |
| NodeInserted: "NodeInserted", |
| NodeRemoved: "NodeRemoved", |
| DocumentUpdated: "DocumentUpdated", |
| ChildNodeCountUpdated: "ChildNodeCountUpdated", |
| InspectElementRequested: "InspectElementRequested", |
| StyleInvalidated: "StyleInvalidated", |
| UndoRedoRequested: "UndoRedoRequested", |
| UndoRedoCompleted: "UndoRedoCompleted" |
| } |
| |
| WebInspector.DOMAgent.prototype = { |
| /** |
| * @param {function(WebInspector.DOMDocument)=} callback |
| */ |
| requestDocument: function(callback) |
| { |
| if (this._document) { |
| if (callback) |
| callback(this._document); |
| return; |
| } |
| |
| if (this._pendingDocumentRequestCallbacks) { |
| this._pendingDocumentRequestCallbacks.push(callback); |
| return; |
| } |
| |
| this._pendingDocumentRequestCallbacks = [callback]; |
| |
| /** |
| * @this {WebInspector.DOMAgent} |
| * @param {?Protocol.Error} error |
| * @param {DOMAgent.Node} root |
| */ |
| function onDocumentAvailable(error, root) |
| { |
| if (!error) |
| this._setDocument(root); |
| |
| for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) { |
| var callback = this._pendingDocumentRequestCallbacks[i]; |
| if (callback) |
| callback(this._document); |
| } |
| delete this._pendingDocumentRequestCallbacks; |
| } |
| |
| DOMAgent.getDocument(onDocumentAvailable.bind(this)); |
| }, |
| |
| /** |
| * @param {RuntimeAgent.RemoteObjectId} objectId |
| * @param {function(?DOMAgent.NodeId)=} callback |
| */ |
| pushNodeToFrontend: function(objectId, callback) |
| { |
| var callbackCast = /** @type {function(*)} */ callback; |
| this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callbackCast); |
| }, |
| |
| /** |
| * @param {string} path |
| * @param {function(?WebInspector.DOMNode)=} callback |
| */ |
| pushNodeByPathToFrontend: function(path, callback) |
| { |
| var callbackCast = /** @type {function(*)} */ callback; |
| this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callbackCast); |
| }, |
| |
| /** |
| * @param {function(*)=} callback |
| * @return {function(?Protocol.Error,*=)|undefined} |
| */ |
| _wrapClientCallback: function(callback) |
| { |
| if (!callback) |
| return; |
| /** |
| * @param {?Protocol.Error} error |
| * @param {*=} result |
| */ |
| return function(error, result) |
| { |
| // Caller is responsible for handling the actual error. |
| callback(error ? null : result); |
| } |
| }, |
| |
| /** |
| * @param {function(function(?Protocol.Error, *=))} func |
| * @param {function(*)=} callback |
| */ |
| _dispatchWhenDocumentAvailable: function(func, callback) |
| { |
| var callbackWrapper = /** @type {function(?Protocol.Error, *=)} */ this._wrapClientCallback(callback); |
| |
| function onDocumentAvailable() |
| { |
| if (this._document) |
| func(callbackWrapper); |
| else { |
| if (callbackWrapper) |
| callbackWrapper("No document"); |
| } |
| } |
| this.requestDocument(onDocumentAvailable.bind(this)); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} name |
| * @param {string} value |
| */ |
| _attributeModified: function(nodeId, name, value) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| if (!node) |
| return; |
| var issueStyleInvalidated = name === "style" && value !== node.getAttribute("style"); |
| |
| node._setAttribute(name, value); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: name }); |
| if (issueStyleInvalidated) |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.StyleInvalidated, node) |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} name |
| */ |
| _attributeRemoved: function(nodeId, name) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| if (!node) |
| return; |
| node._removeAttribute(name); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrRemoved, { node: node, name: name }); |
| }, |
| |
| /** |
| * @param {Array.<DOMAgent.NodeId>} nodeIds |
| */ |
| _inlineStyleInvalidated: function(nodeIds) |
| { |
| for (var i = 0; i < nodeIds.length; ++i) |
| this._attributeLoadNodeIds[nodeIds[i]] = true; |
| if ("_loadNodeAttributesTimeout" in this) |
| return; |
| this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0); |
| }, |
| |
| _loadNodeAttributes: function() |
| { |
| /** |
| * @this {WebInspector.DOMAgent} |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {?Protocol.Error} error |
| * @param {Array.<string>} attributes |
| */ |
| function callback(nodeId, error, attributes) |
| { |
| if (error) { |
| // We are calling _loadNodeAttributes asynchronously, it is ok if node is not found. |
| return; |
| } |
| var node = this._idToDOMNode[nodeId]; |
| if (node) { |
| if (node._setAttributesPayload(attributes)) { |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: "style" }); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.StyleInvalidated, node); |
| } |
| } |
| } |
| |
| delete this._loadNodeAttributesTimeout; |
| |
| for (var nodeId in this._attributeLoadNodeIds) { |
| var nodeIdAsNumber = parseInt(nodeId, 10); |
| DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber)); |
| } |
| this._attributeLoadNodeIds = {}; |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} newValue |
| */ |
| _characterDataModified: function(nodeId, newValue) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| node._nodeValue = newValue; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @return {WebInspector.DOMNode|undefined} |
| */ |
| nodeForId: function(nodeId) |
| { |
| return this._idToDOMNode[nodeId]; |
| }, |
| |
| _documentUpdated: function() |
| { |
| this._setDocument(null); |
| }, |
| |
| /** |
| * @param {DOMAgent.Node} payload |
| */ |
| _setDocument: function(payload) |
| { |
| this._idToDOMNode = {}; |
| if (payload && "nodeId" in payload) |
| this._document = new WebInspector.DOMDocument(this, payload); |
| else |
| this._document = null; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document); |
| }, |
| |
| /** |
| * @param {DOMAgent.Node} payload |
| */ |
| _setDetachedRoot: function(payload) |
| { |
| if (payload.nodeName === "#document") |
| new WebInspector.DOMDocument(this, payload); |
| else |
| new WebInspector.DOMNode(this, null, false, payload); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentId |
| * @param {Array.<DOMAgent.Node>} payloads |
| */ |
| _setChildNodes: function(parentId, payloads) |
| { |
| if (!parentId && payloads.length) { |
| this._setDetachedRoot(payloads[0]); |
| return; |
| } |
| |
| var parent = this._idToDOMNode[parentId]; |
| parent._setChildrenPayload(payloads); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {number} newValue |
| */ |
| _childNodeCountUpdated: function(nodeId, newValue) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| node._childNodeCount = newValue; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentId |
| * @param {DOMAgent.NodeId} prevId |
| * @param {DOMAgent.Node} payload |
| */ |
| _childNodeInserted: function(parentId, prevId, payload) |
| { |
| var parent = this._idToDOMNode[parentId]; |
| var prev = this._idToDOMNode[prevId]; |
| var node = parent._insertChild(prev, payload); |
| this._idToDOMNode[node.id] = node; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentId |
| * @param {DOMAgent.NodeId} nodeId |
| */ |
| _childNodeRemoved: function(parentId, nodeId) |
| { |
| var parent = this._idToDOMNode[parentId]; |
| var node = this._idToDOMNode[nodeId]; |
| parent._removeChild(node); |
| this._unbind(node); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node:node, parent:parent}); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} rootId |
| */ |
| _shadowRootPopped: function(rootId) |
| { |
| }, |
| |
| /** |
| * @param {DOMAgent.Node} node |
| */ |
| _unbind: function(node) |
| { |
| delete this._idToDOMNode[node.id]; |
| for (var i = 0; node.children && i < node.children.length; ++i) |
| this._unbind(node.children[i]); |
| }, |
| |
| /** |
| * @param {number} nodeId |
| */ |
| inspectElement: function(nodeId) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| if (node) |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectElementRequested, node); |
| }, |
| |
| /** |
| * @param {string} query |
| * @param {function(number)} searchCallback |
| */ |
| performSearch: function(query, searchCallback) |
| { |
| this.cancelSearch(); |
| |
| /** |
| * @param {?Protocol.Error} error |
| * @param {string} searchId |
| * @param {number} resultsCount |
| */ |
| function callback(error, searchId, resultsCount) |
| { |
| this._searchId = searchId; |
| searchCallback(resultsCount); |
| } |
| DOMAgent.performSearch(query, callback.bind(this)); |
| }, |
| |
| /** |
| * @param {number} index |
| * @param {?function(DOMAgent.Node)} callback |
| */ |
| searchResult: function(index, callback) |
| { |
| if (this._searchId) { |
| /** |
| * @param {?Protocol.Error} error |
| * @param {Array.<number>} nodeIds |
| */ |
| function mycallback(error, nodeIds) |
| { |
| if (error) { |
| console.error(error); |
| callback(null); |
| return; |
| } |
| if (nodeIds.length != 1) |
| return; |
| |
| callback(this._idToDOMNode[nodeIds[0]]); |
| } |
| DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this)); |
| } else |
| callback(null); |
| }, |
| |
| cancelSearch: function() |
| { |
| if (this._searchId) { |
| DOMAgent.discardSearchResults(this._searchId); |
| delete this._searchId; |
| } |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} selectors |
| * @param {function(?DOMAgent.NodeId)=} callback |
| */ |
| querySelector: function(nodeId, selectors, callback) |
| { |
| var callbackCast = /** @type {function(*)|undefined} */callback; |
| DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callbackCast)); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} selectors |
| * @param {function(?Array.<DOMAgent.NodeId>)=} callback |
| */ |
| querySelectorAll: function(nodeId, selectors, callback) |
| { |
| var callbackCast = /** @type {function(*)|undefined} */callback; |
| DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callbackCast)); |
| }, |
| |
| /** |
| * @param {?number} nodeId |
| * @param {string=} mode |
| */ |
| highlightDOMNode: function(nodeId, mode) |
| { |
| if (this._hideDOMNodeHighlightTimeout) { |
| clearTimeout(this._hideDOMNodeHighlightTimeout); |
| delete this._hideDOMNodeHighlightTimeout; |
| } |
| |
| this._highlightedDOMNodeId = nodeId; |
| if (nodeId) |
| DOMAgent.highlightNode(nodeId, this._buildHighlightConfig(mode)); |
| else |
| DOMAgent.hideHighlight(); |
| }, |
| |
| hideDOMNodeHighlight: function() |
| { |
| this.highlightDOMNode(0); |
| }, |
| |
| /** |
| * @param {?DOMAgent.NodeId} nodeId |
| */ |
| highlightDOMNodeForTwoSeconds: function(nodeId) |
| { |
| this.highlightDOMNode(nodeId); |
| this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000); |
| }, |
| |
| /** |
| * @param {boolean} enabled |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| setInspectModeEnabled: function(enabled, callback) |
| { |
| DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), callback); |
| }, |
| |
| /** |
| * @param {string=} mode |
| */ |
| _buildHighlightConfig: function(mode) |
| { |
| mode = mode || "all"; |
| var highlightConfig = { showInfo: mode === "all" }; |
| if (mode === "all" || mode === "content") |
| highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA(); |
| |
| if (mode === "all" || mode === "padding") |
| highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA(); |
| |
| if (mode === "all" || mode === "border") |
| highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA(); |
| |
| if (mode === "all" || mode === "margin") |
| highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA(); |
| |
| return highlightConfig; |
| }, |
| |
| /** |
| * @param {WebInspector.DOMNode} node |
| * @param {function(?Protocol.Error)=} callback |
| * @return {function(?Protocol.Error)} |
| */ |
| _markRevision: function(node, callback) |
| { |
| function wrapperFunction(error) |
| { |
| if (!error) |
| this.markUndoableState(); |
| |
| if (callback) |
| callback.apply(this, arguments); |
| } |
| return wrapperFunction.bind(this); |
| }, |
| |
| _emulateTouchEventsChanged: function() |
| { |
| const injectedFunction = function() { |
| const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"]; |
| for (var i = 0; i < touchEvents.length; ++i) { |
| if (!(touchEvents[i] in window.__proto__)) |
| Object.defineProperty(window.__proto__, touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true }); |
| if (!(touchEvents[i] in document.__proto__)) |
| Object.defineProperty(document.__proto__, touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true }); |
| } |
| } |
| |
| var emulationEnabled = WebInspector.settings.emulateTouchEvents.get(); |
| if (emulationEnabled && !this._addTouchEventsScriptInjecting) { |
| this._addTouchEventsScriptInjecting = true; |
| PageAgent.addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")", scriptAddedCallback.bind(this)); |
| } else { |
| if (typeof this._addTouchEventsScriptId !== "undefined") { |
| PageAgent.removeScriptToEvaluateOnLoad(this._addTouchEventsScriptId); |
| delete this._addTouchEventsScriptId; |
| } |
| } |
| |
| function scriptAddedCallback(error, scriptId) |
| { |
| delete this._addTouchEventsScriptInjecting; |
| if (error) |
| return; |
| this._addTouchEventsScriptId = scriptId; |
| } |
| |
| DOMAgent.setTouchEmulationEnabled(emulationEnabled); |
| }, |
| |
| markUndoableState: function() |
| { |
| DOMAgent.markUndoableState(); |
| }, |
| |
| /** |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| undo: function(callback) |
| { |
| function mycallback(error) |
| { |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted); |
| callback(error); |
| } |
| |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested); |
| DOMAgent.undo(callback); |
| }, |
| |
| /** |
| * @param {function(?Protocol.Error)=} callback |
| */ |
| redo: function(callback) |
| { |
| function mycallback(error) |
| { |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted); |
| callback(error); |
| } |
| |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested); |
| DOMAgent.redo(callback); |
| } |
| } |
| |
| WebInspector.DOMAgent.prototype.__proto__ = WebInspector.Object.prototype; |
| |
| /** |
| * @constructor |
| * @implements {DOMAgent.Dispatcher} |
| * @param {WebInspector.DOMAgent} domAgent |
| */ |
| WebInspector.DOMDispatcher = function(domAgent) |
| { |
| this._domAgent = domAgent; |
| } |
| |
| WebInspector.DOMDispatcher.prototype = { |
| documentUpdated: function() |
| { |
| this._domAgent._documentUpdated(); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} name |
| * @param {string} value |
| */ |
| attributeModified: function(nodeId, name, value) |
| { |
| this._domAgent._attributeModified(nodeId, name, value); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} name |
| */ |
| attributeRemoved: function(nodeId, name) |
| { |
| this._domAgent._attributeRemoved(nodeId, name); |
| }, |
| |
| /** |
| * @param {Array.<DOMAgent.NodeId>} nodeIds |
| */ |
| inlineStyleInvalidated: function(nodeIds) |
| { |
| this._domAgent._inlineStyleInvalidated(nodeIds); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {string} characterData |
| */ |
| characterDataModified: function(nodeId, characterData) |
| { |
| this._domAgent._characterDataModified(nodeId, characterData); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentId |
| * @param {Array.<DOMAgent.Node>} payloads |
| */ |
| setChildNodes: function(parentId, payloads) |
| { |
| this._domAgent._setChildNodes(parentId, payloads); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} nodeId |
| * @param {number} childNodeCount |
| */ |
| childNodeCountUpdated: function(nodeId, childNodeCount) |
| { |
| this._domAgent._childNodeCountUpdated(nodeId, childNodeCount); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentNodeId |
| * @param {DOMAgent.NodeId} previousNodeId |
| * @param {DOMAgent.Node} payload |
| */ |
| childNodeInserted: function(parentNodeId, previousNodeId, payload) |
| { |
| this._domAgent._childNodeInserted(parentNodeId, previousNodeId, payload); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} parentNodeId |
| * @param {DOMAgent.NodeId} nodeId |
| */ |
| childNodeRemoved: function(parentNodeId, nodeId) |
| { |
| this._domAgent._childNodeRemoved(parentNodeId, nodeId); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} hostId |
| * @param {DOMAgent.Node} root |
| */ |
| shadowRootPushed: function(hostId, root) |
| { |
| this._domAgent._childNodeInserted(hostId, 0, root); |
| }, |
| |
| /** |
| * @param {DOMAgent.NodeId} hostId |
| * @param {DOMAgent.NodeId} rootId |
| */ |
| shadowRootPopped: function(hostId, rootId) |
| { |
| this._domAgent._childNodeRemoved(hostId, rootId); |
| } |
| } |
| |
| /** |
| * @type {?WebInspector.DOMAgent} |
| */ |
| WebInspector.domAgent = null; |