summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/DOMTreeManager.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/DOMTreeManager.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/DOMTreeManager.js526
1 files changed, 526 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/DOMTreeManager.js b/Source/WebInspectorUI/UserInterface/DOMTreeManager.js
new file mode 100644
index 000000000..7544820a2
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/DOMTreeManager.js
@@ -0,0 +1,526 @@
+/*
+ * 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.
+ */
+
+/**
+ * @extends {WebInspector.Object}
+ * @constructor
+ */
+WebInspector.DOMTreeManager = function() {
+ /** @type {Object|undefined} */
+ this._idToDOMNode = {};
+ this._document = null;
+ this._attributeLoadNodeIds = {};
+}
+
+WebInspector.Object.addConstructorFunctions(WebInspector.DOMTreeManager);
+
+WebInspector.DOMTreeManager.Event = {
+ AttributeModified: "dom-tree-manager-attribute-modified",
+ AttributeRemoved: "dom-tree-manager-attribute-removed",
+ CharacterDataModified: "dom-tree-manager-character-data-modified",
+ NodeInserted: "dom-tree-manager-node-inserted",
+ NodeRemoved: "dom-tree-manager-node-removed",
+ DocumentUpdated: "dom-tree-manager-document-updated",
+ ChildNodeCountUpdated: "dom-tree-manager-child-node-count-updated",
+ DOMNodeWasInspected: "dom-tree-manager-dom-node-was-inspected",
+ InspectModeStateChanged: "dom-tree-manager-inspect-mode-state-changed"
+}
+
+WebInspector.DOMTreeManager.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.DOMTreeManager}
+ * @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()=} callback
+ */
+ pushNodeToFrontend: function(objectId, callback)
+ {
+ this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
+ },
+
+ /**
+ * @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;
+ return function(error, result)
+ {
+ if (error)
+ console.error("Error during DOMAgent operation: " + error);
+ callback(error ? null : result);
+ }
+ },
+
+ /**
+ * @param {function(function()=)} 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;
+ node._setAttribute(name, value);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, { node: node, name: name });
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: name});
+ },
+
+ /**
+ * @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.DOMTreeManager.Event.AttributeRemoved, { node: node, name: name });
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeRemoved, {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.DOMTreeManager}
+ * @param {DOMAgent.NodeId} nodeId
+ * @param {?Protocol.Error} error
+ * @param {Array.<string>} attributes
+ */
+ function callback(nodeId, error, attributes)
+ {
+ if (error) {
+ console.error("Error during DOMAgent operation: " + error);
+ return;
+ }
+ var node = this._idToDOMNode[nodeId];
+ if (node) {
+ node._setAttributesPayload(attributes);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, { node: node, name: "style" });
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: "style"});
+ }
+ }
+
+ 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.DOMTreeManager.Event.CharacterDataModified, {node: 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.DOMNode(this, null, false, payload);
+ else
+ this._document = null;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DocumentUpdated, this._document);
+ },
+
+ /**
+ * @param {DOMAgent.Node} payload
+ */
+ _setDetachedRoot: function(payload)
+ {
+ 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.DOMTreeManager.Event.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.DOMTreeManager.Event.NodeInserted, {node: node, parent: parent});
+ },
+
+ /**
+ * @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.DOMTreeManager.Event.NodeRemoved, {node:node, parent: parent});
+ },
+
+ /**
+ * @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.DOMTreeManager.Event.DOMNodeWasInspected, {node: node});
+
+ this._inspectModeEnabled = false;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
+ },
+
+ inspectNodeObject: function(remoteObject)
+ {
+ function nodeAvailable(nodeId)
+ {
+ remoteObject.release();
+
+ console.assert(nodeId);
+ if (!nodeId)
+ return;
+
+ this.inspectElement(nodeId);
+ }
+
+ remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
+ },
+
+ /**
+ * @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.invoke({nodeId: nodeId, highlightConfig: this._buildHighlightConfig(mode)});
+ else
+ DOMAgent.hideHighlight();
+ },
+
+ highlightRect: function(rect, usePageCoordinates)
+ {
+ DOMAgent.highlightRect.invoke({
+ x: rect.x,
+ y: rect.y,
+ width: rect.width,
+ height: rect.height,
+ color: {r: 111, g: 168, b: 220, a: 0.66},
+ outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
+ usePageCoordinates: usePageCoordinates
+ });
+ },
+
+ hideDOMNodeHighlight: function()
+ {
+ this.highlightDOMNode(0);
+ },
+
+ /**
+ * @param {?DOMAgent.NodeId} nodeId
+ */
+ highlightDOMNodeForTwoSeconds: function(nodeId)
+ {
+ this.highlightDOMNode(nodeId);
+ this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
+ },
+
+ get inspectModeEnabled()
+ {
+ return this._inspectModeEnabled;
+ },
+
+ set inspectModeEnabled(enabled)
+ {
+ function callback(error)
+ {
+ this._inspectModeEnabled = error ? false : enabled;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
+ }
+
+ DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), callback.bind(this));
+ },
+
+ /**
+ * @param {string=} mode
+ */
+ _buildHighlightConfig: function(mode)
+ {
+ mode = mode || "all";
+ var highlightConfig = { showInfo: mode === "all" };
+ if (mode === "all" || mode === "content")
+ highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
+
+ if (mode === "all" || mode === "padding")
+ highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
+
+ if (mode === "all" || mode === "border")
+ highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
+
+ if (mode === "all" || mode === "margin")
+ highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
+
+ return highlightConfig;
+ }
+}
+
+WebInspector.DOMTreeManager.prototype.__proto__ = WebInspector.Object.prototype;