summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js789
1 files changed, 789 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
new file mode 100644
index 000000000..6454fc77d
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
@@ -0,0 +1,789 @@
+/*
+ * 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.
+ */
+
+WebInspector.DOMTreeManager = class DOMTreeManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ this._idToDOMNode = {};
+ this._document = null;
+ this._attributeLoadNodeIds = {};
+ this._flows = new Map;
+ this._contentNodesToFlowsMap = new Map;
+ this._restoreSelectedNodeIsAllowed = true;
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ }
+
+ // Static
+
+ static _flowPayloadHashKey(flowPayload)
+ {
+ // Use the flow node id, to avoid collisions when we change main document id.
+ return flowPayload.documentNodeId + ":" + flowPayload.name;
+ }
+
+ // Public
+
+ requestDocument(callback)
+ {
+ if (this._document) {
+ 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 (let callback of this._pendingDocumentRequestCallbacks)
+ callback(this._document);
+
+ this._pendingDocumentRequestCallbacks = null;
+ }
+
+ DOMAgent.getDocument(onDocumentAvailable.bind(this));
+ }
+
+ pushNodeToFrontend(objectId, callback)
+ {
+ this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
+ }
+
+ pushNodeByPathToFrontend(path, callback)
+ {
+ this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callback);
+ }
+
+ // Private
+
+ _wrapClientCallback(callback)
+ {
+ if (!callback)
+ return null;
+
+ return function(error, result) {
+ if (error)
+ console.error("Error during DOMAgent operation: " + error);
+ callback(error ? null : result);
+ };
+ }
+
+ _dispatchWhenDocumentAvailable(func, callback)
+ {
+ var callbackWrapper = this._wrapClientCallback(callback);
+
+ function onDocumentAvailable()
+ {
+ if (this._document)
+ func(callbackWrapper);
+ else {
+ if (callbackWrapper)
+ callbackWrapper("No document");
+ }
+ }
+ this.requestDocument(onDocumentAvailable.bind(this));
+ }
+
+ _attributeModified(nodeId, name, value)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node)
+ return;
+
+ node._setAttribute(name, value);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name});
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name});
+ }
+
+ _attributeRemoved(nodeId, name)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node)
+ return;
+
+ node._removeAttribute(name);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeRemoved, {node, name});
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeRemoved, {name});
+ }
+
+ _inlineStyleInvalidated(nodeIds)
+ {
+ for (var nodeId of nodeIds)
+ this._attributeLoadNodeIds[nodeId] = true;
+ if ("_loadNodeAttributesTimeout" in this)
+ return;
+ this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
+ }
+
+ _loadNodeAttributes()
+ {
+ 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, name: "style" });
+ node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: "style"});
+ }
+ }
+
+ this._loadNodeAttributesTimeout = undefined;
+
+ for (var nodeId in this._attributeLoadNodeIds) {
+ var nodeIdAsNumber = parseInt(nodeId);
+ DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
+ }
+ this._attributeLoadNodeIds = {};
+ }
+
+ _characterDataModified(nodeId, newValue)
+ {
+ var node = this._idToDOMNode[nodeId];
+ node._nodeValue = newValue;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.CharacterDataModified, {node});
+ }
+
+ nodeForId(nodeId)
+ {
+ return this._idToDOMNode[nodeId];
+ }
+
+ _documentUpdated()
+ {
+ this._setDocument(null);
+ }
+
+ _setDocument(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);
+ }
+
+ _setDetachedRoot(payload)
+ {
+ new WebInspector.DOMNode(this, null, false, payload);
+ }
+
+ _setChildNodes(parentId, payloads)
+ {
+ if (!parentId && payloads.length) {
+ this._setDetachedRoot(payloads[0]);
+ return;
+ }
+
+ var parent = this._idToDOMNode[parentId];
+ parent._setChildrenPayload(payloads);
+ }
+
+ _childNodeCountUpdated(nodeId, newValue)
+ {
+ var node = this._idToDOMNode[nodeId];
+ node.childNodeCount = newValue;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ChildNodeCountUpdated, node);
+ }
+
+ _childNodeInserted(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, parent});
+ }
+
+ _childNodeRemoved(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, parent});
+ }
+
+ _pseudoElementAdded(parentId, pseudoElement)
+ {
+ var parent = this._idToDOMNode[parentId];
+ if (!parent)
+ return;
+
+ var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
+ node.parentNode = parent;
+ this._idToDOMNode[node.id] = node;
+ console.assert(!parent.pseudoElements().get(node.pseudoType()));
+ parent.pseudoElements().set(node.pseudoType(), node);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
+ }
+
+ _pseudoElementRemoved(parentId, pseudoElementId)
+ {
+ var pseudoElement = this._idToDOMNode[pseudoElementId];
+ if (!pseudoElement)
+ return;
+
+ var parent = pseudoElement.parentNode;
+ console.assert(parent);
+ console.assert(parent.id === parentId);
+ if (!parent)
+ return;
+
+ parent._removeChild(pseudoElement);
+ this._unbind(pseudoElement);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node: pseudoElement, parent});
+ }
+
+ _unbind(node)
+ {
+ this._removeContentNodeFromFlowIfNeeded(node);
+
+ delete this._idToDOMNode[node.id];
+
+ for (let i = 0; node.children && i < node.children.length; ++i)
+ this._unbind(node.children[i]);
+
+ let templateContent = node.templateContent();
+ if (templateContent)
+ this._unbind(templateContent);
+
+ for (let pseudoElement of node.pseudoElements().values())
+ this._unbind(pseudoElement);
+
+ // FIXME: Handle shadow roots.
+ }
+
+ get restoreSelectedNodeIsAllowed()
+ {
+ return this._restoreSelectedNodeIsAllowed;
+ }
+
+ inspectElement(nodeId)
+ {
+ var node = this._idToDOMNode[nodeId];
+ if (!node || !node.ownerDocument)
+ return;
+
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, {node});
+
+ this._inspectModeEnabled = false;
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
+ }
+
+ inspectNodeObject(remoteObject)
+ {
+ this._restoreSelectedNodeIsAllowed = false;
+
+ function nodeAvailable(nodeId)
+ {
+ remoteObject.release();
+
+ console.assert(nodeId);
+ if (!nodeId)
+ return;
+
+ this.inspectElement(nodeId);
+
+ // Re-resolve the node in the console's object group when adding to the console.
+ let domNode = this.nodeForId(nodeId);
+ WebInspector.RemoteObject.resolveNode(domNode, WebInspector.RuntimeManager.ConsoleObjectGroup, function(remoteObject) {
+ if (!remoteObject)
+ return;
+ let specialLogStyles = true;
+ let synthetic = true;
+ WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(WebInspector.UIString("Selected Element"), remoteObject, specialLogStyles, synthetic);
+ });
+ }
+
+ remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
+ }
+
+ performSearch(query, searchCallback)
+ {
+ this.cancelSearch();
+
+ function callback(error, searchId, resultsCount)
+ {
+ this._searchId = searchId;
+ searchCallback(resultsCount);
+ }
+ DOMAgent.performSearch(query, callback.bind(this));
+ }
+
+ searchResult(index, callback)
+ {
+ function mycallback(error, nodeIds)
+ {
+ if (error) {
+ console.error(error);
+ callback(null);
+ return;
+ }
+ if (nodeIds.length !== 1)
+ return;
+
+ callback(this._idToDOMNode[nodeIds[0]]);
+ }
+
+ if (this._searchId)
+ DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
+ else
+ callback(null);
+ }
+
+ cancelSearch()
+ {
+ if (this._searchId) {
+ DOMAgent.discardSearchResults(this._searchId);
+ this._searchId = undefined;
+ }
+ }
+
+ querySelector(nodeId, selectors, callback)
+ {
+ DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
+ }
+
+ querySelectorAll(nodeId, selectors, callback)
+ {
+ DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
+ }
+
+ highlightDOMNode(nodeId, mode)
+ {
+ if (this._hideDOMNodeHighlightTimeout) {
+ clearTimeout(this._hideDOMNodeHighlightTimeout);
+ this._hideDOMNodeHighlightTimeout = undefined;
+ }
+
+ this._highlightedDOMNodeId = nodeId;
+ if (nodeId)
+ DOMAgent.highlightNode.invoke({nodeId, highlightConfig: this._buildHighlightConfig(mode)});
+ else
+ DOMAgent.hideHighlight();
+ }
+
+ highlightSelector(selectorText, frameId, mode)
+ {
+ // COMPATIBILITY (iOS 8): DOM.highlightSelector did not exist.
+ if (!DOMAgent.highlightSelector)
+ return;
+
+ DOMAgent.highlightSelector(this._buildHighlightConfig(mode), selectorText, frameId);
+ }
+
+ highlightRect(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
+ });
+ }
+
+ hideDOMNodeHighlight()
+ {
+ this.highlightDOMNode(0);
+ }
+
+ highlightDOMNodeForTwoSeconds(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));
+ }
+
+ _buildHighlightConfig(mode = "all")
+ {
+ let 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;
+ }
+
+ _createContentFlowFromPayload(flowPayload)
+ {
+ // FIXME: Collect the regions from the payload.
+ var flow = new WebInspector.ContentFlow(flowPayload.documentNodeId, flowPayload.name, flowPayload.overset, flowPayload.content.map(this.nodeForId.bind(this)));
+
+ for (var contentNode of flow.contentNodes) {
+ console.assert(!this._contentNodesToFlowsMap.has(contentNode.id));
+ this._contentNodesToFlowsMap.set(contentNode.id, flow);
+ }
+
+ return flow;
+ }
+
+ _updateContentFlowFromPayload(contentFlow, flowPayload)
+ {
+ console.assert(contentFlow.contentNodes.length === flowPayload.content.length);
+ console.assert(contentFlow.contentNodes.every(function(node, i) { return node.id === flowPayload.content[i]; }));
+
+ // FIXME: Collect the regions from the payload.
+ contentFlow.overset = flowPayload.overset;
+ }
+
+ getNamedFlowCollection(documentNodeIdentifier)
+ {
+ function onNamedFlowCollectionAvailable(error, flows)
+ {
+ if (error)
+ return;
+ this._contentNodesToFlowsMap.clear();
+ var contentFlows = [];
+ for (var i = 0; i < flows.length; ++i) {
+ var flowPayload = flows[i];
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ var contentFlow = this._flows.get(flowKey);
+ if (contentFlow)
+ this._updateContentFlowFromPayload(contentFlow, flowPayload);
+ else {
+ contentFlow = this._createContentFlowFromPayload(flowPayload);
+ this._flows.set(flowKey, contentFlow);
+ }
+ contentFlows.push(contentFlow);
+ }
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowListWasUpdated, {documentNodeIdentifier, flows: contentFlows});
+ }
+
+ if (window.CSSAgent)
+ CSSAgent.getNamedFlowCollection(documentNodeIdentifier, onNamedFlowCollectionAvailable.bind(this));
+ }
+
+ namedFlowCreated(flowPayload)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ console.assert(!this._flows.has(flowKey));
+ var contentFlow = this._createContentFlowFromPayload(flowPayload);
+ this._flows.set(flowKey, contentFlow);
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasAdded, {flow: contentFlow});
+ }
+
+ namedFlowRemoved(documentNodeIdentifier, flowName)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
+ var contentFlow = this._flows.get(flowKey);
+ console.assert(contentFlow);
+ this._flows.delete(flowKey);
+
+ // Remove any back links to this flow from the content nodes.
+ for (var contentNode of contentFlow.contentNodes)
+ this._contentNodesToFlowsMap.delete(contentNode.id);
+
+ this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasRemoved, {flow: contentFlow});
+ }
+
+ _sendNamedFlowUpdateEvents(flowPayload)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
+ console.assert(this._flows.has(flowKey));
+ this._updateContentFlowFromPayload(this._flows.get(flowKey), flowPayload);
+ }
+
+ regionOversetChanged(flowPayload)
+ {
+ this._sendNamedFlowUpdateEvents(flowPayload);
+ }
+
+ registeredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId, nextContentElementNodeId)
+ {
+ var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
+ console.assert(this._flows.has(flowKey));
+ console.assert(!this._contentNodesToFlowsMap.has(contentNodeId));
+
+ var flow = this._flows.get(flowKey);
+ var contentNode = this.nodeForId(contentNodeId);
+
+ this._contentNodesToFlowsMap.set(contentNode.id, flow);
+
+ if (nextContentElementNodeId)
+ flow.insertContentNodeBefore(contentNode, this.nodeForId(nextContentElementNodeId));
+ else
+ flow.appendContentNode(contentNode);
+ }
+
+ _removeContentNodeFromFlowIfNeeded(node)
+ {
+ if (!this._contentNodesToFlowsMap.has(node.id))
+ return;
+ var flow = this._contentNodesToFlowsMap.get(node.id);
+ this._contentNodesToFlowsMap.delete(node.id);
+ flow.removeContentNode(node);
+ }
+
+ unregisteredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId)
+ {
+ console.assert(this._contentNodesToFlowsMap.has(contentNodeId));
+
+ var flow = this._contentNodesToFlowsMap.get(contentNodeId);
+ console.assert(flow.id === WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName}));
+
+ this._contentNodesToFlowsMap.delete(contentNodeId);
+ flow.removeContentNode(this.nodeForId(contentNodeId));
+ }
+
+ _coerceRemoteArrayOfDOMNodes(objectId, callback)
+ {
+ var length, nodes, received = 0, lastError = null, domTreeManager = this;
+
+ function nodeRequested(index, error, nodeId)
+ {
+ if (error)
+ lastError = error;
+ else
+ nodes[index] = domTreeManager._idToDOMNode[nodeId];
+ if (++received === length)
+ callback(lastError, nodes);
+ }
+
+ WebInspector.runtimeManager.getPropertiesForRemoteObject(objectId, function(error, properties) {
+ if (error) {
+ callback(error);
+ return;
+ }
+
+ var lengthProperty = properties.get("length");
+ if (!lengthProperty || lengthProperty.value.type !== "number") {
+ callback(null);
+ return;
+ }
+
+ length = lengthProperty.value.value;
+ if (!length) {
+ callback(null, []);
+ return;
+ }
+
+ nodes = new Array(length);
+ for (var i = 0; i < length; ++i) {
+ var nodeProperty = properties.get(String(i));
+ console.assert(nodeProperty.value.type === "object");
+ DOMAgent.requestNode(nodeProperty.value.objectId, nodeRequested.bind(null, i));
+ }
+ });
+ }
+
+ getNodeContentFlowInfo(domNode, resultReadyCallback)
+ {
+ DOMAgent.resolveNode(domNode.id, domNodeResolved.bind(this));
+
+ function domNodeResolved(error, remoteObject)
+ {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+ // Serialize "backendFunction" and execute it in the context of the page
+ // passing the DOMNode as the "this" reference.
+ var evalParameters = {
+ objectId: remoteObject.objectId,
+ functionDeclaration: appendWebInspectorSourceURL(backendFunction.toString()),
+ doNotPauseOnExceptionsAndMuteConsole: true,
+ returnByValue: false,
+ generatePreview: false
+ };
+ RuntimeAgent.callFunctionOn.invoke(evalParameters, regionNodesAvailable.bind(this));
+ }
+
+ function regionNodesAvailable(error, remoteObject, wasThrown)
+ {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+
+ if (wasThrown) {
+ // We should never get here, but having the error is useful for debugging.
+ console.error("Error while executing backend function:", JSON.stringify(remoteObject));
+ resultReadyCallback(null);
+ return;
+ }
+
+ // The backend function can never return null.
+ console.assert(remoteObject.type === "object");
+ console.assert(remoteObject.objectId);
+ WebInspector.runtimeManager.getPropertiesForRemoteObject(remoteObject.objectId, remoteObjectPropertiesAvailable.bind(this));
+ }
+
+ function remoteObjectPropertiesAvailable(error, properties) {
+ if (error) {
+ resultReadyCallback(error);
+ return;
+ }
+
+ var result = {
+ regionFlow: null,
+ contentFlow: null,
+ regions: null
+ };
+
+ var regionFlowNameProperty = properties.get("regionFlowName");
+ if (regionFlowNameProperty && regionFlowNameProperty.value && regionFlowNameProperty.value.value) {
+ console.assert(regionFlowNameProperty.value.type === "string");
+ var regionFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: regionFlowNameProperty.value.value});
+ result.regionFlow = this._flows.get(regionFlowKey);
+ }
+
+ var contentFlowNameProperty = properties.get("contentFlowName");
+ if (contentFlowNameProperty && contentFlowNameProperty.value && contentFlowNameProperty.value.value) {
+ console.assert(contentFlowNameProperty.value.type === "string");
+ var contentFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: contentFlowNameProperty.value.value});
+ result.contentFlow = this._flows.get(contentFlowKey);
+ }
+
+ var regionsProperty = properties.get("regions");
+ if (!regionsProperty || !regionsProperty.value.objectId) {
+ // The list of regions is null.
+ resultReadyCallback(null, result);
+ return;
+ }
+
+ console.assert(regionsProperty.value.type === "object");
+ console.assert(regionsProperty.value.subtype === "array");
+ this._coerceRemoteArrayOfDOMNodes(regionsProperty.value.objectId, function(error, nodes) {
+ result.regions = nodes;
+ resultReadyCallback(error, result);
+ });
+ }
+
+ // Note that "backendFunction" is serialized and executed in the context of the page.
+ function backendFunction()
+ {
+ function getComputedProperty(node, propertyName)
+ {
+ if (!node.ownerDocument || !node.ownerDocument.defaultView)
+ return null;
+ var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
+ return computedStyle ? computedStyle[propertyName] : null;
+ }
+
+ function getContentFlowName(node)
+ {
+ for (; node; node = node.parentNode) {
+ var flowName = getComputedProperty(node, "webkitFlowInto");
+ if (flowName && flowName !== "none")
+ return flowName;
+ }
+ return null;
+ }
+
+ var node = this;
+
+ // Even detached nodes have an ownerDocument.
+ console.assert(node.ownerDocument);
+
+ var result = {
+ regionFlowName: getComputedProperty(node, "webkitFlowFrom"),
+ contentFlowName: getContentFlowName(node),
+ regions: null
+ };
+
+ if (result.contentFlowName) {
+ var flowThread = node.ownerDocument.webkitGetNamedFlows().namedItem(result.contentFlowName);
+ if (flowThread)
+ result.regions = flowThread.getRegionsByContent(node);
+ }
+
+ return result;
+ }
+ }
+
+ // Private
+
+ _mainResourceDidChange(event)
+ {
+ if (event.target.isMainFrame())
+ this._restoreSelectedNodeIsAllowed = true;
+ }
+};
+
+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",
+ ContentFlowListWasUpdated: "dom-tree-manager-content-flow-list-was-updated",
+ ContentFlowWasAdded: "dom-tree-manager-content-flow-was-added",
+ ContentFlowWasRemoved: "dom-tree-manager-content-flow-was-removed",
+ RegionOversetChanged: "dom-tree-manager-region-overset-changed"
+};