Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / elements / ElementsPanel.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @implements {WebInspector.Searchable}
34  * @implements {WebInspector.TargetManager.Observer}
35  * @extends {WebInspector.Panel}
36  */
37 WebInspector.ElementsPanel = function()
38 {
39     WebInspector.Panel.call(this, "elements");
40     this.registerRequiredCSS("elementsPanel.css");
41     this.setHideOnDetach();
42
43     this._splitView = new WebInspector.SplitView(true, true, "elementsPanelSplitViewState", 325, 325);
44     this._splitView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this));
45     this._splitView.show(this.element);
46
47     this._searchableView = new WebInspector.SearchableView(this);
48     this._searchableView.setMinimumSize(25, 19);
49     this._searchableView.show(this._splitView.mainElement());
50     var stackElement = this._searchableView.element;
51
52     this.contentElement = stackElement.createChild("div");
53     this.contentElement.id = "elements-content";
54     this.contentElement.classList.add("outline-disclosure");
55     this.contentElement.classList.add("source-code");
56     if (!WebInspector.settings.domWordWrap.get())
57         this.contentElement.classList.add("nowrap");
58     WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this));
59
60     this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
61     this._splitView.sidebarElement().addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
62
63     var crumbsContainer = stackElement.createChild("div");
64     crumbsContainer.id = "elements-crumbs";
65     this.crumbsElement = crumbsContainer.createChild("div", "crumbs");
66     this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
67     this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);
68
69     this.sidebarPanes = {};
70     this.sidebarPanes.platformFonts = new WebInspector.PlatformFontsSidebarPane();
71     this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
72     this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNode.bind(this));
73
74     this._matchedStylesFilterBoxContainer = document.createElement("div");
75     this._matchedStylesFilterBoxContainer.className = "sidebar-pane-filter-box";
76     this._computedStylesFilterBoxContainer = document.createElement("div");
77     this._computedStylesFilterBoxContainer.className = "sidebar-pane-filter-box";
78     this.sidebarPanes.styles.setFilterBoxContainers(this._matchedStylesFilterBoxContainer, this._computedStylesFilterBoxContainer);
79
80     this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
81     this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
82     this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this);
83     this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane();
84
85     this.sidebarPanes.styles.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateStyles.bind(this, false));
86     this.sidebarPanes.metrics.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateMetrics.bind(this));
87     this.sidebarPanes.platformFonts.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updatePlatformFonts.bind(this));
88     this.sidebarPanes.properties.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateProperties.bind(this));
89     this.sidebarPanes.eventListeners.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateEventListeners.bind(this));
90
91     this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
92     this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
93     this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);
94     this._extensionSidebarPanes = [];
95
96     WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
97     WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
98     this._dockSideChanged();
99
100     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
101     this._popoverHelper.setTimeout(0);
102
103     /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */
104     this._treeOutlines = [];
105     /** @type {!Map.<!WebInspector.Target, !WebInspector.ElementsTreeOutline>} */
106     this._targetToTreeOutline = new Map();
107     WebInspector.targetManager.observeTargets(this);
108     WebInspector.settings.showUAShadowDOM.addChangeListener(this._showUAShadowDOMChanged.bind(this));
109     WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
110     WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
111 }
112
113 WebInspector.ElementsPanel.prototype = {
114     /**
115      * @param {!WebInspector.Target} target
116      */
117     targetAdded: function(target)
118     {
119         var treeOutline = new WebInspector.ElementsTreeOutline(target, true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNode.bind(this));
120         treeOutline.wireToDOMModel();
121         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
122         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
123         this._treeOutlines.push(treeOutline);
124         this._targetToTreeOutline.put(target, treeOutline);
125
126         // Perform attach if necessary.
127         if (this.isShowing())
128             this.wasShown();
129     },
130
131     /**
132      * @param {!WebInspector.Target} target
133      */
134     targetRemoved: function(target)
135     {
136         var treeOutline = this._targetToTreeOutline.remove(target);
137         treeOutline.unwireFromDOMModel();
138         this._treeOutlines.remove(treeOutline);
139         treeOutline.element.remove();
140     },
141
142     /**
143      * @return {?WebInspector.ElementsTreeOutline}
144      */
145     _firstTreeOutlineDeprecated: function()
146     {
147         return this._treeOutlines[0] || null;
148     },
149
150     _updateTreeOutlineVisibleWidth: function()
151     {
152         if (!this._treeOutlines.length)
153             return;
154
155         var width = this._splitView.element.offsetWidth;
156         if (this._splitView.isVertical())
157             width -= this._splitView.sidebarSize();
158         for (var i = 0; i < this._treeOutlines.length; ++i) {
159             this._treeOutlines[i].setVisibleWidth(width);
160             this._treeOutlines[i].updateSelection();
161         }
162         this.updateBreadcrumbSizes();
163     },
164
165     /**
166      * @return {!Element}
167      */
168     defaultFocusedElement: function()
169     {
170         return this._treeOutlines.length ? this._treeOutlines[0].element : this.element;
171     },
172
173     /**
174      * @return {!WebInspector.SearchableView}
175      */
176     searchableView: function()
177     {
178         return this._searchableView;
179     },
180
181     wasShown: function()
182     {
183         for (var i = 0; i < this._treeOutlines.length; ++i) {
184             var treeOutline = this._treeOutlines[i];
185             // Attach heavy component lazily
186             if (treeOutline.element.parentElement !== this.contentElement)
187                 this.contentElement.appendChild(treeOutline.element);
188         }
189         WebInspector.Panel.prototype.wasShown.call(this);
190         this.updateBreadcrumb();
191
192         for (var i = 0; i < this._treeOutlines.length; ++i) {
193             var treeOutline = this._treeOutlines[i];
194             treeOutline.updateSelection();
195             treeOutline.setVisible(true);
196
197             if (!treeOutline.rootDOMNode)
198                 if (treeOutline.domModel().existingDocument())
199                     this._documentUpdated(treeOutline.domModel(), treeOutline.domModel().existingDocument());
200                 else
201                     treeOutline.domModel().requestDocument();
202         }
203
204     },
205
206     willHide: function()
207     {
208         for (var i = 0; i < this._treeOutlines.length; ++i) {
209             var treeOutline = this._treeOutlines[i];
210             treeOutline.domModel().hideDOMNodeHighlight();
211             treeOutline.setVisible(false);
212             // Detach heavy component on hide
213             this.contentElement.removeChild(treeOutline.element);
214         }
215         this._popoverHelper.hidePopover();
216         WebInspector.Panel.prototype.willHide.call(this);
217     },
218
219     onResize: function()
220     {
221         this._updateTreeOutlineVisibleWidth();
222     },
223
224     omitDefaultSelection: function()
225     {
226         this._omitDefaultSelection = true;
227     },
228
229     stopOmittingDefaultSelection: function()
230     {
231         delete this._omitDefaultSelection;
232     },
233
234     /**
235      * @param {!WebInspector.DOMNode} node
236      * @param {string} pseudoClass
237      * @param {boolean} enable
238      */
239     _setPseudoClassForNode: function(node, pseudoClass, enable)
240     {
241         if (!node || !node.target().cssModel.forcePseudoState(node, pseudoClass, enable))
242             return;
243
244         this._treeOutlineForNode(node).updateOpenCloseTags(node);
245         this._metricsPaneEdited();
246         this._stylesPaneEdited();
247
248         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
249             action: WebInspector.UserMetrics.UserActionNames.ForcedElementState,
250             selector: WebInspector.DOMPresentationUtils.fullQualifiedSelector(node, false),
251             enabled: enable,
252             state: pseudoClass
253         });
254     },
255
256     /**
257      * @param {!WebInspector.Event} event
258      */
259     _selectedNodeChanged: function(event)
260     {
261         var selectedNode = /** @type {?WebInspector.DOMNode} */ (event.data);
262         for (var i = 0; i < this._treeOutlines.length; ++i) {
263             if (!selectedNode || selectedNode.domModel() !== this._treeOutlines[i].domModel())
264                 this._treeOutlines[i].selectDOMNode(null);
265         }
266
267         if (!selectedNode && this._lastValidSelectedNode)
268             this._selectedPathOnReset = this._lastValidSelectedNode.path();
269
270         this.updateBreadcrumb(false);
271
272         this._updateSidebars();
273
274         if (selectedNode) {
275             ConsoleAgent.addInspectedNode(selectedNode.id);
276             this._lastValidSelectedNode = selectedNode;
277         }
278         WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged);
279     },
280
281     _updateSidebars: function()
282     {
283         for (var pane in this.sidebarPanes)
284            this.sidebarPanes[pane].needsUpdate = true;
285
286         this.updateStyles(true);
287         this.updateMetrics();
288         this.updatePlatformFonts();
289         this.updateProperties();
290         this.updateEventListeners();
291     },
292
293     _reset: function()
294     {
295         delete this.currentQuery;
296     },
297
298     /**
299      * @param {!WebInspector.Event} event
300      */
301     _documentUpdatedEvent: function(event)
302     {
303         this._documentUpdated(/** @type {!WebInspector.DOMModel} */ (event.target), /** @type {?WebInspector.DOMDocument} */ (event.data));
304     },
305
306     /**
307      * @param {!WebInspector.DOMModel} domModel
308      * @param {?WebInspector.DOMDocument} inspectedRootDocument
309      */
310     _documentUpdated: function(domModel, inspectedRootDocument)
311     {
312         this._reset();
313         this.searchCanceled();
314
315         var treeOutline = this._targetToTreeOutline.get(domModel.target());
316         treeOutline.rootDOMNode = inspectedRootDocument;
317
318         if (!inspectedRootDocument) {
319             if (this.isShowing())
320                 domModel.requestDocument();
321             return;
322         }
323
324         WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(domModel.target());
325
326         /**
327          * @this {WebInspector.ElementsPanel}
328          * @param {?WebInspector.DOMNode} candidateFocusNode
329          */
330         function selectNode(candidateFocusNode)
331         {
332             if (!candidateFocusNode)
333                 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
334
335             if (!candidateFocusNode)
336                 return;
337
338             this.selectDOMNode(candidateFocusNode);
339             if (treeOutline.selectedTreeElement)
340                 treeOutline.selectedTreeElement.expand();
341         }
342
343         /**
344          * @param {?DOMAgent.NodeId} nodeId
345          * @this {WebInspector.ElementsPanel}
346          */
347         function selectLastSelectedNode(nodeId)
348         {
349             if (this.selectedDOMNode()) {
350                 // Focused node has been explicitly set while reaching out for the last selected node.
351                 return;
352             }
353             var node = nodeId ? domModel.nodeForId(nodeId) : null;
354             selectNode.call(this, node);
355         }
356
357         if (this._omitDefaultSelection)
358             return;
359
360         if (this._selectedPathOnReset)
361             domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
362         else
363             selectNode.call(this, null);
364         delete this._selectedPathOnReset;
365     },
366
367     searchCanceled: function()
368     {
369         delete this._searchQuery;
370         this._hideSearchHighlights();
371
372         this._searchableView.updateSearchMatchesCount(0);
373
374         delete this._currentSearchResultIndex;
375         delete this._searchResults;
376
377         var targets = WebInspector.targetManager.targets();
378         for (var i = 0; i < targets.length; ++i)
379             targets[i].domModel.cancelSearch();
380     },
381
382     /**
383      * @param {string} query
384      * @param {boolean} shouldJump
385      * @param {boolean=} jumpBackwards
386      */
387     performSearch: function(query, shouldJump, jumpBackwards)
388     {
389         // Call searchCanceled since it will reset everything we need before doing a new search.
390         this.searchCanceled();
391
392         const whitespaceTrimmedQuery = query.trim();
393         if (!whitespaceTrimmedQuery.length)
394             return;
395
396         this._searchQuery = query;
397
398         var targets = WebInspector.targetManager.targets();
399         var promises = [];
400         for (var i = 0; i < targets.length; ++i)
401             promises.push(targets[i].domModel.performSearchPromise(whitespaceTrimmedQuery, WebInspector.settings.showUAShadowDOM.get()));
402         Promise.all(promises).then(resultCountCallback.bind(this));
403
404         /**
405          * @param {!Array.<number>} resultCounts
406          * @this {WebInspector.ElementsPanel}
407          */
408         function resultCountCallback(resultCounts)
409         {
410             /**
411              * @type {!Array.<{target: !WebInspector.Target, index: number, node: (?WebInspector.DOMNode|undefined)}>}
412              */
413             this._searchResults = [];
414             for (var i = 0; i < resultCounts.length; ++i) {
415                 var resultCount = resultCounts[i];
416                 for (var j = 0; j < resultCount; ++j)
417                     this._searchResults.push({target: targets[i], index: j, node: undefined});
418             }
419             this._searchableView.updateSearchMatchesCount(this._searchResults.length);
420             if (!this._searchResults.length)
421                 return;
422             this._currentSearchResultIndex = -1;
423
424             if (shouldJump)
425                 this._jumpToSearchResult(jumpBackwards ? -1 : 0);
426         }
427     },
428
429     _contextMenuEventFired: function(event)
430     {
431         var contextMenu = new WebInspector.ContextMenu(event);
432         for (var i = 0; i < this._treeOutlines.length; ++i)
433             this._treeOutlines[i].populateContextMenu(contextMenu, event);
434         contextMenu.show();
435     },
436
437     _domWordWrapSettingChanged: function(event)
438     {
439         if (event.data)
440             this.contentElement.classList.remove("nowrap");
441         else
442             this.contentElement.classList.add("nowrap");
443
444         var selectedNode = this.selectedDOMNode();
445         if (!selectedNode)
446             return;
447
448         var treeElement = this._treeElementForNode(selectedNode);
449         if (treeElement)
450             treeElement.updateSelection(); // Recalculate selection highlight dimensions.
451     },
452
453     switchToAndFocus: function(node)
454     {
455         // Reset search restore.
456         this._searchableView.cancelSearch();
457         WebInspector.inspectorView.setCurrentPanel(this);
458         this.selectDOMNode(node, true);
459     },
460
461     _populateContextMenu: function(contextMenu, node)
462     {
463         // Add debbuging-related actions
464         contextMenu.appendSeparator();
465         var pane = WebInspector.domBreakpointsSidebarPane;
466         pane.populateNodeContextMenu(node, contextMenu);
467     },
468
469     /**
470      * @param {!Element} element
471      * @param {!Event} event
472      * @return {!Element|!AnchorBox|undefined}
473      */
474     _getPopoverAnchor: function(element, event)
475     {
476         var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
477         if (!anchor || !anchor.href)
478             return;
479
480         var treeOutlineElement = anchor.enclosingNodeOrSelfWithClass("elements-tree-outline");
481         if (!treeOutlineElement)
482             return;
483
484         for (var i = 0; i < this._treeOutlines.length; ++i) {
485             if (this._treeOutlines[i].element !== treeOutlineElement)
486                 continue;
487
488             var resource = this._treeOutlines[i].target().resourceTreeModel.resourceForURL(anchor.href);
489             if (!resource || resource.type !== WebInspector.resourceTypes.Image)
490                 return;
491             anchor.removeAttribute("title");
492             return anchor;
493         }
494     },
495
496     /**
497      * @param {!WebInspector.DOMNode} node
498      * @param {function()} callback
499      */
500     _loadDimensionsForNode: function(node, callback)
501     {
502         if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
503             callback();
504             return;
505         }
506
507         node.resolveToObject("", resolvedNode);
508
509         function resolvedNode(object)
510         {
511             if (!object) {
512                 callback();
513                 return;
514             }
515
516             object.callFunctionJSON(dimensions, undefined, callback);
517             object.release();
518
519             /**
520              * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}}
521              * @suppressReceiverCheck
522              * @this {!Element}
523              */
524             function dimensions()
525             {
526                 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight };
527             }
528         }
529     },
530
531     /**
532      * @param {!Element} anchor
533      * @param {!WebInspector.Popover} popover
534      */
535     _showPopover: function(anchor, popover)
536     {
537         var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
538         // We get here for CSS properties, too.
539         if (listItem && listItem.treeElement && listItem.treeElement.treeOutline instanceof WebInspector.ElementsTreeOutline) {
540             var node = /** @type {!WebInspector.DOMNode} */ (listItem.treeElement.representedObject);
541             this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
542         } else {
543             var node = this.selectedDOMNode();
544             if (node)
545                 WebInspector.DOMPresentationUtils.buildImagePreviewContents(node.target(), anchor.href, true, showPopover);
546         }
547
548         /**
549          * @param {!Element=} contents
550          */
551         function showPopover(contents)
552         {
553             if (!contents)
554                 return;
555             popover.setCanShrink(false);
556             popover.show(contents, anchor);
557         }
558     },
559
560     _jumpToSearchResult: function(index)
561     {
562         this._hideSearchHighlights();
563         this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
564         this._highlightCurrentSearchResult();
565     },
566
567     jumpToNextSearchResult: function()
568     {
569         if (!this._searchResults)
570             return;
571         this._jumpToSearchResult(this._currentSearchResultIndex + 1);
572     },
573
574     jumpToPreviousSearchResult: function()
575     {
576         if (!this._searchResults)
577             return;
578         this._jumpToSearchResult(this._currentSearchResultIndex - 1);
579     },
580
581     _highlightCurrentSearchResult: function()
582     {
583         var index = this._currentSearchResultIndex;
584         var searchResults = this._searchResults;
585         var searchResult = searchResults[index];
586
587         if (searchResult.node === null) {
588             this._searchableView.updateCurrentMatchIndex(index);
589             return;
590         }
591
592         /**
593          * @param {?WebInspector.DOMNode} node
594          * @this {WebInspector.ElementsPanel}
595          */
596         function searchCallback(node)
597         {
598             searchResult.node = node;
599             this._highlightCurrentSearchResult();
600         }
601
602         if (typeof searchResult.node === "undefined") {
603             // No data for slot, request it.
604             searchResult.target.domModel.searchResult(searchResult.index, searchCallback.bind(this));
605             return;
606         }
607
608         this._searchableView.updateCurrentMatchIndex(index);
609
610         var treeElement = this._treeElementForNode(searchResult.node);
611         if (treeElement) {
612             treeElement.highlightSearchResults(this._searchQuery);
613             treeElement.reveal();
614             var matches = treeElement.listItemElement.getElementsByClassName("highlighted-search-result");
615             if (matches.length)
616                 matches[0].scrollIntoViewIfNeeded();
617         }
618     },
619
620     _hideSearchHighlights: function()
621     {
622         if (!this._searchResults || !this._searchResults.length || this._currentSearchResultIndex < 0)
623             return;
624         var searchResult = this._searchResults[this._currentSearchResultIndex];
625         if (!searchResult.node)
626             return;
627         var treeOutline = this._targetToTreeOutline.get(searchResult.target);
628         var treeElement = treeOutline.findTreeElement(searchResult.node);
629         if (treeElement)
630             treeElement.hideSearchHighlights();
631     },
632
633     /**
634      * @return {?WebInspector.DOMNode}
635      */
636     selectedDOMNode: function()
637     {
638         for (var i = 0; i < this._treeOutlines.length; ++i) {
639             var treeOutline = this._treeOutlines[i];
640             if (treeOutline.selectedDOMNode())
641                 return treeOutline.selectedDOMNode();
642         }
643         return null;
644     },
645
646     /**
647      * @param {!WebInspector.DOMNode} node
648      * @param {boolean=} focus
649      */
650     selectDOMNode: function(node, focus)
651     {
652         for (var i = 0; i < this._treeOutlines.length; ++i) {
653             var treeOutline = this._treeOutlines[i];
654             if (treeOutline.target() === node.target())
655                 treeOutline.selectDOMNode(node, focus);
656             else
657                 treeOutline.selectDOMNode(null);
658         }
659     },
660
661     /**
662      * @param {!WebInspector.Event} event
663      */
664     _updateBreadcrumbIfNeeded: function(event)
665     {
666         var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data || []);
667         if (!nodes.length)
668             return;
669
670         var crumbs = this.crumbsElement;
671         for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) {
672             if (nodes.indexOf(crumb.representedObject) !== -1) {
673                 this.updateBreadcrumb(true);
674                 return;
675             }
676         }
677     },
678
679     _stylesPaneEdited: function()
680     {
681         // Once styles are edited, the Metrics pane should be updated.
682         this.sidebarPanes.metrics.needsUpdate = true;
683         this.updateMetrics();
684         this.sidebarPanes.platformFonts.needsUpdate = true;
685         this.updatePlatformFonts();
686     },
687
688     _metricsPaneEdited: function()
689     {
690         // Once metrics are edited, the Styles pane should be updated.
691         this.sidebarPanes.styles.needsUpdate = true;
692         this.updateStyles(true);
693     },
694
695     _mouseMovedInCrumbs: function(event)
696     {
697         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
698         var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
699         var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbElement.representedObject : null);
700         if (node)
701             node.highlight();
702     },
703
704     _mouseMovedOutOfCrumbs: function(event)
705     {
706         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
707         if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
708             return;
709
710         for (var i = 0; i < this._treeOutlines.length; ++i)
711             this._treeOutlines[i].domModel().hideDOMNodeHighlight();
712     },
713
714     /**
715      * @param {boolean=} forceUpdate
716      */
717     updateBreadcrumb: function(forceUpdate)
718     {
719         if (!this.isShowing())
720             return;
721
722         var crumbs = this.crumbsElement;
723
724         var handled = false;
725         var crumb = crumbs.firstChild;
726         while (crumb) {
727             if (crumb.representedObject === this.selectedDOMNode()) {
728                 crumb.classList.add("selected");
729                 handled = true;
730             } else {
731                 crumb.classList.remove("selected");
732             }
733
734             crumb = crumb.nextSibling;
735         }
736
737         if (handled && !forceUpdate) {
738             // We don't need to rebuild the crumbs, but we need to adjust sizes
739             // to reflect the new focused or root node.
740             this.updateBreadcrumbSizes();
741             return;
742         }
743
744         crumbs.removeChildren();
745
746         var panel = this;
747
748         function selectCrumbFunction(event)
749         {
750             var crumb = event.currentTarget;
751             if (crumb.classList.contains("collapsed")) {
752                 // Clicking a collapsed crumb will expose the hidden crumbs.
753                 if (crumb === panel.crumbsElement.firstChild) {
754                     // If the focused crumb is the first child, pick the farthest crumb
755                     // that is still hidden. This allows the user to expose every crumb.
756                     var currentCrumb = crumb;
757                     while (currentCrumb) {
758                         var hidden = currentCrumb.classList.contains("hidden");
759                         var collapsed = currentCrumb.classList.contains("collapsed");
760                         if (!hidden && !collapsed)
761                             break;
762                         crumb = currentCrumb;
763                         currentCrumb = currentCrumb.nextSibling;
764                     }
765                 }
766
767                 panel.updateBreadcrumbSizes(crumb);
768             } else
769                 panel.selectDOMNode(crumb.representedObject, true);
770
771             event.preventDefault();
772         }
773
774         for (var current = this.selectedDOMNode(); current; current = current.parentNode) {
775             if (current.nodeType() === Node.DOCUMENT_NODE)
776                 continue;
777
778             crumb = document.createElement("span");
779             crumb.className = "crumb";
780             crumb.representedObject = current;
781             crumb.addEventListener("mousedown", selectCrumbFunction, false);
782
783             var crumbTitle = "";
784             switch (current.nodeType()) {
785                 case Node.ELEMENT_NODE:
786                     if (current.pseudoType())
787                         crumbTitle = "::" + current.pseudoType();
788                     else
789                         WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb);
790                     break;
791
792                 case Node.TEXT_NODE:
793                     crumbTitle = WebInspector.UIString("(text)");
794                     break;
795
796                 case Node.COMMENT_NODE:
797                     crumbTitle = "<!-->";
798                     break;
799
800                 case Node.DOCUMENT_TYPE_NODE:
801                     crumbTitle = "<!DOCTYPE>";
802                     break;
803
804                 case Node.DOCUMENT_FRAGMENT_NODE:
805                   crumbTitle = current.shadowRootType() ? "#shadow-root" : current.nodeNameInCorrectCase();
806                   break;
807
808                 default:
809                     crumbTitle = current.nodeNameInCorrectCase();
810             }
811
812             if (!crumb.childNodes.length) {
813                 var nameElement = document.createElement("span");
814                 nameElement.textContent = crumbTitle;
815                 crumb.appendChild(nameElement);
816                 crumb.title = crumbTitle;
817             }
818
819             if (current === this.selectedDOMNode())
820                 crumb.classList.add("selected");
821             crumbs.insertBefore(crumb, crumbs.firstChild);
822         }
823
824         this.updateBreadcrumbSizes();
825     },
826
827     /**
828      * @param {!Element=} focusedCrumb
829      */
830     updateBreadcrumbSizes: function(focusedCrumb)
831     {
832         if (!this.isShowing())
833             return;
834
835         var crumbs = this.crumbsElement;
836         if (!crumbs.firstChild)
837             return;
838
839         var selectedIndex = 0;
840         var focusedIndex = 0;
841         var selectedCrumb;
842
843         // Reset crumb styles.
844         for (var i = 0; i < crumbs.childNodes.length; ++i) {
845             var crumb = crumbs.childNodes[i];
846             // Find the selected crumb and index.
847             if (!selectedCrumb && crumb.classList.contains("selected")) {
848                 selectedCrumb = crumb;
849                 selectedIndex = i;
850             }
851
852             // Find the focused crumb index.
853             if (crumb === focusedCrumb)
854                 focusedIndex = i;
855
856             crumb.classList.remove("compact", "collapsed", "hidden");
857         }
858
859         // Layout 1: Measure total and normal crumb sizes
860         var contentElementWidth = this.contentElement.offsetWidth;
861         var normalSizes = [];
862         for (var i = 0; i < crumbs.childNodes.length; ++i) {
863             var crumb = crumbs.childNodes[i];
864             normalSizes[i] = crumb.offsetWidth;
865         }
866
867         // Layout 2: Measure collapsed crumb sizes
868         var compactSizes = [];
869         for (var i = 0; i < crumbs.childNodes.length; ++i) {
870             var crumb = crumbs.childNodes[i];
871             crumb.classList.add("compact");
872         }
873         for (var i = 0; i < crumbs.childNodes.length; ++i) {
874             var crumb = crumbs.childNodes[i];
875             compactSizes[i] = crumb.offsetWidth;
876         }
877
878         // Layout 3: Measure collapsed crumb size
879         crumbs.firstChild.classList.add("collapsed");
880         var collapsedSize = crumbs.firstChild.offsetWidth;
881
882         // Clean up.
883         for (var i = 0; i < crumbs.childNodes.length; ++i) {
884             var crumb = crumbs.childNodes[i];
885             crumb.classList.remove("compact", "collapsed");
886         }
887
888         function crumbsAreSmallerThanContainer()
889         {
890             var totalSize = 0;
891             for (var i = 0; i < crumbs.childNodes.length; ++i) {
892                 var crumb = crumbs.childNodes[i];
893                 if (crumb.classList.contains("hidden"))
894                     continue;
895                 if (crumb.classList.contains("collapsed")) {
896                     totalSize += collapsedSize;
897                     continue;
898                 }
899                 totalSize += crumb.classList.contains("compact") ? compactSizes[i] : normalSizes[i];
900             }
901             const rightPadding = 10;
902             return totalSize + rightPadding < contentElementWidth;
903         }
904
905         if (crumbsAreSmallerThanContainer())
906             return; // No need to compact the crumbs, they all fit at full size.
907
908         var BothSides = 0;
909         var AncestorSide = -1;
910         var ChildSide = 1;
911
912         /**
913          * @param {function(!Element)} shrinkingFunction
914          * @param {number} direction
915          */
916         function makeCrumbsSmaller(shrinkingFunction, direction)
917         {
918             var significantCrumb = focusedCrumb || selectedCrumb;
919             var significantIndex = significantCrumb === selectedCrumb ? selectedIndex : focusedIndex;
920
921             function shrinkCrumbAtIndex(index)
922             {
923                 var shrinkCrumb = crumbs.childNodes[index];
924                 if (shrinkCrumb && shrinkCrumb !== significantCrumb)
925                     shrinkingFunction(shrinkCrumb);
926                 if (crumbsAreSmallerThanContainer())
927                     return true; // No need to compact the crumbs more.
928                 return false;
929             }
930
931             // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
932             // fit in the container or we run out of crumbs to shrink.
933             if (direction) {
934                 // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
935                 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
936                 while (index !== significantIndex) {
937                     if (shrinkCrumbAtIndex(index))
938                         return true;
939                     index += (direction > 0 ? 1 : -1);
940                 }
941             } else {
942                 // Crumbs are shrunk in order of descending distance from the signifcant crumb,
943                 // with a tie going to child crumbs.
944                 var startIndex = 0;
945                 var endIndex = crumbs.childNodes.length - 1;
946                 while (startIndex != significantIndex || endIndex != significantIndex) {
947                     var startDistance = significantIndex - startIndex;
948                     var endDistance = endIndex - significantIndex;
949                     if (startDistance >= endDistance)
950                         var index = startIndex++;
951                     else
952                         var index = endIndex--;
953                     if (shrinkCrumbAtIndex(index))
954                         return true;
955                 }
956             }
957
958             // We are not small enough yet, return false so the caller knows.
959             return false;
960         }
961
962         function coalesceCollapsedCrumbs()
963         {
964             var crumb = crumbs.firstChild;
965             var collapsedRun = false;
966             var newStartNeeded = false;
967             var newEndNeeded = false;
968             while (crumb) {
969                 var hidden = crumb.classList.contains("hidden");
970                 if (!hidden) {
971                     var collapsed = crumb.classList.contains("collapsed");
972                     if (collapsedRun && collapsed) {
973                         crumb.classList.add("hidden");
974                         crumb.classList.remove("compact");
975                         crumb.classList.remove("collapsed");
976
977                         if (crumb.classList.contains("start")) {
978                             crumb.classList.remove("start");
979                             newStartNeeded = true;
980                         }
981
982                         if (crumb.classList.contains("end")) {
983                             crumb.classList.remove("end");
984                             newEndNeeded = true;
985                         }
986
987                         continue;
988                     }
989
990                     collapsedRun = collapsed;
991
992                     if (newEndNeeded) {
993                         newEndNeeded = false;
994                         crumb.classList.add("end");
995                     }
996                 } else
997                     collapsedRun = true;
998                 crumb = crumb.nextSibling;
999             }
1000
1001             if (newStartNeeded) {
1002                 crumb = crumbs.lastChild;
1003                 while (crumb) {
1004                     if (!crumb.classList.contains("hidden")) {
1005                         crumb.classList.add("start");
1006                         break;
1007                     }
1008                     crumb = crumb.previousSibling;
1009                 }
1010             }
1011         }
1012
1013         /**
1014          * @param {!Element} crumb
1015          */
1016         function compact(crumb)
1017         {
1018             if (crumb.classList.contains("hidden"))
1019                 return;
1020             crumb.classList.add("compact");
1021         }
1022
1023         /**
1024          * @param {!Element} crumb
1025          * @param {boolean=} dontCoalesce
1026          */
1027         function collapse(crumb, dontCoalesce)
1028         {
1029             if (crumb.classList.contains("hidden"))
1030                 return;
1031             crumb.classList.add("collapsed");
1032             crumb.classList.remove("compact");
1033             if (!dontCoalesce)
1034                 coalesceCollapsedCrumbs();
1035         }
1036
1037         if (!focusedCrumb) {
1038             // When not focused on a crumb we can be biased and collapse less important
1039             // crumbs that the user might not care much about.
1040
1041             // Compact child crumbs.
1042             if (makeCrumbsSmaller(compact, ChildSide))
1043                 return;
1044
1045             // Collapse child crumbs.
1046             if (makeCrumbsSmaller(collapse, ChildSide))
1047                 return;
1048         }
1049
1050         // Compact ancestor crumbs, or from both sides if focused.
1051         if (makeCrumbsSmaller(compact, focusedCrumb ? BothSides : AncestorSide))
1052             return;
1053
1054         // Collapse ancestor crumbs, or from both sides if focused.
1055         if (makeCrumbsSmaller(collapse, focusedCrumb ? BothSides : AncestorSide))
1056             return;
1057
1058         if (!selectedCrumb)
1059             return;
1060
1061         // Compact the selected crumb.
1062         compact(selectedCrumb);
1063         if (crumbsAreSmallerThanContainer())
1064             return;
1065
1066         // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
1067         collapse(selectedCrumb, true);
1068     },
1069
1070     /**
1071      * @return {boolean}
1072      */
1073     _cssModelEnabledForSelectedNode: function()
1074     {
1075         if (!this.selectedDOMNode())
1076             return true;
1077         return this.selectedDOMNode().target().cssModel.isEnabled();
1078     },
1079
1080     /**
1081      * @param {boolean=} forceUpdate
1082      */
1083     updateStyles: function(forceUpdate)
1084     {
1085         if (!this._cssModelEnabledForSelectedNode())
1086             return;
1087         var stylesSidebarPane = this.sidebarPanes.styles;
1088         var computedStylePane = this.sidebarPanes.computedStyle;
1089         if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate)
1090             return;
1091
1092         stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate);
1093         stylesSidebarPane.needsUpdate = false;
1094     },
1095
1096     updateMetrics: function()
1097     {
1098         if (!this._cssModelEnabledForSelectedNode())
1099             return;
1100         var metricsSidebarPane = this.sidebarPanes.metrics;
1101         if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate)
1102             return;
1103
1104         metricsSidebarPane.update(this.selectedDOMNode());
1105         metricsSidebarPane.needsUpdate = false;
1106     },
1107
1108     updatePlatformFonts: function()
1109     {
1110         if (!this._cssModelEnabledForSelectedNode())
1111             return;
1112         var platformFontsSidebar = this.sidebarPanes.platformFonts;
1113         if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate)
1114             return;
1115
1116         platformFontsSidebar.update(this.selectedDOMNode());
1117         platformFontsSidebar.needsUpdate = false;
1118     },
1119
1120     updateProperties: function()
1121     {
1122         var propertiesSidebarPane = this.sidebarPanes.properties;
1123         if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate)
1124             return;
1125
1126         propertiesSidebarPane.update(this.selectedDOMNode());
1127         propertiesSidebarPane.needsUpdate = false;
1128     },
1129
1130     updateEventListeners: function()
1131     {
1132         var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
1133         if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate)
1134             return;
1135
1136         eventListenersSidebarPane.update(this.selectedDOMNode());
1137         eventListenersSidebarPane.needsUpdate = false;
1138     },
1139
1140     /**
1141      * @param {!KeyboardEvent} event
1142      */
1143     handleShortcut: function(event)
1144     {
1145         /**
1146          * @param {!WebInspector.ElementsTreeOutline} treeOutline
1147          * @this {WebInspector.ElementsPanel}
1148          */
1149         function handleUndoRedo(treeOutline)
1150         {
1151             if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key
1152                 treeOutline.target().domModel.undo(this._updateSidebars.bind(this));
1153                 event.handled = true;
1154                 return;
1155             }
1156
1157             var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
1158                                                    event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
1159             if (isRedoKey) {
1160                 treeOutline.target().domModel.redo(this._updateSidebars.bind(this));
1161                 event.handled = true;
1162             }
1163         }
1164
1165         var element = event.target.enclosingNodeOrSelfWithClass("elements-tree-outline");
1166         if (!element)
1167             return;
1168         var treeOutline = null;
1169         for (var i = 0; i < this._treeOutlines.length; ++i) {
1170             if (this._treeOutlines[i].element === element)
1171                 treeOutline = this._treeOutlines[i];
1172         }
1173         if (!treeOutline)
1174             return;
1175
1176         if (!treeOutline.editing()) {
1177             handleUndoRedo.call(this, treeOutline);
1178             if (event.handled)
1179                 return;
1180         }
1181
1182         treeOutline.handleShortcut(event);
1183     },
1184
1185     /**
1186      * @param {?WebInspector.DOMNode} node
1187      * @return {?WebInspector.ElementsTreeOutline}
1188      */
1189     _treeOutlineForNode: function(node)
1190     {
1191         if (!node)
1192             return null;
1193         return this._targetToTreeOutline.get(node.target()) || null;
1194     },
1195
1196     /**
1197      * @param {!WebInspector.DOMNode} node
1198      * @return {?WebInspector.ElementsTreeElement}
1199      */
1200     _treeElementForNode: function(node)
1201     {
1202         var treeOutline = this._treeOutlineForNode(node);
1203         return /** @type {?WebInspector.ElementsTreeElement} */ (treeOutline.findTreeElement(node));
1204     },
1205
1206     /**
1207      * @param {!Event} event
1208      */
1209     handleCopyEvent: function(event)
1210     {
1211         if (!WebInspector.currentFocusElement().enclosingNodeOrSelfWithClass("elements-tree-outline"))
1212             return;
1213         var treeOutline = this._treeOutlineForNode(this.selectedDOMNode());
1214         if (treeOutline)
1215             treeOutline.handleCopyOrCutKeyboardEvent(false, event);
1216     },
1217
1218     /**
1219      * @param {!Event} event
1220      */
1221     handleCutEvent: function(event)
1222     {
1223         var treeOutline = this._treeOutlineForNode(this.selectedDOMNode());
1224         if (treeOutline)
1225             treeOutline.handleCopyOrCutKeyboardEvent(true, event);
1226     },
1227
1228     /**
1229      * @param {!Event} event
1230      */
1231     handlePasteEvent: function(event)
1232     {
1233         var treeOutline = this._treeOutlineForNode(this.selectedDOMNode());
1234         if (treeOutline)
1235             treeOutline.handlePasteKeyboardEvent(event);
1236     },
1237
1238     /**
1239      * @param {!WebInspector.DOMNode} node
1240      * @return {!WebInspector.DOMNode}
1241      */
1242     _leaveUserAgentShadowDOM: function(node)
1243     {
1244         var userAgentShadowRoot = node.ancestorUserAgentShadowRoot();
1245         return userAgentShadowRoot ? /** @type {!WebInspector.DOMNode} */ (userAgentShadowRoot.parentNode) : node;
1246     },
1247
1248     /**
1249      * @param {!WebInspector.DOMNode} node
1250      */
1251     revealAndSelectNode: function(node)
1252     {
1253         WebInspector.inspectorView.setCurrentPanel(this);
1254         node = WebInspector.settings.showUAShadowDOM.get() ? node : this._leaveUserAgentShadowDOM(node);
1255         node.highlightForTwoSeconds();
1256         this.selectDOMNode(node, true);
1257     },
1258
1259     /**
1260      * @param {!Event} event
1261      * @param {!WebInspector.ContextMenu} contextMenu
1262      * @param {!Object} object
1263      */
1264     appendApplicableItems: function(event, contextMenu, object)
1265     {
1266         if (!(object instanceof WebInspector.RemoteObject && (/** @type {!WebInspector.RemoteObject} */ (object)).isNode())
1267             && !(object instanceof WebInspector.DOMNode)
1268             && !(object instanceof WebInspector.DeferredDOMNode)) {
1269             return;
1270         }
1271         // Skip adding "Reveal..." menu item for our own tree outline.
1272         if (this.element.isAncestor(/** @type {!Node} */ (event.target)))
1273             return;
1274         var commandCallback = WebInspector.Revealer.reveal.bind(WebInspector.Revealer, object);
1275         contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback);
1276     },
1277
1278     _sidebarContextMenuEventFired: function(event)
1279     {
1280         var contextMenu = new WebInspector.ContextMenu(event);
1281         contextMenu.show();
1282     },
1283
1284     _dockSideChanged: function()
1285     {
1286         var vertically = WebInspector.dockController.isVertical() && WebInspector.settings.splitVerticallyWhenDockedToRight.get();
1287         this._splitVertically(vertically);
1288     },
1289
1290     _showUAShadowDOMChanged: function()
1291     {
1292         for (var i = 0; i < this._treeOutlines.length; ++i)
1293             this._treeOutlines[i].update();
1294     },
1295
1296     /**
1297      * @param {boolean} vertically
1298      */
1299     _splitVertically: function(vertically)
1300     {
1301         if (this.sidebarPaneView && vertically === !this._splitView.isVertical())
1302             return;
1303
1304         if (this.sidebarPaneView) {
1305             this.sidebarPaneView.detach();
1306             this._splitView.uninstallResizer(this.sidebarPaneView.headerElement());
1307         }
1308
1309         this._splitView.setVertical(!vertically);
1310
1311         var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed"));
1312         computedPane.element.classList.add("composite");
1313         computedPane.element.classList.add("fill");
1314         var expandComputed = computedPane.expand.bind(computedPane);
1315
1316         computedPane.bodyElement.classList.add("metrics-and-computed");
1317         this.sidebarPanes.computedStyle.setExpandCallback(expandComputed);
1318
1319         var matchedStylePanesWrapper = document.createElement("div");
1320         matchedStylePanesWrapper.className = "style-panes-wrapper";
1321         var computedStylePanesWrapper = document.createElement("div");
1322         computedStylePanesWrapper.className = "style-panes-wrapper";
1323
1324         /**
1325          * @param {boolean} inComputedStyle
1326          * @this {WebInspector.ElementsPanel}
1327          */
1328         function showMetrics(inComputedStyle)
1329         {
1330             if (inComputedStyle)
1331                 this.sidebarPanes.metrics.show(computedStylePanesWrapper, this.sidebarPanes.computedStyle.element);
1332             else
1333                 this.sidebarPanes.metrics.show(matchedStylePanesWrapper);
1334         }
1335
1336         /**
1337          * @param {!WebInspector.Event} event
1338          * @this {WebInspector.ElementsPanel}
1339          */
1340         function tabSelected(event)
1341         {
1342             var tabId = /** @type {string} */ (event.data.tabId);
1343             if (tabId === computedPane.title())
1344                 showMetrics.call(this, true);
1345             else if (tabId === stylesPane.title())
1346                 showMetrics.call(this, false);
1347         }
1348
1349         this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
1350
1351         if (vertically) {
1352             this._splitView.installResizer(this.sidebarPaneView.headerElement());
1353             this.sidebarPanes.metrics.setExpandCallback(expandComputed);
1354
1355             var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1356             compositePane.element.classList.add("composite");
1357             compositePane.element.classList.add("fill");
1358             var expandComposite = compositePane.expand.bind(compositePane);
1359
1360             var splitView = new WebInspector.SplitView(true, true, "stylesPaneSplitViewState", 0.5);
1361             splitView.show(compositePane.bodyElement);
1362
1363             splitView.mainElement().appendChild(matchedStylePanesWrapper);
1364             splitView.sidebarElement().appendChild(computedStylePanesWrapper);
1365
1366             this.sidebarPanes.styles.setExpandCallback(expandComposite);
1367
1368             computedPane.show(computedStylePanesWrapper);
1369             computedPane.setExpandCallback(expandComposite);
1370
1371             splitView.mainElement().appendChild(this._matchedStylesFilterBoxContainer);
1372             splitView.sidebarElement().appendChild(this._computedStylesFilterBoxContainer);
1373
1374             this.sidebarPaneView.addPane(compositePane);
1375         } else {
1376             var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1377             stylesPane.element.classList.add("composite");
1378             stylesPane.element.classList.add("fill");
1379             var expandStyles = stylesPane.expand.bind(stylesPane);
1380             stylesPane.bodyElement.classList.add("metrics-and-styles");
1381
1382             stylesPane.bodyElement.appendChild(matchedStylePanesWrapper);
1383             computedPane.bodyElement.appendChild(computedStylePanesWrapper);
1384
1385             this.sidebarPanes.styles.setExpandCallback(expandStyles);
1386             this.sidebarPanes.metrics.setExpandCallback(expandStyles);
1387
1388             this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this);
1389
1390             stylesPane.bodyElement.appendChild(this._matchedStylesFilterBoxContainer);
1391             computedPane.bodyElement.appendChild(this._computedStylesFilterBoxContainer);
1392
1393             this.sidebarPaneView.addPane(stylesPane);
1394             this.sidebarPaneView.addPane(computedPane);
1395         }
1396
1397         this.sidebarPanes.styles.show(matchedStylePanesWrapper);
1398         this.sidebarPanes.computedStyle.show(computedStylePanesWrapper);
1399         matchedStylePanesWrapper.appendChild(this.sidebarPanes.styles.titleElement);
1400         showMetrics.call(this, vertically);
1401         this.sidebarPanes.platformFonts.show(computedStylePanesWrapper);
1402
1403         this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners);
1404         this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints);
1405         this.sidebarPaneView.addPane(this.sidebarPanes.properties);
1406         this._extensionSidebarPanesContainer = this.sidebarPaneView;
1407
1408         for (var i = 0; i < this._extensionSidebarPanes.length; ++i)
1409             this._extensionSidebarPanesContainer.addPane(this._extensionSidebarPanes[i]);
1410
1411         this.sidebarPaneView.show(this._splitView.sidebarElement());
1412         this.sidebarPanes.styles.expand();
1413     },
1414
1415     /**
1416      * @param {string} id
1417      * @param {!WebInspector.SidebarPane} pane
1418      */
1419     addExtensionSidebarPane: function(id, pane)
1420     {
1421         this._extensionSidebarPanes.push(pane);
1422         this._extensionSidebarPanesContainer.addPane(pane);
1423     },
1424
1425     __proto__: WebInspector.Panel.prototype
1426 }
1427
1428 /**
1429  * @constructor
1430  * @implements {WebInspector.ContextMenu.Provider}
1431  */
1432 WebInspector.ElementsPanel.ContextMenuProvider = function()
1433 {
1434 }
1435
1436 WebInspector.ElementsPanel.ContextMenuProvider.prototype = {
1437     /**
1438      * @param {!Event} event
1439      * @param {!WebInspector.ContextMenu} contextMenu
1440      * @param {!Object} target
1441      */
1442     appendApplicableItems: function(event, contextMenu, target)
1443     {
1444         /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).appendApplicableItems(event, contextMenu, target);
1445     }
1446 }
1447
1448 /**
1449  * @constructor
1450  * @implements {WebInspector.Revealer}
1451  */
1452 WebInspector.ElementsPanel.DOMNodeRevealer = function()
1453 {
1454 }
1455
1456 WebInspector.ElementsPanel.DOMNodeRevealer.prototype = {
1457     /**
1458      * @param {!Object} node
1459      */
1460     reveal: function(node)
1461     {
1462         if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.enabled()) {
1463             InspectorFrontendHost.bringToFront();
1464             WebInspector.inspectElementModeController.disable();
1465         }
1466
1467         var panel = /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements"));
1468         if (node instanceof WebInspector.DOMNode)
1469             panel.revealAndSelectNode(/** @type {!WebInspector.DOMNode} */ (node));
1470         else if (node instanceof WebInspector.DeferredDOMNode)
1471             (/** @type {!WebInspector.DeferredDOMNode} */ (node)).resolve(onNodeResolved);
1472
1473         /**
1474          * @param {?WebInspector.DOMNode} resolvedNode
1475          */
1476         function onNodeResolved(resolvedNode)
1477         {
1478             if (resolvedNode)
1479                 panel.revealAndSelectNode(resolvedNode);
1480         }
1481     }
1482 }
1483
1484 /**
1485  * @constructor
1486  * @implements {WebInspector.Revealer}
1487  */
1488 WebInspector.ElementsPanel.NodeRemoteObjectRevealer = function()
1489 {
1490 }
1491
1492 WebInspector.ElementsPanel.NodeRemoteObjectRevealer.prototype = {
1493     /**
1494      * @param {!Object} remoteObject
1495      */
1496     reveal: function(remoteObject)
1497     {
1498         revealElement(/** @type {!WebInspector.RemoteObject} */ (remoteObject));
1499
1500         /**
1501          * @param {?WebInspector.RemoteObject} remoteObject
1502          */
1503         function revealElement(remoteObject)
1504         {
1505             if (remoteObject)
1506                 remoteObject.pushNodeToFrontend(selectNode.bind(null, remoteObject));
1507         }
1508
1509         /**
1510          * @param {?WebInspector.RemoteObject} remoteObject
1511          * @param {?WebInspector.DOMNode} node
1512          */
1513         function selectNode(remoteObject, node)
1514         {
1515             if (node) {
1516                 WebInspector.Revealer.reveal(node);
1517                 return;
1518             }
1519             if (!remoteObject || remoteObject.description !== "#text" || !remoteObject.isNode())
1520                 return;
1521             remoteObject.callFunction(parentElement, undefined, revealElement);
1522         }
1523
1524         /**
1525          * @suppressReceiverCheck
1526          * @this {Element}
1527          */
1528         function parentElement()
1529         {
1530             return this.parentElement;
1531         }
1532     }
1533 }
1534
1535 /**
1536  * @constructor
1537  */
1538 WebInspector.ElementsPanel.NodeRemoteObjectInspector = function()
1539 {
1540 }
1541
1542 WebInspector.ElementsPanel.NodeRemoteObjectInspector.prototype = {
1543     /**
1544      * @param {!Object} object
1545      */
1546     inspectNodeObject: function(object)
1547     {
1548         var remoteObject = /** @type {!WebInspector.RemoteObject} */ (object);
1549         if (!remoteObject.isNode()) {
1550             remoteObject.release();
1551             return;
1552         }
1553         var elementsPanel = /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements"));
1554         revealElement(remoteObject);
1555
1556         /**
1557          * @param {?WebInspector.RemoteObject} remoteObject
1558          */
1559         function revealElement(remoteObject)
1560         {
1561             if (!remoteObject)
1562                 return;
1563             remoteObject.pushNodeToFrontend(selectNode.bind(null, remoteObject));
1564             elementsPanel.omitDefaultSelection();
1565             WebInspector.inspectorView.setCurrentPanel(elementsPanel);
1566         }
1567
1568         /**
1569          * @param {!WebInspector.RemoteObject} remoteObject
1570          * @param {?WebInspector.DOMNode} node
1571          */
1572         function selectNode(remoteObject, node)
1573         {
1574             elementsPanel.stopOmittingDefaultSelection();
1575             if (node) {
1576                 WebInspector.Revealer.reveal(node);
1577                 if (!WebInspector._notFirstInspectElement && !WebInspector.inspectorView.drawerVisible())
1578                     InspectorFrontendHost.inspectElementCompleted();
1579                 WebInspector._notFirstInspectElement = true;
1580                 remoteObject.release();
1581                 return;
1582             }
1583             if (remoteObject.description !== "#text" || !remoteObject.isNode()) {
1584                 remoteObject.release();
1585                 return;
1586             }
1587             remoteObject.callFunction(parentElement, undefined, revealElement);
1588         }
1589
1590         /**
1591          * @suppressReceiverCheck
1592          * @this {Element}
1593          */
1594         function parentElement()
1595         {
1596             return this.parentElement;
1597         }
1598     }
1599 }