Web Inspector: Remove harmless error for not getting named flows
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / DOMTreeManager.js
1 /*
2  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  * Copyright (C) 2013 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following disclaimer
14  * in the documentation and/or other materials provided with the
15  * distribution.
16  *     * Neither the name of Google Inc. nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 WebInspector.DOMTreeManager = class DOMTreeManager extends WebInspector.Object
34 {
35     constructor()
36     {
37         super();
38
39         this._idToDOMNode = {};
40         this._document = null;
41         this._attributeLoadNodeIds = {};
42         this._flows = new Map;
43         this._contentNodesToFlowsMap = new Map;
44         this._restoreSelectedNodeIsAllowed = true;
45
46         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
47     }
48
49     // Static
50
51     static _flowPayloadHashKey(flowPayload)
52     {
53         // Use the flow node id, to avoid collisions when we change main document id.
54         return flowPayload.documentNodeId + ":" + flowPayload.name;
55     }
56
57     // Public
58
59     requestDocument(callback)
60     {
61         if (this._document) {
62             if (callback)
63                 callback(this._document);
64             return;
65         }
66
67         if (this._pendingDocumentRequestCallbacks) {
68             this._pendingDocumentRequestCallbacks.push(callback);
69             return;
70         }
71
72         this._pendingDocumentRequestCallbacks = [callback];
73
74         function onDocumentAvailable(error, root)
75         {
76             if (!error)
77                 this._setDocument(root);
78
79             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
80                 var callback = this._pendingDocumentRequestCallbacks[i];
81                 if (callback)
82                     callback(this._document);
83             }
84             delete this._pendingDocumentRequestCallbacks;
85         }
86
87         DOMAgent.getDocument(onDocumentAvailable.bind(this));
88     }
89
90     pushNodeToFrontend(objectId, callback)
91     {
92         this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
93     }
94
95     pushNodeByPathToFrontend(path, callback)
96     {
97         var callbackCast = callback;
98         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callbackCast);
99     }
100
101     // Private
102
103     _wrapClientCallback(callback)
104     {
105         if (!callback)
106             return;
107         return function(error, result) {
108             if (error)
109                 console.error("Error during DOMAgent operation: " + error);
110             callback(error ? null : result);
111         };
112     }
113
114     _dispatchWhenDocumentAvailable(func, callback)
115     {
116         var callbackWrapper = this._wrapClientCallback(callback);
117
118         function onDocumentAvailable()
119         {
120             if (this._document)
121                 func(callbackWrapper);
122             else {
123                 if (callbackWrapper)
124                     callbackWrapper("No document");
125             }
126         }
127         this.requestDocument(onDocumentAvailable.bind(this));
128     }
129
130     _attributeModified(nodeId, name, value)
131     {
132         var node = this._idToDOMNode[nodeId];
133         if (!node)
134             return;
135         node._setAttribute(name, value);
136         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, {node, name});
137         node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name});
138     }
139
140     _attributeRemoved(nodeId, name)
141     {
142         var node = this._idToDOMNode[nodeId];
143         if (!node)
144             return;
145         node._removeAttribute(name);
146         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeRemoved, {node, name});
147         node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeRemoved, {name});
148     }
149
150     _inlineStyleInvalidated(nodeIds)
151     {
152         for (var i = 0; i < nodeIds.length; ++i)
153             this._attributeLoadNodeIds[nodeIds[i]] = true;
154         if ("_loadNodeAttributesTimeout" in this)
155             return;
156         this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
157     }
158
159     _loadNodeAttributes()
160     {
161         function callback(nodeId, error, attributes)
162         {
163             if (error) {
164                 console.error("Error during DOMAgent operation: " + error);
165                 return;
166             }
167             var node = this._idToDOMNode[nodeId];
168             if (node) {
169                 node._setAttributesPayload(attributes);
170                 this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.AttributeModified, { node, name: "style" });
171                 node.dispatchEventToListeners(WebInspector.DOMNode.Event.AttributeModified, {name: "style"});
172             }
173         }
174
175         delete this._loadNodeAttributesTimeout;
176
177         for (var nodeId in this._attributeLoadNodeIds) {
178             var nodeIdAsNumber = parseInt(nodeId, 10);
179             DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
180         }
181         this._attributeLoadNodeIds = {};
182     }
183
184     _characterDataModified(nodeId, newValue)
185     {
186         var node = this._idToDOMNode[nodeId];
187         node._nodeValue = newValue;
188         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.CharacterDataModified, {node});
189     }
190
191     nodeForId(nodeId)
192     {
193         return this._idToDOMNode[nodeId];
194     }
195
196     _documentUpdated()
197     {
198         this._setDocument(null);
199     }
200
201     _setDocument(payload)
202     {
203         this._idToDOMNode = {};
204         if (payload && "nodeId" in payload)
205             this._document = new WebInspector.DOMNode(this, null, false, payload);
206         else
207             this._document = null;
208         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DocumentUpdated, this._document);
209     }
210
211     _setDetachedRoot(payload)
212     {
213         new WebInspector.DOMNode(this, null, false, payload);
214     }
215
216     _setChildNodes(parentId, payloads)
217     {
218         if (!parentId && payloads.length) {
219             this._setDetachedRoot(payloads[0]);
220             return;
221         }
222
223         var parent = this._idToDOMNode[parentId];
224         parent._setChildrenPayload(payloads);
225     }
226
227     _childNodeCountUpdated(nodeId, newValue)
228     {
229         var node = this._idToDOMNode[nodeId];
230         node.childNodeCount = newValue;
231         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ChildNodeCountUpdated, node);
232     }
233
234     _childNodeInserted(parentId, prevId, payload)
235     {
236         var parent = this._idToDOMNode[parentId];
237         var prev = this._idToDOMNode[prevId];
238         var node = parent._insertChild(prev, payload);
239         this._idToDOMNode[node.id] = node;
240         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeInserted, {node, parent});
241     }
242
243     _childNodeRemoved(parentId, nodeId)
244     {
245         var parent = this._idToDOMNode[parentId];
246         var node = this._idToDOMNode[nodeId];
247         parent._removeChild(node);
248         this._unbind(node);
249         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.NodeRemoved, {node:node, parent});
250     }
251
252     _unbind(node)
253     {
254         this._removeContentNodeFromFlowIfNeeded(node);
255
256         delete this._idToDOMNode[node.id];
257         for (var i = 0; node.children && i < node.children.length; ++i)
258             this._unbind(node.children[i]);
259     }
260
261     get restoreSelectedNodeIsAllowed()
262     {
263         return this._restoreSelectedNodeIsAllowed;
264     }
265
266     inspectElement(nodeId)
267     {
268         var node = this._idToDOMNode[nodeId];
269         if (!node || !node.ownerDocument)
270             return;
271
272         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, {node});
273
274         this._inspectModeEnabled = false;
275         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
276     }
277
278     inspectNodeObject(remoteObject)
279     {
280         this._restoreSelectedNodeIsAllowed = false;
281
282         function nodeAvailable(nodeId)
283         {
284             remoteObject.release();
285
286             console.assert(nodeId);
287             if (!nodeId)
288                 return;
289
290             this.inspectElement(nodeId);
291         }
292
293         remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
294     }
295
296     performSearch(query, searchCallback)
297     {
298         this.cancelSearch();
299
300         function callback(error, searchId, resultsCount)
301         {
302             this._searchId = searchId;
303             searchCallback(resultsCount);
304         }
305         DOMAgent.performSearch(query, callback.bind(this));
306     }
307
308     searchResult(index, callback)
309     {
310         function mycallback(error, nodeIds)
311         {
312             if (error) {
313                 console.error(error);
314                 callback(null);
315                 return;
316             }
317             if (nodeIds.length !== 1)
318                 return;
319
320             callback(this._idToDOMNode[nodeIds[0]]);
321         }
322
323         if (this._searchId)
324             DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
325         else
326             callback(null);
327     }
328
329     cancelSearch()
330     {
331         if (this._searchId) {
332             DOMAgent.discardSearchResults(this._searchId);
333             delete this._searchId;
334         }
335     }
336
337     querySelector(nodeId, selectors, callback)
338     {
339         var callbackCast = callback;
340         DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callbackCast));
341     }
342
343     querySelectorAll(nodeId, selectors, callback)
344     {
345         var callbackCast = callback;
346         DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callbackCast));
347     }
348
349     highlightDOMNode(nodeId, mode)
350     {
351         if (this._hideDOMNodeHighlightTimeout) {
352             clearTimeout(this._hideDOMNodeHighlightTimeout);
353             delete this._hideDOMNodeHighlightTimeout;
354         }
355
356         this._highlightedDOMNodeId = nodeId;
357         if (nodeId)
358             DOMAgent.highlightNode.invoke({nodeId, highlightConfig: this._buildHighlightConfig(mode)});
359         else
360             DOMAgent.hideHighlight();
361     }
362
363     highlightRect(rect, usePageCoordinates)
364     {
365         DOMAgent.highlightRect.invoke({
366             x: rect.x,
367             y: rect.y,
368             width: rect.width,
369             height: rect.height,
370             color: {r: 111, g: 168, b: 220, a: 0.66},
371             outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
372             usePageCoordinates
373         });
374     }
375
376     hideDOMNodeHighlight()
377     {
378         this.highlightDOMNode(0);
379     }
380
381     highlightDOMNodeForTwoSeconds(nodeId)
382     {
383         this.highlightDOMNode(nodeId);
384         this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
385     }
386
387     get inspectModeEnabled()
388     {
389         return this._inspectModeEnabled;
390     }
391
392     set inspectModeEnabled(enabled)
393     {
394         function callback(error)
395         {
396             this._inspectModeEnabled = error ? false : enabled;
397             this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.InspectModeStateChanged);
398         }
399
400         DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), callback.bind(this));
401     }
402
403     _buildHighlightConfig(mode)
404     {
405         mode = mode || "all";
406         var highlightConfig = { showInfo: mode === "all" };
407         if (mode === "all" || mode === "content")
408             highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
409
410         if (mode === "all" || mode === "padding")
411             highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
412
413         if (mode === "all" || mode === "border")
414             highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
415
416         if (mode === "all" || mode === "margin")
417             highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
418
419         return highlightConfig;
420     }
421
422     _createContentFlowFromPayload(flowPayload)
423     {
424         // FIXME: Collect the regions from the payload.
425         var flow = new WebInspector.ContentFlow(flowPayload.documentNodeId, flowPayload.name, flowPayload.overset, flowPayload.content.map(this.nodeForId.bind(this)));
426
427         for (var contentNode of flow.contentNodes) {
428             console.assert(!this._contentNodesToFlowsMap.has(contentNode.id));
429             this._contentNodesToFlowsMap.set(contentNode.id, flow);
430         }
431
432         return flow;
433     }
434
435     _updateContentFlowFromPayload(contentFlow, flowPayload)
436     {
437         console.assert(contentFlow.contentNodes.length === flowPayload.content.length);
438         console.assert(contentFlow.contentNodes.every(function(node, i) { return node.id === flowPayload.content[i]; }));
439
440         // FIXME: Collect the regions from the payload.
441         contentFlow.overset = flowPayload.overset;
442     }
443
444     getNamedFlowCollection(documentNodeIdentifier)
445     {
446         function onNamedFlowCollectionAvailable(error, flows)
447         {
448             if (error)
449                 return;
450             this._contentNodesToFlowsMap.clear();
451             var contentFlows = [];
452             for (var i = 0; i < flows.length; ++i) {
453                 var flowPayload = flows[i];
454                 var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
455                 var contentFlow = this._flows.get(flowKey);
456                 if (contentFlow)
457                     this._updateContentFlowFromPayload(contentFlow, flowPayload);
458                 else {
459                     contentFlow = this._createContentFlowFromPayload(flowPayload);
460                     this._flows.set(flowKey, contentFlow);
461                 }
462                 contentFlows.push(contentFlow);
463             }
464             this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowListWasUpdated, {documentNodeIdentifier, flows: contentFlows});
465         }
466
467         if (window.CSSAgent && CSSAgent.getNamedFlowCollection)
468             CSSAgent.getNamedFlowCollection(documentNodeIdentifier, onNamedFlowCollectionAvailable.bind(this));
469     }
470
471     namedFlowCreated(flowPayload)
472     {
473         var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
474         console.assert(!this._flows.has(flowKey));
475         var contentFlow = this._createContentFlowFromPayload(flowPayload);
476         this._flows.set(flowKey, contentFlow);
477         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasAdded, {flow: contentFlow});
478     }
479
480     namedFlowRemoved(documentNodeIdentifier, flowName)
481     {
482         var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
483         var contentFlow = this._flows.get(flowKey);
484         console.assert(contentFlow);
485         this._flows.delete(flowKey);
486
487         // Remove any back links to this flow from the content nodes.
488         for (var contentNode of contentFlow.contentNodes)
489             this._contentNodesToFlowsMap.delete(contentNode.id);
490
491         this.dispatchEventToListeners(WebInspector.DOMTreeManager.Event.ContentFlowWasRemoved, {flow: contentFlow});
492     }
493
494     _sendNamedFlowUpdateEvents(flowPayload)
495     {
496         var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey(flowPayload);
497         console.assert(this._flows.has(flowKey));
498         this._updateContentFlowFromPayload(this._flows.get(flowKey), flowPayload);
499     }
500
501     regionOversetChanged(flowPayload)
502     {
503         this._sendNamedFlowUpdateEvents(flowPayload);
504     }
505
506     registeredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId, nextContentElementNodeId)
507     {
508         var flowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName});
509         console.assert(this._flows.has(flowKey));
510         console.assert(!this._contentNodesToFlowsMap.has(contentNodeId));
511
512         var flow = this._flows.get(flowKey);
513         var contentNode = this.nodeForId(contentNodeId);
514
515         this._contentNodesToFlowsMap.set(contentNode.id, flow);
516
517         if (nextContentElementNodeId)
518             flow.insertContentNodeBefore(contentNode, this.nodeForId(nextContentElementNodeId));
519         else
520             flow.appendContentNode(contentNode);
521     }
522
523     _removeContentNodeFromFlowIfNeeded(node)
524     {
525         if (!this._contentNodesToFlowsMap.has(node.id))
526             return;
527         var flow = this._contentNodesToFlowsMap.get(node.id);
528         this._contentNodesToFlowsMap.delete(node.id);
529         flow.removeContentNode(node);
530     }
531
532     unregisteredNamedFlowContentElement(documentNodeIdentifier, flowName, contentNodeId)
533     {
534         console.assert(this._contentNodesToFlowsMap.has(contentNodeId));
535
536         var flow = this._contentNodesToFlowsMap.get(contentNodeId);
537         console.assert(flow.id === WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: documentNodeIdentifier, name: flowName}));
538
539         this._contentNodesToFlowsMap.delete(contentNodeId);
540         flow.removeContentNode(this.nodeForId(contentNodeId));
541     }
542
543     _coerceRemoteArrayOfDOMNodes(objectId, callback)
544     {
545         var length, nodes, received = 0, lastError = null, domTreeManager = this;
546
547         function nodeRequested(index, error, nodeId)
548         {
549             if (error)
550                 lastError = error;
551             else
552                 nodes[index] = domTreeManager._idToDOMNode[nodeId];
553             if (++received === length)
554                 callback(lastError, nodes);
555         }
556
557         WebInspector.runtimeManager.getPropertiesForRemoteObject(objectId, function(error, properties) {
558             if (error) {
559                 callback(error);
560                 return;
561             }
562
563             var lengthProperty = properties.get("length");
564             if (!lengthProperty || lengthProperty.value.type !== "number") {
565                 callback(null);
566                 return;
567             }
568
569             length = lengthProperty.value.value;
570             if (!length) {
571                 callback(null, []);
572                 return;
573             }
574
575             nodes = new Array(length);
576             for (var i = 0; i < length; ++i) {
577                 var nodeProperty = properties.get(String(i));
578                 console.assert(nodeProperty.value.type === "object");
579                 DOMAgent.requestNode(nodeProperty.value.objectId, nodeRequested.bind(null, i));
580             }
581         });
582     }
583
584     getNodeContentFlowInfo(domNode, resultReadyCallback)
585     {
586         DOMAgent.resolveNode(domNode.id, domNodeResolved.bind(this));
587
588         function domNodeResolved(error, remoteObject)
589         {
590             if (error) {
591                 resultReadyCallback(error);
592                 return;
593             }
594             // Serialize "backendFunction" and execute it in the context of the page
595             // passing the DOMNode as the "this" reference.
596             var evalParameters = {
597                 objectId: remoteObject.objectId,
598                 functionDeclaration: appendWebInspectorSourceURL(backendFunction.toString()),
599                 doNotPauseOnExceptionsAndMuteConsole: true,
600                 returnByValue: false,
601                 generatePreview: false
602             };
603             RuntimeAgent.callFunctionOn.invoke(evalParameters, regionNodesAvailable.bind(this));
604         }
605
606         function regionNodesAvailable(error, remoteObject, wasThrown)
607         {
608             if (error) {
609                 resultReadyCallback(error);
610                 return;
611             }
612
613             if (wasThrown) {
614                 // We should never get here, but having the error is useful for debugging.
615                 console.error("Error while executing backend function:", JSON.stringify(remoteObject));
616                 resultReadyCallback(null);
617                 return;
618             }
619
620             // The backend function can never return null.
621             console.assert(remoteObject.type === "object");
622             console.assert(remoteObject.objectId);
623             WebInspector.runtimeManager.getPropertiesForRemoteObject(remoteObject.objectId, remoteObjectPropertiesAvailable.bind(this));
624         }
625
626         function remoteObjectPropertiesAvailable(error, properties) {
627             if (error) {
628                 resultReadyCallback(error);
629                 return;
630             }
631
632             var result = {
633                 regionFlow: null,
634                 contentFlow: null,
635                 regions: null
636             };
637
638             var regionFlowNameProperty = properties.get("regionFlowName");
639             if (regionFlowNameProperty && regionFlowNameProperty.value && regionFlowNameProperty.value.value) {
640                 console.assert(regionFlowNameProperty.value.type === "string");
641                 var regionFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: regionFlowNameProperty.value.value});
642                 result.regionFlow = this._flows.get(regionFlowKey);
643             }
644
645             var contentFlowNameProperty = properties.get("contentFlowName");
646             if (contentFlowNameProperty && contentFlowNameProperty.value && contentFlowNameProperty.value.value) {
647                 console.assert(contentFlowNameProperty.value.type === "string");
648                 var contentFlowKey = WebInspector.DOMTreeManager._flowPayloadHashKey({documentNodeId: domNode.ownerDocument.id, name: contentFlowNameProperty.value.value});
649                 result.contentFlow = this._flows.get(contentFlowKey);
650             }
651
652             var regionsProperty = properties.get("regions");
653             if (!regionsProperty || !regionsProperty.value.objectId) {
654                 // The list of regions is null.
655                 resultReadyCallback(null, result);
656                 return;
657             }
658
659             console.assert(regionsProperty.value.type === "object");
660             console.assert(regionsProperty.value.subtype === "array");
661             this._coerceRemoteArrayOfDOMNodes(regionsProperty.value.objectId, function(error, nodes) {
662                 result.regions = nodes;
663                 resultReadyCallback(error, result);
664             });
665         }
666
667         // Note that "backendFunction" is serialized and executed in the context of the page.
668         function backendFunction()
669         {
670             function getComputedProperty(node, propertyName)
671             {
672                 if (!node.ownerDocument || !node.ownerDocument.defaultView)
673                     return null;
674                 var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node);
675                 return computedStyle ? computedStyle[propertyName] : null;
676             }
677
678             function getContentFlowName(node)
679             {
680                 for (; node; node = node.parentNode) {
681                     var flowName = getComputedProperty(node, "webkitFlowInto");
682                     if (flowName && flowName !== "none")
683                         return flowName;
684                 }
685                 return null;
686             }
687
688             var node = this;
689
690             // Even detached nodes have an ownerDocument.
691             console.assert(node.ownerDocument);
692
693             var result = {
694                 regionFlowName: getComputedProperty(node, "webkitFlowFrom"),
695                 contentFlowName: getContentFlowName(node),
696                 regions: null
697             };
698
699             if (result.contentFlowName) {
700                 var flowThread = node.ownerDocument.webkitGetNamedFlows().namedItem(result.contentFlowName);
701                 if (flowThread)
702                     result.regions = flowThread.getRegionsByContent(node);
703             }
704
705             return result;
706         }
707     }
708
709     // Private
710
711     _mainResourceDidChange(event)
712     {
713         if (event.target.isMainFrame())
714             this._restoreSelectedNodeIsAllowed = true;
715     }
716 };
717
718 WebInspector.DOMTreeManager.Event = {
719     AttributeModified: "dom-tree-manager-attribute-modified",
720     AttributeRemoved: "dom-tree-manager-attribute-removed",
721     CharacterDataModified: "dom-tree-manager-character-data-modified",
722     NodeInserted: "dom-tree-manager-node-inserted",
723     NodeRemoved: "dom-tree-manager-node-removed",
724     DocumentUpdated: "dom-tree-manager-document-updated",
725     ChildNodeCountUpdated: "dom-tree-manager-child-node-count-updated",
726     DOMNodeWasInspected: "dom-tree-manager-dom-node-was-inspected",
727     InspectModeStateChanged: "dom-tree-manager-inspect-mode-state-changed",
728     ContentFlowListWasUpdated: "dom-tree-manager-content-flow-list-was-updated",
729     ContentFlowWasAdded: "dom-tree-manager-content-flow-was-added",
730     ContentFlowWasRemoved: "dom-tree-manager-content-flow-was-removed",
731     RegionOversetChanged: "dom-tree-manager-region-overset-changed"
732 };