blob: 4210e420a4d9a52717f0792cc6ed4e4cbac2bb41 [file] [log] [blame]
/*
* 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);
}
}