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 /**
     32  * @constructor
     33  * @implements {WebInspector.Searchable}
     34  * @implements {WebInspector.TargetManager.Observer}
     35  * @extends {WebInspector.Panel}
     36  */
     37 WebInspector.ElementsPanel = function()
     38 {
     39     WebInspector.Panel.call(this, "elements");
     40     this.registerRequiredCSS("elementsPanel.css");
     41     this.setHideOnDetach();
     42 
     43     this._splitView = new WebInspector.SplitView(true, true, "elementsPanelSplitViewState", 325, 325);
     44     this._splitView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this));
     45     this._splitView.show(this.element);
     46 
     47     this._searchableView = new WebInspector.SearchableView(this);
     48     this._searchableView.setMinimumSize(25, 19);
     49     this._searchableView.show(this._splitView.mainElement());
     50     var stackElement = this._searchableView.element;
     51 
     52     this.contentElement = stackElement.createChild("div");
     53     this.contentElement.id = "elements-content";
     54     this.contentElement.classList.add("outline-disclosure");
     55     this.contentElement.classList.add("source-code");
     56     if (!WebInspector.settings.domWordWrap.get())
     57         this.contentElement.classList.add("nowrap");
     58     WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this));
     59 
     60     this._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 
    114 WebInspector.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  */
   1445 WebInspector.ElementsPanel.ContextMenuProvider = function()
   1446 {
   1447 }
   1448 
   1449 WebInspector.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  */
   1465 WebInspector.ElementsPanel.DOMNodeRevealer = function()
   1466 {
   1467 }
   1468 
   1469 WebInspector.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  */
   1501 WebInspector.ElementsPanel.NodeRemoteObjectRevealer = function()
   1502 {
   1503 }
   1504 
   1505 WebInspector.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  */
   1551 WebInspector.ElementsPanel.NodeRemoteObjectInspector = function()
   1552 {
   1553 }
   1554 
   1555 WebInspector.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