Devtools: Add force element state menu to the elements toolbar
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / elements / ElementsTreeOutline.js
blob7a6d52b7a76a4e95af6b96c3598df78915ae647e
1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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 /**
32 * @constructor
33 * @extends {TreeOutline}
34 * @param {!WebInspector.DOMModel} domModel
35 * @param {boolean=} omitRootDOMNode
36 * @param {boolean=} selectEnabled
38 WebInspector.ElementsTreeOutline = function(domModel, omitRootDOMNode, selectEnabled)
40 this._domModel = domModel;
41 this._treeElementSymbol = Symbol("treeElement");
43 var element = createElement("div");
45 this._shadowRoot = WebInspector.createShadowRootWithCoreStyles(element);
46 this._shadowRoot.appendChild(WebInspector.Widget.createStyleElement("elements/elementsTreeOutline.css"));
47 var outlineDisclosureElement = this._shadowRoot.createChild("div", "elements-disclosure");
49 TreeOutline.call(this);
50 this._element = this.element;
51 this._element.classList.add("elements-tree-outline", "source-code");
52 this._element.addEventListener("mousedown", this._onmousedown.bind(this), false);
53 this._element.addEventListener("mousemove", this._onmousemove.bind(this), false);
54 this._element.addEventListener("mouseleave", this._onmouseleave.bind(this), false);
55 this._element.addEventListener("dragstart", this._ondragstart.bind(this), false);
56 this._element.addEventListener("dragover", this._ondragover.bind(this), false);
57 this._element.addEventListener("dragleave", this._ondragleave.bind(this), false);
58 this._element.addEventListener("drop", this._ondrop.bind(this), false);
59 this._element.addEventListener("dragend", this._ondragend.bind(this), false);
60 this._element.addEventListener("webkitAnimationEnd", this._onAnimationEnd.bind(this), false);
61 this._element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
63 outlineDisclosureElement.appendChild(this._element);
64 this.element = element;
66 this._includeRootDOMNode = !omitRootDOMNode;
67 this._selectEnabled = selectEnabled;
68 /** @type {?WebInspector.DOMNode} */
69 this._rootDOMNode = null;
70 /** @type {?WebInspector.DOMNode} */
71 this._selectedDOMNode = null;
73 this._visible = false;
74 this._pickNodeMode = false;
76 this._createNodeDecorators();
78 this._popoverHelper = new WebInspector.PopoverHelper(this._element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
79 this._popoverHelper.setTimeout(0);
81 /** @type {!Map<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.UpdateRecord>} */
82 this._updateRecords = new Map();
83 /** @type {!Set<!WebInspector.ElementsTreeElement>} */
84 this._treeElementsBeingUpdated = new Set();
87 /** @typedef {{node: !WebInspector.DOMNode, isCut: boolean}} */
88 WebInspector.ElementsTreeOutline.ClipboardData;
90 /**
91 * @enum {string}
93 WebInspector.ElementsTreeOutline.Events = {
94 NodePicked: "NodePicked",
95 SelectedNodeChanged: "SelectedNodeChanged",
96 ElementsTreeUpdated: "ElementsTreeUpdated"
99 /**
100 * @const
101 * @type {!Object.<string, string>}
103 WebInspector.ElementsTreeOutline.MappedCharToEntity = {
104 "\u00a0": "nbsp",
105 "\u0093": "#147", // <control>
106 "\u00ad": "shy",
107 "\u2002": "ensp",
108 "\u2003": "emsp",
109 "\u2009": "thinsp",
110 "\u200a": "#8202", // Hairspace
111 "\u200b": "#8203", // ZWSP
112 "\u200c": "zwnj",
113 "\u200d": "zwj",
114 "\u200e": "lrm",
115 "\u200f": "rlm",
116 "\u202a": "#8234", // LRE
117 "\u202b": "#8235", // RLE
118 "\u202c": "#8236", // PDF
119 "\u202d": "#8237", // LRO
120 "\u202e": "#8238", // RLO
121 "\ufeff": "#65279" // BOM
124 WebInspector.ElementsTreeOutline.prototype = {
126 * @return {symbol}
128 treeElementSymbol: function()
130 return this._treeElementSymbol;
133 focus: function()
135 this._element.focus();
139 * @return {boolean}
141 hasFocus: function()
143 return this._element === WebInspector.currentFocusElement();
147 * @param {boolean} wrap
149 setWordWrap: function(wrap)
151 this._element.classList.toggle("elements-tree-nowrap", !wrap);
155 * @param {!Event} event
157 _onAnimationEnd: function(event)
159 event.target.classList.remove("elements-tree-element-pick-node-1");
160 event.target.classList.remove("elements-tree-element-pick-node-2");
164 * @return {boolean}
166 pickNodeMode: function()
168 return this._pickNodeMode;
172 * @param {boolean} value
174 setPickNodeMode: function(value)
176 this._pickNodeMode = value;
177 this._element.classList.toggle("pick-node-mode", value);
181 * @param {!Element} element
182 * @param {?WebInspector.DOMNode} node
183 * @return {boolean}
185 handlePickNode: function(element, node)
187 if (!this._pickNodeMode)
188 return false;
190 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.NodePicked, node);
191 var hasRunningAnimation = element.classList.contains("elements-tree-element-pick-node-1") || element.classList.contains("elements-tree-element-pick-node-2");
192 element.classList.toggle("elements-tree-element-pick-node-1");
193 if (hasRunningAnimation)
194 element.classList.toggle("elements-tree-element-pick-node-2");
195 return true;
199 * @return {!WebInspector.DOMModel}
201 domModel: function()
203 return this._domModel;
207 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
209 setMultilineEditing: function(multilineEditing)
211 this._multilineEditing = multilineEditing;
215 * @return {number}
217 visibleWidth: function()
219 return this._visibleWidth;
223 * @param {number} width
225 setVisibleWidth: function(width)
227 this._visibleWidth = width;
228 if (this._multilineEditing)
229 this._multilineEditing.setWidth(this._visibleWidth);
233 * @return {!Array<!WebInspector.ElementsTreeOutline.ElementDecorator>}
235 nodeDecorators: function()
237 return this._nodeDecorators;
240 _createNodeDecorators: function()
242 this._nodeDecorators = [];
243 this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator());
247 * @param {?WebInspector.ElementsTreeOutline.ClipboardData} data
249 _setClipboardData: function(data)
251 if (this._clipboardNodeData) {
252 var treeElement = this.findTreeElement(this._clipboardNodeData.node);
253 if (treeElement)
254 treeElement.setInClipboard(false);
255 delete this._clipboardNodeData;
258 if (data) {
259 var treeElement = this.findTreeElement(data.node);
260 if (treeElement)
261 treeElement.setInClipboard(true);
262 this._clipboardNodeData = data;
267 * @param {!WebInspector.DOMNode} removedNode
269 resetClipboardIfNeeded: function(removedNode)
271 if (this._clipboardNodeData && this._clipboardNodeData.node === removedNode)
272 this._setClipboardData(null);
276 * @param {boolean} isCut
277 * @param {!Event} event
279 handleCopyOrCutKeyboardEvent: function(isCut, event)
281 this._setClipboardData(null);
283 // Don't prevent the normal copy if the user has a selection.
284 if (!event.target.isComponentSelectionCollapsed())
285 return;
287 // Do not interfere with text editing.
288 if (WebInspector.isEditing())
289 return;
291 var targetNode = this.selectedDOMNode();
292 if (!targetNode)
293 return;
295 event.clipboardData.clearData();
296 event.preventDefault();
298 this.performCopyOrCut(isCut, targetNode);
302 * @param {boolean} isCut
303 * @param {?WebInspector.DOMNode} node
305 performCopyOrCut: function(isCut, node)
307 if (isCut && (node.isShadowRoot() || node.ancestorUserAgentShadowRoot()))
308 return;
310 node.copyNode();
311 this._setClipboardData({ node: node, isCut: isCut });
315 * @param {!WebInspector.DOMNode} targetNode
316 * @return {boolean}
318 canPaste: function(targetNode)
320 if (targetNode.isShadowRoot() || targetNode.ancestorUserAgentShadowRoot())
321 return false;
323 if (!this._clipboardNodeData)
324 return false;
326 var node = this._clipboardNodeData.node;
327 if (this._clipboardNodeData.isCut && (node === targetNode || node.isAncestor(targetNode)))
328 return false;
330 if (targetNode.target() !== node.target())
331 return false;
332 return true;
336 * @param {!WebInspector.DOMNode} targetNode
338 pasteNode: function(targetNode)
340 if (this.canPaste(targetNode))
341 this._performPaste(targetNode);
345 * @param {!Event} event
347 handlePasteKeyboardEvent: function(event)
349 // Do not interfere with text editing.
350 if (WebInspector.isEditing())
351 return;
353 var targetNode = this.selectedDOMNode();
354 if (!targetNode || !this.canPaste(targetNode))
355 return;
357 event.preventDefault();
358 this._performPaste(targetNode);
362 * @param {!WebInspector.DOMNode} targetNode
364 _performPaste: function(targetNode)
366 if (this._clipboardNodeData.isCut) {
367 this._clipboardNodeData.node.moveTo(targetNode, null, expandCallback.bind(this));
368 this._setClipboardData(null);
369 } else {
370 this._clipboardNodeData.node.copyTo(targetNode, null, expandCallback.bind(this));
374 * @param {?Protocol.Error} error
375 * @param {!DOMAgent.NodeId} nodeId
376 * @this {WebInspector.ElementsTreeOutline}
378 function expandCallback(error, nodeId)
380 if (error)
381 return;
382 var pastedNode = this._domModel.nodeForId(nodeId);
383 if (!pastedNode)
384 return;
385 this.selectDOMNode(pastedNode);
390 * @param {boolean} visible
392 setVisible: function(visible)
394 this._visible = visible;
395 if (!this._visible) {
396 this._popoverHelper.hidePopover();
397 if (this._multilineEditing)
398 this._multilineEditing.cancel();
399 return;
402 this.runPendingUpdates();
403 if (this._selectedDOMNode)
404 this._revealAndSelectNode(this._selectedDOMNode, false);
407 get rootDOMNode()
409 return this._rootDOMNode;
412 set rootDOMNode(x)
414 if (this._rootDOMNode === x)
415 return;
417 this._rootDOMNode = x;
419 this._isXMLMimeType = x && x.isXMLNode();
421 this.update();
424 get isXMLMimeType()
426 return this._isXMLMimeType;
430 * @return {?WebInspector.DOMNode}
432 selectedDOMNode: function()
434 return this._selectedDOMNode;
438 * @param {?WebInspector.DOMNode} node
439 * @param {boolean=} focus
441 selectDOMNode: function(node, focus)
443 if (this._selectedDOMNode === node) {
444 this._revealAndSelectNode(node, !focus);
445 return;
448 this._selectedDOMNode = node;
449 this._revealAndSelectNode(node, !focus);
451 // The _revealAndSelectNode() method might find a different element if there is inlined text,
452 // and the select() call would change the selectedDOMNode and reenter this setter. So to
453 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same
454 // node as the one passed in.
455 if (this._selectedDOMNode === node)
456 this._selectedNodeChanged();
460 * @return {boolean}
462 editing: function()
464 var node = this.selectedDOMNode();
465 if (!node)
466 return false;
467 var treeElement = this.findTreeElement(node);
468 if (!treeElement)
469 return false;
470 return treeElement.isEditing() || false;
473 update: function()
475 var selectedTreeElement = this.selectedTreeElement;
476 if (!(selectedTreeElement instanceof WebInspector.ElementsTreeElement))
477 selectedTreeElement = null;
479 var selectedNode = selectedTreeElement ? selectedTreeElement.node() : null;
481 this.removeChildren();
483 if (!this.rootDOMNode)
484 return;
486 var treeElement;
487 if (this._includeRootDOMNode) {
488 treeElement = this._createElementTreeElement(this.rootDOMNode);
489 this.appendChild(treeElement);
490 } else {
491 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
492 var node = this.rootDOMNode.firstChild;
493 while (node) {
494 treeElement = this._createElementTreeElement(node);
495 this.appendChild(treeElement);
496 node = node.nextSibling;
500 if (selectedNode)
501 this._revealAndSelectNode(selectedNode, true);
504 updateSelection: function()
506 if (!this.selectedTreeElement)
507 return;
508 var element = this.selectedTreeElement;
509 element.updateSelection();
513 * @param {!WebInspector.DOMNode} node
515 updateOpenCloseTags: function(node)
517 var treeElement = this.findTreeElement(node);
518 if (treeElement)
519 treeElement.updateTitle(this._updateRecordForHighlight(node));
520 var closingTagElement = treeElement.lastChild();
521 if (closingTagElement && closingTagElement.isClosingTag())
522 closingTagElement.updateTitle(this._updateRecordForHighlight(node));
525 _selectedNodeChanged: function()
527 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode);
531 * @param {!Array.<!WebInspector.DOMNode>} nodes
533 _fireElementsTreeUpdated: function(nodes)
535 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, nodes);
539 * @param {!WebInspector.DOMNode} node
540 * @return {?WebInspector.ElementsTreeElement}
542 findTreeElement: function(node)
544 var treeElement = this._lookUpTreeElement(node);
545 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
546 // The text node might have been inlined if it was short, so try to find the parent element.
547 treeElement = this._lookUpTreeElement(node.parentNode);
550 return /** @type {?WebInspector.ElementsTreeElement} */ (treeElement);
554 * @param {?WebInspector.DOMNode} node
555 * @return {?TreeElement}
557 _lookUpTreeElement: function(node)
559 if (!node)
560 return null;
562 var cachedElement = node[this._treeElementSymbol];
563 if (cachedElement)
564 return cachedElement;
566 // Walk up the parent pointers from the desired node
567 var ancestors = [];
568 for (var currentNode = node.parentNode; currentNode; currentNode = currentNode.parentNode) {
569 ancestors.push(currentNode);
570 if (currentNode[this._treeElementSymbol]) // stop climbing as soon as we hit
571 break;
574 if (!currentNode)
575 return null;
577 // Walk down to populate each ancestor's children, to fill in the tree and the cache.
578 for (var i = ancestors.length - 1; i >= 0; --i) {
579 var treeElement = ancestors[i][this._treeElementSymbol];
580 if (treeElement)
581 treeElement.onpopulate(); // fill the cache with the children of treeElement
584 return node[this._treeElementSymbol];
588 * @param {!WebInspector.DOMNode} node
589 * @return {?WebInspector.ElementsTreeElement}
591 createTreeElementFor: function(node)
593 var treeElement = this.findTreeElement(node);
594 if (treeElement)
595 return treeElement;
596 if (!node.parentNode)
597 return null;
599 treeElement = this.createTreeElementFor(node.parentNode);
600 return treeElement ? this._showChild(treeElement, node) : null;
603 set suppressRevealAndSelect(x)
605 if (this._suppressRevealAndSelect === x)
606 return;
607 this._suppressRevealAndSelect = x;
611 * @param {?WebInspector.DOMNode} node
612 * @param {boolean} omitFocus
614 _revealAndSelectNode: function(node, omitFocus)
616 if (this._suppressRevealAndSelect)
617 return;
619 if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootDOMNode)
620 node = this.rootDOMNode.firstChild;
621 if (!node)
622 return;
623 var treeElement = this.createTreeElementFor(node);
624 if (!treeElement)
625 return;
627 treeElement.revealAndSelect(omitFocus);
631 * @return {?TreeElement}
633 _treeElementFromEvent: function(event)
635 var scrollContainer = this.element.parentElement;
637 // We choose this X coordinate based on the knowledge that our list
638 // items extend at least to the right edge of the outer <ol> container.
639 // In the no-word-wrap mode the outer <ol> may be wider than the tree container
640 // (and partially hidden), in which case we are left to use only its right boundary.
641 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36;
643 var y = event.pageY;
645 // Our list items have 1-pixel cracks between them vertically. We avoid
646 // the cracks by checking slightly above and slightly below the mouse
647 // and seeing if we hit the same element each time.
648 var elementUnderMouse = this.treeElementFromPoint(x, y);
649 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
650 var element;
651 if (elementUnderMouse === elementAboveMouse)
652 element = elementUnderMouse;
653 else
654 element = this.treeElementFromPoint(x, y + 2);
656 return element;
660 * @param {!Element} element
661 * @param {!Event} event
662 * @return {!Element|!AnchorBox|undefined}
664 _getPopoverAnchor: function(element, event)
666 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
667 if (!anchor || !anchor.href)
668 return;
670 return anchor;
674 * @param {!WebInspector.DOMNode} node
675 * @param {function()} callback
677 _loadDimensionsForNode: function(node, callback)
679 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
680 callback();
681 return;
684 node.resolveToObject("", resolvedNode);
686 function resolvedNode(object)
688 if (!object) {
689 callback();
690 return;
693 object.callFunctionJSON(features, undefined, callback);
694 object.release();
697 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number, currentSrc: (string|undefined)}}
698 * @suppressReceiverCheck
699 * @this {!Element}
701 function features()
703 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight, currentSrc: this.currentSrc };
709 * @param {!Element} anchor
710 * @param {!WebInspector.Popover} popover
712 _showPopover: function(anchor, popover)
714 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
715 var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.treeElement).node();
716 this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
719 * @param {!Element=} contents
721 function showPopover(contents)
723 if (!contents)
724 return;
725 popover.setCanShrink(false);
726 popover.showForAnchor(contents, anchor);
730 _onmousedown: function(event)
732 var element = this._treeElementFromEvent(event);
734 if (!element || element.isEventWithinDisclosureTriangle(event))
735 return;
737 element.select();
740 _onmousemove: function(event)
742 var element = this._treeElementFromEvent(event);
743 if (element && this._previousHoveredElement === element)
744 return;
746 if (this._previousHoveredElement) {
747 this._previousHoveredElement.hovered = false;
748 delete this._previousHoveredElement;
751 if (element) {
752 element.hovered = true;
753 this._previousHoveredElement = element;
756 if (element instanceof WebInspector.ElementsTreeElement) {
757 this._domModel.highlightDOMNodeWithConfig(element.node().id, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) });
758 return;
761 if (element instanceof WebInspector.ElementsTreeOutline.ShortcutTreeElement)
762 this._domModel.highlightDOMNodeWithConfig(undefined, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) }, element.backendNodeId());
765 _onmouseleave: function(event)
767 if (this._previousHoveredElement) {
768 this._previousHoveredElement.hovered = false;
769 delete this._previousHoveredElement;
772 WebInspector.DOMModel.hideDOMNodeHighlight();
775 _ondragstart: function(event)
777 if (!event.target.isComponentSelectionCollapsed())
778 return false;
779 if (event.target.nodeName === "A")
780 return false;
782 var treeElement = this._treeElementFromEvent(event);
783 if (!this._isValidDragSourceOrTarget(treeElement))
784 return false;
786 if (treeElement.node().nodeName() === "BODY" || treeElement.node().nodeName() === "HEAD")
787 return false;
789 event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent.replace(/\u200b/g, ""));
790 event.dataTransfer.effectAllowed = "copyMove";
791 this._treeElementBeingDragged = treeElement;
793 WebInspector.DOMModel.hideDOMNodeHighlight();
795 return true;
798 _ondragover: function(event)
800 if (!this._treeElementBeingDragged)
801 return false;
803 var treeElement = this._treeElementFromEvent(event);
804 if (!this._isValidDragSourceOrTarget(treeElement))
805 return false;
807 var node = treeElement.node();
808 while (node) {
809 if (node === this._treeElementBeingDragged._node)
810 return false;
811 node = node.parentNode;
814 treeElement.updateSelection();
815 treeElement.listItemElement.classList.add("elements-drag-over");
816 this._dragOverTreeElement = treeElement;
817 event.preventDefault();
818 event.dataTransfer.dropEffect = 'move';
819 return false;
822 _ondragleave: function(event)
824 this._clearDragOverTreeElementMarker();
825 event.preventDefault();
826 return false;
830 * @param {?TreeElement} treeElement
831 * @return {boolean}
833 _isValidDragSourceOrTarget: function(treeElement)
835 if (!treeElement)
836 return false;
838 if (!(treeElement instanceof WebInspector.ElementsTreeElement))
839 return false;
840 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (treeElement);
842 var node = elementsTreeElement.node();
843 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
844 return false;
846 return true;
849 _ondrop: function(event)
851 event.preventDefault();
852 var treeElement = this._treeElementFromEvent(event);
853 if (treeElement)
854 this._doMove(treeElement);
858 * @param {!TreeElement} treeElement
860 _doMove: function(treeElement)
862 if (!this._treeElementBeingDragged)
863 return;
865 var parentNode;
866 var anchorNode;
868 if (treeElement.isClosingTag()) {
869 // Drop onto closing tag -> insert as last child.
870 parentNode = treeElement.node();
871 } else {
872 var dragTargetNode = treeElement.node();
873 parentNode = dragTargetNode.parentNode;
874 anchorNode = dragTargetNode;
877 var wasExpanded = this._treeElementBeingDragged.expanded;
878 this._treeElementBeingDragged._node.moveTo(parentNode, anchorNode, this.selectNodeAfterEdit.bind(this, wasExpanded));
880 delete this._treeElementBeingDragged;
883 _ondragend: function(event)
885 event.preventDefault();
886 this._clearDragOverTreeElementMarker();
887 delete this._treeElementBeingDragged;
890 _clearDragOverTreeElementMarker: function()
892 if (this._dragOverTreeElement) {
893 this._dragOverTreeElement.updateSelection();
894 this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over");
895 delete this._dragOverTreeElement;
899 _contextMenuEventFired: function(event)
901 var treeElement = this._treeElementFromEvent(event);
902 if (!(treeElement instanceof WebInspector.ElementsTreeElement) || WebInspector.isEditing())
903 return;
905 var contextMenu = new WebInspector.ContextMenu(event);
906 var isPseudoElement = !!treeElement.node().pseudoType();
907 var isTag = treeElement.node().nodeType() === Node.ELEMENT_NODE && !isPseudoElement;
908 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
909 if (textNode && textNode.classList.contains("bogus"))
910 textNode = null;
911 var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment");
912 contextMenu.appendApplicableItems(event.target);
913 if (textNode) {
914 contextMenu.appendSeparator();
915 treeElement.populateTextContextMenu(contextMenu, textNode);
916 } else if (isTag) {
917 contextMenu.appendSeparator();
918 treeElement.populateTagContextMenu(contextMenu, event);
919 } else if (commentNode) {
920 contextMenu.appendSeparator();
921 treeElement.populateNodeContextMenu(contextMenu);
922 } else if (isPseudoElement) {
923 treeElement.populateScrollIntoView(contextMenu);
926 contextMenu.appendApplicableItems(treeElement.node());
927 contextMenu.show();
930 runPendingUpdates: function()
932 this._updateModifiedNodes();
935 handleShortcut: function(event)
937 var node = this.selectedDOMNode();
938 if (!node)
939 return;
940 var treeElement = node[this._treeElementSymbol];
941 if (!treeElement)
942 return;
944 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.parentNode) {
945 if (event.keyIdentifier === "Up" && node.previousSibling) {
946 node.moveTo(node.parentNode, node.previousSibling, this.selectNodeAfterEdit.bind(this, treeElement.expanded));
947 event.handled = true;
948 return;
950 if (event.keyIdentifier === "Down" && node.nextSibling) {
951 node.moveTo(node.parentNode, node.nextSibling.nextSibling, this.selectNodeAfterEdit.bind(this, treeElement.expanded));
952 event.handled = true;
953 return;
959 * @param {!WebInspector.DOMNode} node
960 * @param {boolean=} startEditing
961 * @param {function()=} callback
963 toggleEditAsHTML: function(node, startEditing, callback)
965 var treeElement = node[this._treeElementSymbol];
966 if (!treeElement || !treeElement.hasEditableNode())
967 return;
969 if (node.pseudoType())
970 return;
972 var parentNode = node.parentNode;
973 var index = node.index;
974 var wasExpanded = treeElement.expanded;
976 treeElement.toggleEditAsHTML(editingFinished.bind(this), startEditing);
979 * @this {WebInspector.ElementsTreeOutline}
980 * @param {boolean} success
982 function editingFinished(success)
984 if (callback)
985 callback();
986 if (!success)
987 return;
989 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
990 this.runPendingUpdates();
992 var newNode = parentNode ? parentNode.children()[index] || parentNode : null;
993 if (!newNode)
994 return;
996 this.selectDOMNode(newNode, true);
998 if (wasExpanded) {
999 var newTreeItem = this.findTreeElement(newNode);
1000 if (newTreeItem)
1001 newTreeItem.expand();
1007 * @param {boolean} wasExpanded
1008 * @param {?Protocol.Error} error
1009 * @param {!DOMAgent.NodeId=} nodeId
1010 * @return {?WebInspector.ElementsTreeElement} nodeId
1012 selectNodeAfterEdit: function(wasExpanded, error, nodeId)
1014 if (error)
1015 return null;
1017 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
1018 this.runPendingUpdates();
1020 var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null;
1021 if (!newNode)
1022 return null;
1024 this.selectDOMNode(newNode, true);
1026 var newTreeItem = this.findTreeElement(newNode);
1027 if (wasExpanded) {
1028 if (newTreeItem)
1029 newTreeItem.expand();
1031 return newTreeItem;
1035 * Runs a script on the node's remote object that toggles a class name on
1036 * the node and injects a stylesheet into the head of the node's document
1037 * containing a rule to set "visibility: hidden" on the class and all it's
1038 * ancestors.
1040 * @param {!WebInspector.DOMNode} node
1041 * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback
1043 toggleHideElement: function(node, userCallback)
1045 var pseudoType = node.pseudoType();
1046 var effectiveNode = pseudoType ? node.parentNode : node;
1047 if (!effectiveNode)
1048 return;
1050 function resolvedNode(object)
1052 if (!object)
1053 return;
1056 * @param {?string} pseudoType
1057 * @suppressGlobalPropertiesCheck
1058 * @suppressReceiverCheck
1059 * @this {!Element}
1061 function toggleClassAndInjectStyleRule(pseudoType)
1063 const classNamePrefix = "__web-inspector-hide";
1064 const classNameSuffix = "-shortcut__";
1065 const styleTagId = "__web-inspector-hide-shortcut-style__";
1066 var selectors = [];
1067 selectors.push(".__web-inspector-hide-shortcut__");
1068 selectors.push(".__web-inspector-hide-shortcut__ *");
1069 selectors.push(".__web-inspector-hidebefore-shortcut__::before");
1070 selectors.push(".__web-inspector-hideafter-shortcut__::after");
1071 var selector = selectors.join(", ");
1072 var ruleBody = " visibility: hidden !important;";
1073 var rule = "\n" + selector + "\n{\n" + ruleBody + "\n}\n";
1074 var className = classNamePrefix + (pseudoType || "") + classNameSuffix;
1075 this.classList.toggle(className);
1077 var localRoot = this;
1078 while (localRoot.parentNode)
1079 localRoot = localRoot.parentNode;
1080 if (localRoot.nodeType === Node.DOCUMENT_NODE)
1081 localRoot = document.head;
1083 var style = localRoot.querySelector("style#" + styleTagId);
1084 if (style)
1085 return;
1087 style = document.createElement("style");
1088 style.id = styleTagId;
1089 style.type = "text/css";
1090 style.textContent = rule;
1092 localRoot.appendChild(style);
1095 object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoType }], userCallback);
1096 object.release();
1099 effectiveNode.resolveToObject("", resolvedNode);
1102 _reset: function()
1104 this.rootDOMNode = null;
1105 this.selectDOMNode(null, false);
1106 this._popoverHelper.hidePopover();
1107 delete this._clipboardNodeData;
1108 WebInspector.DOMModel.hideDOMNodeHighlight();
1109 this._updateRecords.clear();
1112 wireToDOMModel: function()
1114 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
1115 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
1116 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this);
1117 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this);
1118 this._domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
1119 this._domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
1120 this._domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
1121 this._domModel.addEventListener(WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedNodesChanged, this);
1124 unwireFromDOMModel: function()
1126 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
1127 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
1128 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this);
1129 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this);
1130 this._domModel.removeEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
1131 this._domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
1132 this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
1133 this._domModel.removeEventListener(WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedNodesChanged, this);
1137 * @param {!WebInspector.DOMNode} node
1138 * @return {!WebInspector.ElementsTreeOutline.UpdateRecord}
1140 _addUpdateRecord: function(node)
1142 var record = this._updateRecords.get(node);
1143 if (!record) {
1144 record = new WebInspector.ElementsTreeOutline.UpdateRecord();
1145 this._updateRecords.set(node, record);
1147 return record;
1151 * @param {!WebInspector.DOMNode} node
1152 * @return {?WebInspector.ElementsTreeOutline.UpdateRecord}
1154 _updateRecordForHighlight: function(node)
1156 if (!WebInspector.moduleSetting("highlightDOMUpdates").get() || !this._visible)
1157 return null;
1158 return this._updateRecords.get(node) || null;
1162 * @param {!WebInspector.Event} event
1164 _documentUpdated: function(event)
1166 var inspectedRootDocument = event.data;
1168 this._reset();
1170 if (!inspectedRootDocument)
1171 return;
1173 this.rootDOMNode = inspectedRootDocument;
1177 * @param {!WebInspector.Event} event
1179 _attributeModified: function(event)
1181 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1182 this._addUpdateRecord(node).attributeModified(event.data.name);
1183 this._updateModifiedNodesSoon();
1187 * @param {!WebInspector.Event} event
1189 _attributeRemoved: function(event)
1191 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1192 this._addUpdateRecord(node).attributeRemoved(event.data.name);
1193 this._updateModifiedNodesSoon();
1197 * @param {!WebInspector.Event} event
1199 _characterDataModified: function(event)
1201 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1202 this._addUpdateRecord(node).charDataModified();
1203 this._updateModifiedNodesSoon();
1207 * @param {!WebInspector.Event} event
1209 _nodeInserted: function(event)
1211 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1212 this._addUpdateRecord(/** @type {!WebInspector.DOMNode} */ (node.parentNode)).nodeInserted(node);
1213 this._updateModifiedNodesSoon();
1217 * @param {!WebInspector.Event} event
1219 _nodeRemoved: function(event)
1221 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1222 var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent);
1223 this.resetClipboardIfNeeded(node);
1224 this._addUpdateRecord(parentNode).nodeRemoved(node);
1225 this._updateModifiedNodesSoon();
1229 * @param {!WebInspector.Event} event
1231 _childNodeCountUpdated: function(event)
1233 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1234 this._addUpdateRecord(node).childrenModified();
1235 this._updateModifiedNodesSoon();
1239 * @param {!WebInspector.Event} event
1241 _distributedNodesChanged: function(event)
1243 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1244 this._addUpdateRecord(node).childrenModified();
1245 this._updateModifiedNodesSoon();
1248 _updateModifiedNodesSoon: function()
1250 if (!this._updateRecords.size)
1251 return;
1252 if (this._updateModifiedNodesTimeout)
1253 return;
1254 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 50);
1257 _updateModifiedNodes: function()
1259 if (this._updateModifiedNodesTimeout) {
1260 clearTimeout(this._updateModifiedNodesTimeout);
1261 delete this._updateModifiedNodesTimeout;
1264 var updatedNodes = this._updateRecords.keysArray();
1265 var hidePanelWhileUpdating = updatedNodes.length > 10;
1266 if (hidePanelWhileUpdating) {
1267 var treeOutlineContainerElement = this.element.parentNode;
1268 var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainerElement.scrollTop : 0;
1269 this._element.classList.add("hidden");
1272 if (this._rootDOMNode && this._updateRecords.get(this._rootDOMNode) && this._updateRecords.get(this._rootDOMNode).hasChangedChildren()) {
1273 // Document's children have changed, perform total update.
1274 this.update();
1275 } else {
1276 for (var node of this._updateRecords.keys()) {
1277 if (this._updateRecords.get(node).hasChangedChildren())
1278 this._updateModifiedParentNode(node);
1279 else
1280 this._updateModifiedNode(node);
1284 if (hidePanelWhileUpdating) {
1285 this._element.classList.remove("hidden");
1286 if (originalScrollTop)
1287 treeOutlineContainerElement.scrollTop = originalScrollTop;
1288 this.updateSelection();
1291 this._updateRecords.clear();
1292 this._fireElementsTreeUpdated(updatedNodes);
1295 _updateModifiedNode: function(node)
1297 var treeElement = this.findTreeElement(node);
1298 if (treeElement)
1299 treeElement.updateTitle(this._updateRecordForHighlight(node));
1302 _updateModifiedParentNode: function(node)
1304 var parentTreeElement = this.findTreeElement(node);
1305 if (parentTreeElement) {
1306 parentTreeElement.setExpandable(this._hasVisibleChildren(node));
1307 parentTreeElement.updateTitle(this._updateRecordForHighlight(node));
1308 if (parentTreeElement.populated)
1309 this._updateChildren(parentTreeElement);
1314 * @param {!WebInspector.ElementsTreeElement} treeElement
1316 populateTreeElement: function(treeElement)
1318 if (treeElement.childCount() || !treeElement.isExpandable())
1319 return;
1321 this._updateModifiedParentNode(treeElement.node());
1325 * @param {!WebInspector.DOMNode} node
1326 * @param {boolean=} closingTag
1327 * @return {!WebInspector.ElementsTreeElement}
1329 _createElementTreeElement: function(node, closingTag)
1331 var treeElement = new WebInspector.ElementsTreeElement(node, closingTag);
1332 treeElement.setExpandable(!closingTag && this._hasVisibleChildren(node));
1333 treeElement.selectable = this._selectEnabled;
1334 return treeElement;
1338 * @param {!WebInspector.ElementsTreeElement} treeElement
1339 * @param {!WebInspector.DOMNode} child
1340 * @return {?WebInspector.ElementsTreeElement}
1342 _showChild: function(treeElement, child)
1344 if (treeElement.isClosingTag())
1345 return null;
1347 var index = this._visibleChildren(treeElement.node()).indexOf(child);
1348 if (index === -1)
1349 return null;
1351 if (index >= treeElement.expandedChildrenLimit())
1352 this.setExpandedChildrenLimit(treeElement, index + 1);
1353 return /** @type {!WebInspector.ElementsTreeElement} */ (treeElement.childAt(index));
1357 * @param {!WebInspector.DOMNode} node
1358 * @return {!Array.<!WebInspector.DOMNode>} visibleChildren
1360 _visibleChildren: function(node)
1362 var visibleChildren = WebInspector.ElementsTreeElement.visibleShadowRoots(node);
1364 if (node.importedDocument())
1365 visibleChildren.push(node.importedDocument());
1367 if (node.templateContent())
1368 visibleChildren.push(node.templateContent());
1370 var beforePseudoElement = node.beforePseudoElement();
1371 if (beforePseudoElement)
1372 visibleChildren.push(beforePseudoElement);
1374 if (node.childNodeCount())
1375 visibleChildren = visibleChildren.concat(node.children());
1377 var afterPseudoElement = node.afterPseudoElement();
1378 if (afterPseudoElement)
1379 visibleChildren.push(afterPseudoElement);
1381 return visibleChildren;
1385 * @param {!WebInspector.DOMNode} node
1386 * @return {boolean}
1388 _hasVisibleChildren: function(node)
1390 if (WebInspector.ElementsTreeElement.canShowInlineText(node))
1391 return false;
1393 if (node.importedDocument())
1394 return true;
1395 if (node.templateContent())
1396 return true;
1397 if (node.childNodeCount())
1398 return true;
1399 if (WebInspector.ElementsTreeElement.visibleShadowRoots(node).length)
1400 return true;
1401 if (node.hasPseudoElements())
1402 return true;
1403 if (node.isInsertionPoint())
1404 return true;
1405 return false;
1409 * @param {!WebInspector.ElementsTreeElement} treeElement
1411 _createExpandAllButtonTreeElement: function(treeElement)
1413 var button = createTextButton("", handleLoadAllChildren.bind(this));
1414 button.value = "";
1415 var expandAllButtonElement = new TreeElement(button);
1416 expandAllButtonElement.selectable = false;
1417 expandAllButtonElement.expandAllButton = true;
1418 expandAllButtonElement.button = button;
1419 return expandAllButtonElement;
1422 * @this {WebInspector.ElementsTreeOutline}
1423 * @param {!Event} event
1425 function handleLoadAllChildren(event)
1427 var visibleChildCount = this._visibleChildren(treeElement.node()).length;
1428 this.setExpandedChildrenLimit(treeElement, Math.max(visibleChildCount, treeElement.expandedChildrenLimit() + WebInspector.ElementsTreeElement.InitialChildrenLimit));
1429 event.consume();
1434 * @param {!WebInspector.ElementsTreeElement} treeElement
1435 * @param {number} expandedChildrenLimit
1437 setExpandedChildrenLimit: function(treeElement, expandedChildrenLimit)
1439 if (treeElement.expandedChildrenLimit() === expandedChildrenLimit)
1440 return;
1442 treeElement.setExpandedChildrenLimit(expandedChildrenLimit);
1443 if (treeElement.treeOutline && !this._treeElementsBeingUpdated.has(treeElement))
1444 this._updateModifiedParentNode(treeElement.node());
1448 * @param {!WebInspector.ElementsTreeElement} treeElement
1450 _updateChildren: function(treeElement)
1452 if (!treeElement.isExpandable()) {
1453 var selectedTreeElement = treeElement.treeOutline.selectedTreeElement;
1454 if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
1455 treeElement.select(true);
1456 treeElement.removeChildren();
1457 return;
1460 console.assert(!treeElement.isClosingTag());
1462 treeElement.node().getChildNodes(childNodesLoaded.bind(this));
1465 * @param {?Array.<!WebInspector.DOMNode>} children
1466 * @this {WebInspector.ElementsTreeOutline}
1468 function childNodesLoaded(children)
1470 // FIXME: sort this out, it should not happen.
1471 if (!children)
1472 return;
1473 this._innerUpdateChildren(treeElement);
1478 * @param {!WebInspector.ElementsTreeElement} treeElement
1479 * @param {!WebInspector.DOMNode} child
1480 * @param {number} index
1481 * @param {boolean=} closingTag
1482 * @return {!WebInspector.ElementsTreeElement}
1484 insertChildElement: function(treeElement, child, index, closingTag)
1486 var newElement = this._createElementTreeElement(child, closingTag);
1487 treeElement.insertChild(newElement, index);
1488 return newElement;
1492 * @param {!WebInspector.ElementsTreeElement} treeElement
1493 * @param {!WebInspector.ElementsTreeElement} child
1494 * @param {number} targetIndex
1496 _moveChild: function(treeElement, child, targetIndex)
1498 if (treeElement.indexOfChild(child) === targetIndex)
1499 return;
1500 var wasSelected = child.selected;
1501 if (child.parent)
1502 child.parent.removeChild(child);
1503 treeElement.insertChild(child, targetIndex);
1504 if (wasSelected)
1505 child.select();
1509 * @param {!WebInspector.ElementsTreeElement} treeElement
1511 _innerUpdateChildren: function(treeElement)
1513 if (this._treeElementsBeingUpdated.has(treeElement))
1514 return;
1516 this._treeElementsBeingUpdated.add(treeElement);
1518 var node = treeElement.node();
1519 var visibleChildren = this._visibleChildren(node);
1520 var visibleChildrenSet = new Set(visibleChildren);
1522 // Remove any tree elements that no longer have this node as their parent and save
1523 // all existing elements that could be reused. This also removes closing tag element.
1524 var existingTreeElements = new Map();
1525 for (var i = treeElement.childCount() - 1; i >= 0; --i) {
1526 var existingTreeElement = treeElement.childAt(i);
1527 if (!(existingTreeElement instanceof WebInspector.ElementsTreeElement)) {
1528 // Remove expand all button and shadow host toolbar.
1529 treeElement.removeChildAtIndex(i);
1530 continue;
1532 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (existingTreeElement);
1533 var existingNode = elementsTreeElement.node();
1535 if (visibleChildrenSet.has(existingNode)) {
1536 existingTreeElements.set(existingNode, existingTreeElement);
1537 continue;
1540 treeElement.removeChildAtIndex(i);
1543 for (var i = 0; i < visibleChildren.length && i < treeElement.expandedChildrenLimit(); ++i) {
1544 var child = visibleChildren[i];
1545 var existingTreeElement = existingTreeElements.get(child) || this.findTreeElement(child);
1546 if (existingTreeElement && existingTreeElement !== treeElement) {
1547 // If an existing element was found, just move it.
1548 this._moveChild(treeElement, existingTreeElement, i);
1549 } else {
1550 // No existing element found, insert a new element.
1551 var newElement = this.insertChildElement(treeElement, child, i);
1552 if (this._updateRecordForHighlight(node) && treeElement.expanded)
1553 WebInspector.ElementsTreeElement.animateOnDOMUpdate(newElement);
1554 // If a node was inserted in the middle of existing list dynamically we might need to increase the limit.
1555 if (treeElement.childCount() > treeElement.expandedChildrenLimit())
1556 this.setExpandedChildrenLimit(treeElement, treeElement.expandedChildrenLimit() + 1);
1560 // Update expand all button.
1561 var expandedChildCount = treeElement.childCount();
1562 if (visibleChildren.length > expandedChildCount) {
1563 var targetButtonIndex = expandedChildCount;
1564 if (!treeElement.expandAllButtonElement)
1565 treeElement.expandAllButtonElement = this._createExpandAllButtonTreeElement(treeElement);
1566 treeElement.insertChild(treeElement.expandAllButtonElement, targetButtonIndex);
1567 treeElement.expandAllButtonElement.button.textContent = WebInspector.UIString("Show All Nodes (%d More)", visibleChildren.length - expandedChildCount);
1568 } else if (treeElement.expandAllButtonElement) {
1569 delete treeElement.expandAllButtonElement;
1572 // Insert shortcuts to distrubuted children.
1573 if (node.isInsertionPoint()) {
1574 for (var distributedNode of node.distributedNodes())
1575 treeElement.appendChild(new WebInspector.ElementsTreeOutline.ShortcutTreeElement(distributedNode));
1578 // Insert close tag.
1579 if (node.nodeType() === Node.ELEMENT_NODE && treeElement.isExpandable())
1580 this.insertChildElement(treeElement, node, treeElement.childCount(), true);
1582 this._treeElementsBeingUpdated.delete(treeElement);
1585 __proto__: TreeOutline.prototype
1589 * @interface
1591 WebInspector.ElementsTreeOutline.ElementDecorator = function()
1595 WebInspector.ElementsTreeOutline.ElementDecorator.prototype = {
1597 * @param {!WebInspector.DOMNode} node
1598 * @return {?string}
1600 decorate: function(node)
1605 * @param {!WebInspector.DOMNode} node
1606 * @return {?string}
1608 decorateAncestor: function(node)
1614 * @constructor
1615 * @implements {WebInspector.ElementsTreeOutline.ElementDecorator}
1617 WebInspector.ElementsTreeOutline.PseudoStateDecorator = function()
1619 WebInspector.ElementsTreeOutline.ElementDecorator.call(this);
1622 WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = {
1624 * @override
1625 * @param {!WebInspector.DOMNode} node
1626 * @return {?string}
1628 decorate: function(node)
1630 if (node.nodeType() !== Node.ELEMENT_NODE)
1631 return null;
1632 var propertyValue = node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName);
1633 if (!propertyValue)
1634 return null;
1635 return WebInspector.UIString("Element state: %s", ":" + propertyValue.join(", :"));
1639 * @override
1640 * @param {!WebInspector.DOMNode} node
1641 * @return {?string}
1643 decorateAncestor: function(node)
1645 if (node.nodeType() !== Node.ELEMENT_NODE)
1646 return null;
1648 var descendantCount = node.descendantUserPropertyCount(WebInspector.CSSStyleModel.PseudoStatePropertyName);
1649 if (!descendantCount)
1650 return null;
1651 if (descendantCount === 1)
1652 return WebInspector.UIString("%d descendant with forced state", descendantCount);
1653 return WebInspector.UIString("%d descendants with forced state", descendantCount);
1658 * @constructor
1660 WebInspector.ElementsTreeOutline.UpdateRecord = function()
1664 WebInspector.ElementsTreeOutline.UpdateRecord.prototype = {
1666 * @param {string} attrName
1668 attributeModified: function(attrName)
1670 if (this._removedAttributes && this._removedAttributes.has(attrName))
1671 this._removedAttributes.delete(attrName);
1672 if (!this._modifiedAttributes)
1673 this._modifiedAttributes = /** @type {!Set.<string>} */ (new Set());
1674 this._modifiedAttributes.add(attrName);
1678 * @param {string} attrName
1680 attributeRemoved: function(attrName)
1682 if (this._modifiedAttributes && this._modifiedAttributes.has(attrName))
1683 this._modifiedAttributes.delete(attrName);
1684 if (!this._removedAttributes)
1685 this._removedAttributes = /** @type {!Set.<string>} */ (new Set());
1686 this._removedAttributes.add(attrName);
1690 * @param {!WebInspector.DOMNode} node
1692 nodeInserted: function(node)
1694 this._hasChangedChildren = true;
1697 nodeRemoved: function(node)
1699 this._hasChangedChildren = true;
1700 this._hasRemovedChildren = true;
1703 charDataModified: function()
1705 this._charDataModified = true;
1708 childrenModified: function()
1710 this._hasChangedChildren = true;
1714 * @param {string} attributeName
1715 * @return {boolean}
1717 isAttributeModified: function(attributeName)
1719 return this._modifiedAttributes && this._modifiedAttributes.has(attributeName);
1723 * @return {boolean}
1725 hasRemovedAttributes: function()
1727 return !!this._removedAttributes && !!this._removedAttributes.size;
1731 * @return {boolean}
1733 isCharDataModified: function()
1735 return !!this._charDataModified;
1739 * @return {boolean}
1741 hasChangedChildren: function()
1743 return !!this._hasChangedChildren;
1747 * @return {boolean}
1749 hasRemovedChildren: function()
1751 return !!this._hasRemovedChildren;
1756 * @constructor
1757 * @implements {WebInspector.Renderer}
1759 WebInspector.ElementsTreeOutline.Renderer = function()
1763 WebInspector.ElementsTreeOutline.Renderer.prototype = {
1765 * @override
1766 * @param {!Object} object
1767 * @return {!Promise.<!Element>}
1769 render: function(object)
1771 return new Promise(renderPromise);
1774 * @param {function(!Element)} resolve
1775 * @param {function(!Error)} reject
1777 function renderPromise(resolve, reject)
1779 if (object instanceof WebInspector.DOMNode) {
1780 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (object));
1781 } else if (object instanceof WebInspector.DeferredDOMNode) {
1782 (/** @type {!WebInspector.DeferredDOMNode} */ (object)).resolve(onNodeResolved);
1783 } else if (object instanceof WebInspector.RemoteObject) {
1784 var domModel = WebInspector.DOMModel.fromTarget((/** @type {!WebInspector.RemoteObject} */ (object)).target());
1785 if (domModel)
1786 domModel.pushObjectAsNodeToFrontend(object, onNodeResolved);
1787 else
1788 reject(new Error("No dom model for given JS object target found."));
1789 } else {
1790 reject(new Error("Can't reveal not a node."));
1794 * @param {?WebInspector.DOMNode} node
1796 function onNodeResolved(node)
1798 if (!node) {
1799 reject(new Error("Could not resolve node."));
1800 return;
1802 var treeOutline = new WebInspector.ElementsTreeOutline(node.domModel(), false, false);
1803 treeOutline.rootDOMNode = node;
1804 if (!treeOutline.firstChild().isExpandable())
1805 treeOutline._element.classList.add("single-node");
1806 treeOutline.setVisible(true);
1807 treeOutline.element.treeElementForTest = treeOutline.firstChild();
1808 resolve(treeOutline.element);
1815 * @constructor
1816 * @extends {TreeElement}
1817 * @param {!WebInspector.DOMNodeShortcut} nodeShortcut
1819 WebInspector.ElementsTreeOutline.ShortcutTreeElement = function(nodeShortcut)
1821 TreeElement.call(this, "");
1822 this.listItemElement.createChild("div", "selection");
1823 var title = this.listItemElement.createChild("span", "elements-tree-shortcut-title");
1824 var text = nodeShortcut.nodeName.toLowerCase();
1825 if (nodeShortcut.nodeType === Node.ELEMENT_NODE)
1826 text = "<" + text + ">";
1827 title.textContent = "\u21AA " + text;
1829 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReference(nodeShortcut.deferredNode);
1830 this.listItemElement.createTextChild(" ");
1831 link.classList.add("elements-tree-shortcut-link");
1832 link.textContent = WebInspector.UIString("reveal");
1833 this.listItemElement.appendChild(link);
1834 this._nodeShortcut = nodeShortcut;
1837 WebInspector.ElementsTreeOutline.ShortcutTreeElement.prototype = {
1839 * @return {boolean}
1841 get hovered()
1843 return this._hovered;
1846 set hovered(x)
1848 if (this._hovered === x)
1849 return;
1850 this._hovered = x;
1851 this.listItemElement.classList.toggle("hovered", x);
1854 updateSelection: function()
1859 * @return {number}
1861 backendNodeId: function()
1863 return this._nodeShortcut.deferredNode.backendNodeId();
1867 * @override
1868 * @param {boolean=} selectedByUser
1869 * @return {boolean}
1871 onselect: function(selectedByUser)
1873 if (!selectedByUser)
1874 return true;
1875 this._nodeShortcut.deferredNode.highlight();
1876 this._nodeShortcut.deferredNode.resolve(resolved.bind(this));
1878 * @param {?WebInspector.DOMNode} node
1879 * @this {WebInspector.ElementsTreeOutline.ShortcutTreeElement}
1881 function resolved(node)
1883 if (node) {
1884 this.treeOutline._selectedDOMNode = node;
1885 this.treeOutline._selectedNodeChanged();
1888 return true;
1891 __proto__: TreeElement.prototype