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.
33 * @implements {WebInspector.Searchable}
34 * @implements {WebInspector.TargetManager.Observer}
35 * @extends {WebInspector.Panel}
37 WebInspector.ElementsPanel = function()
39 WebInspector.Panel.call(this, "elements");
40 this.registerRequiredCSS("elements/elementsPanel.css");
42 this._splitWidget = new WebInspector.SplitWidget(true, true, "elementsPanelSplitViewState", 325, 325);
43 this._splitWidget.addEventListener(WebInspector.SplitWidget.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this));
44 this._splitWidget.show(this.element);
46 this._searchableView = new WebInspector.SearchableView(this);
47 this._searchableView.setMinimumSize(25, 28);
48 this._searchableView.setPlaceholder(WebInspector.UIString("Find by string, selector, or XPath"));
49 var stackElement = this._searchableView.element;
51 this._contentElement = createElement("div");
52 var crumbsContainer = createElement("div");
53 this._showLayoutEditor = false;
54 if (Runtime.experiments.isEnabled("materialDesign"))
55 this._initializeActionsToolbar();
56 stackElement.appendChild(this._contentElement);
57 stackElement.appendChild(crumbsContainer);
59 this._elementsPanelTreeOutilneSplit = new WebInspector.SplitWidget(false, true, "treeOutlineAnimationTimelineWidget", 300, 300);
60 this._elementsPanelTreeOutilneSplit.hideSidebar();
61 this._elementsPanelTreeOutilneSplit.setMainWidget(this._searchableView);
62 this._splitWidget.setMainWidget(this._elementsPanelTreeOutilneSplit);
64 this._contentElement.id = "elements-content";
65 // FIXME: crbug.com/425984
66 if (WebInspector.moduleSetting("domWordWrap").get())
67 this._contentElement.classList.add("elements-wrap");
68 WebInspector.moduleSetting("domWordWrap").addChangeListener(this._domWordWrapSettingChanged.bind(this));
70 crumbsContainer.id = "elements-crumbs";
71 this._breadcrumbs = new WebInspector.ElementsBreadcrumbs();
72 this._breadcrumbs.show(crumbsContainer);
73 this._breadcrumbs.addEventListener(WebInspector.ElementsBreadcrumbs.Events.NodeSelected, this._crumbNodeSelected, this);
75 this.sidebarPanes = {};
76 /** @type !Array<!WebInspector.ElementsSidebarViewWrapperPane> */
77 this._elementsSidebarViewWrappers = [];
78 var sharedSidebarModel = new WebInspector.SharedSidebarModel();
79 this.sidebarPanes.platformFonts = WebInspector.PlatformFontsWidget.createSidebarWrapper(sharedSidebarModel);
80 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane();
82 this.sidebarPanes.computedStyle = WebInspector.ComputedStyleWidget.createSidebarWrapper(this.sidebarPanes.styles, sharedSidebarModel);
84 this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted, this._onEditingSelectorStarted.bind(this));
85 this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded, this._onEditingSelectorEnded.bind(this));
87 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
88 this.sidebarPanes.properties = WebInspector.PropertiesWidget.createSidebarWrapper();
89 this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this);
90 this.sidebarPanes.eventListeners = WebInspector.EventListenersWidget.createSidebarWrapper();
92 WebInspector.moduleSetting("sidebarPosition").addChangeListener(this._updateSidebarPosition.bind(this));
93 this._updateSidebarPosition();
94 this._loadSidebarViews();
96 /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */
97 this._treeOutlines = [];
98 /** @type {!Map.<!WebInspector.DOMModel, !WebInspector.ElementsTreeOutline>} */
99 this._modelToTreeOutline = new Map();
100 WebInspector.targetManager.observeTargets(this);
101 WebInspector.moduleSetting("showUAShadowDOM").addChangeListener(this._showUAShadowDOMChanged.bind(this));
102 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
103 WebInspector.extensionServer.addEventListener(WebInspector.ExtensionServer.Events.SidebarPaneAdded, this._extensionSidebarPaneAdded, this);
106 WebInspector.ElementsPanel._elementsSidebarViewTitleSymbol = Symbol("title");
108 WebInspector.ElementsPanel.prototype = {
109 _initializeActionsToolbar: function()
111 this._nodeActionsElement = createElementWithClass("div", "node-actions-container");
112 var button = this._nodeActionsElement.createChild("div", "node-actions-toggle");
113 button.addEventListener("click", this._toggleActionsToolbar.bind(this, undefined));
114 this._nodeActionsToolbar = new WebInspector.Toolbar();
115 this._nodeActionsElement.appendChild(this._nodeActionsToolbar.element);
116 this._nodeActionsToolbar.element.addEventListener("mousedown", consumeEvent);
117 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.MarkersChanged, this._markersChanged, this);
119 this._editAsHTMLButton = new WebInspector.ToolbarButton(WebInspector.UIString("Edit as HTML"), "edit-toolbar-item");
120 this._editAsHTMLButton.setAction("elements.edit-as-html");
121 this._nodeActionsToolbar.appendToolbarItem(this._editAsHTMLButton);
122 this._nodeActionsToolbar.element.classList.add("node-actions-toolbar");
123 this._hideElementButton = new WebInspector.ToolbarButton(WebInspector.UIString("Hide element"), "visibility-off-toolbar-item");
124 this._hideElementButton.setAction("elements.hide-element");
125 this._nodeActionsToolbar.appendToolbarItem(this._hideElementButton);
126 this._forceElementStateButton = new WebInspector.ToolbarMenuButton(WebInspector.UIString("Force element state"), "pin-toolbar-item", this._showForceElementStateMenu.bind(this));
127 this._nodeActionsToolbar.appendToolbarItem(this._forceElementStateButton);
128 this._breakpointsButton = new WebInspector.ToolbarMenuButton(WebInspector.UIString("Toggle breakpoints"), "add-breakpoint-toolbar-item", this._showBreakpointsMenu.bind(this));
129 this._nodeActionsToolbar.appendToolbarItem(this._breakpointsButton);
132 _toggleHideElement: function()
134 var node = this.selectedDOMNode();
135 var treeOutline = this._treeOutlineForNode(node);
136 if (!node || !treeOutline)
138 treeOutline.toggleHideElement(node);
142 * @param {!WebInspector.DOMNode} node
144 _updateActionsToolbar: function(node)
146 if (!Runtime.experiments.isEnabled("materialDesign"))
148 var classText = node.getAttribute("class");
149 var treeOutline = this._treeOutlineForNode(node);
150 this._hideElementButton.setToggled(treeOutline && treeOutline.isToggledToHidden(node));
151 this._editAsHTMLButton.setToggled(false);
152 this._breakpointsButton.setEnabled(!node.pseudoType());
153 this._breakpointsButton.setToggled(WebInspector.domBreakpointsSidebarPane.hasBreakpoints(node));
154 this._forceElementStateButton.setEnabled(node.nodeType() === Node.ELEMENT_NODE && !node.pseudoType());
155 this._forceElementStateButton.setToggled(!!WebInspector.CSSStyleModel.fromNode(node).pseudoState(node).length);
157 var treeElement = this._treeOutlineForNode(node).selectedTreeElement;
160 if (node.nodeType() !== Node.ELEMENT_NODE) {
161 this._nodeActionsElement.remove();
165 var actionsToolbar = this._nodeActionsElement;
166 if (actionsToolbar.__node !== node) {
167 treeElement.gutterElement().appendChild(actionsToolbar);
168 this._positionActionsToolbar();
169 actionsToolbar.__node = node;
170 this._toggleActionsToolbar(false);
174 _toggleEditAsHTML: function()
176 var node = this.selectedDOMNode();
177 var treeOutline = this._treeOutlineForNode(node);
178 if (!node || !treeOutline)
181 var startEditing = true;
182 if (Runtime.experiments.isEnabled("materialDesign")) {
183 startEditing = !this._editAsHTMLButton.toggled();
184 this._editAsHTMLButton.setToggled(startEditing);
186 treeOutline.toggleEditAsHTML(node, startEditing, this._updateActionsToolbar.bind(this, node));
190 * @param {!WebInspector.ContextMenu} contextMenu
192 _showBreakpointsMenu: function(contextMenu)
194 var node = this.selectedDOMNode();
197 WebInspector.domBreakpointsSidebarPane.populateNodeContextMenu(node, contextMenu, false);
201 * @param {!WebInspector.ContextMenu} contextMenu
203 _showForceElementStateMenu: function(contextMenu)
205 var node = this.selectedDOMNode();
208 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems(contextMenu, node);
212 * @param {!WebInspector.Event} event
214 _decorationsClicked: function(event)
216 var node = /** @type {!WebInspector.DOMNode} */(event.data);
217 this.selectDOMNode(node, true);
218 this._toggleActionsToolbar(true);
222 * @param {boolean=} toggled
224 _toggleActionsToolbar: function(toggled)
226 if (toggled === undefined)
227 toggled = !this._actionsToolbarShown();
228 this._nodeActionsElement.classList.toggle("expanded", toggled);
229 this._positionActionsToolbar();
232 _positionActionsToolbar: function()
234 if (!this._actionsToolbarShown())
236 var toolbarElement = this._nodeActionsToolbar.element;
237 if (toolbarElement.totalOffsetTop() < this.element.totalOffsetTop()) {
238 toolbarElement.style.top = this._nodeActionsElement.parentElement.offsetHeight + "px";
239 toolbarElement.classList.add("node-actions-toolbar-below");
241 toolbarElement.style.top = "";
242 toolbarElement.classList.remove("node-actions-toolbar-below");
249 _actionsToolbarShown: function()
251 return this._nodeActionsElement.classList.contains("expanded");
255 * @param {!WebInspector.Event} event
257 _markersChanged: function(event)
259 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
260 if (node !== this.selectedDOMNode())
262 this._updateActionsToolbar(node);
265 _loadSidebarViews: function()
267 var extensions = self.runtime.extensions("@WebInspector.Widget");
269 for (var i = 0; i < extensions.length; ++i) {
270 var descriptor = extensions[i].descriptor();
271 if (descriptor["location"] !== "elements-panel")
274 var title = WebInspector.UIString(descriptor["title"]);
275 extensions[i].instancePromise().then(addSidebarView.bind(this, title));
279 * @param {string} title
280 * @param {!Object} object
281 * @this {WebInspector.ElementsPanel}
283 function addSidebarView(title, object)
285 var widget = /** @type {!WebInspector.Widget} */ (object);
286 var elementsSidebarViewWrapperPane = new WebInspector.ElementsSidebarViewWrapperPane(title, widget);
287 this._elementsSidebarViewWrappers.push(elementsSidebarViewWrapperPane);
289 if (this.sidebarPaneView)
290 this.sidebarPaneView.addPane(elementsSidebarViewWrapperPane);
294 _onEditingSelectorStarted: function()
296 for (var i = 0; i < this._treeOutlines.length; ++i)
297 this._treeOutlines[i].setPickNodeMode(true);
300 _onEditingSelectorEnded: function()
302 for (var i = 0; i < this._treeOutlines.length; ++i)
303 this._treeOutlines[i].setPickNodeMode(false);
308 * @param {!WebInspector.Target} target
310 targetAdded: function(target)
312 var domModel = WebInspector.DOMModel.fromTarget(target);
315 var treeOutline = new WebInspector.ElementsTreeOutline(domModel, true, true);
316 treeOutline.setWordWrap(WebInspector.moduleSetting("domWordWrap").get());
317 treeOutline.wireToDOMModel();
318 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
319 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.NodePicked, this._onNodePicked, this);
320 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
321 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.DecorationsClicked, this._decorationsClicked, this);
322 this._treeOutlines.push(treeOutline);
323 this._modelToTreeOutline.set(domModel, treeOutline);
325 // Perform attach if necessary.
326 if (this.isShowing())
333 * @param {!WebInspector.Target} target
335 targetRemoved: function(target)
337 var domModel = WebInspector.DOMModel.fromTarget(target);
340 var treeOutline = this._modelToTreeOutline.remove(domModel);
341 treeOutline.unwireFromDOMModel();
342 this._treeOutlines.remove(treeOutline);
343 treeOutline.element.remove();
346 _updateTreeOutlineVisibleWidth: function()
348 if (!this._treeOutlines.length)
351 var width = this._splitWidget.element.offsetWidth;
352 if (this._splitWidget.isVertical())
353 width -= this._splitWidget.sidebarSize();
354 for (var i = 0; i < this._treeOutlines.length; ++i) {
355 this._treeOutlines[i].setVisibleWidth(width);
356 this._treeOutlines[i].updateSelection();
358 this._breadcrumbs.updateSizes();
365 defaultFocusedElement: function()
367 return this._treeOutlines.length ? this._treeOutlines[0].element : this.element;
372 * @return {!WebInspector.SearchableView}
374 searchableView: function()
376 return this._searchableView;
381 for (var i = 0; i < this._treeOutlines.length; ++i) {
382 var treeOutline = this._treeOutlines[i];
383 // Attach heavy component lazily
384 if (treeOutline.element.parentElement !== this._contentElement)
385 this._contentElement.appendChild(treeOutline.element);
387 WebInspector.Panel.prototype.wasShown.call(this);
388 this._breadcrumbs.update();
390 for (var i = 0; i < this._treeOutlines.length; ++i) {
391 var treeOutline = this._treeOutlines[i];
392 treeOutline.updateSelection();
393 treeOutline.setVisible(true);
395 if (!treeOutline.rootDOMNode)
396 if (treeOutline.domModel().existingDocument())
397 this._documentUpdated(treeOutline.domModel(), treeOutline.domModel().existingDocument());
399 treeOutline.domModel().requestDocument();
406 WebInspector.DOMModel.hideDOMNodeHighlight();
407 for (var i = 0; i < this._treeOutlines.length; ++i) {
408 var treeOutline = this._treeOutlines[i];
409 treeOutline.setVisible(false);
410 // Detach heavy component on hide
411 this._contentElement.removeChild(treeOutline.element);
413 if (this._popoverHelper)
414 this._popoverHelper.hidePopover();
415 WebInspector.Panel.prototype.willHide.call(this);
420 if (WebInspector.moduleSetting("sidebarPosition").get() === "auto")
421 this.element.window().requestAnimationFrame(this._updateSidebarPosition.bind(this)); // Do not force layout.
422 this._updateTreeOutlineVisibleWidth();
426 * @param {!WebInspector.Event} event
428 _onNodePicked: function(event)
430 if (!this.sidebarPanes.styles.isEditingSelector())
432 this.sidebarPanes.styles.updateEditingSelectorForNode(/** @type {!WebInspector.DOMNode} */(event.data));
436 * @param {!WebInspector.Event} event
438 _selectedNodeChanged: function(event)
440 var selectedNode = /** @type {?WebInspector.DOMNode} */ (event.data);
441 for (var i = 0; i < this._treeOutlines.length; ++i) {
442 if (!selectedNode || selectedNode.domModel() !== this._treeOutlines[i].domModel())
443 this._treeOutlines[i].selectDOMNode(null);
446 if (!selectedNode && this._lastValidSelectedNode)
447 this._selectedPathOnReset = this._lastValidSelectedNode.path();
449 this._breadcrumbs.setSelectedNode(selectedNode);
451 WebInspector.context.setFlavor(WebInspector.DOMNode, selectedNode);
454 selectedNode.setAsInspectedNode();
455 this._lastValidSelectedNode = selectedNode;
456 this._updateActionsToolbar(selectedNode);
458 WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged);
459 this._selectedNodeChangedForTest();
462 _selectedNodeChangedForTest: function() { },
466 delete this.currentQuery;
470 * @param {!WebInspector.Event} event
472 _documentUpdatedEvent: function(event)
474 this._documentUpdated(/** @type {!WebInspector.DOMModel} */ (event.target), /** @type {?WebInspector.DOMDocument} */ (event.data));
478 * @param {!WebInspector.DOMModel} domModel
479 * @param {?WebInspector.DOMDocument} inspectedRootDocument
481 _documentUpdated: function(domModel, inspectedRootDocument)
484 this.searchCanceled();
486 var treeOutline = this._modelToTreeOutline.get(domModel);
487 treeOutline.rootDOMNode = inspectedRootDocument;
489 if (!inspectedRootDocument) {
490 if (this.isShowing())
491 domModel.requestDocument();
495 WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(domModel);
498 * @this {WebInspector.ElementsPanel}
499 * @param {?WebInspector.DOMNode} candidateFocusNode
501 function selectNode(candidateFocusNode)
503 if (!candidateFocusNode)
504 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
506 if (!candidateFocusNode)
509 if (!this._pendingNodeReveal) {
510 this.selectDOMNode(candidateFocusNode);
511 if (treeOutline.selectedTreeElement)
512 treeOutline.selectedTreeElement.expand();
517 * @param {?DOMAgent.NodeId} nodeId
518 * @this {WebInspector.ElementsPanel}
520 function selectLastSelectedNode(nodeId)
522 if (this.selectedDOMNode()) {
523 // Focused node has been explicitly set while reaching out for the last selected node.
526 var node = nodeId ? domModel.nodeForId(nodeId) : null;
527 selectNode.call(this, node);
530 if (this._omitDefaultSelection)
533 if (this._selectedPathOnReset)
534 domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
536 selectNode.call(this, null);
537 delete this._selectedPathOnReset;
543 searchCanceled: function()
545 delete this._searchQuery;
546 this._hideSearchHighlights();
548 this._searchableView.updateSearchMatchesCount(0);
550 delete this._currentSearchResultIndex;
551 delete this._searchResults;
553 WebInspector.DOMModel.cancelSearch();
558 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
559 * @param {boolean} shouldJump
560 * @param {boolean=} jumpBackwards
562 performSearch: function(searchConfig, shouldJump, jumpBackwards)
564 var query = searchConfig.query;
565 // Call searchCanceled since it will reset everything we need before doing a new search.
566 this.searchCanceled();
568 const whitespaceTrimmedQuery = query.trim();
569 if (!whitespaceTrimmedQuery.length)
572 this._searchQuery = query;
575 var domModels = WebInspector.DOMModel.instances();
576 for (var domModel of domModels)
577 promises.push(domModel.performSearchPromise(whitespaceTrimmedQuery, WebInspector.moduleSetting("showUAShadowDOM").get()));
578 Promise.all(promises).then(resultCountCallback.bind(this));
581 * @param {!Array.<number>} resultCounts
582 * @this {WebInspector.ElementsPanel}
584 function resultCountCallback(resultCounts)
587 * @type {!Array.<{domModel: !WebInspector.DOMModel, index: number, node: (?WebInspector.DOMNode|undefined)}>}
589 this._searchResults = [];
590 for (var i = 0; i < resultCounts.length; ++i) {
591 var resultCount = resultCounts[i];
592 for (var j = 0; j < resultCount; ++j)
593 this._searchResults.push({domModel: domModels[i], index: j, node: undefined});
595 this._searchableView.updateSearchMatchesCount(this._searchResults.length);
596 if (!this._searchResults.length)
598 this._currentSearchResultIndex = -1;
601 this._jumpToSearchResult(jumpBackwards ? -1 : 0);
605 _domWordWrapSettingChanged: function(event)
607 // FIXME: crbug.com/425984
608 this._contentElement.classList.toggle("elements-wrap", event.data);
609 for (var i = 0; i < this._treeOutlines.length; ++i)
610 this._treeOutlines[i].setWordWrap(/** @type {boolean} */ (event.data));
612 var selectedNode = this.selectedDOMNode();
616 var treeElement = this._treeElementForNode(selectedNode);
618 treeElement.updateSelection(); // Recalculate selection highlight dimensions.
621 switchToAndFocus: function(node)
623 // Reset search restore.
624 this._searchableView.cancelSearch();
625 WebInspector.inspectorView.setCurrentPanel(this);
626 this.selectDOMNode(node, true);
630 * @param {!Element} element
631 * @param {!Event} event
632 * @return {!Element|!AnchorBox|undefined}
634 _getPopoverAnchor: function(element, event)
636 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
637 if (!anchor || !anchor.href)
644 * @param {!Element} anchor
645 * @param {!WebInspector.Popover} popover
647 _showPopover: function(anchor, popover)
649 var node = this.selectedDOMNode();
651 WebInspector.DOMPresentationUtils.buildImagePreviewContents(node.target(), anchor.href, true, showPopover);
654 * @param {!Element=} contents
656 function showPopover(contents)
660 popover.setCanShrink(false);
661 popover.showForAnchor(contents, anchor);
665 _jumpToSearchResult: function(index)
667 this._hideSearchHighlights();
668 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
669 this._highlightCurrentSearchResult();
675 jumpToNextSearchResult: function()
677 if (!this._searchResults)
679 this._jumpToSearchResult(this._currentSearchResultIndex + 1);
685 jumpToPreviousSearchResult: function()
687 if (!this._searchResults)
689 this._jumpToSearchResult(this._currentSearchResultIndex - 1);
696 supportsCaseSensitiveSearch: function()
705 supportsRegexSearch: function()
710 _highlightCurrentSearchResult: function()
712 var index = this._currentSearchResultIndex;
713 var searchResults = this._searchResults;
714 var searchResult = searchResults[index];
716 if (searchResult.node === null) {
717 this._searchableView.updateCurrentMatchIndex(index);
722 * @param {?WebInspector.DOMNode} node
723 * @this {WebInspector.ElementsPanel}
725 function searchCallback(node)
727 searchResult.node = node;
728 this._highlightCurrentSearchResult();
731 if (typeof searchResult.node === "undefined") {
732 // No data for slot, request it.
733 searchResult.domModel.searchResult(searchResult.index, searchCallback.bind(this));
737 this._searchableView.updateCurrentMatchIndex(index);
739 var treeElement = this._treeElementForNode(searchResult.node);
741 treeElement.highlightSearchResults(this._searchQuery);
742 treeElement.reveal();
743 var matches = treeElement.listItemElement.getElementsByClassName(WebInspector.highlightedSearchResultClassName);
745 matches[0].scrollIntoViewIfNeeded();
749 _hideSearchHighlights: function()
751 if (!this._searchResults || !this._searchResults.length || this._currentSearchResultIndex < 0)
753 var searchResult = this._searchResults[this._currentSearchResultIndex];
754 if (!searchResult.node)
756 var treeOutline = this._modelToTreeOutline.get(searchResult.node.domModel());
757 var treeElement = treeOutline.findTreeElement(searchResult.node);
759 treeElement.hideSearchHighlights();
763 * @return {?WebInspector.DOMNode}
765 selectedDOMNode: function()
767 for (var i = 0; i < this._treeOutlines.length; ++i) {
768 var treeOutline = this._treeOutlines[i];
769 if (treeOutline.selectedDOMNode())
770 return treeOutline.selectedDOMNode();
776 * @param {!WebInspector.DOMNode} node
777 * @param {boolean=} focus
779 selectDOMNode: function(node, focus)
781 for (var i = 0; i < this._treeOutlines.length; ++i) {
782 var treeOutline = this._treeOutlines[i];
783 if (treeOutline.domModel() === node.domModel())
784 treeOutline.selectDOMNode(node, focus);
786 treeOutline.selectDOMNode(null);
791 * @param {!WebInspector.Event} event
793 _updateBreadcrumbIfNeeded: function(event)
795 var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data);
796 this._breadcrumbs.updateNodes(nodes);
800 * @param {!WebInspector.Event} event
802 _crumbNodeSelected: function(event)
804 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
805 this.selectDOMNode(node, true);
810 * @param {!KeyboardEvent} event
812 handleShortcut: function(event)
815 * @param {!WebInspector.ElementsTreeOutline} treeOutline
817 function handleUndoRedo(treeOutline)
819 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key
820 treeOutline.domModel().undo();
821 event.handled = true;
825 var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
826 event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
828 treeOutline.domModel().redo();
829 event.handled = true;
833 if (WebInspector.isEditing() && event.keyCode !== WebInspector.KeyboardShortcut.Keys.F2.code)
836 var treeOutline = null;
837 for (var i = 0; i < this._treeOutlines.length; ++i) {
838 if (this._treeOutlines[i].selectedDOMNode() === this._lastValidSelectedNode)
839 treeOutline = this._treeOutlines[i];
844 if (!treeOutline.editing()) {
845 handleUndoRedo.call(null, treeOutline);
847 this.sidebarPanes.styles.onUndoOrRedoHappened();
852 treeOutline.handleShortcut(event);
856 WebInspector.Panel.prototype.handleShortcut.call(this, event);
860 * @param {?WebInspector.DOMNode} node
861 * @return {?WebInspector.ElementsTreeOutline}
863 _treeOutlineForNode: function(node)
867 return this._modelToTreeOutline.get(node.domModel()) || null;
871 * @return {?WebInspector.ElementsTreeOutline}
873 _focusedTreeOutline: function()
875 for (var i = 0; i < this._treeOutlines.length; ++i) {
876 if (this._treeOutlines[i].hasFocus())
877 return this._treeOutlines[i];
883 * @param {!WebInspector.DOMNode} node
884 * @return {?WebInspector.ElementsTreeElement}
886 _treeElementForNode: function(node)
888 var treeOutline = this._treeOutlineForNode(node);
889 return /** @type {?WebInspector.ElementsTreeElement} */ (treeOutline.findTreeElement(node));
893 * @param {!Event} event
895 handleCopyEvent: function(event)
897 var treeOutline = this._focusedTreeOutline();
899 treeOutline.handleCopyOrCutKeyboardEvent(false, event);
903 * @param {!Event} event
905 handleCutEvent: function(event)
907 var treeOutline = this._focusedTreeOutline();
909 treeOutline.handleCopyOrCutKeyboardEvent(true, event);
913 * @param {!Event} event
915 handlePasteEvent: function(event)
917 var treeOutline = this._focusedTreeOutline();
919 treeOutline.handlePasteKeyboardEvent(event);
923 * @param {!WebInspector.DOMNode} node
924 * @return {!WebInspector.DOMNode}
926 _leaveUserAgentShadowDOM: function(node)
928 var userAgentShadowRoot = node.ancestorUserAgentShadowRoot();
929 return userAgentShadowRoot ? /** @type {!WebInspector.DOMNode} */ (userAgentShadowRoot.parentNode) : node;
933 * @param {!WebInspector.DOMNode} node
935 revealAndSelectNode: function(node)
937 if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.started())
938 WebInspector.inspectElementModeController.stop();
940 this._omitDefaultSelection = true;
942 WebInspector.inspectorView.setCurrentPanel(this, this._showLayoutEditor);
943 node = WebInspector.moduleSetting("showUAShadowDOM").get() ? node : this._leaveUserAgentShadowDOM(node);
944 if (!this._showLayoutEditor)
945 node.highlightForTwoSeconds();
947 this.selectDOMNode(node, true);
948 delete this._omitDefaultSelection;
950 if (!this._notFirstInspectElement)
951 InspectorFrontendHost.inspectElementCompleted();
952 this._notFirstInspectElement = true;
956 * @param {!Event} event
957 * @param {!WebInspector.ContextMenu} contextMenu
958 * @param {!Object} object
960 appendApplicableItems: function(event, contextMenu, object)
962 if (!(object instanceof WebInspector.RemoteObject && (/** @type {!WebInspector.RemoteObject} */ (object)).isNode())
963 && !(object instanceof WebInspector.DOMNode)
964 && !(object instanceof WebInspector.DeferredDOMNode)) {
968 // Add debbuging-related actions
969 if (object instanceof WebInspector.DOMNode) {
970 contextMenu.appendSeparator();
971 WebInspector.domBreakpointsSidebarPane.populateNodeContextMenu(object, contextMenu, true);
974 // Skip adding "Reveal..." menu item for our own tree outline.
975 if (this.element.isAncestor(/** @type {!Node} */ (event.target)))
977 var commandCallback = WebInspector.Revealer.reveal.bind(WebInspector.Revealer, object);
979 contextMenu.appendItem(WebInspector.UIString.capitalize("Reveal in Elements ^panel"), commandCallback);
982 _sidebarContextMenuEventFired: function(event)
984 var contextMenu = new WebInspector.ContextMenu(event);
985 contextMenu.appendApplicableItems(/** @type {!Object} */ (event.deepElementFromPoint()));
989 _showUAShadowDOMChanged: function()
991 for (var i = 0; i < this._treeOutlines.length; ++i)
992 this._treeOutlines[i].update();
995 _updateSidebarPosition: function()
998 var position = WebInspector.moduleSetting("sidebarPosition").get();
999 if (position === "right")
1001 else if (position === "bottom")
1004 vertically = WebInspector.inspectorView.element.offsetWidth < 680;
1006 if (this.sidebarPaneView && vertically === !this._splitWidget.isVertical())
1009 if (this.sidebarPaneView && this.sidebarPaneView.shouldHideOnDetach())
1010 return; // We can't reparent extension iframes.
1012 var selectedTabId = this.sidebarPaneView ? this.sidebarPaneView.selectedTabId : null;
1014 var extensionSidebarPanes = WebInspector.extensionServer.sidebarPanes();
1015 if (this.sidebarPaneView) {
1016 this.sidebarPaneView.detach();
1017 this._splitWidget.uninstallResizer(this.sidebarPaneView.headerElement());
1020 this._splitWidget.setVertical(!vertically);
1022 var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed"));
1023 computedPane.element.classList.add("composite");
1024 computedPane.element.classList.add("fill");
1026 computedPane.element.classList.add("metrics-and-computed");
1028 var matchedStylePanesWrapper = new WebInspector.VBox();
1029 matchedStylePanesWrapper.element.classList.add("style-panes-wrapper");
1030 var computedStylePanesWrapper = new WebInspector.VBox();
1031 computedStylePanesWrapper.element.classList.add("style-panes-wrapper");
1034 * @param {boolean} inComputedStyle
1035 * @this {WebInspector.ElementsPanel}
1037 function showMetrics(inComputedStyle)
1039 if (inComputedStyle)
1040 this.sidebarPanes.metrics.show(computedStylePanesWrapper.element, this.sidebarPanes.computedStyle.element);
1042 this.sidebarPanes.metrics.show(matchedStylePanesWrapper.element);
1046 * @param {!WebInspector.Event} event
1047 * @this {WebInspector.ElementsPanel}
1049 function tabSelected(event)
1051 var tabId = /** @type {string} */ (event.data.tabId);
1052 if (tabId === computedPane.title())
1053 showMetrics.call(this, true);
1054 else if (tabId === stylesPane.title())
1055 showMetrics.call(this, false);
1058 this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
1059 this.sidebarPaneView.element.addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
1060 if (this._popoverHelper)
1061 this._popoverHelper.hidePopover();
1062 this._popoverHelper = new WebInspector.PopoverHelper(this.sidebarPaneView.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
1063 this._popoverHelper.setTimeout(0);
1066 this._splitWidget.installResizer(this.sidebarPaneView.headerElement());
1068 var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1069 compositePane.element.classList.add("composite");
1070 compositePane.element.classList.add("fill");
1072 var splitWidget = new WebInspector.SplitWidget(true, true, "stylesPaneSplitViewState", 215);
1073 splitWidget.show(compositePane.element);
1075 splitWidget.setMainWidget(matchedStylePanesWrapper);
1076 splitWidget.setSidebarWidget(computedStylePanesWrapper);
1078 computedPane.show(computedStylePanesWrapper.element);
1079 this.sidebarPaneView.addPane(compositePane);
1081 var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
1082 stylesPane.element.classList.add("composite", "fill", "metrics-and-styles");
1084 matchedStylePanesWrapper.show(stylesPane.element);
1085 computedStylePanesWrapper.show(computedPane.element);
1087 this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this);
1089 this.sidebarPaneView.addPane(stylesPane);
1090 this.sidebarPaneView.addPane(computedPane);
1093 this.sidebarPanes.styles.show(matchedStylePanesWrapper.element);
1094 this.sidebarPanes.computedStyle.show(computedStylePanesWrapper.element);
1095 showMetrics.call(this, vertically);
1096 this.sidebarPanes.platformFonts.show(computedStylePanesWrapper.element);
1098 this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners);
1099 this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints);
1100 this.sidebarPaneView.addPane(this.sidebarPanes.properties);
1102 for (var sidebarViewWrapper of this._elementsSidebarViewWrappers)
1103 this.sidebarPaneView.addPane(sidebarViewWrapper);
1105 this._extensionSidebarPanesContainer = this.sidebarPaneView;
1107 for (var i = 0; i < extensionSidebarPanes.length; ++i)
1108 this._addExtensionSidebarPane(extensionSidebarPanes[i]);
1110 this._splitWidget.setSidebarWidget(this.sidebarPaneView);
1111 this.sidebarPanes.styles.expand();
1114 this.sidebarPaneView.selectTab(selectedTabId);
1118 * @param {!WebInspector.Event} event
1120 _extensionSidebarPaneAdded: function(event)
1122 var pane = /** @type {!WebInspector.ExtensionSidebarPane} */ (event.data);
1123 this._addExtensionSidebarPane(pane);
1127 * @param {!WebInspector.ExtensionSidebarPane} pane
1129 _addExtensionSidebarPane: function(pane)
1131 if (pane.panelName() === this.name)
1132 this._extensionSidebarPanesContainer.addPane(pane);
1136 * @param {?WebInspector.Widget} widget
1138 setWidgetBelowDOM: function(widget)
1141 this._elementsPanelTreeOutilneSplit.setSidebarWidget(widget);
1142 this._elementsPanelTreeOutilneSplit.showBoth(true);
1144 this._elementsPanelTreeOutilneSplit.hideSidebar(true);
1148 __proto__: WebInspector.Panel.prototype
1153 * @implements {WebInspector.ContextMenu.Provider}
1155 WebInspector.ElementsPanel.ContextMenuProvider = function()
1159 WebInspector.ElementsPanel.ContextMenuProvider.prototype = {
1162 * @param {!Event} event
1163 * @param {!WebInspector.ContextMenu} contextMenu
1164 * @param {!Object} target
1166 appendApplicableItems: function(event, contextMenu, target)
1168 WebInspector.ElementsPanel.instance().appendApplicableItems(event, contextMenu, target);
1174 * @implements {WebInspector.Revealer}
1176 WebInspector.ElementsPanel.DOMNodeRevealer = function()
1180 WebInspector.ElementsPanel.DOMNodeRevealer.prototype = {
1183 * @param {!Object} node
1184 * @return {!Promise}
1186 reveal: function(node)
1188 var panel = WebInspector.ElementsPanel.instance();
1189 panel._pendingNodeReveal = true;
1191 return new Promise(revealPromise);
1194 * @param {function(undefined)} resolve
1195 * @param {function(!Error)} reject
1197 function revealPromise(resolve, reject)
1199 if (node instanceof WebInspector.DOMNode) {
1200 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (node));
1201 } else if (node instanceof WebInspector.DeferredDOMNode) {
1202 (/** @type {!WebInspector.DeferredDOMNode} */ (node)).resolve(onNodeResolved);
1203 } else if (node instanceof WebInspector.RemoteObject) {
1204 var domModel = WebInspector.DOMModel.fromTarget(/** @type {!WebInspector.RemoteObject} */ (node).target());
1206 domModel.pushObjectAsNodeToFrontend(node, onNodeResolved);
1208 reject(new Error("Could not resolve a node to reveal."));
1210 reject(new Error("Can't reveal a non-node."));
1211 panel._pendingNodeReveal = false;
1215 * @param {?WebInspector.DOMNode} resolvedNode
1217 function onNodeResolved(resolvedNode)
1219 panel._pendingNodeReveal = false;
1222 panel.revealAndSelectNode(resolvedNode);
1226 reject(new Error("Could not resolve node to reveal."));
1232 WebInspector.ElementsPanel.show = function()
1234 WebInspector.inspectorView.setCurrentPanel(WebInspector.ElementsPanel.instance());
1238 * @return {!WebInspector.ElementsPanel}
1240 WebInspector.ElementsPanel.instance = function()
1242 if (!WebInspector.ElementsPanel._instanceObject)
1243 WebInspector.ElementsPanel._instanceObject = new WebInspector.ElementsPanel();
1244 return WebInspector.ElementsPanel._instanceObject;
1249 * @implements {WebInspector.PanelFactory}
1251 WebInspector.ElementsPanelFactory = function()
1255 WebInspector.ElementsPanelFactory.prototype = {
1258 * @return {!WebInspector.Panel}
1260 createPanel: function()
1262 return WebInspector.ElementsPanel.instance();
1268 * @implements {WebInspector.ActionDelegate}
1270 WebInspector.ElementsActionDelegate = function() { }
1272 WebInspector.ElementsActionDelegate.prototype = {
1275 * @param {!WebInspector.Context} context
1276 * @param {string} actionId
1278 handleAction: function(context, actionId)
1280 var elementsPanel = WebInspector.ElementsPanel.instance();
1281 if (actionId === "elements.hide-element")
1282 elementsPanel._toggleHideElement();
1283 else if (actionId === "elements.edit-as-html")
1284 elementsPanel._toggleEditAsHTML();
1290 * @implements {WebInspector.DOMPresentationUtils.MarkerDecorator}
1292 WebInspector.ElementsPanel.PseudoStateMarkerDecorator = function()
1296 WebInspector.ElementsPanel.PseudoStateMarkerDecorator.prototype = {
1299 * @param {!WebInspector.DOMNode} node
1300 * @return {?{title: string, color: string}}
1302 decorate: function(node)
1304 return { color: "orange", title: WebInspector.UIString("Element state: %s", ":" + WebInspector.CSSStyleModel.fromNode(node).pseudoState(node).join(", :")) };
1310 * @implements {WebInspector.DOMPresentationUtils.MarkerDecorator}
1312 WebInspector.ElementsPanel.HiddenMarkerDecorator = function()
1316 WebInspector.ElementsPanel.HiddenMarkerDecorator.prototype = {
1319 * @param {!WebInspector.DOMNode} node
1320 * @return {?{title: string, color: string}}
1322 decorate: function(node)
1324 return { color: "#555", title: WebInspector.UIString("Element is hidden") };