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
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
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");
43 * @implements {WebInspector.Searchable}
44 * @extends {WebInspector.Panel}
46 WebInspector.ElementsPanel = function()
48 WebInspector.Panel.call(this, "elements");
49 this.registerRequiredCSS("breadcrumbList.css");
50 this.registerRequiredCSS("elementsPanel.css");
51 this.registerRequiredCSS("textPrompt.css");
52 this.setHideOnDetach();
54 const initialSidebarWidth = 325;
55 const minimumContentWidthPercent = 0.34;
56 const initialSidebarHeight = 325;
57 const minimumContentHeightPercent = 0.34;
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);
65 this._searchableView = new WebInspector.SearchableView(this);
66 this._searchableView.show(this._splitView.mainElement());
67 var stackElement = this._searchableView.element;
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));
77 this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
78 this._splitView.sidebarElement().addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
80 this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNodeId.bind(this));
81 this.treeOutline.wireToDomAgent();
83 this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
84 this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
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);
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();
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));
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 = [];
112 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
113 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
114 this._dockSideChanged();
116 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
117 this._popoverHelper.setTimeout(0);
119 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdatedEvent, this);
120 WebInspector.settings.showShadowDOM.addChangeListener(this._showShadowDOMChanged.bind(this));
122 if (WebInspector.domAgent.existingDocument())
123 this._documentUpdated(WebInspector.domAgent.existingDocument());
125 WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
128 WebInspector.ElementsPanel.prototype = {
129 _updateTreeOutlineVisibleWidth: function()
131 if (!this.treeOutline)
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();
145 defaultFocusedElement: function()
147 return this.treeOutline.element;
151 * @return {!WebInspector.SearchableView}
153 searchableView: function()
155 return this._searchableView;
160 // Attach heavy component lazily
161 if (this.treeOutline.element.parentElement !== this.contentElement)
162 this.contentElement.appendChild(this.treeOutline.element);
164 WebInspector.Panel.prototype.wasShown.call(this);
166 this.updateBreadcrumb();
167 this.treeOutline.updateSelection();
168 this.treeOutline.setVisible(true);
170 if (!this.treeOutline.rootDOMNode)
171 WebInspector.domAgent.requestDocument();
176 WebInspector.domAgent.hideDOMNodeHighlight();
177 this.treeOutline.setVisible(false);
178 this._popoverHelper.hidePopover();
180 // Detach heavy component on hide
181 this.contentElement.removeChild(this.treeOutline.element);
183 WebInspector.Panel.prototype.willHide.call(this);
188 this._updateTreeOutlineVisibleWidth();
192 * @param {!DOMAgent.NodeId} nodeId
193 * @param {string} pseudoClass
194 * @param {boolean} enable
196 _setPseudoClassForNodeId: function(nodeId, pseudoClass, enable)
198 var node = WebInspector.domAgent.nodeForId(nodeId);
202 var pseudoClasses = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
204 pseudoClasses = pseudoClasses || [];
205 if (pseudoClasses.indexOf(pseudoClass) >= 0)
207 pseudoClasses.push(pseudoClass);
208 node.setUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName, pseudoClasses);
210 if (!pseudoClasses || pseudoClasses.indexOf(pseudoClass) < 0)
212 pseudoClasses.remove(pseudoClass);
213 if (!pseudoClasses.length)
214 node.removeUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
217 this.treeOutline.updateOpenCloseTags(node);
218 WebInspector.cssModel.forcePseudoState(node.id, node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName));
219 this._metricsPaneEdited();
220 this._stylesPaneEdited();
222 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
223 action: WebInspector.UserMetrics.UserActionNames.ForcedElementState,
224 selector: WebInspector.DOMPresentationUtils.fullQualifiedSelector(node, false),
230 _selectedNodeChanged: function()
232 var selectedNode = this.selectedDOMNode();
233 if (!selectedNode && this._lastValidSelectedNode)
234 this._selectedPathOnReset = this._lastValidSelectedNode.path();
236 this.updateBreadcrumb(false);
238 this._updateSidebars();
241 ConsoleAgent.addInspectedNode(selectedNode.id);
242 this._lastValidSelectedNode = selectedNode;
244 WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged);
247 _updateSidebars: function()
249 for (var pane in this.sidebarPanes)
250 this.sidebarPanes[pane].needsUpdate = true;
252 this.updateStyles(true);
253 this.updateMetrics();
254 this.updatePlatformFonts();
255 this.updateProperties();
256 this.updateEventListeners();
261 delete this.currentQuery;
264 _documentUpdatedEvent: function(event)
266 this._documentUpdated(event.data);
269 _documentUpdated: function(inspectedRootDocument)
272 this.searchCanceled();
274 this.treeOutline.rootDOMNode = inspectedRootDocument;
276 if (!inspectedRootDocument) {
277 if (this.isShowing())
278 WebInspector.domAgent.requestDocument();
282 WebInspector.domBreakpointsSidebarPane.restoreBreakpoints();
285 * @this {WebInspector.ElementsPanel}
286 * @param {?WebInspector.DOMNode} candidateFocusNode
288 function selectNode(candidateFocusNode)
290 if (!candidateFocusNode)
291 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
293 if (!candidateFocusNode)
296 this.selectDOMNode(candidateFocusNode);
297 if (this.treeOutline.selectedTreeElement)
298 this.treeOutline.selectedTreeElement.expand();
302 * @param {?DOMAgent.NodeId} nodeId
303 * @this {WebInspector.ElementsPanel}
305 function selectLastSelectedNode(nodeId)
307 if (this.selectedDOMNode()) {
308 // Focused node has been explicitly set while reaching out for the last selected node.
311 var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null;
312 selectNode.call(this, node);
315 if (this._selectedPathOnReset)
316 WebInspector.domAgent.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
318 selectNode.call(this, null);
319 delete this._selectedPathOnReset;
322 searchCanceled: function()
324 delete this._searchQuery;
325 this._hideSearchHighlights();
327 this._searchableView.updateSearchMatchesCount(0);
329 delete this._currentSearchResultIndex;
330 delete this._searchResults;
331 WebInspector.domAgent.cancelSearch();
335 * @param {string} query
336 * @param {boolean} shouldJump
338 performSearch: function(query, shouldJump)
340 // Call searchCanceled since it will reset everything we need before doing a new search.
341 this.searchCanceled();
343 const whitespaceTrimmedQuery = query.trim();
344 if (!whitespaceTrimmedQuery.length)
347 this._searchQuery = query;
350 * @param {number} resultCount
351 * @this {WebInspector.ElementsPanel}
353 function resultCountCallback(resultCount)
355 this._searchableView.updateSearchMatchesCount(resultCount);
359 this._searchResults = new Array(resultCount);
360 this._currentSearchResultIndex = -1;
362 this.jumpToNextSearchResult();
364 WebInspector.domAgent.performSearch(whitespaceTrimmedQuery, resultCountCallback.bind(this));
367 _contextMenuEventFired: function(event)
369 function toggleWordWrap()
371 WebInspector.settings.domWordWrap.set(!WebInspector.settings.domWordWrap.get());
374 var contextMenu = new WebInspector.ContextMenu(event);
375 this.treeOutline.populateContextMenu(contextMenu, event);
377 contextMenu.appendSeparator();
378 contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Word wrap" : "Word Wrap"), toggleWordWrap.bind(this), WebInspector.settings.domWordWrap.get());
383 _domWordWrapSettingChanged: function(event)
386 this.contentElement.classList.remove("nowrap");
388 this.contentElement.classList.add("nowrap");
390 var selectedNode = this.selectedDOMNode();
394 var treeElement = this.treeOutline.findTreeElement(selectedNode);
396 treeElement.updateSelection(); // Recalculate selection highlight dimensions.
399 switchToAndFocus: function(node)
401 // Reset search restore.
402 this._searchableView.cancelSearch();
403 WebInspector.inspectorView.setCurrentPanel(this);
404 this.selectDOMNode(node, true);
407 _populateContextMenu: function(contextMenu, node)
409 // Add debbuging-related actions
410 contextMenu.appendSeparator();
411 var pane = WebInspector.domBreakpointsSidebarPane;
412 pane.populateNodeContextMenu(node, contextMenu);
415 _getPopoverAnchor: function(element)
417 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
422 var resource = WebInspector.resourceTreeModel.resourceForURL(anchor.href);
423 if (!resource || resource.type !== WebInspector.resourceTypes.Image)
426 anchor.removeAttribute("title");
431 _loadDimensionsForNode: function(treeElement, callback)
433 // We get here for CSS properties, too, so bail out early for non-DOM treeElements.
434 if (treeElement.treeOutline !== this.treeOutline) {
439 var node = /** @type {!WebInspector.DOMNode} */ (treeElement.representedObject);
441 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
446 WebInspector.RemoteObject.resolveNode(node, "", resolvedNode);
448 function resolvedNode(object)
455 object.callFunctionJSON(dimensions, undefined, callback);
459 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}}
462 function dimensions()
464 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight };
470 * @param {!Element} anchor
471 * @param {!WebInspector.Popover} popover
473 _showPopover: function(anchor, popover)
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));
479 WebInspector.DOMPresentationUtils.buildImagePreviewContents(anchor.href, true, showPopover);
482 * @param {!Element=} contents
484 function showPopover(contents)
488 popover.setCanShrink(false);
489 popover.show(contents, anchor);
493 jumpToNextSearchResult: function()
495 if (!this._searchResults)
498 this._hideSearchHighlights();
499 if (++this._currentSearchResultIndex >= this._searchResults.length)
500 this._currentSearchResultIndex = 0;
502 this._highlightCurrentSearchResult();
505 jumpToPreviousSearchResult: function()
507 if (!this._searchResults)
510 this._hideSearchHighlights();
511 if (--this._currentSearchResultIndex < 0)
512 this._currentSearchResultIndex = (this._searchResults.length - 1);
514 this._highlightCurrentSearchResult();
517 _highlightCurrentSearchResult: function()
519 var index = this._currentSearchResultIndex;
520 var searchResults = this._searchResults;
521 var searchResult = searchResults[index];
523 if (searchResult === null) {
524 this._searchableView.updateCurrentMatchIndex(index);
529 * @param {?WebInspector.DOMNode} node
530 * @this {WebInspector.ElementsPanel}
532 function searchCallback(node)
534 searchResults[index] = node;
535 this._highlightCurrentSearchResult();
538 if (typeof searchResult === "undefined") {
539 // No data for slot, request it.
540 WebInspector.domAgent.searchResult(index, searchCallback.bind(this));
544 this._searchableView.updateCurrentMatchIndex(index);
546 var treeElement = this.treeOutline.findTreeElement(searchResult);
548 treeElement.highlightSearchResults(this._searchQuery);
549 treeElement.reveal();
550 var matches = treeElement.listItemElement.getElementsByClassName("highlighted-search-result");
552 matches[0].scrollIntoViewIfNeeded();
556 _hideSearchHighlights: function()
558 if (!this._searchResults)
560 var searchResult = this._searchResults[this._currentSearchResultIndex];
563 var treeElement = this.treeOutline.findTreeElement(searchResult);
565 treeElement.hideSearchHighlights();
569 * @return {?WebInspector.DOMNode}
571 selectedDOMNode: function()
573 return this.treeOutline.selectedDOMNode();
577 * @param {boolean=} focus
579 selectDOMNode: function(node, focus)
581 this.treeOutline.selectDOMNode(node, focus);
585 * @param {!WebInspector.Event} event
587 _updateBreadcrumbIfNeeded: function(event)
589 var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data || []);
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);
602 _stylesPaneEdited: function()
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();
611 _metricsPaneEdited: function()
613 // Once metrics are edited, the Styles pane should be updated.
614 this.sidebarPanes.styles.needsUpdate = true;
615 this.updateStyles(true);
618 _mouseMovedInCrumbs: function(event)
620 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
621 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
623 WebInspector.domAgent.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0);
625 if ("_mouseOutOfCrumbsTimeout" in this) {
626 clearTimeout(this._mouseOutOfCrumbsTimeout);
627 delete this._mouseOutOfCrumbsTimeout;
631 _mouseMovedOutOfCrumbs: function(event)
633 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
634 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
637 WebInspector.domAgent.hideDOMNodeHighlight();
639 this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
643 * @param {boolean=} forceUpdate
645 updateBreadcrumb: function(forceUpdate)
647 if (!this.isShowing())
650 var crumbs = this.crumbsElement;
653 var crumb = crumbs.firstChild;
655 if (crumb.representedObject === this.selectedDOMNode()) {
656 crumb.classList.add("selected");
659 crumb.classList.remove("selected");
662 crumb = crumb.nextSibling;
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();
672 crumbs.removeChildren();
676 function selectCrumbFunction(event)
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)
690 crumb = currentCrumb;
691 currentCrumb = currentCrumb.nextSibling;
695 panel.updateBreadcrumbSizes(crumb);
697 panel.selectDOMNode(crumb.representedObject, true);
699 event.preventDefault();
702 for (var current = this.selectedDOMNode(); current; current = current.parentNode) {
703 if (current.nodeType() === Node.DOCUMENT_NODE)
706 crumb = document.createElement("span");
707 crumb.className = "crumb";
708 crumb.representedObject = current;
709 crumb.addEventListener("mousedown", selectCrumbFunction, false);
712 switch (current.nodeType()) {
713 case Node.ELEMENT_NODE:
714 if (current.pseudoType())
715 crumbTitle = "::" + current.pseudoType();
717 WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb);
721 crumbTitle = WebInspector.UIString("(text)");
724 case Node.COMMENT_NODE:
725 crumbTitle = "<!-->";
728 case Node.DOCUMENT_TYPE_NODE:
729 crumbTitle = "<!DOCTYPE>";
732 case Node.DOCUMENT_FRAGMENT_NODE:
733 crumbTitle = current.shadowRootType() ? "#shadow-root" : current.nodeNameInCorrectCase();
737 crumbTitle = current.nodeNameInCorrectCase();
740 if (!crumb.childNodes.length) {
741 var nameElement = document.createElement("span");
742 nameElement.textContent = crumbTitle;
743 crumb.appendChild(nameElement);
744 crumb.title = crumbTitle;
747 if (current === this.selectedDOMNode())
748 crumb.classList.add("selected");
749 if (!crumbs.childNodes.length)
750 crumb.classList.add("end");
752 crumbs.insertBefore(crumb, crumbs.firstChild);
755 if (crumbs.hasChildNodes())
756 crumbs.lastChild.classList.add("start");
758 this.updateBreadcrumbSizes();
762 * @param {!Element=} focusedCrumb
764 updateBreadcrumbSizes: function(focusedCrumb)
766 if (!this.isShowing())
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.
775 var crumbs = this.crumbsElement;
776 if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
777 return; // No crumbs, do nothing.
779 // A Zero index is the right most child crumb in the breadcrumb.
780 var selectedIndex = 0;
781 var focusedIndex = 0;
785 var crumb = crumbs.firstChild;
787 // Find the selected crumb and index.
788 if (!selectedCrumb && crumb.classList.contains("selected")) {
789 selectedCrumb = crumb;
793 // Find the focused crumb index.
794 if (crumb === focusedCrumb)
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");
804 crumb.classList.remove("compact");
805 crumb.classList.remove("collapsed");
806 crumb.classList.remove("hidden");
808 crumb = crumb.nextSibling;
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");
817 var contentElement = this.contentElement;
818 function crumbsAreSmallerThanContainer()
820 const rightPadding = 10;
821 return crumbs.offsetWidth + rightPadding < contentElement.offsetWidth;
824 if (crumbsAreSmallerThanContainer())
825 return; // No need to compact the crumbs, they all fit at full size.
828 var AncestorSide = -1;
832 * @param {boolean=} significantCrumb
834 function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
836 if (!significantCrumb)
837 significantCrumb = (focusedCrumb || selectedCrumb);
839 if (significantCrumb === selectedCrumb)
840 var significantIndex = selectedIndex;
841 else if (significantCrumb === focusedCrumb)
842 var significantIndex = focusedIndex;
844 var significantIndex = 0;
845 for (var i = 0; i < crumbs.childNodes.length; ++i) {
846 if (crumbs.childNodes[i] === significantCrumb) {
847 significantIndex = i;
853 function shrinkCrumbAtIndex(index)
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.
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.
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))
871 index += (direction > 0 ? 1 : -1);
874 // Crumbs are shrunk in order of descending distance from the signifcant crumb,
875 // with a tie going to child crumbs.
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++;
884 var index = endIndex--;
885 if (shrinkCrumbAtIndex(index))
890 // We are not small enough yet, return false so the caller knows.
894 function coalesceCollapsedCrumbs()
896 var crumb = crumbs.firstChild;
897 var collapsedRun = false;
898 var newStartNeeded = false;
899 var newEndNeeded = false;
901 var hidden = crumb.classList.contains("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");
909 if (crumb.classList.contains("start")) {
910 crumb.classList.remove("start");
911 newStartNeeded = true;
914 if (crumb.classList.contains("end")) {
915 crumb.classList.remove("end");
922 collapsedRun = collapsed;
925 newEndNeeded = false;
926 crumb.classList.add("end");
930 crumb = crumb.nextSibling;
933 if (newStartNeeded) {
934 crumb = crumbs.lastChild;
936 if (!crumb.classList.contains("hidden")) {
937 crumb.classList.add("start");
940 crumb = crumb.previousSibling;
945 function compact(crumb)
947 if (crumb.classList.contains("hidden"))
949 crumb.classList.add("compact");
952 function collapse(crumb, dontCoalesce)
954 if (crumb.classList.contains("hidden"))
956 crumb.classList.add("collapsed");
957 crumb.classList.remove("compact");
959 coalesceCollapsedCrumbs();
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.
966 // Compact child crumbs.
967 if (makeCrumbsSmaller(compact, ChildSide))
970 // Collapse child crumbs.
971 if (makeCrumbsSmaller(collapse, ChildSide))
975 // Compact ancestor crumbs, or from both sides if focused.
976 if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
979 // Collapse ancestor crumbs, or from both sides if focused.
980 if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
986 // Compact the selected crumb.
987 compact(selectedCrumb);
988 if (crumbsAreSmallerThanContainer())
991 // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
992 collapse(selectedCrumb, true);
996 * @param {boolean=} forceUpdate
998 updateStyles: function(forceUpdate)
1000 if (!WebInspector.cssModel.isEnabled())
1002 var stylesSidebarPane = this.sidebarPanes.styles;
1003 var computedStylePane = this.sidebarPanes.computedStyle;
1004 if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate)
1007 stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate);
1008 stylesSidebarPane.needsUpdate = false;
1011 updateMetrics: function()
1013 if (!WebInspector.cssModel.isEnabled())
1015 var metricsSidebarPane = this.sidebarPanes.metrics;
1016 if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate)
1019 metricsSidebarPane.update(this.selectedDOMNode());
1020 metricsSidebarPane.needsUpdate = false;
1023 updatePlatformFonts: function()
1025 if (!WebInspector.cssModel.isEnabled())
1027 var platformFontsSidebar = this.sidebarPanes.platformFonts;
1028 if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate)
1031 platformFontsSidebar.update(this.selectedDOMNode());
1032 platformFontsSidebar.needsUpdate = false;
1035 updateProperties: function()
1037 var propertiesSidebarPane = this.sidebarPanes.properties;
1038 if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate)
1041 propertiesSidebarPane.update(this.selectedDOMNode());
1042 propertiesSidebarPane.needsUpdate = false;
1045 updateEventListeners: function()
1047 var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
1048 if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate)
1051 eventListenersSidebarPane.update(this.selectedDOMNode());
1052 eventListenersSidebarPane.needsUpdate = false;
1056 * @param {!KeyboardEvent} event
1058 handleShortcut: function(event)
1061 * @this {WebInspector.ElementsPanel}
1063 function handleUndoRedo()
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;
1071 var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
1072 event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
1074 DOMAgent.redo(this._updateSidebars.bind(this));
1075 event.handled = true;
1079 if (!this.treeOutline.editing()) {
1080 handleUndoRedo.call(this);
1085 this.treeOutline.handleShortcut(event);
1088 handleCopyEvent: function(event)
1090 var currentFocusElement = WebInspector.currentFocusElement();
1091 if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement))
1094 // Don't prevent the normal copy if the user has a selection.
1095 if (!window.getSelection().isCollapsed)
1097 event.clipboardData.clearData();
1098 event.preventDefault();
1099 this.selectedDOMNode().copyNode();
1102 revealAndSelectNode: function(nodeId)
1104 WebInspector.inspectorView.setCurrentPanel(this);
1106 var node = WebInspector.domAgent.nodeForId(nodeId);
1110 while (!WebInspector.ElementsTreeOutline.showShadowDOM() && node && node.isInShadowTree())
1111 node = node.parentNode;
1113 WebInspector.domAgent.highlightDOMNodeForTwoSeconds(nodeId);
1114 this.selectDOMNode(node, true);
1118 * @param {!WebInspector.ContextMenu} contextMenu
1119 * @param {!Object} target
1121 appendApplicableItems: function(event, contextMenu, target)
1124 * @param {?DOMAgent.NodeId} nodeId
1126 function selectNode(nodeId)
1129 WebInspector.domAgent.inspectElement(nodeId);
1133 * @param {!WebInspector.RemoteObject} remoteObject
1135 function revealElement(remoteObject)
1137 remoteObject.pushNodeToFrontend(selectNode);
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);
1148 commandCallback = WebInspector.domAgent.inspectElement.bind(WebInspector.domAgent, domNode.id);
1150 if (!commandCallback)
1152 // Skip adding "Reveal..." menu item for our own tree outline.
1153 if (this.treeOutline.element.isAncestor(event.target))
1155 contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback);
1158 _sidebarContextMenuEventFired: function(event)
1160 var contextMenu = new WebInspector.ContextMenu(event);
1164 _dockSideChanged: function()
1166 var vertically = WebInspector.dockController.isVertical() && WebInspector.settings.splitVerticallyWhenDockedToRight.get();
1167 this._splitVertically(vertically);
1170 _showShadowDOMChanged: function()
1172 this.treeOutline.update();
1176 * @param {boolean} vertically
1178 _splitVertically: function(vertically)
1180 if (this.sidebarPaneView && vertically === !this._splitView.isVertical())
1183 if (this.sidebarPaneView) {
1184 this.sidebarPaneView.detach();
1185 this._splitView.uninstallResizer(this.sidebarPaneView.headerElement());
1188 this._splitView.setVertical(!vertically);
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);
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);
1200 this.sidebarPanes.platformFonts.show(computedPane.bodyElement);
1203 * @param {!WebInspector.SidebarPane} pane
1204 * @param {!Element=} beforeElement
1205 * @this {WebInspector.ElementsPanel}
1207 function showMetrics(pane, beforeElement)
1209 this.sidebarPanes.metrics.show(pane.bodyElement, beforeElement);
1213 * @param {!WebInspector.Event} event
1214 * @this {WebInspector.ElementsPanel}
1216 function tabSelected(event)
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);
1225 this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
1228 this._splitView.installResizer(this.sidebarPaneView.headerElement());
1229 this.sidebarPanes.metrics.show(computedPane.bodyElement, this.sidebarPanes.computedStyle.element);
1230 this.sidebarPanes.metrics.setExpandCallback(expandComputed);
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);
1237 var splitView = new WebInspector.SplitView(true, true, "StylesPaneSplitRatio", 0.5);
1238 splitView.show(compositePane.bodyElement);
1240 this.sidebarPanes.styles.show(splitView.mainElement());
1241 splitView.mainElement().appendChild(this.sidebarPanes.styles.titleElement);
1242 this.sidebarPanes.styles.setExpandCallback(expandComposite);
1244 computedPane.show(splitView.sidebarElement());
1245 computedPane.setExpandCallback(expandComposite);
1247 this.sidebarPaneView.addPane(compositePane);
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);
1259 this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this);
1261 showMetrics.call(this, stylesPane);
1262 this.sidebarPaneView.addPane(stylesPane);
1263 this.sidebarPaneView.addPane(computedPane);
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;
1271 for (var i = 0; i < this._extensionSidebarPanes.length; ++i)
1272 this._extensionSidebarPanesContainer.addPane(this._extensionSidebarPanes[i]);
1274 this.sidebarPaneView.show(this._splitView.sidebarElement());
1275 this.sidebarPanes.styles.expand();
1279 * @param {string} id
1280 * @param {!WebInspector.SidebarPane} pane
1282 addExtensionSidebarPane: function(id, pane)
1284 this._extensionSidebarPanes.push(pane);
1285 this._extensionSidebarPanesContainer.addPane(pane);
1288 __proto__: WebInspector.Panel.prototype
1293 * @implements {WebInspector.ContextMenu.Provider}
1295 WebInspector.ElementsPanel.ContextMenuProvider = function()
1299 WebInspector.ElementsPanel.ContextMenuProvider.prototype = {
1301 * @param {!Event} event
1302 * @param {!WebInspector.ContextMenu} contextMenu
1303 * @param {!Object} target
1305 appendApplicableItems: function(event, contextMenu, target)
1307 WebInspector.panel("elements").appendApplicableItems(event, contextMenu, target);
1314 * @extends {WebInspector.Drawer.SingletonViewFactory}
1316 WebInspector.ElementsPanel.OverridesViewFactory = function()
1318 WebInspector.Drawer.SingletonViewFactory.call(this, WebInspector.OverridesView);
1321 WebInspector.ElementsPanel.OverridesViewFactory.prototype = {
1322 __proto__: WebInspector.Drawer.SingletonViewFactory.prototype
1328 * @extends {WebInspector.Drawer.SingletonViewFactory}
1330 WebInspector.ElementsPanel.RenderingViewFactory = function()
1332 WebInspector.Drawer.SingletonViewFactory.call(this, WebInspector.RenderingOptionsView);
1335 WebInspector.ElementsPanel.RenderingViewFactory.prototype = {
1336 __proto__: WebInspector.Drawer.SingletonViewFactory.prototype
1341 * @implements {WebInspector.Revealer}
1343 WebInspector.ElementsPanel.DOMNodeRevealer = function()
1347 WebInspector.ElementsPanel.DOMNodeRevealer.prototype = {
1349 * @param {!Object} node
1351 reveal: function(node)
1353 if (node instanceof WebInspector.DOMNode)
1354 /** @type {!WebInspector.ElementsPanel} */ (WebInspector.showPanel("elements")).revealAndSelectNode(node.id);