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 * @implements {WebInspector.TargetManager.Observer}
45 * @extends {WebInspector.Panel}
47 WebInspector.ElementsPanel = function()
49 WebInspector.Panel.call(this, "elements");
50 this.registerRequiredCSS("breadcrumbList.css");
51 this.registerRequiredCSS("elementsPanel.css");
52 this.registerRequiredCSS("suggestBox.css");
53 this.setHideOnDetach();
55 this._splitView = new WebInspector.SplitView(true, true, "elementsPanelSplitViewState", 325, 325);
56 this._splitView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this));
57 this._splitView.show(this.element);
59 this._searchableView = new WebInspector.SearchableView(this);
60 this._searchableView.setMinimumSize(25, 19);
61 this._searchableView.show(this._splitView.mainElement());
62 var stackElement = this._searchableView.element;
64 this.contentElement = stackElement.createChild("div");
65 this.contentElement.id = "elements-content";
66 this.contentElement.classList.add("outline-disclosure");
67 this.contentElement.classList.add("source-code");
68 if (!WebInspector.settings.domWordWrap.get())
69 this.contentElement.classList.add("nowrap");
70 WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this));
72 this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
73 this._splitView.sidebarElement().addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
75 var crumbsContainer = stackElement.createChild("div");
76 crumbsContainer.id = "elements-crumbs";
77 this.crumbsElement = crumbsContainer.createChild("div", "crumbs");
78 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
79 this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);
81 this.sidebarPanes = {};
82 this.sidebarPanes.platformFonts = new WebInspector.PlatformFontsSidebarPane();
83 this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
84 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNode.bind(this));
86 this._matchedStylesFilterBoxContainer = document.createElement("div");
87 this._matchedStylesFilterBoxContainer.className = "sidebar-pane-filter-box";
88 this._computedStylesFilterBoxContainer = document.createElement("div");
89 this._computedStylesFilterBoxContainer.className = "sidebar-pane-filter-box";
90 this.sidebarPanes.styles.setFilterBoxContainers(this._matchedStylesFilterBoxContainer, this._computedStylesFilterBoxContainer);
92 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
93 this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
94 this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this);
95 this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane();
97 this.sidebarPanes.styles.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateStyles.bind(this, false));
98 this.sidebarPanes.metrics.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateMetrics.bind(this));
99 this.sidebarPanes.platformFonts.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updatePlatformFonts.bind(this));
100 this.sidebarPanes.properties.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateProperties.bind(this));
101 this.sidebarPanes.eventListeners.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateEventListeners.bind(this));
103 this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
104 this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
105 this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);
106 this._extensionSidebarPanes = [];
108 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
109 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
110 this._dockSideChanged();
112 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
113 this._popoverHelper.setTimeout(0);
115 /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */
116 this._treeOutlines = [];
117 /** @type {!Map.<!WebInspector.Target, !WebInspector.ElementsTreeOutline>} */
118 this._targetToTreeOutline = new Map();
119 WebInspector.targetManager.observeTargets(this);
120 WebInspector.settings.showUAShadowDOM.addChangeListener(this._showUAShadowDOMChanged.bind(this));
123 WebInspector.ElementsPanel.prototype = {
125 * @param {!WebInspector.Target} target
127 targetAdded: function(target)
129 var treeOutline = new WebInspector.ElementsTreeOutline(target, true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNode.bind(this));
130 treeOutline.wireToDOMModel();
131 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
132 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
133 this._treeOutlines.push(treeOutline);
134 this._targetToTreeOutline.put(target, treeOutline);
136 target.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
137 target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
139 // Perform attach if necessary.
140 if (this.isShowing())
145 * @param {!WebInspector.Target} target
147 targetRemoved: function(target)
149 var treeOutline = this._targetToTreeOutline.get(target);
150 treeOutline.unwireFromDOMModel();
151 this._treeOutlines.remove(treeOutline);
152 treeOutline.element.remove();
154 target.domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
155 target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
159 * @return {?WebInspector.ElementsTreeOutline}
161 _firstTreeOutlineDeprecated: function()
163 return this._treeOutlines[0] || null;
166 _updateTreeOutlineVisibleWidth: function()
168 if (!this._treeOutlines.length)
171 var width = this._splitView.element.offsetWidth;
172 if (this._splitView.isVertical())
173 width -= this._splitView.sidebarSize();
174 for (var i = 0; i < this._treeOutlines.length; ++i) {
175 this._treeOutlines[i].setVisibleWidth(width);
176 this._treeOutlines[i].updateSelection();
178 this.updateBreadcrumbSizes();
184 defaultFocusedElement: function()
186 return this._treeOutlines.length ? this._treeOutlines[0].element : this.element;
190 * @return {!WebInspector.SearchableView}
192 searchableView: function()
194 return this._searchableView;
199 for (var i = 0; i < this._treeOutlines.length; ++i) {
200 var treeOutline = this._treeOutlines[i];
201 // Attach heavy component lazily
202 if (treeOutline.element.parentElement !== this.contentElement)
203 this.contentElement.appendChild(treeOutline.element);
205 WebInspector.Panel.prototype.wasShown.call(this);
206 this.updateBreadcrumb();
208 for (var i = 0; i < this._treeOutlines.length; ++i) {
209 var treeOutline = this._treeOutlines[i];
210 treeOutline.updateSelection();
211 treeOutline.setVisible(true);
213 if (!treeOutline.rootDOMNode)
214 if (treeOutline.domModel().existingDocument())
215 this._documentUpdated(treeOutline.domModel(), treeOutline.domModel().existingDocument());
217 treeOutline.domModel().requestDocument();
224 for (var i = 0; i < this._treeOutlines.length; ++i) {
225 var treeOutline = this._treeOutlines[i];
226 treeOutline.domModel().hideDOMNodeHighlight();
227 treeOutline.setVisible(false);
228 // Detach heavy component on hide
229 this.contentElement.removeChild(treeOutline.element);
231 this._popoverHelper.hidePopover();
232 WebInspector.Panel.prototype.willHide.call(this);
237 this._updateTreeOutlineVisibleWidth();
240 omitDefaultSelection: function()
242 this._omitDefaultSelection = true;
245 stopOmittingDefaultSelection: function()
247 delete this._omitDefaultSelection;
251 * @param {!WebInspector.DOMNode} node
252 * @param {string} pseudoClass
253 * @param {boolean} enable
255 _setPseudoClassForNode: function(node, pseudoClass, enable)
257 if (!node || !node.target().cssModel.forcePseudoState(node, pseudoClass, enable))
260 this._targetToTreeOutline.get(node.target()).updateOpenCloseTags(node);
261 this._metricsPaneEdited();
262 this._stylesPaneEdited();
264 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
265 action: WebInspector.UserMetrics.UserActionNames.ForcedElementState,
266 selector: WebInspector.DOMPresentationUtils.fullQualifiedSelector(node, false),
273 * @param {!WebInspector.Event} event
275 _selectedNodeChanged: function(event)
277 var selectedNode = /** @type {?WebInspector.DOMNode} */ (event.data);
278 for (var i = 0; i < this._treeOutlines.length; ++i) {
279 if (!selectedNode || selectedNode.domModel() !== this._treeOutlines[i].domModel())
280 this._treeOutlines[i].selectDOMNode(null);
283 if (!selectedNode && this._lastValidSelectedNode)
284 this._selectedPathOnReset = this._lastValidSelectedNode.path();
286 this.updateBreadcrumb(false);
288 this._updateSidebars();
291 ConsoleAgent.addInspectedNode(selectedNode.id);
292 this._lastValidSelectedNode = selectedNode;
294 WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged);
297 _updateSidebars: function()
299 for (var pane in this.sidebarPanes)
300 this.sidebarPanes[pane].needsUpdate = true;
302 this.updateStyles(true);
303 this.updateMetrics();
304 this.updatePlatformFonts();
305 this.updateProperties();
306 this.updateEventListeners();
311 delete this.currentQuery;
315 * @param {!WebInspector.Event} event
317 _documentUpdatedEvent: function(event)
319 this._documentUpdated(/** @type {!WebInspector.DOMModel} */ (event.target), /** @type {?WebInspector.DOMDocument} */ (event.data));
323 * @param {!WebInspector.DOMModel} domModel
324 * @param {?WebInspector.DOMDocument} inspectedRootDocument
326 _documentUpdated: function(domModel, inspectedRootDocument)
329 this.searchCanceled();
331 var treeOutline = this._targetToTreeOutline.get(domModel.target());
332 treeOutline.rootDOMNode = inspectedRootDocument;
334 if (!inspectedRootDocument) {
335 if (this.isShowing())
336 domModel.requestDocument();
340 WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(domModel.target());
343 * @this {WebInspector.ElementsPanel}
344 * @param {?WebInspector.DOMNode} candidateFocusNode
346 function selectNode(candidateFocusNode)
348 if (!candidateFocusNode)
349 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
351 if (!candidateFocusNode)
354 this.selectDOMNode(candidateFocusNode);
355 if (treeOutline.selectedTreeElement)
356 treeOutline.selectedTreeElement.expand();
360 * @param {?DOMAgent.NodeId} nodeId
361 * @this {WebInspector.ElementsPanel}
363 function selectLastSelectedNode(nodeId)
365 if (this.selectedDOMNode()) {
366 // Focused node has been explicitly set while reaching out for the last selected node.
369 var node = nodeId ? domModel.nodeForId(nodeId) : null;
370 selectNode.call(this, node);
373 if (this._omitDefaultSelection)
376 if (this._selectedPathOnReset)
377 domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
379 selectNode.call(this, null);
380 delete this._selectedPathOnReset;
383 searchCanceled: function()
385 delete this._searchQuery;
386 this._hideSearchHighlights();
388 this._searchableView.updateSearchMatchesCount(0);
390 delete this._currentSearchResultIndex;
391 delete this._searchResults;
392 WebInspector.domModel.cancelSearch();
396 * @param {string} query
397 * @param {boolean} shouldJump
398 * @param {boolean=} jumpBackwards
400 performSearch: function(query, shouldJump, jumpBackwards)
402 // Call searchCanceled since it will reset everything we need before doing a new search.
403 this.searchCanceled();
405 const whitespaceTrimmedQuery = query.trim();
406 if (!whitespaceTrimmedQuery.length)
409 this._searchQuery = query;
412 * @param {number} resultCount
413 * @this {WebInspector.ElementsPanel}
415 function resultCountCallback(resultCount)
417 this._searchableView.updateSearchMatchesCount(resultCount);
421 this._currentSearchResultIndex = -1;
422 this._searchResults = new Array(resultCount);
424 this._jumpToSearchResult(jumpBackwards ? -1 : 0);
426 WebInspector.domModel.performSearch(whitespaceTrimmedQuery, resultCountCallback.bind(this));
429 _contextMenuEventFired: function(event)
431 var contextMenu = new WebInspector.ContextMenu(event);
432 for (var i = 0; i < this._treeOutlines.length; ++i)
433 this._treeOutlines[i].populateContextMenu(contextMenu, event);
437 _domWordWrapSettingChanged: function(event)
440 this.contentElement.classList.remove("nowrap");
442 this.contentElement.classList.add("nowrap");
444 var selectedNode = this.selectedDOMNode();
448 var treeOutline = this._targetToTreeOutline.get(selectedNode.target());
449 var treeElement = treeOutline.findTreeElement(selectedNode);
451 treeElement.updateSelection(); // Recalculate selection highlight dimensions.
454 switchToAndFocus: function(node)
456 // Reset search restore.
457 this._searchableView.cancelSearch();
458 WebInspector.inspectorView.setCurrentPanel(this);
459 this.selectDOMNode(node, true);
462 _populateContextMenu: function(contextMenu, node)
464 // Add debbuging-related actions
465 contextMenu.appendSeparator();
466 var pane = WebInspector.domBreakpointsSidebarPane;
467 pane.populateNodeContextMenu(node, contextMenu);
470 _getPopoverAnchor: function(element)
472 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
473 if (!anchor || !anchor.href)
476 var treeOutlineElement = anchor.enclosingNodeOrSelfWithClass("elements-tree-outline");
477 if (!treeOutlineElement)
480 for (var i = 0; i < this._treeOutlines.length; ++i) {
481 if (this._treeOutlines[i].element !== treeOutlineElement)
484 var resource = this._treeOutlines[i].target().resourceTreeModel.resourceForURL(anchor.href);
485 if (!resource || resource.type !== WebInspector.resourceTypes.Image)
487 anchor.removeAttribute("title");
494 * @param {!WebInspector.DOMNode} node
496 _loadDimensionsForNode: function(node, callback)
498 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
503 node.resolveToObject("", resolvedNode);
505 function resolvedNode(object)
512 object.callFunctionJSON(dimensions, undefined, callback);
516 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}}
517 * @suppressReceiverCheck
520 function dimensions()
522 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight };
528 * @param {!Element} anchor
529 * @param {!WebInspector.Popover} popover
531 _showPopover: function(anchor, popover)
533 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
534 // We get here for CSS properties, too.
535 if (listItem && listItem.treeElement && listItem.treeElement.treeOutline instanceof WebInspector.ElementsTreeOutline) {
536 var node = /** @type {!WebInspector.DOMNode} */ (listItem.treeElement.representedObject);
537 this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
539 var node = this.selectedDOMNode();
541 WebInspector.DOMPresentationUtils.buildImagePreviewContents(node.target(), anchor.href, true, showPopover);
545 * @param {!Element=} contents
547 function showPopover(contents)
551 popover.setCanShrink(false);
552 popover.show(contents, anchor);
556 _jumpToSearchResult: function(index)
558 this._hideSearchHighlights();
559 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
560 this._highlightCurrentSearchResult();
563 jumpToNextSearchResult: function()
565 if (!this._searchResults)
567 this._jumpToSearchResult(this._currentSearchResultIndex + 1);
570 jumpToPreviousSearchResult: function()
572 if (!this._searchResults)
574 this._jumpToSearchResult(this._currentSearchResultIndex - 1);
577 _highlightCurrentSearchResult: function()
579 var treeOutline = this._firstTreeOutlineDeprecated();
583 var index = this._currentSearchResultIndex;
584 var searchResults = this._searchResults;
585 var searchResult = searchResults[index];
587 if (searchResult === null) {
588 this._searchableView.updateCurrentMatchIndex(index);
593 * @param {?WebInspector.DOMNode} node
594 * @this {WebInspector.ElementsPanel}
596 function searchCallback(node)
598 searchResults[index] = node;
599 this._highlightCurrentSearchResult();
602 if (typeof searchResult === "undefined") {
603 // No data for slot, request it.
604 WebInspector.domModel.searchResult(index, searchCallback.bind(this));
608 this._searchableView.updateCurrentMatchIndex(index);
610 var treeElement = treeOutline.findTreeElement(searchResult);
612 treeElement.highlightSearchResults(this._searchQuery);
613 treeElement.reveal();
614 var matches = treeElement.listItemElement.getElementsByClassName("highlighted-search-result");
616 matches[0].scrollIntoViewIfNeeded();
620 _hideSearchHighlights: function()
622 if (!this._searchResults)
624 var searchResult = this._searchResults[this._currentSearchResultIndex];
627 var treeOutline = this._targetToTreeOutline.get(searchResult.target());
628 var treeElement = treeOutline.findTreeElement(searchResult);
630 treeElement.hideSearchHighlights();
634 * @return {?WebInspector.DOMNode}
636 selectedDOMNode: function()
638 for (var i = 0; i < this._treeOutlines.length; ++i) {
639 var treeOutline = this._treeOutlines[i];
640 if (treeOutline.selectedDOMNode())
641 return treeOutline.selectedDOMNode();
647 * @param {boolean=} focus
649 selectDOMNode: function(node, focus)
651 for (var i = 0; i < this._treeOutlines.length; ++i) {
652 var treeOutline = this._treeOutlines[i];
653 if (treeOutline.target() === node.target())
654 treeOutline.selectDOMNode(node, focus);
656 treeOutline.selectDOMNode(null);
661 * @param {!WebInspector.Event} event
663 _updateBreadcrumbIfNeeded: function(event)
665 var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data || []);
669 var crumbs = this.crumbsElement;
670 for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) {
671 if (nodes.indexOf(crumb.representedObject) !== -1) {
672 this.updateBreadcrumb(true);
678 _stylesPaneEdited: function()
680 // Once styles are edited, the Metrics pane should be updated.
681 this.sidebarPanes.metrics.needsUpdate = true;
682 this.updateMetrics();
683 this.sidebarPanes.platformFonts.needsUpdate = true;
684 this.updatePlatformFonts();
687 _metricsPaneEdited: function()
689 // Once metrics are edited, the Styles pane should be updated.
690 this.sidebarPanes.styles.needsUpdate = true;
691 this.updateStyles(true);
694 _mouseMovedInCrumbs: function(event)
696 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
697 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
698 var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbElement.representedObject : null);
703 _mouseMovedOutOfCrumbs: function(event)
705 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
706 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
709 for (var i = 0; i < this._treeOutlines.length; ++i)
710 this._treeOutlines[i].domModel().hideDOMNodeHighlight();
714 * @param {boolean=} forceUpdate
716 updateBreadcrumb: function(forceUpdate)
718 if (!this.isShowing())
721 var crumbs = this.crumbsElement;
724 var crumb = crumbs.firstChild;
726 if (crumb.representedObject === this.selectedDOMNode()) {
727 crumb.classList.add("selected");
730 crumb.classList.remove("selected");
733 crumb = crumb.nextSibling;
736 if (handled && !forceUpdate) {
737 // We don't need to rebuild the crumbs, but we need to adjust sizes
738 // to reflect the new focused or root node.
739 this.updateBreadcrumbSizes();
743 crumbs.removeChildren();
747 function selectCrumbFunction(event)
749 var crumb = event.currentTarget;
750 if (crumb.classList.contains("collapsed")) {
751 // Clicking a collapsed crumb will expose the hidden crumbs.
752 if (crumb === panel.crumbsElement.firstChild) {
753 // If the focused crumb is the first child, pick the farthest crumb
754 // that is still hidden. This allows the user to expose every crumb.
755 var currentCrumb = crumb;
756 while (currentCrumb) {
757 var hidden = currentCrumb.classList.contains("hidden");
758 var collapsed = currentCrumb.classList.contains("collapsed");
759 if (!hidden && !collapsed)
761 crumb = currentCrumb;
762 currentCrumb = currentCrumb.nextSibling;
766 panel.updateBreadcrumbSizes(crumb);
768 panel.selectDOMNode(crumb.representedObject, true);
770 event.preventDefault();
773 for (var current = this.selectedDOMNode(); current; current = current.parentNode) {
774 if (current.nodeType() === Node.DOCUMENT_NODE)
777 crumb = document.createElement("span");
778 crumb.className = "crumb";
779 crumb.representedObject = current;
780 crumb.addEventListener("mousedown", selectCrumbFunction, false);
783 switch (current.nodeType()) {
784 case Node.ELEMENT_NODE:
785 if (current.pseudoType())
786 crumbTitle = "::" + current.pseudoType();
788 WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb);
792 crumbTitle = WebInspector.UIString("(text)");
795 case Node.COMMENT_NODE:
796 crumbTitle = "<!-->";
799 case Node.DOCUMENT_TYPE_NODE:
800 crumbTitle = "<!DOCTYPE>";
803 case Node.DOCUMENT_FRAGMENT_NODE:
804 crumbTitle = current.shadowRootType() ? "#shadow-root" : current.nodeNameInCorrectCase();
808 crumbTitle = current.nodeNameInCorrectCase();
811 if (!crumb.childNodes.length) {
812 var nameElement = document.createElement("span");
813 nameElement.textContent = crumbTitle;
814 crumb.appendChild(nameElement);
815 crumb.title = crumbTitle;
818 if (current === this.selectedDOMNode())
819 crumb.classList.add("selected");
820 crumbs.insertBefore(crumb, crumbs.firstChild);
823 this.updateBreadcrumbSizes();
827 * @param {!Element=} focusedCrumb
829 updateBreadcrumbSizes: function(focusedCrumb)
831 if (!this.isShowing())
834 var crumbs = this.crumbsElement;
835 if (!crumbs.firstChild)
838 var selectedIndex = 0;
839 var focusedIndex = 0;
842 // Reset crumb styles.
843 for (var i = 0; i < crumbs.childNodes.length; ++i) {
844 var crumb = crumbs.childNodes[i];
845 // Find the selected crumb and index.
846 if (!selectedCrumb && crumb.classList.contains("selected")) {
847 selectedCrumb = crumb;
851 // Find the focused crumb index.
852 if (crumb === focusedCrumb)
855 crumb.classList.remove("compact", "collapsed", "hidden");
858 // Layout 1: Measure total and normal crumb sizes
859 var contentElementWidth = this.contentElement.offsetWidth;
860 var normalSizes = [];
861 for (var i = 0; i < crumbs.childNodes.length; ++i) {
862 var crumb = crumbs.childNodes[i];
863 normalSizes[i] = crumb.offsetWidth;
866 // Layout 2: Measure collapsed crumb sizes
867 var compactSizes = [];
868 for (var i = 0; i < crumbs.childNodes.length; ++i) {
869 var crumb = crumbs.childNodes[i];
870 crumb.classList.add("compact");
872 for (var i = 0; i < crumbs.childNodes.length; ++i) {
873 var crumb = crumbs.childNodes[i];
874 compactSizes[i] = crumb.offsetWidth;
877 // Layout 3: Measure collapsed crumb size
878 crumbs.firstChild.classList.add("collapsed");
879 var collapsedSize = crumbs.firstChild.offsetWidth;
882 for (var i = 0; i < crumbs.childNodes.length; ++i) {
883 var crumb = crumbs.childNodes[i];
884 crumb.classList.remove("compact", "collapsed");
887 function crumbsAreSmallerThanContainer()
890 for (var i = 0; i < crumbs.childNodes.length; ++i) {
891 var crumb = crumbs.childNodes[i];
892 if (crumb.classList.contains("hidden"))
894 if (crumb.classList.contains("collapsed")) {
895 totalSize += collapsedSize;
898 totalSize += crumb.classList.contains("compact") ? compactSizes[i] : normalSizes[i];
900 const rightPadding = 10;
901 return totalSize + rightPadding < contentElementWidth;
904 if (crumbsAreSmallerThanContainer())
905 return; // No need to compact the crumbs, they all fit at full size.
908 var AncestorSide = -1;
912 * @param {function(!Element)} shrinkingFunction
913 * @param {number} direction
915 function makeCrumbsSmaller(shrinkingFunction, direction)
917 var significantCrumb = focusedCrumb || selectedCrumb;
918 var significantIndex = significantCrumb === selectedCrumb ? selectedIndex : focusedIndex;
920 function shrinkCrumbAtIndex(index)
922 var shrinkCrumb = crumbs.childNodes[index];
923 if (shrinkCrumb && shrinkCrumb !== significantCrumb)
924 shrinkingFunction(shrinkCrumb);
925 if (crumbsAreSmallerThanContainer())
926 return true; // No need to compact the crumbs more.
930 // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
931 // fit in the container or we run out of crumbs to shrink.
933 // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
934 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
935 while (index !== significantIndex) {
936 if (shrinkCrumbAtIndex(index))
938 index += (direction > 0 ? 1 : -1);
941 // Crumbs are shrunk in order of descending distance from the signifcant crumb,
942 // with a tie going to child crumbs.
944 var endIndex = crumbs.childNodes.length - 1;
945 while (startIndex != significantIndex || endIndex != significantIndex) {
946 var startDistance = significantIndex - startIndex;
947 var endDistance = endIndex - significantIndex;
948 if (startDistance >= endDistance)
949 var index = startIndex++;
951 var index = endIndex--;
952 if (shrinkCrumbAtIndex(index))
957 // We are not small enough yet, return false so the caller knows.
961 function coalesceCollapsedCrumbs()
963 var crumb = crumbs.firstChild;
964 var collapsedRun = false;
965 var newStartNeeded = false;
966 var newEndNeeded = false;
968 var hidden = crumb.classList.contains("hidden");
970 var collapsed = crumb.classList.contains("collapsed");
971 if (collapsedRun && collapsed) {
972 crumb.classList.add("hidden");
973 crumb.classList.remove("compact");
974 crumb.classList.remove("collapsed");
976 if (crumb.classList.contains("start")) {
977 crumb.classList.remove("start");
978 newStartNeeded = true;
981 if (crumb.classList.contains("end")) {
982 crumb.classList.remove("end");
989 collapsedRun = collapsed;
992 newEndNeeded = false;
993 crumb.classList.add("end");
997 crumb = crumb.nextSibling;
1000 if (newStartNeeded) {
1001 crumb = crumbs.lastChild;
1003 if (!crumb.classList.contains("hidden")) {
1004 crumb.classList.add("start");
1007 crumb = crumb.previousSibling;
1013 * @param {!Element} crumb
1015 function compact(crumb)
1017 if (crumb.classList.contains("hidden"))
1019 crumb.classList.add("compact");
1023 * @param {!Element} crumb
1024 * @param {boolean=} dontCoalesce
1026 function collapse(crumb, dontCoalesce)
1028 if (crumb.classList.contains("hidden"))
1030 crumb.classList.add("collapsed");
1031 crumb.classList.remove("compact");
1033 coalesceCollapsedCrumbs();
1036 if (!focusedCrumb) {
1037 // When not focused on a crumb we can be biased and collapse less important
1038 // crumbs that the user might not care much about.
1040 // Compact child crumbs.
1041 if (makeCrumbsSmaller(compact, ChildSide))
1044 // Collapse child crumbs.
1045 if (makeCrumbsSmaller(collapse, ChildSide))
1049 // Compact ancestor crumbs, or from both sides if focused.
1050 if (makeCrumbsSmaller(compact, focusedCrumb ? BothSides : AncestorSide))
1053 // Collapse ancestor crumbs, or from both sides if focused.
1054 if (makeCrumbsSmaller(collapse, focusedCrumb ? BothSides : AncestorSide))
1060 // Compact the selected crumb.
1061 compact(selectedCrumb);
1062 if (crumbsAreSmallerThanContainer())
1065 // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
1066 collapse(selectedCrumb, true);
1072 _cssModelEnabledForSelectedNode: function()
1074 if (!this.selectedDOMNode())
1076 return this.selectedDOMNode().target().cssModel.isEnabled();
1080 * @param {boolean=} forceUpdate
1082 updateStyles: function(forceUpdate)
1084 if (!this._cssModelEnabledForSelectedNode())
1086 var stylesSidebarPane = this.sidebarPanes.styles;
1087 var computedStylePane = this.sidebarPanes.computedStyle;
1088 if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate)
1091 stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate);
1092 stylesSidebarPane.needsUpdate = false;
1095 updateMetrics: function()
1097 if (!this._cssModelEnabledForSelectedNode())
1099 var metricsSidebarPane = this.sidebarPanes.metrics;
1100 if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate)
1103 metricsSidebarPane.update(this.selectedDOMNode());
1104 metricsSidebarPane.needsUpdate = false;
1107 updatePlatformFonts: function()
1109 if (!this._cssModelEnabledForSelectedNode())
1111 var platformFontsSidebar = this.sidebarPanes.platformFonts;
1112 if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate)
1115 platformFontsSidebar.update(this.selectedDOMNode());
1116 platformFontsSidebar.needsUpdate = false;
1119 updateProperties: function()
1121 var propertiesSidebarPane = this.sidebarPanes.properties;
1122 if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate)
1125 propertiesSidebarPane.update(this.selectedDOMNode());
1126 propertiesSidebarPane.needsUpdate = false;
1129 updateEventListeners: function()
1131 var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
1132 if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate)
1135 eventListenersSidebarPane.update(this.selectedDOMNode());
1136 eventListenersSidebarPane.needsUpdate = false;
1140 * @param {!KeyboardEvent} event
1142 handleShortcut: function(event)
1145 * @this {WebInspector.ElementsPanel}
1147 function handleUndoRedo()
1149 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key
1150 WebInspector.domModel.undo(this._updateSidebars.bind(this));
1151 event.handled = true;
1155 var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
1156 event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
1158 WebInspector.domModel.redo(this._updateSidebars.bind(this));
1159 event.handled = true;
1163 var treeOutline = this._firstTreeOutlineDeprecated();
1167 if (!treeOutline.editing()) {
1168 handleUndoRedo.call(this);
1173 treeOutline.handleShortcut(event);
1176 handleCopyEvent: function(event)
1178 var currentFocusElement = WebInspector.currentFocusElement();
1179 if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement))
1182 // Don't prevent the normal copy if the user has a selection.
1183 if (!window.getSelection().isCollapsed)
1185 event.clipboardData.clearData();
1186 event.preventDefault();
1187 this.selectedDOMNode().copyNode();
1191 * @param {!WebInspector.DOMNode} node
1192 * @return {!WebInspector.DOMNode}
1194 _leaveUserAgentShadowDOM: function(node)
1196 var userAgentShadowRoot = node.ancestorUserAgentShadowRoot();
1197 return userAgentShadowRoot ? /** @type {!WebInspector.DOMNode} */ (userAgentShadowRoot.parentNode) : node;
1201 * @param {!WebInspector.DOMNode} node
1203 revealAndSelectNode: function(node)
1205 WebInspector.inspectorView.setCurrentPanel(this);
1206 node = WebInspector.settings.showUAShadowDOM.get() ? node : this._leaveUserAgentShadowDOM(node);
1207 node.highlightForTwoSeconds();
1208 this.selectDOMNode(node, true);
1212 * @param {!WebInspector.ContextMenu} contextMenu
1213 * @param {!Object} object
1215 appendApplicableItems: function(event, contextMenu, object)
1217 var commandCallback;
1218 if (object instanceof WebInspector.RemoteObject) {
1219 var remoteObject = /** @type {!WebInspector.RemoteObject} */ (object);
1220 if (remoteObject.isNode())
1221 commandCallback = remoteObject.reveal.bind(remoteObject);
1222 } else if (object instanceof WebInspector.DOMNode) {
1223 var domNode = /** @type {!WebInspector.DOMNode} */ (object);
1224 commandCallback = domNode.reveal.bind(domNode);
1226 if (!commandCallback)
1228 // Skip adding "Reveal..." menu item for our own tree outline.
1229 if (this.element.isAncestor(event.target))
1231 contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback);
1234 _sidebarContextMenuEventFired: function(event)
1236 var contextMenu = new WebInspector.ContextMenu(event);
1240 _dockSideChanged: function()
1242 var vertically = WebInspector.dockController.isVertical() && WebInspector.settings.splitVerticallyWhenDockedToRight.get();
1243 this._splitVertically(vertically);
1246 _showUAShadowDOMChanged: function()
1248 for (var i = 0; i < this._treeOutlines.length; ++i)
1249 this._treeOutlines[i].update();
1253 * @param {boolean} vertically
1255 _splitVertically: function(vertically)
1257 if (this.sidebarPaneView && vertically === !this._splitView.isVertical())
1260 if (this.sidebarPaneView) {
1261 this.sidebarPaneView.detach();
1262 this._splitView.uninstallResizer(this.sidebarPaneView.headerElement());
1265 this._splitView.setVertical(!vertically);
1267 var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed"));
1268 computedPane.element.classList.add("composite");
1269 computedPane.element.classList.add("fill");
1270 var expandComputed = computedPane.expand.bind(computedPane);
1272 computedPane.bodyElement.classList.add("metrics-and-computed");
1273 this.sidebarPanes.computedStyle.setExpandCallback(expandComputed);
1275 var matchedStylePanesWrapper = document.createElement("div");
1276 matchedStylePanesWrapper.className = "style-panes-wrapper";
1277 var computedStylePanesWrapper = document.createElement("div");
1278 computedStylePanesWrapper.className = "style-panes-wrapper";
1281 * @param {boolean} inComputedStyle
1282 * @this {WebInspector.ElementsPanel}
1284 function showMetrics(inComputedStyle)
1286 if (inComputedStyle)
1287 this.sidebarPanes.metrics.show(computedStylePanesWrapper, this.sidebarPanes.computedStyle.element);
1289 this.sidebarPanes.metrics.show(matchedStylePanesWrapper);
1293 * @param {!WebInspector.Event} event
1294 * @this {WebInspector.ElementsPanel}
1296 function tabSelected(event)
1298 var tabId = /** @type {string} */ (event.data.tabId);
1299 if (tabId === computedPane.title())
1300 showMetrics.call(this, true);
1301 else if (tabId === stylesPane.title())
1302 showMetrics.call(this, false);
1305 this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
1308 this._splitView.installResizer(this.sidebarPaneView.headerElement());
1309 this.sidebarPanes.metrics.setExpandCallback(expandComputed);
1311 var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1312 compositePane.element.classList.add("composite");
1313 compositePane.element.classList.add("fill");
1314 var expandComposite = compositePane.expand.bind(compositePane);
1316 var splitView = new WebInspector.SplitView(true, true, "stylesPaneSplitViewState", 0.5);
1317 splitView.show(compositePane.bodyElement);
1319 splitView.mainElement().appendChild(matchedStylePanesWrapper);
1320 splitView.sidebarElement().appendChild(computedStylePanesWrapper);
1322 this.sidebarPanes.styles.setExpandCallback(expandComposite);
1324 computedPane.show(computedStylePanesWrapper);
1325 computedPane.setExpandCallback(expandComposite);
1327 splitView.mainElement().appendChild(this._matchedStylesFilterBoxContainer);
1328 splitView.sidebarElement().appendChild(this._computedStylesFilterBoxContainer);
1330 this.sidebarPaneView.addPane(compositePane);
1332 var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1333 stylesPane.element.classList.add("composite");
1334 stylesPane.element.classList.add("fill");
1335 var expandStyles = stylesPane.expand.bind(stylesPane);
1336 stylesPane.bodyElement.classList.add("metrics-and-styles");
1338 stylesPane.bodyElement.appendChild(matchedStylePanesWrapper);
1339 computedPane.bodyElement.appendChild(computedStylePanesWrapper);
1341 this.sidebarPanes.styles.setExpandCallback(expandStyles);
1342 this.sidebarPanes.metrics.setExpandCallback(expandStyles);
1344 this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this);
1346 stylesPane.bodyElement.appendChild(this._matchedStylesFilterBoxContainer);
1347 computedPane.bodyElement.appendChild(this._computedStylesFilterBoxContainer);
1349 this.sidebarPaneView.addPane(stylesPane);
1350 this.sidebarPaneView.addPane(computedPane);
1353 this.sidebarPanes.styles.show(matchedStylePanesWrapper);
1354 this.sidebarPanes.computedStyle.show(computedStylePanesWrapper);
1355 matchedStylePanesWrapper.appendChild(this.sidebarPanes.styles.titleElement);
1356 showMetrics.call(this, vertically);
1357 this.sidebarPanes.platformFonts.show(computedStylePanesWrapper);
1359 this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners);
1360 this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints);
1361 this.sidebarPaneView.addPane(this.sidebarPanes.properties);
1362 this._extensionSidebarPanesContainer = this.sidebarPaneView;
1364 for (var i = 0; i < this._extensionSidebarPanes.length; ++i)
1365 this._extensionSidebarPanesContainer.addPane(this._extensionSidebarPanes[i]);
1367 this.sidebarPaneView.show(this._splitView.sidebarElement());
1368 this.sidebarPanes.styles.expand();
1372 * @param {string} id
1373 * @param {!WebInspector.SidebarPane} pane
1375 addExtensionSidebarPane: function(id, pane)
1377 this._extensionSidebarPanes.push(pane);
1378 this._extensionSidebarPanesContainer.addPane(pane);
1381 __proto__: WebInspector.Panel.prototype
1386 * @implements {WebInspector.ContextMenu.Provider}
1388 WebInspector.ElementsPanel.ContextMenuProvider = function()
1392 WebInspector.ElementsPanel.ContextMenuProvider.prototype = {
1394 * @param {!Event} event
1395 * @param {!WebInspector.ContextMenu} contextMenu
1396 * @param {!Object} target
1398 appendApplicableItems: function(event, contextMenu, target)
1400 /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).appendApplicableItems(event, contextMenu, target);
1406 * @implements {WebInspector.Revealer}
1408 WebInspector.ElementsPanel.DOMNodeRevealer = function()
1412 WebInspector.ElementsPanel.DOMNodeRevealer.prototype = {
1414 * @param {!Object} node
1416 reveal: function(node)
1418 if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.enabled()) {
1419 InspectorFrontendHost.bringToFront();
1420 WebInspector.inspectElementModeController.disable();
1423 /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).revealAndSelectNode(/** @type {!WebInspector.DOMNode} */ (node));
1429 * @implements {WebInspector.Revealer}
1431 WebInspector.ElementsPanel.NodeRemoteObjectRevealer = function()
1435 WebInspector.ElementsPanel.NodeRemoteObjectRevealer.prototype = {
1437 * @param {!Object} remoteObject
1439 reveal: function(remoteObject)
1441 revealElement(/** @type {!WebInspector.RemoteObject} */ (remoteObject));
1444 * @param {?WebInspector.RemoteObject} remoteObject
1446 function revealElement(remoteObject)
1449 remoteObject.pushNodeToFrontend(selectNode.bind(null, remoteObject));
1453 * @param {?WebInspector.RemoteObject} remoteObject
1454 * @param {?WebInspector.DOMNode} node
1456 function selectNode(remoteObject, node)
1462 if (!remoteObject || remoteObject.description !== "#text" || !remoteObject.isNode())
1464 remoteObject.callFunction(parentElement, undefined, revealElement);
1468 * @suppressReceiverCheck
1471 function parentElement()
1473 return this.parentElement;