Home | History | Annotate | Download | only in elements
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2008 Matt Lilek <webkit (at) 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 importScript("Spectrum.js");
     32 importScript("DOMSyntaxHighlighter.js");
     33 importScript("ElementsTreeOutline.js");
     34 importScript("EventListenersSidebarPane.js");
     35 importScript("MetricsSidebarPane.js");
     36 importScript("OverridesView.js");
     37 importScript("PlatformFontsSidebarPane.js");
     38 importScript("PropertiesSidebarPane.js");
     39 importScript("RenderingOptionsView.js");
     40 importScript("StylesSidebarPane.js");
     41 
     42 /**
     43  * @constructor
     44  * @implements {WebInspector.Searchable}
     45  * @implements {WebInspector.TargetManager.Observer}
     46  * @extends {WebInspector.Panel}
     47  */
     48 WebInspector.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 
    124 WebInspector.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  */
   1392 WebInspector.ElementsPanel.ContextMenuProvider = function()
   1393 {
   1394 }
   1395 
   1396 WebInspector.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  */
   1412 WebInspector.ElementsPanel.DOMNodeRevealer = function()
   1413 {
   1414 }
   1415 
   1416 WebInspector.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  */
   1435 WebInspector.ElementsPanel.NodeRemoteObjectRevealer = function()
   1436 {
   1437 }
   1438 
   1439 WebInspector.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