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