Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / elements / ElementsPanel.js
blob3bc557c848dc16323894a4ada3320b58d62c83a9
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
31 /**
32  * @constructor
33  * @implements {WebInspector.Searchable}
34  * @implements {WebInspector.TargetManager.Observer}
35  * @extends {WebInspector.Panel}
36  */
37 WebInspector.ElementsPanel = function()
39     WebInspector.Panel.call(this, "elements");
40     this.registerRequiredCSS("elements/elementsPanel.css");
42     this._splitWidget = new WebInspector.SplitWidget(true, true, "elementsPanelSplitViewState", 325, 325);
43     this._splitWidget.addEventListener(WebInspector.SplitWidget.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this));
44     this._splitWidget.show(this.element);
46     this._searchableView = new WebInspector.SearchableView(this);
47     this._searchableView.setMinimumSize(25, 28);
48     this._searchableView.setPlaceholder(WebInspector.UIString("Find by string, selector, or XPath"));
49     var stackElement = this._searchableView.element;
51     this._contentElement = createElement("div");
52     var crumbsContainer = createElement("div");
53     this._showLayoutEditor = false;
54     if (Runtime.experiments.isEnabled("materialDesign"))
55         this._initializeActionsToolbar();
56     stackElement.appendChild(this._contentElement);
57     stackElement.appendChild(crumbsContainer);
59     this._elementsPanelTreeOutilneSplit = new WebInspector.SplitWidget(false, true, "treeOutlineAnimationTimelineWidget", 300, 300);
60     this._elementsPanelTreeOutilneSplit.hideSidebar();
61     this._elementsPanelTreeOutilneSplit.setMainWidget(this._searchableView);
62     this._splitWidget.setMainWidget(this._elementsPanelTreeOutilneSplit);
64     this._contentElement.id = "elements-content";
65     // FIXME: crbug.com/425984
66     if (WebInspector.moduleSetting("domWordWrap").get())
67         this._contentElement.classList.add("elements-wrap");
68     WebInspector.moduleSetting("domWordWrap").addChangeListener(this._domWordWrapSettingChanged.bind(this));
70     crumbsContainer.id = "elements-crumbs";
71     this._breadcrumbs = new WebInspector.ElementsBreadcrumbs();
72     this._breadcrumbs.show(crumbsContainer);
73     this._breadcrumbs.addEventListener(WebInspector.ElementsBreadcrumbs.Events.NodeSelected, this._crumbNodeSelected, this);
75     this.sidebarPanes = {};
76     /** @type !Array<!WebInspector.ElementsSidebarViewWrapperPane> */
77     this._elementsSidebarViewWrappers = [];
78     var sharedSidebarModel = new WebInspector.SharedSidebarModel();
79     this.sidebarPanes.platformFonts = WebInspector.PlatformFontsWidget.createSidebarWrapper(sharedSidebarModel);
80     this.sidebarPanes.styles = new WebInspector.StylesSidebarPane();
82     this.sidebarPanes.computedStyle = WebInspector.ComputedStyleWidget.createSidebarWrapper(this.sidebarPanes.styles, sharedSidebarModel);
84     this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted, this._onEditingSelectorStarted.bind(this));
85     this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded, this._onEditingSelectorEnded.bind(this));
87     this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
88     this.sidebarPanes.properties = WebInspector.PropertiesWidget.createSidebarWrapper();
89     this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this);
90     this.sidebarPanes.eventListeners = WebInspector.EventListenersWidget.createSidebarWrapper();
92     WebInspector.moduleSetting("sidebarPosition").addChangeListener(this._updateSidebarPosition.bind(this));
93     this._updateSidebarPosition();
94     this._loadSidebarViews();
96     /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */
97     this._treeOutlines = [];
98     /** @type {!Map.<!WebInspector.DOMModel, !WebInspector.ElementsTreeOutline>} */
99     this._modelToTreeOutline = new Map();
100     WebInspector.targetManager.observeTargets(this);
101     WebInspector.moduleSetting("showUAShadowDOM").addChangeListener(this._showUAShadowDOMChanged.bind(this));
102     WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
103     WebInspector.extensionServer.addEventListener(WebInspector.ExtensionServer.Events.SidebarPaneAdded, this._extensionSidebarPaneAdded, this);
106 WebInspector.ElementsPanel._elementsSidebarViewTitleSymbol = Symbol("title");
108 WebInspector.ElementsPanel.prototype = {
109     _initializeActionsToolbar: function()
110     {
111         this._nodeActionsElement = createElementWithClass("div", "node-actions-container");
112         var button = this._nodeActionsElement.createChild("div", "node-actions-toggle");
113         button.addEventListener("click", this._toggleActionsToolbar.bind(this, undefined));
114         this._nodeActionsToolbar = new WebInspector.Toolbar();
115         this._nodeActionsElement.appendChild(this._nodeActionsToolbar.element);
116         this._nodeActionsToolbar.element.addEventListener("mousedown", consumeEvent);
117         WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.MarkersChanged, this._markersChanged, this);
119         this._editAsHTMLButton = new WebInspector.ToolbarButton(WebInspector.UIString("Edit as HTML"), "edit-toolbar-item");
120         this._editAsHTMLButton.setAction("elements.edit-as-html");
121         this._nodeActionsToolbar.appendToolbarItem(this._editAsHTMLButton);
122         this._nodeActionsToolbar.element.classList.add("node-actions-toolbar");
123         this._hideElementButton = new WebInspector.ToolbarButton(WebInspector.UIString("Hide element"), "visibility-off-toolbar-item");
124         this._hideElementButton.setAction("elements.hide-element");
125         this._nodeActionsToolbar.appendToolbarItem(this._hideElementButton);
126         this._forceElementStateButton = new WebInspector.ToolbarMenuButton(WebInspector.UIString("Force element state"), "pin-toolbar-item", this._showForceElementStateMenu.bind(this));
127         this._nodeActionsToolbar.appendToolbarItem(this._forceElementStateButton);
128         this._breakpointsButton = new WebInspector.ToolbarMenuButton(WebInspector.UIString("Toggle breakpoints"), "add-breakpoint-toolbar-item", this._showBreakpointsMenu.bind(this));
129         this._nodeActionsToolbar.appendToolbarItem(this._breakpointsButton);
130     },
132     _toggleHideElement: function()
133     {
134         var node = this.selectedDOMNode();
135         var treeOutline = this._treeOutlineForNode(node);
136         if (!node || !treeOutline)
137             return;
138         treeOutline.toggleHideElement(node);
139     },
141     /**
142      * @param {!WebInspector.DOMNode} node
143      */
144     _updateActionsToolbar: function(node)
145     {
146         if (!Runtime.experiments.isEnabled("materialDesign"))
147             return;
148         var classText = node.getAttribute("class");
149         var treeOutline = this._treeOutlineForNode(node);
150         this._hideElementButton.setToggled(treeOutline && treeOutline.isToggledToHidden(node));
151         this._editAsHTMLButton.setToggled(false);
152         this._breakpointsButton.setEnabled(!node.pseudoType());
153         this._breakpointsButton.setToggled(WebInspector.domBreakpointsSidebarPane.hasBreakpoints(node));
154         this._forceElementStateButton.setEnabled(node.nodeType() === Node.ELEMENT_NODE && !node.pseudoType());
155         this._forceElementStateButton.setToggled(!!WebInspector.CSSStyleModel.fromNode(node).pseudoState(node).length);
157         var treeElement = this._treeOutlineForNode(node).selectedTreeElement;
158         if (!treeElement)
159             return;
160         if (node.nodeType() !== Node.ELEMENT_NODE) {
161             this._nodeActionsElement.remove();
162             return;
163         }
165         var actionsToolbar = this._nodeActionsElement;
166         if (actionsToolbar.__node !== node) {
167             treeElement.gutterElement().appendChild(actionsToolbar);
168             this._positionActionsToolbar();
169             actionsToolbar.__node = node;
170             this._toggleActionsToolbar(false);
171         }
172     },
174     _toggleEditAsHTML: function()
175     {
176         var node = this.selectedDOMNode();
177         var treeOutline = this._treeOutlineForNode(node);
178         if (!node || !treeOutline)
179             return;
181         var startEditing = true;
182         if (Runtime.experiments.isEnabled("materialDesign")) {
183             startEditing = !this._editAsHTMLButton.toggled();
184             this._editAsHTMLButton.setToggled(startEditing);
185         }
186         treeOutline.toggleEditAsHTML(node, startEditing, this._updateActionsToolbar.bind(this, node));
187     },
189     /**
190      * @param {!WebInspector.ContextMenu} contextMenu
191      */
192     _showBreakpointsMenu: function(contextMenu)
193     {
194         var node = this.selectedDOMNode();
195         if (!node)
196             return;
197         WebInspector.domBreakpointsSidebarPane.populateNodeContextMenu(node, contextMenu, false);
198     },
200     /**
201      * @param {!WebInspector.ContextMenu} contextMenu
202      */
203     _showForceElementStateMenu: function(contextMenu)
204     {
205         var node = this.selectedDOMNode();
206         if (!node)
207             return;
208         WebInspector.ElementsTreeElement.populateForcedPseudoStateItems(contextMenu, node);
209     },
211     /**
212      * @param {!WebInspector.Event} event
213      */
214     _decorationsClicked: function(event)
215     {
216         var node = /** @type {!WebInspector.DOMNode} */(event.data);
217         this.selectDOMNode(node, true);
218         this._toggleActionsToolbar(true);
219     },
221     /**
222      * @param {boolean=} toggled
223      */
224     _toggleActionsToolbar: function(toggled)
225     {
226         if (toggled === undefined)
227             toggled = !this._actionsToolbarShown();
228         this._nodeActionsElement.classList.toggle("expanded", toggled);
229         this._positionActionsToolbar();
230     },
232     _positionActionsToolbar: function()
233     {
234         if (!this._actionsToolbarShown())
235             return;
236         var toolbarElement = this._nodeActionsToolbar.element;
237         if (toolbarElement.totalOffsetTop() < this.element.totalOffsetTop()) {
238             toolbarElement.style.top = this._nodeActionsElement.parentElement.offsetHeight + "px";
239             toolbarElement.classList.add("node-actions-toolbar-below");
240         } else {
241             toolbarElement.style.top = "";
242             toolbarElement.classList.remove("node-actions-toolbar-below");
243         }
244     },
246     /**
247      * @return {boolean}
248      */
249     _actionsToolbarShown: function()
250     {
251         return this._nodeActionsElement.classList.contains("expanded");
252     },
254     /**
255      * @param {!WebInspector.Event} event
256      */
257     _markersChanged: function(event)
258     {
259         var node = /** @type {!WebInspector.DOMNode} */ (event.data);
260         if (node !== this.selectedDOMNode())
261             return;
262         this._updateActionsToolbar(node);
263     },
265     _loadSidebarViews: function()
266     {
267         var extensions = self.runtime.extensions("@WebInspector.Widget");
269         for (var i = 0; i < extensions.length; ++i) {
270             var descriptor = extensions[i].descriptor();
271             if (descriptor["location"] !== "elements-panel")
272                 continue;
274             var title = WebInspector.UIString(descriptor["title"]);
275             extensions[i].instancePromise().then(addSidebarView.bind(this, title));
276         }
278         /**
279          * @param {string} title
280          * @param {!Object} object
281          * @this {WebInspector.ElementsPanel}
282          */
283         function addSidebarView(title, object)
284         {
285             var widget = /** @type {!WebInspector.Widget} */ (object);
286             var elementsSidebarViewWrapperPane = new WebInspector.ElementsSidebarViewWrapperPane(title, widget);
287             this._elementsSidebarViewWrappers.push(elementsSidebarViewWrapperPane);
289             if (this.sidebarPaneView)
290                 this.sidebarPaneView.addPane(elementsSidebarViewWrapperPane);
291         }
292     },
294     _onEditingSelectorStarted: function()
295     {
296         for (var i = 0; i < this._treeOutlines.length; ++i)
297             this._treeOutlines[i].setPickNodeMode(true);
298     },
300     _onEditingSelectorEnded: function()
301     {
302         for (var i = 0; i < this._treeOutlines.length; ++i)
303             this._treeOutlines[i].setPickNodeMode(false);
304     },
306     /**
307      * @override
308      * @param {!WebInspector.Target} target
309      */
310     targetAdded: function(target)
311     {
312         var domModel = WebInspector.DOMModel.fromTarget(target);
313         if (!domModel)
314             return;
315         var treeOutline = new WebInspector.ElementsTreeOutline(domModel, true, true);
316         treeOutline.setWordWrap(WebInspector.moduleSetting("domWordWrap").get());
317         treeOutline.wireToDOMModel();
318         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
319         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.NodePicked, this._onNodePicked, this);
320         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
321         treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.DecorationsClicked, this._decorationsClicked, this);
322         this._treeOutlines.push(treeOutline);
323         this._modelToTreeOutline.set(domModel, treeOutline);
325         // Perform attach if necessary.
326         if (this.isShowing())
327             this.wasShown();
329     },
331     /**
332      * @override
333      * @param {!WebInspector.Target} target
334      */
335     targetRemoved: function(target)
336     {
337         var domModel = WebInspector.DOMModel.fromTarget(target);
338         if (!domModel)
339             return;
340         var treeOutline = this._modelToTreeOutline.remove(domModel);
341         treeOutline.unwireFromDOMModel();
342         this._treeOutlines.remove(treeOutline);
343         treeOutline.element.remove();
344     },
346     _updateTreeOutlineVisibleWidth: function()
347     {
348         if (!this._treeOutlines.length)
349             return;
351         var width = this._splitWidget.element.offsetWidth;
352         if (this._splitWidget.isVertical())
353             width -= this._splitWidget.sidebarSize();
354         for (var i = 0; i < this._treeOutlines.length; ++i) {
355             this._treeOutlines[i].setVisibleWidth(width);
356             this._treeOutlines[i].updateSelection();
357         }
358         this._breadcrumbs.updateSizes();
359     },
361     /**
362      * @override
363      * @return {!Element}
364      */
365     defaultFocusedElement: function()
366     {
367         return this._treeOutlines.length ? this._treeOutlines[0].element : this.element;
368     },
370     /**
371      * @override
372      * @return {!WebInspector.SearchableView}
373      */
374     searchableView: function()
375     {
376         return this._searchableView;
377     },
379     wasShown: function()
380     {
381         for (var i = 0; i < this._treeOutlines.length; ++i) {
382             var treeOutline = this._treeOutlines[i];
383             // Attach heavy component lazily
384             if (treeOutline.element.parentElement !== this._contentElement)
385                 this._contentElement.appendChild(treeOutline.element);
386         }
387         WebInspector.Panel.prototype.wasShown.call(this);
388         this._breadcrumbs.update();
390         for (var i = 0; i < this._treeOutlines.length; ++i) {
391             var treeOutline = this._treeOutlines[i];
392             treeOutline.updateSelection();
393             treeOutline.setVisible(true);
395             if (!treeOutline.rootDOMNode)
396                 if (treeOutline.domModel().existingDocument())
397                     this._documentUpdated(treeOutline.domModel(), treeOutline.domModel().existingDocument());
398                 else
399                     treeOutline.domModel().requestDocument();
400         }
402     },
404     willHide: function()
405     {
406         WebInspector.DOMModel.hideDOMNodeHighlight();
407         for (var i = 0; i < this._treeOutlines.length; ++i) {
408             var treeOutline = this._treeOutlines[i];
409             treeOutline.setVisible(false);
410             // Detach heavy component on hide
411             this._contentElement.removeChild(treeOutline.element);
412         }
413         if (this._popoverHelper)
414             this._popoverHelper.hidePopover();
415         WebInspector.Panel.prototype.willHide.call(this);
416     },
418     onResize: function()
419     {
420         if (WebInspector.moduleSetting("sidebarPosition").get() === "auto")
421             this.element.window().requestAnimationFrame(this._updateSidebarPosition.bind(this));  // Do not force layout.
422         this._updateTreeOutlineVisibleWidth();
423     },
425     /**
426      * @param {!WebInspector.Event} event
427      */
428     _onNodePicked: function(event)
429     {
430         if (!this.sidebarPanes.styles.isEditingSelector())
431             return;
432         this.sidebarPanes.styles.updateEditingSelectorForNode(/** @type {!WebInspector.DOMNode} */(event.data));
433     },
435     /**
436      * @param {!WebInspector.Event} event
437      */
438     _selectedNodeChanged: function(event)
439     {
440         var selectedNode = /** @type {?WebInspector.DOMNode} */ (event.data);
441         for (var i = 0; i < this._treeOutlines.length; ++i) {
442             if (!selectedNode || selectedNode.domModel() !== this._treeOutlines[i].domModel())
443                 this._treeOutlines[i].selectDOMNode(null);
444         }
446         if (!selectedNode && this._lastValidSelectedNode)
447             this._selectedPathOnReset = this._lastValidSelectedNode.path();
449         this._breadcrumbs.setSelectedNode(selectedNode);
451         WebInspector.context.setFlavor(WebInspector.DOMNode, selectedNode);
453         if (selectedNode) {
454             selectedNode.setAsInspectedNode();
455             this._lastValidSelectedNode = selectedNode;
456             this._updateActionsToolbar(selectedNode);
457         }
458         WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged);
459         this._selectedNodeChangedForTest();
460     },
462     _selectedNodeChangedForTest: function() { },
464     _reset: function()
465     {
466         delete this.currentQuery;
467     },
469     /**
470      * @param {!WebInspector.Event} event
471      */
472     _documentUpdatedEvent: function(event)
473     {
474         this._documentUpdated(/** @type {!WebInspector.DOMModel} */ (event.target), /** @type {?WebInspector.DOMDocument} */ (event.data));
475     },
477     /**
478      * @param {!WebInspector.DOMModel} domModel
479      * @param {?WebInspector.DOMDocument} inspectedRootDocument
480      */
481     _documentUpdated: function(domModel, inspectedRootDocument)
482     {
483         this._reset();
484         this.searchCanceled();
486         var treeOutline = this._modelToTreeOutline.get(domModel);
487         treeOutline.rootDOMNode = inspectedRootDocument;
489         if (!inspectedRootDocument) {
490             if (this.isShowing())
491                 domModel.requestDocument();
492             return;
493         }
495         WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(domModel);
497         /**
498          * @this {WebInspector.ElementsPanel}
499          * @param {?WebInspector.DOMNode} candidateFocusNode
500          */
501         function selectNode(candidateFocusNode)
502         {
503             if (!candidateFocusNode)
504                 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
506             if (!candidateFocusNode)
507                 return;
509             if (!this._pendingNodeReveal) {
510                 this.selectDOMNode(candidateFocusNode);
511                 if (treeOutline.selectedTreeElement)
512                     treeOutline.selectedTreeElement.expand();
513             }
514         }
516         /**
517          * @param {?DOMAgent.NodeId} nodeId
518          * @this {WebInspector.ElementsPanel}
519          */
520         function selectLastSelectedNode(nodeId)
521         {
522             if (this.selectedDOMNode()) {
523                 // Focused node has been explicitly set while reaching out for the last selected node.
524                 return;
525             }
526             var node = nodeId ? domModel.nodeForId(nodeId) : null;
527             selectNode.call(this, node);
528         }
530         if (this._omitDefaultSelection)
531             return;
533         if (this._selectedPathOnReset)
534             domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
535         else
536             selectNode.call(this, null);
537         delete this._selectedPathOnReset;
538     },
540     /**
541      * @override
542      */
543     searchCanceled: function()
544     {
545         delete this._searchQuery;
546         this._hideSearchHighlights();
548         this._searchableView.updateSearchMatchesCount(0);
550         delete this._currentSearchResultIndex;
551         delete this._searchResults;
553         WebInspector.DOMModel.cancelSearch();
554     },
556     /**
557      * @override
558      * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
559      * @param {boolean} shouldJump
560      * @param {boolean=} jumpBackwards
561      */
562     performSearch: function(searchConfig, shouldJump, jumpBackwards)
563     {
564         var query = searchConfig.query;
565         // Call searchCanceled since it will reset everything we need before doing a new search.
566         this.searchCanceled();
568         const whitespaceTrimmedQuery = query.trim();
569         if (!whitespaceTrimmedQuery.length)
570             return;
572         this._searchQuery = query;
574         var promises = [];
575         var domModels = WebInspector.DOMModel.instances();
576         for (var domModel of domModels)
577             promises.push(domModel.performSearchPromise(whitespaceTrimmedQuery, WebInspector.moduleSetting("showUAShadowDOM").get()));
578         Promise.all(promises).then(resultCountCallback.bind(this));
580         /**
581          * @param {!Array.<number>} resultCounts
582          * @this {WebInspector.ElementsPanel}
583          */
584         function resultCountCallback(resultCounts)
585         {
586             /**
587              * @type {!Array.<{domModel: !WebInspector.DOMModel, index: number, node: (?WebInspector.DOMNode|undefined)}>}
588              */
589             this._searchResults = [];
590             for (var i = 0; i < resultCounts.length; ++i) {
591                 var resultCount = resultCounts[i];
592                 for (var j = 0; j < resultCount; ++j)
593                     this._searchResults.push({domModel: domModels[i], index: j, node: undefined});
594             }
595             this._searchableView.updateSearchMatchesCount(this._searchResults.length);
596             if (!this._searchResults.length)
597                 return;
598             this._currentSearchResultIndex = -1;
600             if (shouldJump)
601                 this._jumpToSearchResult(jumpBackwards ? -1 : 0);
602         }
603     },
605     _domWordWrapSettingChanged: function(event)
606     {
607         // FIXME: crbug.com/425984
608         this._contentElement.classList.toggle("elements-wrap", event.data);
609         for (var i = 0; i < this._treeOutlines.length; ++i)
610             this._treeOutlines[i].setWordWrap(/** @type {boolean} */ (event.data));
612         var selectedNode = this.selectedDOMNode();
613         if (!selectedNode)
614             return;
616         var treeElement = this._treeElementForNode(selectedNode);
617         if (treeElement)
618             treeElement.updateSelection(); // Recalculate selection highlight dimensions.
619     },
621     switchToAndFocus: function(node)
622     {
623         // Reset search restore.
624         this._searchableView.cancelSearch();
625         WebInspector.inspectorView.setCurrentPanel(this);
626         this.selectDOMNode(node, true);
627     },
629     /**
630      * @param {!Element} element
631      * @param {!Event} event
632      * @return {!Element|!AnchorBox|undefined}
633      */
634     _getPopoverAnchor: function(element, event)
635     {
636         var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
637         if (!anchor || !anchor.href)
638             return;
640         return anchor;
641     },
643     /**
644      * @param {!Element} anchor
645      * @param {!WebInspector.Popover} popover
646      */
647     _showPopover: function(anchor, popover)
648     {
649         var node = this.selectedDOMNode();
650         if (node)
651             WebInspector.DOMPresentationUtils.buildImagePreviewContents(node.target(), anchor.href, true, showPopover);
653         /**
654          * @param {!Element=} contents
655          */
656         function showPopover(contents)
657         {
658             if (!contents)
659                 return;
660             popover.setCanShrink(false);
661             popover.showForAnchor(contents, anchor);
662         }
663     },
665     _jumpToSearchResult: function(index)
666     {
667         this._hideSearchHighlights();
668         this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
669         this._highlightCurrentSearchResult();
670     },
672     /**
673      * @override
674      */
675     jumpToNextSearchResult: function()
676     {
677         if (!this._searchResults)
678             return;
679         this._jumpToSearchResult(this._currentSearchResultIndex + 1);
680     },
682     /**
683      * @override
684      */
685     jumpToPreviousSearchResult: function()
686     {
687         if (!this._searchResults)
688             return;
689         this._jumpToSearchResult(this._currentSearchResultIndex - 1);
690     },
692     /**
693      * @override
694      * @return {boolean}
695      */
696     supportsCaseSensitiveSearch: function()
697     {
698         return false;
699     },
701     /**
702      * @override
703      * @return {boolean}
704      */
705     supportsRegexSearch: function()
706     {
707         return false;
708     },
710     _highlightCurrentSearchResult: function()
711     {
712         var index = this._currentSearchResultIndex;
713         var searchResults = this._searchResults;
714         var searchResult = searchResults[index];
716         if (searchResult.node === null) {
717             this._searchableView.updateCurrentMatchIndex(index);
718             return;
719         }
721         /**
722          * @param {?WebInspector.DOMNode} node
723          * @this {WebInspector.ElementsPanel}
724          */
725         function searchCallback(node)
726         {
727             searchResult.node = node;
728             this._highlightCurrentSearchResult();
729         }
731         if (typeof searchResult.node === "undefined") {
732             // No data for slot, request it.
733             searchResult.domModel.searchResult(searchResult.index, searchCallback.bind(this));
734             return;
735         }
737         this._searchableView.updateCurrentMatchIndex(index);
739         var treeElement = this._treeElementForNode(searchResult.node);
740         if (treeElement) {
741             treeElement.highlightSearchResults(this._searchQuery);
742             treeElement.reveal();
743             var matches = treeElement.listItemElement.getElementsByClassName(WebInspector.highlightedSearchResultClassName);
744             if (matches.length)
745                 matches[0].scrollIntoViewIfNeeded();
746         }
747     },
749     _hideSearchHighlights: function()
750     {
751         if (!this._searchResults || !this._searchResults.length || this._currentSearchResultIndex < 0)
752             return;
753         var searchResult = this._searchResults[this._currentSearchResultIndex];
754         if (!searchResult.node)
755             return;
756         var treeOutline = this._modelToTreeOutline.get(searchResult.node.domModel());
757         var treeElement = treeOutline.findTreeElement(searchResult.node);
758         if (treeElement)
759             treeElement.hideSearchHighlights();
760     },
762     /**
763      * @return {?WebInspector.DOMNode}
764      */
765     selectedDOMNode: function()
766     {
767         for (var i = 0; i < this._treeOutlines.length; ++i) {
768             var treeOutline = this._treeOutlines[i];
769             if (treeOutline.selectedDOMNode())
770                 return treeOutline.selectedDOMNode();
771         }
772         return null;
773     },
775     /**
776      * @param {!WebInspector.DOMNode} node
777      * @param {boolean=} focus
778      */
779     selectDOMNode: function(node, focus)
780     {
781         for (var i = 0; i < this._treeOutlines.length; ++i) {
782             var treeOutline = this._treeOutlines[i];
783             if (treeOutline.domModel() === node.domModel())
784                 treeOutline.selectDOMNode(node, focus);
785             else
786                 treeOutline.selectDOMNode(null);
787         }
788     },
790     /**
791      * @param {!WebInspector.Event} event
792      */
793     _updateBreadcrumbIfNeeded: function(event)
794     {
795         var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data);
796         this._breadcrumbs.updateNodes(nodes);
797     },
799     /**
800      * @param {!WebInspector.Event} event
801      */
802     _crumbNodeSelected: function(event)
803     {
804         var node = /** @type {!WebInspector.DOMNode} */ (event.data);
805         this.selectDOMNode(node, true);
806     },
808     /**
809      * @override
810      * @param {!KeyboardEvent} event
811      */
812     handleShortcut: function(event)
813     {
814         /**
815          * @param {!WebInspector.ElementsTreeOutline} treeOutline
816          */
817         function handleUndoRedo(treeOutline)
818         {
819             if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key
820                 treeOutline.domModel().undo();
821                 event.handled = true;
822                 return;
823             }
825             var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
826                                                    event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
827             if (isRedoKey) {
828                 treeOutline.domModel().redo();
829                 event.handled = true;
830             }
831         }
833         if (WebInspector.isEditing() && event.keyCode !== WebInspector.KeyboardShortcut.Keys.F2.code)
834             return;
836         var treeOutline = null;
837         for (var i = 0; i < this._treeOutlines.length; ++i) {
838             if (this._treeOutlines[i].selectedDOMNode() === this._lastValidSelectedNode)
839                 treeOutline = this._treeOutlines[i];
840         }
841         if (!treeOutline)
842             return;
844         if (!treeOutline.editing()) {
845             handleUndoRedo.call(null, treeOutline);
846             if (event.handled) {
847                 this.sidebarPanes.styles.onUndoOrRedoHappened();
848                 return;
849             }
850         }
852         treeOutline.handleShortcut(event);
853         if (event.handled)
854             return;
856         WebInspector.Panel.prototype.handleShortcut.call(this, event);
857     },
859     /**
860      * @param {?WebInspector.DOMNode} node
861      * @return {?WebInspector.ElementsTreeOutline}
862      */
863     _treeOutlineForNode: function(node)
864     {
865         if (!node)
866             return null;
867         return this._modelToTreeOutline.get(node.domModel()) || null;
868     },
870     /**
871      * @return {?WebInspector.ElementsTreeOutline}
872      */
873     _focusedTreeOutline: function()
874     {
875         for (var i = 0; i < this._treeOutlines.length; ++i) {
876             if (this._treeOutlines[i].hasFocus())
877                 return this._treeOutlines[i];
878         }
879         return null;
880     },
882     /**
883      * @param {!WebInspector.DOMNode} node
884      * @return {?WebInspector.ElementsTreeElement}
885      */
886     _treeElementForNode: function(node)
887     {
888         var treeOutline = this._treeOutlineForNode(node);
889         return /** @type {?WebInspector.ElementsTreeElement} */ (treeOutline.findTreeElement(node));
890     },
892     /**
893      * @param {!Event} event
894      */
895     handleCopyEvent: function(event)
896     {
897         var treeOutline = this._focusedTreeOutline();
898         if (treeOutline)
899             treeOutline.handleCopyOrCutKeyboardEvent(false, event);
900     },
902     /**
903      * @param {!Event} event
904      */
905     handleCutEvent: function(event)
906     {
907         var treeOutline = this._focusedTreeOutline();
908         if (treeOutline)
909             treeOutline.handleCopyOrCutKeyboardEvent(true, event);
910     },
912     /**
913      * @param {!Event} event
914      */
915     handlePasteEvent: function(event)
916     {
917         var treeOutline = this._focusedTreeOutline();
918         if (treeOutline)
919             treeOutline.handlePasteKeyboardEvent(event);
920     },
922     /**
923      * @param {!WebInspector.DOMNode} node
924      * @return {!WebInspector.DOMNode}
925      */
926     _leaveUserAgentShadowDOM: function(node)
927     {
928         var userAgentShadowRoot = node.ancestorUserAgentShadowRoot();
929         return userAgentShadowRoot ? /** @type {!WebInspector.DOMNode} */ (userAgentShadowRoot.parentNode) : node;
930     },
932     /**
933      * @param {!WebInspector.DOMNode} node
934      */
935     revealAndSelectNode: function(node)
936     {
937         if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.started())
938             WebInspector.inspectElementModeController.stop();
940         this._omitDefaultSelection = true;
942         WebInspector.inspectorView.setCurrentPanel(this, this._showLayoutEditor);
943         node = WebInspector.moduleSetting("showUAShadowDOM").get() ? node : this._leaveUserAgentShadowDOM(node);
944         if (!this._showLayoutEditor)
945             node.highlightForTwoSeconds();
947         this.selectDOMNode(node, true);
948         delete this._omitDefaultSelection;
950         if (!this._notFirstInspectElement)
951             InspectorFrontendHost.inspectElementCompleted();
952         this._notFirstInspectElement = true;
953     },
955     /**
956      * @param {!Event} event
957      * @param {!WebInspector.ContextMenu} contextMenu
958      * @param {!Object} object
959      */
960     appendApplicableItems: function(event, contextMenu, object)
961     {
962         if (!(object instanceof WebInspector.RemoteObject && (/** @type {!WebInspector.RemoteObject} */ (object)).isNode())
963             && !(object instanceof WebInspector.DOMNode)
964             && !(object instanceof WebInspector.DeferredDOMNode)) {
965             return;
966         }
968         // Add debbuging-related actions
969         if (object instanceof WebInspector.DOMNode) {
970             contextMenu.appendSeparator();
971             WebInspector.domBreakpointsSidebarPane.populateNodeContextMenu(object, contextMenu, true);
972         }
974         // Skip adding "Reveal..." menu item for our own tree outline.
975         if (this.element.isAncestor(/** @type {!Node} */ (event.target)))
976             return;
977         var commandCallback = WebInspector.Revealer.reveal.bind(WebInspector.Revealer, object);
979         contextMenu.appendItem(WebInspector.UIString.capitalize("Reveal in Elements ^panel"), commandCallback);
980     },
982     _sidebarContextMenuEventFired: function(event)
983     {
984         var contextMenu = new WebInspector.ContextMenu(event);
985         contextMenu.appendApplicableItems(/** @type {!Object} */ (event.deepElementFromPoint()));
986         contextMenu.show();
987     },
989     _showUAShadowDOMChanged: function()
990     {
991         for (var i = 0; i < this._treeOutlines.length; ++i)
992             this._treeOutlines[i].update();
993     },
995     _updateSidebarPosition: function()
996     {
997         var vertically;
998         var position = WebInspector.moduleSetting("sidebarPosition").get();
999         if (position === "right")
1000             vertically = false;
1001         else if (position === "bottom")
1002             vertically = true;
1003         else
1004             vertically = WebInspector.inspectorView.element.offsetWidth < 680;
1006         if (this.sidebarPaneView && vertically === !this._splitWidget.isVertical())
1007             return;
1009         if (this.sidebarPaneView && this.sidebarPaneView.shouldHideOnDetach())
1010             return; // We can't reparent extension iframes.
1012         var selectedTabId = this.sidebarPaneView ? this.sidebarPaneView.selectedTabId : null;
1014         var extensionSidebarPanes = WebInspector.extensionServer.sidebarPanes();
1015         if (this.sidebarPaneView) {
1016             this.sidebarPaneView.detach();
1017             this._splitWidget.uninstallResizer(this.sidebarPaneView.headerElement());
1018         }
1020         this._splitWidget.setVertical(!vertically);
1022         var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed"));
1023         computedPane.element.classList.add("composite");
1024         computedPane.element.classList.add("fill");
1026         computedPane.element.classList.add("metrics-and-computed");
1028         var matchedStylePanesWrapper = new WebInspector.VBox();
1029         matchedStylePanesWrapper.element.classList.add("style-panes-wrapper");
1030         var computedStylePanesWrapper = new WebInspector.VBox();
1031         computedStylePanesWrapper.element.classList.add("style-panes-wrapper");
1033         /**
1034          * @param {boolean} inComputedStyle
1035          * @this {WebInspector.ElementsPanel}
1036          */
1037         function showMetrics(inComputedStyle)
1038         {
1039             if (inComputedStyle)
1040                 this.sidebarPanes.metrics.show(computedStylePanesWrapper.element, this.sidebarPanes.computedStyle.element);
1041             else
1042                 this.sidebarPanes.metrics.show(matchedStylePanesWrapper.element);
1043         }
1045         /**
1046          * @param {!WebInspector.Event} event
1047          * @this {WebInspector.ElementsPanel}
1048          */
1049         function tabSelected(event)
1050         {
1051             var tabId = /** @type {string} */ (event.data.tabId);
1052             if (tabId === computedPane.title())
1053                 showMetrics.call(this, true);
1054             else if (tabId === stylesPane.title())
1055                 showMetrics.call(this, false);
1056         }
1058         this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
1059         this.sidebarPaneView.element.addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
1060         if (this._popoverHelper)
1061             this._popoverHelper.hidePopover();
1062         this._popoverHelper = new WebInspector.PopoverHelper(this.sidebarPaneView.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
1063         this._popoverHelper.setTimeout(0);
1065         if (vertically) {
1066             this._splitWidget.installResizer(this.sidebarPaneView.headerElement());
1068             var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1069             compositePane.element.classList.add("composite");
1070             compositePane.element.classList.add("fill");
1072             var splitWidget = new WebInspector.SplitWidget(true, true, "stylesPaneSplitViewState", 215);
1073             splitWidget.show(compositePane.element);
1075             splitWidget.setMainWidget(matchedStylePanesWrapper);
1076             splitWidget.setSidebarWidget(computedStylePanesWrapper);
1078             computedPane.show(computedStylePanesWrapper.element);
1079             this.sidebarPaneView.addPane(compositePane);
1080         } else {
1081             var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1082             stylesPane.element.classList.add("composite", "fill", "metrics-and-styles");
1084             matchedStylePanesWrapper.show(stylesPane.element);
1085             computedStylePanesWrapper.show(computedPane.element);
1087             this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this);
1089             this.sidebarPaneView.addPane(stylesPane);
1090             this.sidebarPaneView.addPane(computedPane);
1091         }
1093         this.sidebarPanes.styles.show(matchedStylePanesWrapper.element);
1094         this.sidebarPanes.computedStyle.show(computedStylePanesWrapper.element);
1095         showMetrics.call(this, vertically);
1096         this.sidebarPanes.platformFonts.show(computedStylePanesWrapper.element);
1098         this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners);
1099         this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints);
1100         this.sidebarPaneView.addPane(this.sidebarPanes.properties);
1102         for (var sidebarViewWrapper of this._elementsSidebarViewWrappers)
1103             this.sidebarPaneView.addPane(sidebarViewWrapper);
1105         this._extensionSidebarPanesContainer = this.sidebarPaneView;
1107         for (var i = 0; i < extensionSidebarPanes.length; ++i)
1108             this._addExtensionSidebarPane(extensionSidebarPanes[i]);
1110         this._splitWidget.setSidebarWidget(this.sidebarPaneView);
1111         this.sidebarPanes.styles.expand();
1113         if (selectedTabId)
1114             this.sidebarPaneView.selectTab(selectedTabId);
1115     },
1117     /**
1118      * @param {!WebInspector.Event} event
1119      */
1120     _extensionSidebarPaneAdded: function(event)
1121     {
1122         var pane = /** @type {!WebInspector.ExtensionSidebarPane} */ (event.data);
1123         this._addExtensionSidebarPane(pane);
1124     },
1126     /**
1127      * @param {!WebInspector.ExtensionSidebarPane} pane
1128      */
1129     _addExtensionSidebarPane: function(pane)
1130     {
1131         if (pane.panelName() === this.name)
1132             this._extensionSidebarPanesContainer.addPane(pane);
1133     },
1135     /**
1136      * @param {?WebInspector.Widget} widget
1137      */
1138     setWidgetBelowDOM: function(widget)
1139     {
1140         if (widget) {
1141             this._elementsPanelTreeOutilneSplit.setSidebarWidget(widget);
1142             this._elementsPanelTreeOutilneSplit.showBoth(true);
1143         } else {
1144             this._elementsPanelTreeOutilneSplit.hideSidebar(true);
1145         }
1146     },
1148     __proto__: WebInspector.Panel.prototype
1152  * @constructor
1153  * @implements {WebInspector.ContextMenu.Provider}
1154  */
1155 WebInspector.ElementsPanel.ContextMenuProvider = function()
1159 WebInspector.ElementsPanel.ContextMenuProvider.prototype = {
1160     /**
1161      * @override
1162      * @param {!Event} event
1163      * @param {!WebInspector.ContextMenu} contextMenu
1164      * @param {!Object} target
1165      */
1166     appendApplicableItems: function(event, contextMenu, target)
1167     {
1168         WebInspector.ElementsPanel.instance().appendApplicableItems(event, contextMenu, target);
1169     }
1173  * @constructor
1174  * @implements {WebInspector.Revealer}
1175  */
1176 WebInspector.ElementsPanel.DOMNodeRevealer = function()
1180 WebInspector.ElementsPanel.DOMNodeRevealer.prototype = {
1181     /**
1182      * @override
1183      * @param {!Object} node
1184      * @return {!Promise}
1185      */
1186     reveal: function(node)
1187     {
1188         var panel = WebInspector.ElementsPanel.instance();
1189         panel._pendingNodeReveal = true;
1191         return new Promise(revealPromise);
1193         /**
1194          * @param {function(undefined)} resolve
1195          * @param {function(!Error)} reject
1196          */
1197         function revealPromise(resolve, reject)
1198         {
1199             if (node instanceof WebInspector.DOMNode) {
1200                 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (node));
1201             } else if (node instanceof WebInspector.DeferredDOMNode) {
1202                 (/** @type {!WebInspector.DeferredDOMNode} */ (node)).resolve(onNodeResolved);
1203             } else if (node instanceof WebInspector.RemoteObject) {
1204                 var domModel = WebInspector.DOMModel.fromTarget(/** @type {!WebInspector.RemoteObject} */ (node).target());
1205                 if (domModel)
1206                     domModel.pushObjectAsNodeToFrontend(node, onNodeResolved);
1207                 else
1208                     reject(new Error("Could not resolve a node to reveal."));
1209             } else {
1210                 reject(new Error("Can't reveal a non-node."));
1211                 panel._pendingNodeReveal = false;
1212             }
1214             /**
1215              * @param {?WebInspector.DOMNode} resolvedNode
1216              */
1217             function onNodeResolved(resolvedNode)
1218             {
1219                 panel._pendingNodeReveal = false;
1221                 if (resolvedNode) {
1222                     panel.revealAndSelectNode(resolvedNode);
1223                     resolve(undefined);
1224                     return;
1225                 }
1226                 reject(new Error("Could not resolve node to reveal."));
1227             }
1228         }
1229     }
1232 WebInspector.ElementsPanel.show = function()
1234     WebInspector.inspectorView.setCurrentPanel(WebInspector.ElementsPanel.instance());
1238  * @return {!WebInspector.ElementsPanel}
1239  */
1240 WebInspector.ElementsPanel.instance = function()
1242     if (!WebInspector.ElementsPanel._instanceObject)
1243         WebInspector.ElementsPanel._instanceObject = new WebInspector.ElementsPanel();
1244     return WebInspector.ElementsPanel._instanceObject;
1248  * @constructor
1249  * @implements {WebInspector.PanelFactory}
1250  */
1251 WebInspector.ElementsPanelFactory = function()
1255 WebInspector.ElementsPanelFactory.prototype = {
1256     /**
1257      * @override
1258      * @return {!WebInspector.Panel}
1259      */
1260     createPanel: function()
1261     {
1262         return WebInspector.ElementsPanel.instance();
1263     }
1267  * @constructor
1268  * @implements {WebInspector.ActionDelegate}
1269  */
1270 WebInspector.ElementsActionDelegate = function() { }
1272 WebInspector.ElementsActionDelegate.prototype = {
1273     /**
1274      * @override
1275      * @param {!WebInspector.Context} context
1276      * @param {string} actionId
1277      */
1278     handleAction: function(context, actionId)
1279     {
1280         var elementsPanel = WebInspector.ElementsPanel.instance();
1281         if (actionId === "elements.hide-element")
1282             elementsPanel._toggleHideElement();
1283         else if (actionId === "elements.edit-as-html")
1284             elementsPanel._toggleEditAsHTML();
1285     }
1289  * @constructor
1290  * @implements {WebInspector.DOMPresentationUtils.MarkerDecorator}
1291  */
1292 WebInspector.ElementsPanel.PseudoStateMarkerDecorator = function()
1296 WebInspector.ElementsPanel.PseudoStateMarkerDecorator.prototype = {
1297     /**
1298      * @override
1299      * @param {!WebInspector.DOMNode} node
1300      * @return {?{title: string, color: string}}
1301      */
1302     decorate: function(node)
1303     {
1304         return { color: "orange", title: WebInspector.UIString("Element state: %s", ":" + WebInspector.CSSStyleModel.fromNode(node).pseudoState(node).join(", :")) };
1305     }
1309  * @constructor
1310  * @implements {WebInspector.DOMPresentationUtils.MarkerDecorator}
1311  */
1312 WebInspector.ElementsPanel.HiddenMarkerDecorator = function()
1316 WebInspector.ElementsPanel.HiddenMarkerDecorator.prototype = {
1317     /**
1318      * @override
1319      * @param {!WebInspector.DOMNode} node
1320      * @return {?{title: string, color: string}}
1321      */
1322     decorate: function(node)
1323     {
1324         return { color: "#555", title: WebInspector.UIString("Element is hidden") };
1325     }