| /* |
| * 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. |
| */ |
| |
| WebInspector.DOMNode = function(doc, payload) { |
| this.ownerDocument = doc; |
| |
| this.id = payload.id; |
| this._nodeType = payload.nodeType; |
| this._nodeName = payload.nodeName; |
| this._localName = payload.localName; |
| this._nodeValue = payload.nodeValue; |
| |
| this._attributes = []; |
| this._attributesMap = {}; |
| if (payload.attributes) |
| this._setAttributesPayload(payload.attributes); |
| |
| this._childNodeCount = payload.childNodeCount; |
| this.children = null; |
| |
| this.nextSibling = null; |
| this.prevSibling = null; |
| this.firstChild = null; |
| this.lastChild = null; |
| this.parentNode = null; |
| |
| if (payload.children) |
| this._setChildrenPayload(payload.children); |
| |
| this._computedStyle = null; |
| this.style = null; |
| this._matchedCSSRules = []; |
| |
| if (this._nodeType === Node.ELEMENT_NODE) { |
| // HTML and BODY from internal iframes should not overwrite top-level ones. |
| if (!this.ownerDocument.documentElement && this._nodeName === "HTML") |
| this.ownerDocument.documentElement = this; |
| if (!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; |
| } else if (this._nodeType === Node.ATTRIBUTE_NODE) { |
| this.name = payload.name; |
| this.value = payload.value; |
| } |
| } |
| |
| WebInspector.DOMNode.prototype = { |
| hasAttributes: function() |
| { |
| return this._attributes.length > 0; |
| }, |
| |
| hasChildNodes: function() |
| { |
| return this._childNodeCount > 0; |
| }, |
| |
| nodeType: function() |
| { |
| return this._nodeType; |
| }, |
| |
| nodeName: function() |
| { |
| return this._nodeName; |
| }, |
| |
| setNodeName: function(name, callback) |
| { |
| DOMAgent.setNodeName(this.id, name, callback); |
| }, |
| |
| localName: function() |
| { |
| return this._localName; |
| }, |
| |
| nodeValue: function() |
| { |
| return this._nodeValue; |
| }, |
| |
| setNodeValue: function(value, callback) |
| { |
| DOMAgent.setNodeValue(this.id, value, callback); |
| }, |
| |
| getAttribute: function(name) |
| { |
| var attr = this._attributesMap[name]; |
| return attr ? attr.value : undefined; |
| }, |
| |
| setAttribute: function(name, value, callback) |
| { |
| function mycallback(error) |
| { |
| if (!error) { |
| var attr = this._attributesMap[name]; |
| if (attr) |
| attr.value = value; |
| else |
| attr = this._addAttribute(name, value); |
| } |
| |
| if (callback) |
| callback(); |
| } |
| DOMAgent.setAttribute(this.id, name, value, mycallback.bind(this)); |
| }, |
| |
| attributes: function() |
| { |
| return this._attributes; |
| }, |
| |
| removeAttribute: function(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; |
| } |
| } |
| } |
| |
| if (callback) |
| callback(); |
| } |
| DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); |
| }, |
| |
| getChildNodes: function(callback) |
| { |
| if (this.children) { |
| if (callback) |
| callback(this.children); |
| return; |
| } |
| |
| function mycallback(error) { |
| if (!error && callback) |
| callback(this.children); |
| } |
| DOMAgent.getChildNodes(this.id, mycallback.bind(this)); |
| }, |
| |
| getOuterHTML: function(callback) |
| { |
| DOMAgent.getOuterHTML(this.id, callback); |
| }, |
| |
| setOuterHTML: function(html, callback) |
| { |
| DOMAgent.setOuterHTML(this.id, html, callback); |
| }, |
| |
| removeNode: function(callback) |
| { |
| DOMAgent.removeNode(this.id, callback); |
| }, |
| |
| copyNode: function(callback) |
| { |
| DOMAgent.copyNode(this.id, callback); |
| }, |
| |
| eventListeners: function(callback) |
| { |
| DOMAgent.getEventListenersForNode(this.id, callback); |
| }, |
| |
| 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(","); |
| }, |
| |
| appropriateSelectorFor: function(justSelector) |
| { |
| var lowerCaseName = this.localName() || node.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.replace(/\s+/, "."); |
| return (justSelector ? selector : lowerCaseName + selector); |
| } |
| |
| if (lowerCaseName === "input" && this.getAttribute("type")) |
| return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]"; |
| |
| return lowerCaseName; |
| }, |
| |
| _setAttributesPayload: function(attrs) |
| { |
| this._attributes = []; |
| this._attributesMap = {}; |
| for (var i = 0; i < attrs.length; i += 2) |
| this._addAttribute(attrs[i], attrs[i + 1]); |
| }, |
| |
| _insertChild: function(prev, payload) |
| { |
| var node = new WebInspector.DOMNode(this.ownerDocument, payload); |
| if (!prev) { |
| if (!this.children) { |
| // First node |
| this.children = [ node ]; |
| } else |
| this.children.unshift(node); |
| } else |
| this.children.splice(this.children.indexOf(prev) + 1, 0, node); |
| this._renumber(); |
| return node; |
| }, |
| |
| removeChild_: function(node) |
| { |
| this.children.splice(this.children.indexOf(node), 1); |
| node.parentNode = null; |
| this._renumber(); |
| }, |
| |
| _setChildrenPayload: function(payloads) |
| { |
| this.children = []; |
| for (var i = 0; i < payloads.length; ++i) { |
| var payload = payloads[i]; |
| var node = new WebInspector.DOMNode(this.ownerDocument, 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.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null; |
| child.parentNode = this; |
| } |
| }, |
| |
| _addAttribute: function(name, value) |
| { |
| var attr = { |
| "name": name, |
| "value": value, |
| "_node": this |
| }; |
| this._attributesMap[name] = attr; |
| this._attributes.push(attr); |
| }, |
| |
| ownerDocumentElement: function() |
| { |
| // document element is the child of the document / frame owner node that has documentURL property. |
| // FIXME: return document nodes as a part of the DOM tree structure. |
| var node = this; |
| while (node.parentNode && !node.parentNode.documentURL) |
| node = node.parentNode; |
| return node; |
| } |
| } |
| |
| WebInspector.DOMDocument = function(domAgent, payload) |
| { |
| WebInspector.DOMNode.call(this, this, payload); |
| this._listeners = {}; |
| this._domAgent = domAgent; |
| } |
| |
| WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype; |
| |
| WebInspector.DOMAgent = function() { |
| this._idToDOMNode = null; |
| this._document = null; |
| InspectorBackend.registerDomainDispatcher("DOM", new WebInspector.DOMDispatcher(this)); |
| } |
| |
| WebInspector.DOMAgent.Events = { |
| AttrModified: "AttrModified", |
| CharacterDataModified: "CharacterDataModified", |
| NodeInserted: "NodeInserted", |
| NodeRemoved: "NodeRemoved", |
| DocumentUpdated: "DocumentUpdated", |
| ChildNodeCountUpdated: "ChildNodeCountUpdated" |
| } |
| |
| WebInspector.DOMAgent.prototype = { |
| requestDocument: function(callback) |
| { |
| if (this._document) { |
| if (callback) |
| callback(this._document); |
| return; |
| } |
| |
| if (this._pendingDocumentRequestCallbacks) { |
| this._pendingDocumentRequestCallbacks.push(callback); |
| return; |
| } |
| |
| this._pendingDocumentRequestCallbacks = [callback]; |
| |
| 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)); |
| }, |
| |
| pushNodeToFrontend: function(objectId, callback) |
| { |
| this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeToFrontend.bind(DOMAgent), objectId, callback); |
| }, |
| |
| pushNodeByPathToFrontend: function(path, callback) |
| { |
| this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent), path, callback); |
| }, |
| |
| _wrapClientCallback: function(callback) |
| { |
| if (!callback) |
| return; |
| return function(error, result) |
| { |
| if (error) |
| console.error("Error during DOMAgent operation: " + error); |
| callback(error ? null : result); |
| } |
| }, |
| |
| _dispatchWhenDocumentAvailable: function(action) |
| { |
| var requestArguments = Array.prototype.slice.call(arguments, 1); |
| var callbackWrapper; |
| |
| if (typeof requestArguments[requestArguments.length - 1] === "function") { |
| var callback = requestArguments.pop(); |
| callbackWrapper = this._wrapClientCallback(callback); |
| requestArguments.push(callbackWrapper); |
| } |
| function onDocumentAvailable() |
| { |
| if (this._document) |
| action.apply(null, requestArguments); |
| else { |
| if (callbackWrapper) |
| callbackWrapper("No document"); |
| } |
| } |
| this.requestDocument(onDocumentAvailable.bind(this)); |
| }, |
| |
| _attributesUpdated: function(nodeId, attrsArray) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| node._setAttributesPayload(attrsArray); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, node); |
| }, |
| |
| _characterDataModified: function(nodeId, newValue) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| node._nodeValue = newValue; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node); |
| }, |
| |
| nodeForId: function(nodeId) |
| { |
| return this._idToDOMNode[nodeId]; |
| }, |
| |
| _documentUpdated: function() |
| { |
| this._setDocument(null); |
| this.requestDocument(); |
| }, |
| |
| _setDocument: function(payload) |
| { |
| this._idToDOMNode = {}; |
| if (payload && "id" in payload) { |
| this._document = new WebInspector.DOMDocument(this, payload); |
| this._idToDOMNode[payload.id] = this._document; |
| if (this._document.children) |
| this._bindNodes(this._document.children); |
| } else |
| this._document = null; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document); |
| }, |
| |
| _setDetachedRoot: function(payload) |
| { |
| var root = new WebInspector.DOMNode(this._document, payload); |
| this._idToDOMNode[payload.id] = root; |
| }, |
| |
| _setChildNodes: function(parentId, payloads) |
| { |
| if (!parentId && payloads.length) { |
| this._setDetachedRoot(payloads[0]); |
| return; |
| } |
| |
| var parent = this._idToDOMNode[parentId]; |
| parent._setChildrenPayload(payloads); |
| this._bindNodes(parent.children); |
| }, |
| |
| _bindNodes: function(children) |
| { |
| for (var i = 0; i < children.length; ++i) { |
| var child = children[i]; |
| this._idToDOMNode[child.id] = child; |
| if (child.children) |
| this._bindNodes(child.children); |
| } |
| }, |
| |
| _childNodeCountUpdated: function(nodeId, newValue) |
| { |
| var node = this._idToDOMNode[nodeId]; |
| node._childNodeCount = newValue; |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node); |
| }, |
| |
| _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); |
| }, |
| |
| _childNodeRemoved: function(parentId, nodeId) |
| { |
| var parent = this._idToDOMNode[parentId]; |
| var node = this._idToDOMNode[nodeId]; |
| parent.removeChild_(node); |
| this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node:node, parent:parent}); |
| delete this._idToDOMNode[nodeId]; |
| if (Preferences.nativeInstrumentationEnabled) |
| WebInspector.panels.elements.sidebarPanes.domBreakpoints.nodeRemoved(node); |
| }, |
| |
| performSearch: function(query, searchResultCollector, searchSynchronously) |
| { |
| this._searchResultCollector = searchResultCollector; |
| DOMAgent.performSearch(query, !!searchSynchronously); |
| }, |
| |
| cancelSearch: function() |
| { |
| delete this._searchResultCollector; |
| DOMAgent.cancelSearch(); |
| }, |
| |
| querySelector: function(nodeId, selectors, callback) |
| { |
| DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback)); |
| }, |
| |
| querySelectorAll: function(nodeId, selectors, callback) |
| { |
| DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback)); |
| } |
| } |
| |
| WebInspector.DOMAgent.prototype.__proto__ = WebInspector.Object.prototype; |
| |
| WebInspector.DOMDispatcher = function(domAgent) |
| { |
| this._domAgent = domAgent; |
| } |
| |
| WebInspector.DOMDispatcher.prototype = { |
| documentUpdated: function() |
| { |
| this._domAgent._documentUpdated(); |
| }, |
| |
| attributesUpdated: function(nodeId, attrsArray) |
| { |
| this._domAgent._attributesUpdated(nodeId, attrsArray); |
| }, |
| |
| characterDataModified: function(nodeId, newValue) |
| { |
| this._domAgent._characterDataModified(nodeId, newValue); |
| }, |
| |
| setChildNodes: function(parentId, payloads) |
| { |
| this._domAgent._setChildNodes(parentId, payloads); |
| }, |
| |
| childNodeCountUpdated: function(nodeId, newValue) |
| { |
| this._domAgent._childNodeCountUpdated(nodeId, newValue); |
| }, |
| |
| childNodeInserted: function(parentId, prevId, payload) |
| { |
| this._domAgent._childNodeInserted(parentId, prevId, payload); |
| }, |
| |
| childNodeRemoved: function(parentId, nodeId) |
| { |
| this._domAgent._childNodeRemoved(parentId, nodeId); |
| }, |
| |
| inspectElementRequested: function(nodeId) |
| { |
| WebInspector.updateFocusedNode(nodeId); |
| }, |
| |
| searchResults: function(nodeIds) |
| { |
| if (this._domAgent._searchResultCollector) |
| this._domAgent._searchResultCollector(nodeIds); |
| } |
| } |