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