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