diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/DOMTreeManager.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/DOMTreeManager.js | 526 |
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; |