Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / elements / ElementsTreeOutline.js
blobd51739a07b05852fea4947f45097e07418dd8925
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._popoverHelper = new WebInspector.PopoverHelper(this._element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
77 this._popoverHelper.setTimeout(0);
79 /** @type {!Map<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.UpdateRecord>} */
80 this._updateRecords = new Map();
81 /** @type {!Set<!WebInspector.ElementsTreeElement>} */
82 this._treeElementsBeingUpdated = new Set();
84 this._domModel.addEventListener(WebInspector.DOMModel.Events.MarkersChanged, this._markersChanged, this);
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",
97 DecorationsClicked: "DecorationsClicked"
101 * @const
102 * @type {!Object.<string, string>}
104 WebInspector.ElementsTreeOutline.MappedCharToEntity = {
105 "\u00a0": "nbsp",
106 "\u0093": "#147", // <control>
107 "\u00ad": "shy",
108 "\u2002": "ensp",
109 "\u2003": "emsp",
110 "\u2009": "thinsp",
111 "\u200a": "#8202", // Hairspace
112 "\u200b": "#8203", // ZWSP
113 "\u200c": "zwnj",
114 "\u200d": "zwj",
115 "\u200e": "lrm",
116 "\u200f": "rlm",
117 "\u202a": "#8234", // LRE
118 "\u202b": "#8235", // RLE
119 "\u202c": "#8236", // PDF
120 "\u202d": "#8237", // LRO
121 "\u202e": "#8238", // RLO
122 "\ufeff": "#65279" // BOM
125 WebInspector.ElementsTreeOutline.prototype = {
127 * @return {symbol}
129 treeElementSymbol: function()
131 return this._treeElementSymbol;
134 focus: function()
136 this._element.focus();
140 * @return {boolean}
142 hasFocus: function()
144 return this._element === WebInspector.currentFocusElement();
148 * @param {boolean} wrap
150 setWordWrap: function(wrap)
152 this._element.classList.toggle("elements-tree-nowrap", !wrap);
156 * @param {!Event} event
158 _onAnimationEnd: function(event)
160 event.target.classList.remove("elements-tree-element-pick-node-1");
161 event.target.classList.remove("elements-tree-element-pick-node-2");
165 * @return {boolean}
167 pickNodeMode: function()
169 return this._pickNodeMode;
173 * @param {boolean} value
175 setPickNodeMode: function(value)
177 this._pickNodeMode = value;
178 this._element.classList.toggle("pick-node-mode", value);
182 * @param {!Element} element
183 * @param {?WebInspector.DOMNode} node
184 * @return {boolean}
186 handlePickNode: function(element, node)
188 if (!this._pickNodeMode)
189 return false;
191 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.NodePicked, node);
192 var hasRunningAnimation = element.classList.contains("elements-tree-element-pick-node-1") || element.classList.contains("elements-tree-element-pick-node-2");
193 element.classList.toggle("elements-tree-element-pick-node-1");
194 if (hasRunningAnimation)
195 element.classList.toggle("elements-tree-element-pick-node-2");
196 return true;
200 * @return {!WebInspector.DOMModel}
202 domModel: function()
204 return this._domModel;
208 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
210 setMultilineEditing: function(multilineEditing)
212 this._multilineEditing = multilineEditing;
216 * @return {number}
218 visibleWidth: function()
220 return this._visibleWidth;
224 * @param {number} width
226 setVisibleWidth: function(width)
228 this._visibleWidth = width;
229 if (this._multilineEditing)
230 this._multilineEditing.setWidth(this._visibleWidth);
234 * @param {?WebInspector.ElementsTreeOutline.ClipboardData} data
236 _setClipboardData: function(data)
238 if (this._clipboardNodeData) {
239 var treeElement = this.findTreeElement(this._clipboardNodeData.node);
240 if (treeElement)
241 treeElement.setInClipboard(false);
242 delete this._clipboardNodeData;
245 if (data) {
246 var treeElement = this.findTreeElement(data.node);
247 if (treeElement)
248 treeElement.setInClipboard(true);
249 this._clipboardNodeData = data;
254 * @param {!WebInspector.DOMNode} removedNode
256 resetClipboardIfNeeded: function(removedNode)
258 if (this._clipboardNodeData && this._clipboardNodeData.node === removedNode)
259 this._setClipboardData(null);
263 * @param {boolean} isCut
264 * @param {!Event} event
266 handleCopyOrCutKeyboardEvent: function(isCut, event)
268 this._setClipboardData(null);
270 // Don't prevent the normal copy if the user has a selection.
271 if (!event.target.isComponentSelectionCollapsed())
272 return;
274 // Do not interfere with text editing.
275 if (WebInspector.isEditing())
276 return;
278 var targetNode = this.selectedDOMNode();
279 if (!targetNode)
280 return;
282 event.clipboardData.clearData();
283 event.preventDefault();
285 this.performCopyOrCut(isCut, targetNode);
289 * @param {boolean} isCut
290 * @param {?WebInspector.DOMNode} node
292 performCopyOrCut: function(isCut, node)
294 if (isCut && (node.isShadowRoot() || node.ancestorUserAgentShadowRoot()))
295 return;
297 node.copyNode();
298 this._setClipboardData({ node: node, isCut: isCut });
302 * @param {!WebInspector.DOMNode} targetNode
303 * @return {boolean}
305 canPaste: function(targetNode)
307 if (targetNode.isShadowRoot() || targetNode.ancestorUserAgentShadowRoot())
308 return false;
310 if (!this._clipboardNodeData)
311 return false;
313 var node = this._clipboardNodeData.node;
314 if (this._clipboardNodeData.isCut && (node === targetNode || node.isAncestor(targetNode)))
315 return false;
317 if (targetNode.target() !== node.target())
318 return false;
319 return true;
323 * @param {!WebInspector.DOMNode} targetNode
325 pasteNode: function(targetNode)
327 if (this.canPaste(targetNode))
328 this._performPaste(targetNode);
332 * @param {!Event} event
334 handlePasteKeyboardEvent: function(event)
336 // Do not interfere with text editing.
337 if (WebInspector.isEditing())
338 return;
340 var targetNode = this.selectedDOMNode();
341 if (!targetNode || !this.canPaste(targetNode))
342 return;
344 event.preventDefault();
345 this._performPaste(targetNode);
349 * @param {!WebInspector.DOMNode} targetNode
351 _performPaste: function(targetNode)
353 if (this._clipboardNodeData.isCut) {
354 this._clipboardNodeData.node.moveTo(targetNode, null, expandCallback.bind(this));
355 this._setClipboardData(null);
356 } else {
357 this._clipboardNodeData.node.copyTo(targetNode, null, expandCallback.bind(this));
361 * @param {?Protocol.Error} error
362 * @param {!DOMAgent.NodeId} nodeId
363 * @this {WebInspector.ElementsTreeOutline}
365 function expandCallback(error, nodeId)
367 if (error)
368 return;
369 var pastedNode = this._domModel.nodeForId(nodeId);
370 if (!pastedNode)
371 return;
372 this.selectDOMNode(pastedNode);
377 * @param {boolean} visible
379 setVisible: function(visible)
381 this._visible = visible;
382 if (!this._visible) {
383 this._popoverHelper.hidePopover();
384 if (this._multilineEditing)
385 this._multilineEditing.cancel();
386 return;
389 this.runPendingUpdates();
390 if (this._selectedDOMNode)
391 this._revealAndSelectNode(this._selectedDOMNode, false);
394 get rootDOMNode()
396 return this._rootDOMNode;
399 set rootDOMNode(x)
401 if (this._rootDOMNode === x)
402 return;
404 this._rootDOMNode = x;
406 this._isXMLMimeType = x && x.isXMLNode();
408 this.update();
411 get isXMLMimeType()
413 return this._isXMLMimeType;
417 * @return {?WebInspector.DOMNode}
419 selectedDOMNode: function()
421 return this._selectedDOMNode;
425 * @param {?WebInspector.DOMNode} node
426 * @param {boolean=} focus
428 selectDOMNode: function(node, focus)
430 if (this._selectedDOMNode === node) {
431 this._revealAndSelectNode(node, !focus);
432 return;
435 this._selectedDOMNode = node;
436 this._revealAndSelectNode(node, !focus);
438 // The _revealAndSelectNode() method might find a different element if there is inlined text,
439 // and the select() call would change the selectedDOMNode and reenter this setter. So to
440 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same
441 // node as the one passed in.
442 if (this._selectedDOMNode === node)
443 this._selectedNodeChanged();
447 * @return {boolean}
449 editing: function()
451 var node = this.selectedDOMNode();
452 if (!node)
453 return false;
454 var treeElement = this.findTreeElement(node);
455 if (!treeElement)
456 return false;
457 return treeElement.isEditing() || false;
460 update: function()
462 var selectedTreeElement = this.selectedTreeElement;
463 if (!(selectedTreeElement instanceof WebInspector.ElementsTreeElement))
464 selectedTreeElement = null;
466 var selectedNode = selectedTreeElement ? selectedTreeElement.node() : null;
468 this.removeChildren();
470 if (!this.rootDOMNode)
471 return;
473 var treeElement;
474 if (this._includeRootDOMNode) {
475 treeElement = this._createElementTreeElement(this.rootDOMNode);
476 this.appendChild(treeElement);
477 } else {
478 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
479 var node = this.rootDOMNode.firstChild;
480 while (node) {
481 treeElement = this._createElementTreeElement(node);
482 this.appendChild(treeElement);
483 node = node.nextSibling;
487 if (selectedNode)
488 this._revealAndSelectNode(selectedNode, true);
491 updateSelection: function()
493 if (!this.selectedTreeElement)
494 return;
495 var element = this.selectedTreeElement;
496 element.updateSelection();
500 * @param {!WebInspector.DOMNode} node
502 updateOpenCloseTags: function(node)
504 var treeElement = this.findTreeElement(node);
505 if (treeElement)
506 treeElement.updateTitle(this._updateRecordForHighlight(node));
507 var closingTagElement = treeElement.lastChild();
508 if (closingTagElement && closingTagElement.isClosingTag())
509 closingTagElement.updateTitle(this._updateRecordForHighlight(node));
512 _selectedNodeChanged: function()
514 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode);
518 * @param {!Array.<!WebInspector.DOMNode>} nodes
520 _fireElementsTreeUpdated: function(nodes)
522 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, nodes);
526 * @param {!WebInspector.DOMNode} node
527 * @return {?WebInspector.ElementsTreeElement}
529 findTreeElement: function(node)
531 var treeElement = this._lookUpTreeElement(node);
532 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
533 // The text node might have been inlined if it was short, so try to find the parent element.
534 treeElement = this._lookUpTreeElement(node.parentNode);
537 return /** @type {?WebInspector.ElementsTreeElement} */ (treeElement);
541 * @param {?WebInspector.DOMNode} node
542 * @return {?TreeElement}
544 _lookUpTreeElement: function(node)
546 if (!node)
547 return null;
549 var cachedElement = node[this._treeElementSymbol];
550 if (cachedElement)
551 return cachedElement;
553 // Walk up the parent pointers from the desired node
554 var ancestors = [];
555 for (var currentNode = node.parentNode; currentNode; currentNode = currentNode.parentNode) {
556 ancestors.push(currentNode);
557 if (currentNode[this._treeElementSymbol]) // stop climbing as soon as we hit
558 break;
561 if (!currentNode)
562 return null;
564 // Walk down to populate each ancestor's children, to fill in the tree and the cache.
565 for (var i = ancestors.length - 1; i >= 0; --i) {
566 var treeElement = ancestors[i][this._treeElementSymbol];
567 if (treeElement)
568 treeElement.onpopulate(); // fill the cache with the children of treeElement
571 return node[this._treeElementSymbol];
575 * @param {!WebInspector.DOMNode} node
576 * @return {?WebInspector.ElementsTreeElement}
578 createTreeElementFor: function(node)
580 var treeElement = this.findTreeElement(node);
581 if (treeElement)
582 return treeElement;
583 if (!node.parentNode)
584 return null;
586 treeElement = this.createTreeElementFor(node.parentNode);
587 return treeElement ? this._showChild(treeElement, node) : null;
590 set suppressRevealAndSelect(x)
592 if (this._suppressRevealAndSelect === x)
593 return;
594 this._suppressRevealAndSelect = x;
598 * @param {?WebInspector.DOMNode} node
599 * @param {boolean} omitFocus
601 _revealAndSelectNode: function(node, omitFocus)
603 if (this._suppressRevealAndSelect)
604 return;
606 if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootDOMNode)
607 node = this.rootDOMNode.firstChild;
608 if (!node)
609 return;
610 var treeElement = this.createTreeElementFor(node);
611 if (!treeElement)
612 return;
614 treeElement.revealAndSelect(omitFocus);
618 * @return {?TreeElement}
620 _treeElementFromEvent: function(event)
622 var scrollContainer = this.element.parentElement;
624 // We choose this X coordinate based on the knowledge that our list
625 // items extend at least to the right edge of the outer <ol> container.
626 // In the no-word-wrap mode the outer <ol> may be wider than the tree container
627 // (and partially hidden), in which case we are left to use only its right boundary.
628 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36;
630 var y = event.pageY;
632 // Our list items have 1-pixel cracks between them vertically. We avoid
633 // the cracks by checking slightly above and slightly below the mouse
634 // and seeing if we hit the same element each time.
635 var elementUnderMouse = this.treeElementFromPoint(x, y);
636 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
637 var element;
638 if (elementUnderMouse === elementAboveMouse)
639 element = elementUnderMouse;
640 else
641 element = this.treeElementFromPoint(x, y + 2);
643 return element;
647 * @param {!Element} element
648 * @param {!Event} event
649 * @return {!Element|!AnchorBox|undefined}
651 _getPopoverAnchor: function(element, event)
653 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
654 if (!anchor || !anchor.href)
655 return;
657 return anchor;
661 * @param {!WebInspector.DOMNode} node
662 * @param {function()} callback
664 _loadDimensionsForNode: function(node, callback)
666 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
667 callback();
668 return;
671 node.resolveToObject("", resolvedNode);
673 function resolvedNode(object)
675 if (!object) {
676 callback();
677 return;
680 object.callFunctionJSON(features, undefined, callback);
681 object.release();
684 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number, currentSrc: (string|undefined)}}
685 * @suppressReceiverCheck
686 * @this {!Element}
688 function features()
690 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight, currentSrc: this.currentSrc };
696 * @param {!Element} anchor
697 * @param {!WebInspector.Popover} popover
699 _showPopover: function(anchor, popover)
701 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
702 var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.treeElement).node();
703 this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
706 * @param {!Element=} contents
708 function showPopover(contents)
710 if (!contents)
711 return;
712 popover.setCanShrink(false);
713 popover.showForAnchor(contents, anchor);
717 _onmousedown: function(event)
719 var element = this._treeElementFromEvent(event);
721 if (!element || element.isEventWithinDisclosureTriangle(event))
722 return;
724 element.select();
727 _onmousemove: function(event)
729 var element = this._treeElementFromEvent(event);
730 if (element && this._previousHoveredElement === element)
731 return;
733 if (this._previousHoveredElement) {
734 this._previousHoveredElement.hovered = false;
735 delete this._previousHoveredElement;
738 if (element) {
739 element.hovered = true;
740 this._previousHoveredElement = element;
743 if (element instanceof WebInspector.ElementsTreeElement) {
744 this._domModel.highlightDOMNodeWithConfig(element.node().id, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) });
745 return;
748 if (element instanceof WebInspector.ElementsTreeOutline.ShortcutTreeElement)
749 this._domModel.highlightDOMNodeWithConfig(undefined, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) }, element.backendNodeId());
752 _onmouseleave: function(event)
754 if (this._previousHoveredElement) {
755 this._previousHoveredElement.hovered = false;
756 delete this._previousHoveredElement;
759 WebInspector.DOMModel.hideDOMNodeHighlight();
762 _ondragstart: function(event)
764 if (!event.target.isComponentSelectionCollapsed())
765 return false;
766 if (event.target.nodeName === "A")
767 return false;
769 var treeElement = this._treeElementFromEvent(event);
770 if (!this._isValidDragSourceOrTarget(treeElement))
771 return false;
773 if (treeElement.node().nodeName() === "BODY" || treeElement.node().nodeName() === "HEAD")
774 return false;
776 event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent.replace(/\u200b/g, ""));
777 event.dataTransfer.effectAllowed = "copyMove";
778 this._treeElementBeingDragged = treeElement;
780 WebInspector.DOMModel.hideDOMNodeHighlight();
782 return true;
785 _ondragover: function(event)
787 if (!this._treeElementBeingDragged)
788 return false;
790 var treeElement = this._treeElementFromEvent(event);
791 if (!this._isValidDragSourceOrTarget(treeElement))
792 return false;
794 var node = treeElement.node();
795 while (node) {
796 if (node === this._treeElementBeingDragged._node)
797 return false;
798 node = node.parentNode;
801 treeElement.updateSelection();
802 treeElement.listItemElement.classList.add("elements-drag-over");
803 this._dragOverTreeElement = treeElement;
804 event.preventDefault();
805 event.dataTransfer.dropEffect = 'move';
806 return false;
809 _ondragleave: function(event)
811 this._clearDragOverTreeElementMarker();
812 event.preventDefault();
813 return false;
817 * @param {?TreeElement} treeElement
818 * @return {boolean}
820 _isValidDragSourceOrTarget: function(treeElement)
822 if (!treeElement)
823 return false;
825 if (!(treeElement instanceof WebInspector.ElementsTreeElement))
826 return false;
827 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (treeElement);
829 var node = elementsTreeElement.node();
830 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
831 return false;
833 return true;
836 _ondrop: function(event)
838 event.preventDefault();
839 var treeElement = this._treeElementFromEvent(event);
840 if (treeElement)
841 this._doMove(treeElement);
845 * @param {!TreeElement} treeElement
847 _doMove: function(treeElement)
849 if (!this._treeElementBeingDragged)
850 return;
852 var parentNode;
853 var anchorNode;
855 if (treeElement.isClosingTag()) {
856 // Drop onto closing tag -> insert as last child.
857 parentNode = treeElement.node();
858 } else {
859 var dragTargetNode = treeElement.node();
860 parentNode = dragTargetNode.parentNode;
861 anchorNode = dragTargetNode;
864 var wasExpanded = this._treeElementBeingDragged.expanded;
865 this._treeElementBeingDragged._node.moveTo(parentNode, anchorNode, this.selectNodeAfterEdit.bind(this, wasExpanded));
867 delete this._treeElementBeingDragged;
870 _ondragend: function(event)
872 event.preventDefault();
873 this._clearDragOverTreeElementMarker();
874 delete this._treeElementBeingDragged;
877 _clearDragOverTreeElementMarker: function()
879 if (this._dragOverTreeElement) {
880 this._dragOverTreeElement.updateSelection();
881 this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over");
882 delete this._dragOverTreeElement;
886 _contextMenuEventFired: function(event)
888 var treeElement = this._treeElementFromEvent(event);
889 if (!(treeElement instanceof WebInspector.ElementsTreeElement) || WebInspector.isEditing())
890 return;
892 var contextMenu = new WebInspector.ContextMenu(event);
893 var isPseudoElement = !!treeElement.node().pseudoType();
894 var isTag = treeElement.node().nodeType() === Node.ELEMENT_NODE && !isPseudoElement;
895 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
896 if (textNode && textNode.classList.contains("bogus"))
897 textNode = null;
898 var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment");
899 contextMenu.appendApplicableItems(event.target);
900 if (textNode) {
901 contextMenu.appendSeparator();
902 treeElement.populateTextContextMenu(contextMenu, textNode);
903 } else if (isTag) {
904 contextMenu.appendSeparator();
905 treeElement.populateTagContextMenu(contextMenu, event);
906 } else if (commentNode) {
907 contextMenu.appendSeparator();
908 treeElement.populateNodeContextMenu(contextMenu);
909 } else if (isPseudoElement) {
910 treeElement.populateScrollIntoView(contextMenu);
913 contextMenu.appendApplicableItems(treeElement.node());
914 contextMenu.show();
917 runPendingUpdates: function()
919 this._updateModifiedNodes();
922 handleShortcut: function(event)
924 var node = this.selectedDOMNode();
925 if (!node)
926 return;
927 var treeElement = node[this._treeElementSymbol];
928 if (!treeElement)
929 return;
931 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.parentNode) {
932 if (event.keyIdentifier === "Up" && node.previousSibling) {
933 node.moveTo(node.parentNode, node.previousSibling, this.selectNodeAfterEdit.bind(this, treeElement.expanded));
934 event.handled = true;
935 return;
937 if (event.keyIdentifier === "Down" && node.nextSibling) {
938 node.moveTo(node.parentNode, node.nextSibling.nextSibling, this.selectNodeAfterEdit.bind(this, treeElement.expanded));
939 event.handled = true;
940 return;
946 * @param {!WebInspector.DOMNode} node
947 * @param {boolean=} startEditing
948 * @param {function()=} callback
950 toggleEditAsHTML: function(node, startEditing, callback)
952 var treeElement = node[this._treeElementSymbol];
953 if (!treeElement || !treeElement.hasEditableNode())
954 return;
956 if (node.pseudoType())
957 return;
959 var parentNode = node.parentNode;
960 var index = node.index;
961 var wasExpanded = treeElement.expanded;
963 treeElement.toggleEditAsHTML(editingFinished.bind(this), startEditing);
966 * @this {WebInspector.ElementsTreeOutline}
967 * @param {boolean} success
969 function editingFinished(success)
971 if (callback)
972 callback();
973 if (!success)
974 return;
976 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
977 this.runPendingUpdates();
979 var newNode = parentNode ? parentNode.children()[index] || parentNode : null;
980 if (!newNode)
981 return;
983 this.selectDOMNode(newNode, true);
985 if (wasExpanded) {
986 var newTreeItem = this.findTreeElement(newNode);
987 if (newTreeItem)
988 newTreeItem.expand();
994 * @param {boolean} wasExpanded
995 * @param {?Protocol.Error} error
996 * @param {!DOMAgent.NodeId=} nodeId
997 * @return {?WebInspector.ElementsTreeElement} nodeId
999 selectNodeAfterEdit: function(wasExpanded, error, nodeId)
1001 if (error)
1002 return null;
1004 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
1005 this.runPendingUpdates();
1007 var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null;
1008 if (!newNode)
1009 return null;
1011 this.selectDOMNode(newNode, true);
1013 var newTreeItem = this.findTreeElement(newNode);
1014 if (wasExpanded) {
1015 if (newTreeItem)
1016 newTreeItem.expand();
1018 return newTreeItem;
1022 * Runs a script on the node's remote object that toggles a class name on
1023 * the node and injects a stylesheet into the head of the node's document
1024 * containing a rule to set "visibility: hidden" on the class and all it's
1025 * ancestors.
1027 * @param {!WebInspector.DOMNode} node
1028 * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback
1030 toggleHideElement: function(node, userCallback)
1032 var pseudoType = node.pseudoType();
1033 var effectiveNode = pseudoType ? node.parentNode : node;
1034 if (!effectiveNode)
1035 return;
1037 var hidden = node.marker("hidden-marker");
1039 function resolvedNode(object)
1041 if (!object)
1042 return;
1045 * @param {?string} pseudoType
1046 * @param {boolean} hidden
1047 * @suppressGlobalPropertiesCheck
1048 * @suppressReceiverCheck
1049 * @this {!Element}
1051 function toggleClassAndInjectStyleRule(pseudoType, hidden)
1053 const classNamePrefix = "__web-inspector-hide";
1054 const classNameSuffix = "-shortcut__";
1055 const styleTagId = "__web-inspector-hide-shortcut-style__";
1056 var selectors = [];
1057 selectors.push(".__web-inspector-hide-shortcut__");
1058 selectors.push(".__web-inspector-hide-shortcut__ *");
1059 selectors.push(".__web-inspector-hidebefore-shortcut__::before");
1060 selectors.push(".__web-inspector-hideafter-shortcut__::after");
1061 var selector = selectors.join(", ");
1062 var ruleBody = " visibility: hidden !important;";
1063 var rule = "\n" + selector + "\n{\n" + ruleBody + "\n}\n";
1064 var className = classNamePrefix + (pseudoType || "") + classNameSuffix;
1065 this.classList.toggle(className, hidden);
1067 var localRoot = this;
1068 while (localRoot.parentNode)
1069 localRoot = localRoot.parentNode;
1070 if (localRoot.nodeType === Node.DOCUMENT_NODE)
1071 localRoot = document.head;
1073 var style = localRoot.querySelector("style#" + styleTagId);
1074 if (style)
1075 return;
1077 style = document.createElement("style");
1078 style.id = styleTagId;
1079 style.type = "text/css";
1080 style.textContent = rule;
1082 localRoot.appendChild(style);
1085 object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoType }, { value: !hidden}], userCallback);
1086 object.release();
1087 node.setMarker("hidden-marker", hidden ? null : true);
1090 effectiveNode.resolveToObject("", resolvedNode);
1094 * @param {!WebInspector.DOMNode} node
1095 * @return {boolean}
1097 isToggledToHidden: function(node)
1099 return !!node.marker("hidden-marker");
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 (!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 // Text could be large and force us to render itself as the child in the tree outline.
1204 if (node.parentNode && node.parentNode.firstChild === node.parentNode.lastChild)
1205 this._addUpdateRecord(node.parentNode).childrenModified();
1206 this._updateModifiedNodesSoon();
1210 * @param {!WebInspector.Event} event
1212 _nodeInserted: function(event)
1214 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1215 this._addUpdateRecord(/** @type {!WebInspector.DOMNode} */ (node.parentNode)).nodeInserted(node);
1216 this._updateModifiedNodesSoon();
1220 * @param {!WebInspector.Event} event
1222 _nodeRemoved: function(event)
1224 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1225 var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent);
1226 this.resetClipboardIfNeeded(node);
1227 this._addUpdateRecord(parentNode).nodeRemoved(node);
1228 this._updateModifiedNodesSoon();
1232 * @param {!WebInspector.Event} event
1234 _childNodeCountUpdated: function(event)
1236 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1237 this._addUpdateRecord(node).childrenModified();
1238 this._updateModifiedNodesSoon();
1242 * @param {!WebInspector.Event} event
1244 _distributedNodesChanged: function(event)
1246 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1247 this._addUpdateRecord(node).childrenModified();
1248 this._updateModifiedNodesSoon();
1251 _updateModifiedNodesSoon: function()
1253 if (!this._updateRecords.size)
1254 return;
1255 if (this._updateModifiedNodesTimeout)
1256 return;
1257 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 50);
1260 _updateModifiedNodes: function()
1262 if (this._updateModifiedNodesTimeout) {
1263 clearTimeout(this._updateModifiedNodesTimeout);
1264 delete this._updateModifiedNodesTimeout;
1267 var updatedNodes = this._updateRecords.keysArray();
1268 var hidePanelWhileUpdating = updatedNodes.length > 10;
1269 if (hidePanelWhileUpdating) {
1270 var treeOutlineContainerElement = this.element.parentNode;
1271 var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainerElement.scrollTop : 0;
1272 this._element.classList.add("hidden");
1275 if (this._rootDOMNode && this._updateRecords.get(this._rootDOMNode) && this._updateRecords.get(this._rootDOMNode).hasChangedChildren()) {
1276 // Document's children have changed, perform total update.
1277 this.update();
1278 } else {
1279 for (var node of this._updateRecords.keys()) {
1280 if (this._updateRecords.get(node).hasChangedChildren())
1281 this._updateModifiedParentNode(node);
1282 else
1283 this._updateModifiedNode(node);
1287 if (hidePanelWhileUpdating) {
1288 this._element.classList.remove("hidden");
1289 if (originalScrollTop)
1290 treeOutlineContainerElement.scrollTop = originalScrollTop;
1291 this.updateSelection();
1294 this._updateRecords.clear();
1295 this._fireElementsTreeUpdated(updatedNodes);
1298 _updateModifiedNode: function(node)
1300 var treeElement = this.findTreeElement(node);
1301 if (treeElement)
1302 treeElement.updateTitle(this._updateRecordForHighlight(node));
1305 _updateModifiedParentNode: function(node)
1307 var parentTreeElement = this.findTreeElement(node);
1308 if (parentTreeElement) {
1309 parentTreeElement.setExpandable(this._hasVisibleChildren(node));
1310 parentTreeElement.updateTitle(this._updateRecordForHighlight(node));
1311 if (parentTreeElement.populated)
1312 this._updateChildren(parentTreeElement);
1317 * @param {!WebInspector.ElementsTreeElement} treeElement
1319 populateTreeElement: function(treeElement)
1321 if (treeElement.childCount() || !treeElement.isExpandable())
1322 return;
1324 this._updateModifiedParentNode(treeElement.node());
1328 * @param {!WebInspector.DOMNode} node
1329 * @param {boolean=} closingTag
1330 * @return {!WebInspector.ElementsTreeElement}
1332 _createElementTreeElement: function(node, closingTag)
1334 var treeElement = new WebInspector.ElementsTreeElement(node, closingTag);
1335 treeElement.setExpandable(!closingTag && this._hasVisibleChildren(node));
1336 treeElement.selectable = this._selectEnabled;
1337 return treeElement;
1341 * @param {!WebInspector.ElementsTreeElement} treeElement
1342 * @param {!WebInspector.DOMNode} child
1343 * @return {?WebInspector.ElementsTreeElement}
1345 _showChild: function(treeElement, child)
1347 if (treeElement.isClosingTag())
1348 return null;
1350 var index = this._visibleChildren(treeElement.node()).indexOf(child);
1351 if (index === -1)
1352 return null;
1354 if (index >= treeElement.expandedChildrenLimit())
1355 this.setExpandedChildrenLimit(treeElement, index + 1);
1356 return /** @type {!WebInspector.ElementsTreeElement} */ (treeElement.childAt(index));
1360 * @param {!WebInspector.DOMNode} node
1361 * @return {!Array.<!WebInspector.DOMNode>} visibleChildren
1363 _visibleChildren: function(node)
1365 var visibleChildren = WebInspector.ElementsTreeElement.visibleShadowRoots(node);
1367 if (node.importedDocument())
1368 visibleChildren.push(node.importedDocument());
1370 if (node.templateContent())
1371 visibleChildren.push(node.templateContent());
1373 var beforePseudoElement = node.beforePseudoElement();
1374 if (beforePseudoElement)
1375 visibleChildren.push(beforePseudoElement);
1377 if (node.childNodeCount())
1378 visibleChildren = visibleChildren.concat(node.children());
1380 var afterPseudoElement = node.afterPseudoElement();
1381 if (afterPseudoElement)
1382 visibleChildren.push(afterPseudoElement);
1384 return visibleChildren;
1388 * @param {!WebInspector.DOMNode} node
1389 * @return {boolean}
1391 _hasVisibleChildren: function(node)
1393 if (node.importedDocument())
1394 return true;
1395 if (node.templateContent())
1396 return true;
1397 if (WebInspector.ElementsTreeElement.visibleShadowRoots(node).length)
1398 return true;
1399 if (node.hasPseudoElements())
1400 return true;
1401 if (node.isInsertionPoint())
1402 return true;
1403 return !!node.childNodeCount() && !WebInspector.ElementsTreeElement.canShowInlineText(node);
1407 * @param {!WebInspector.ElementsTreeElement} treeElement
1409 _createExpandAllButtonTreeElement: function(treeElement)
1411 var button = createTextButton("", handleLoadAllChildren.bind(this));
1412 button.value = "";
1413 var expandAllButtonElement = new TreeElement(button);
1414 expandAllButtonElement.selectable = false;
1415 expandAllButtonElement.expandAllButton = true;
1416 expandAllButtonElement.button = button;
1417 return expandAllButtonElement;
1420 * @this {WebInspector.ElementsTreeOutline}
1421 * @param {!Event} event
1423 function handleLoadAllChildren(event)
1425 var visibleChildCount = this._visibleChildren(treeElement.node()).length;
1426 this.setExpandedChildrenLimit(treeElement, Math.max(visibleChildCount, treeElement.expandedChildrenLimit() + WebInspector.ElementsTreeElement.InitialChildrenLimit));
1427 event.consume();
1432 * @param {!WebInspector.ElementsTreeElement} treeElement
1433 * @param {number} expandedChildrenLimit
1435 setExpandedChildrenLimit: function(treeElement, expandedChildrenLimit)
1437 if (treeElement.expandedChildrenLimit() === expandedChildrenLimit)
1438 return;
1440 treeElement.setExpandedChildrenLimit(expandedChildrenLimit);
1441 if (treeElement.treeOutline && !this._treeElementsBeingUpdated.has(treeElement))
1442 this._updateModifiedParentNode(treeElement.node());
1446 * @param {!WebInspector.ElementsTreeElement} treeElement
1448 _updateChildren: function(treeElement)
1450 if (!treeElement.isExpandable()) {
1451 var selectedTreeElement = treeElement.treeOutline.selectedTreeElement;
1452 if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
1453 treeElement.select(true);
1454 treeElement.removeChildren();
1455 return;
1458 console.assert(!treeElement.isClosingTag());
1460 treeElement.node().getChildNodes(childNodesLoaded.bind(this));
1463 * @param {?Array.<!WebInspector.DOMNode>} children
1464 * @this {WebInspector.ElementsTreeOutline}
1466 function childNodesLoaded(children)
1468 // FIXME: sort this out, it should not happen.
1469 if (!children)
1470 return;
1471 this._innerUpdateChildren(treeElement);
1476 * @param {!WebInspector.ElementsTreeElement} treeElement
1477 * @param {!WebInspector.DOMNode} child
1478 * @param {number} index
1479 * @param {boolean=} closingTag
1480 * @return {!WebInspector.ElementsTreeElement}
1482 insertChildElement: function(treeElement, child, index, closingTag)
1484 var newElement = this._createElementTreeElement(child, closingTag);
1485 treeElement.insertChild(newElement, index);
1486 return newElement;
1490 * @param {!WebInspector.ElementsTreeElement} treeElement
1491 * @param {!WebInspector.ElementsTreeElement} child
1492 * @param {number} targetIndex
1494 _moveChild: function(treeElement, child, targetIndex)
1496 if (treeElement.indexOfChild(child) === targetIndex)
1497 return;
1498 var wasSelected = child.selected;
1499 if (child.parent)
1500 child.parent.removeChild(child);
1501 treeElement.insertChild(child, targetIndex);
1502 if (wasSelected)
1503 child.select();
1507 * @param {!WebInspector.ElementsTreeElement} treeElement
1509 _innerUpdateChildren: function(treeElement)
1511 if (this._treeElementsBeingUpdated.has(treeElement))
1512 return;
1514 this._treeElementsBeingUpdated.add(treeElement);
1516 var node = treeElement.node();
1517 var visibleChildren = this._visibleChildren(node);
1518 var visibleChildrenSet = new Set(visibleChildren);
1520 // Remove any tree elements that no longer have this node as their parent and save
1521 // all existing elements that could be reused. This also removes closing tag element.
1522 var existingTreeElements = new Map();
1523 for (var i = treeElement.childCount() - 1; i >= 0; --i) {
1524 var existingTreeElement = treeElement.childAt(i);
1525 if (!(existingTreeElement instanceof WebInspector.ElementsTreeElement)) {
1526 // Remove expand all button and shadow host toolbar.
1527 treeElement.removeChildAtIndex(i);
1528 continue;
1530 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (existingTreeElement);
1531 var existingNode = elementsTreeElement.node();
1533 if (visibleChildrenSet.has(existingNode)) {
1534 existingTreeElements.set(existingNode, existingTreeElement);
1535 continue;
1538 treeElement.removeChildAtIndex(i);
1541 for (var i = 0; i < visibleChildren.length && i < treeElement.expandedChildrenLimit(); ++i) {
1542 var child = visibleChildren[i];
1543 var existingTreeElement = existingTreeElements.get(child) || this.findTreeElement(child);
1544 if (existingTreeElement && existingTreeElement !== treeElement) {
1545 // If an existing element was found, just move it.
1546 this._moveChild(treeElement, existingTreeElement, i);
1547 } else {
1548 // No existing element found, insert a new element.
1549 var newElement = this.insertChildElement(treeElement, child, i);
1550 if (this._updateRecordForHighlight(node) && treeElement.expanded)
1551 WebInspector.ElementsTreeElement.animateOnDOMUpdate(newElement);
1552 // If a node was inserted in the middle of existing list dynamically we might need to increase the limit.
1553 if (treeElement.childCount() > treeElement.expandedChildrenLimit())
1554 this.setExpandedChildrenLimit(treeElement, treeElement.expandedChildrenLimit() + 1);
1558 // Update expand all button.
1559 var expandedChildCount = treeElement.childCount();
1560 if (visibleChildren.length > expandedChildCount) {
1561 var targetButtonIndex = expandedChildCount;
1562 if (!treeElement.expandAllButtonElement)
1563 treeElement.expandAllButtonElement = this._createExpandAllButtonTreeElement(treeElement);
1564 treeElement.insertChild(treeElement.expandAllButtonElement, targetButtonIndex);
1565 treeElement.expandAllButtonElement.button.textContent = WebInspector.UIString("Show All Nodes (%d More)", visibleChildren.length - expandedChildCount);
1566 } else if (treeElement.expandAllButtonElement) {
1567 delete treeElement.expandAllButtonElement;
1570 // Insert shortcuts to distrubuted children.
1571 if (node.isInsertionPoint()) {
1572 for (var distributedNode of node.distributedNodes())
1573 treeElement.appendChild(new WebInspector.ElementsTreeOutline.ShortcutTreeElement(distributedNode));
1576 // Insert close tag.
1577 if (node.nodeType() === Node.ELEMENT_NODE && treeElement.isExpandable())
1578 this.insertChildElement(treeElement, node, treeElement.childCount(), true);
1580 this._treeElementsBeingUpdated.delete(treeElement);
1584 * @param {!WebInspector.Event} event
1586 _markersChanged: function(event)
1588 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1589 var treeElement = node[this._treeElementSymbol];
1590 if (treeElement)
1591 treeElement.updateDecorations();
1594 __proto__: TreeOutline.prototype
1598 * @constructor
1600 WebInspector.ElementsTreeOutline.UpdateRecord = function()
1604 WebInspector.ElementsTreeOutline.UpdateRecord.prototype = {
1606 * @param {string} attrName
1608 attributeModified: function(attrName)
1610 if (this._removedAttributes && this._removedAttributes.has(attrName))
1611 this._removedAttributes.delete(attrName);
1612 if (!this._modifiedAttributes)
1613 this._modifiedAttributes = /** @type {!Set.<string>} */ (new Set());
1614 this._modifiedAttributes.add(attrName);
1618 * @param {string} attrName
1620 attributeRemoved: function(attrName)
1622 if (this._modifiedAttributes && this._modifiedAttributes.has(attrName))
1623 this._modifiedAttributes.delete(attrName);
1624 if (!this._removedAttributes)
1625 this._removedAttributes = /** @type {!Set.<string>} */ (new Set());
1626 this._removedAttributes.add(attrName);
1630 * @param {!WebInspector.DOMNode} node
1632 nodeInserted: function(node)
1634 this._hasChangedChildren = true;
1637 nodeRemoved: function(node)
1639 this._hasChangedChildren = true;
1640 this._hasRemovedChildren = true;
1643 charDataModified: function()
1645 this._charDataModified = true;
1648 childrenModified: function()
1650 this._hasChangedChildren = true;
1654 * @param {string} attributeName
1655 * @return {boolean}
1657 isAttributeModified: function(attributeName)
1659 return this._modifiedAttributes && this._modifiedAttributes.has(attributeName);
1663 * @return {boolean}
1665 hasRemovedAttributes: function()
1667 return !!this._removedAttributes && !!this._removedAttributes.size;
1671 * @return {boolean}
1673 isCharDataModified: function()
1675 return !!this._charDataModified;
1679 * @return {boolean}
1681 hasChangedChildren: function()
1683 return !!this._hasChangedChildren;
1687 * @return {boolean}
1689 hasRemovedChildren: function()
1691 return !!this._hasRemovedChildren;
1696 * @constructor
1697 * @implements {WebInspector.Renderer}
1699 WebInspector.ElementsTreeOutline.Renderer = function()
1703 WebInspector.ElementsTreeOutline.Renderer.prototype = {
1705 * @override
1706 * @param {!Object} object
1707 * @return {!Promise.<!Element>}
1709 render: function(object)
1711 return new Promise(renderPromise);
1714 * @param {function(!Element)} resolve
1715 * @param {function(!Error)} reject
1717 function renderPromise(resolve, reject)
1719 if (object instanceof WebInspector.DOMNode) {
1720 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (object));
1721 } else if (object instanceof WebInspector.DeferredDOMNode) {
1722 (/** @type {!WebInspector.DeferredDOMNode} */ (object)).resolve(onNodeResolved);
1723 } else if (object instanceof WebInspector.RemoteObject) {
1724 var domModel = WebInspector.DOMModel.fromTarget((/** @type {!WebInspector.RemoteObject} */ (object)).target());
1725 if (domModel)
1726 domModel.pushObjectAsNodeToFrontend(object, onNodeResolved);
1727 else
1728 reject(new Error("No dom model for given JS object target found."));
1729 } else {
1730 reject(new Error("Can't reveal not a node."));
1734 * @param {?WebInspector.DOMNode} node
1736 function onNodeResolved(node)
1738 if (!node) {
1739 reject(new Error("Could not resolve node."));
1740 return;
1742 var treeOutline = new WebInspector.ElementsTreeOutline(node.domModel(), false, false);
1743 treeOutline.rootDOMNode = node;
1744 if (!treeOutline.firstChild().isExpandable())
1745 treeOutline._element.classList.add("single-node");
1746 treeOutline.setVisible(true);
1747 treeOutline.element.treeElementForTest = treeOutline.firstChild();
1748 resolve(treeOutline.element);
1755 * @constructor
1756 * @extends {TreeElement}
1757 * @param {!WebInspector.DOMNodeShortcut} nodeShortcut
1759 WebInspector.ElementsTreeOutline.ShortcutTreeElement = function(nodeShortcut)
1761 TreeElement.call(this, "");
1762 this.listItemElement.createChild("div", "selection");
1763 var title = this.listItemElement.createChild("span", "elements-tree-shortcut-title");
1764 var text = nodeShortcut.nodeName.toLowerCase();
1765 if (nodeShortcut.nodeType === Node.ELEMENT_NODE)
1766 text = "<" + text + ">";
1767 title.textContent = "\u21AA " + text;
1769 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReference(nodeShortcut.deferredNode);
1770 this.listItemElement.createTextChild(" ");
1771 link.classList.add("elements-tree-shortcut-link");
1772 link.textContent = WebInspector.UIString("reveal");
1773 this.listItemElement.appendChild(link);
1774 this._nodeShortcut = nodeShortcut;
1777 WebInspector.ElementsTreeOutline.ShortcutTreeElement.prototype = {
1779 * @return {boolean}
1781 get hovered()
1783 return this._hovered;
1786 set hovered(x)
1788 if (this._hovered === x)
1789 return;
1790 this._hovered = x;
1791 this.listItemElement.classList.toggle("hovered", x);
1794 updateSelection: function()
1799 * @return {number}
1801 backendNodeId: function()
1803 return this._nodeShortcut.deferredNode.backendNodeId();
1807 * @override
1808 * @param {boolean=} selectedByUser
1809 * @return {boolean}
1811 onselect: function(selectedByUser)
1813 if (!selectedByUser)
1814 return true;
1815 this._nodeShortcut.deferredNode.highlight();
1816 this._nodeShortcut.deferredNode.resolve(resolved.bind(this));
1818 * @param {?WebInspector.DOMNode} node
1819 * @this {WebInspector.ElementsTreeOutline.ShortcutTreeElement}
1821 function resolved(node)
1823 if (node) {
1824 this.treeOutline._selectedDOMNode = node;
1825 this.treeOutline._selectedNodeChanged();
1828 return true;
1831 __proto__: TreeElement.prototype