2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @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
;
93 WebInspector
.ElementsTreeOutline
.Events
= {
94 NodePicked
: "NodePicked",
95 SelectedNodeChanged
: "SelectedNodeChanged",
96 ElementsTreeUpdated
: "ElementsTreeUpdated"
101 * @type {!Object.<string, string>}
103 WebInspector
.ElementsTreeOutline
.MappedCharToEntity
= {
105 "\u0093": "#147", // <control>
110 "\u200a": "#8202", // Hairspace
111 "\u200b": "#8203", // ZWSP
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 = {
128 treeElementSymbol: function()
130 return this._treeElementSymbol
;
135 this._element
.focus();
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");
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
185 handlePickNode: function(element
, node
)
187 if (!this._pickNodeMode
)
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");
199 * @return {!WebInspector.DOMModel}
203 return this._domModel
;
207 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
209 setMultilineEditing: function(multilineEditing
)
211 this._multilineEditing
= multilineEditing
;
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
);
254 treeElement
.setInClipboard(false);
255 delete this._clipboardNodeData
;
259 var treeElement
= this.findTreeElement(data
.node
);
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())
287 // Do not interfere with text editing.
288 if (WebInspector
.isEditing())
291 var targetNode
= this.selectedDOMNode();
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()))
311 this._setClipboardData({ node
: node
, isCut
: isCut
});
315 * @param {!WebInspector.DOMNode} targetNode
318 canPaste: function(targetNode
)
320 if (targetNode
.isShadowRoot() || targetNode
.ancestorUserAgentShadowRoot())
323 if (!this._clipboardNodeData
)
326 var node
= this._clipboardNodeData
.node
;
327 if (this._clipboardNodeData
.isCut
&& (node
=== targetNode
|| node
.isAncestor(targetNode
)))
330 if (targetNode
.target() !== node
.target())
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())
353 var targetNode
= this.selectedDOMNode();
354 if (!targetNode
|| !this.canPaste(targetNode
))
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);
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
)
382 var pastedNode
= this._domModel
.nodeForId(nodeId
);
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();
402 this.runPendingUpdates();
403 if (this._selectedDOMNode
)
404 this._revealAndSelectNode(this._selectedDOMNode
, false);
409 return this._rootDOMNode
;
414 if (this._rootDOMNode
=== x
)
417 this._rootDOMNode
= x
;
419 this._isXMLMimeType
= x
&& x
.isXMLNode();
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
);
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();
464 var node
= this.selectedDOMNode();
467 var treeElement
= this.findTreeElement(node
);
470 return treeElement
.isEditing() || false;
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
)
487 if (this._includeRootDOMNode
) {
488 treeElement
= this._createElementTreeElement(this.rootDOMNode
);
489 this.appendChild(treeElement
);
491 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
492 var node
= this.rootDOMNode
.firstChild
;
494 treeElement
= this._createElementTreeElement(node
);
495 this.appendChild(treeElement
);
496 node
= node
.nextSibling
;
501 this._revealAndSelectNode(selectedNode
, true);
504 updateSelection: function()
506 if (!this.selectedTreeElement
)
508 var element
= this.selectedTreeElement
;
509 element
.updateSelection();
513 * @param {!WebInspector.DOMNode} node
515 updateOpenCloseTags: function(node
)
517 var treeElement
= this.findTreeElement(node
);
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
)
562 var cachedElement
= node
[this._treeElementSymbol
];
564 return cachedElement
;
566 // Walk up the parent pointers from the desired node
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
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
];
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
);
596 if (!node
.parentNode
)
599 treeElement
= this.createTreeElementFor(node
.parentNode
);
600 return treeElement
? this._showChild(treeElement
, node
) : null;
603 set suppressRevealAndSelect(x
)
605 if (this._suppressRevealAndSelect
=== x
)
607 this._suppressRevealAndSelect
= x
;
611 * @param {?WebInspector.DOMNode} node
612 * @param {boolean} omitFocus
614 _revealAndSelectNode: function(node
, omitFocus
)
616 if (this._suppressRevealAndSelect
)
619 if (!this._includeRootDOMNode
&& node
=== this.rootDOMNode
&& this.rootDOMNode
)
620 node
= this.rootDOMNode
.firstChild
;
623 var treeElement
= this.createTreeElementFor(node
);
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;
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);
651 if (elementUnderMouse
=== elementAboveMouse
)
652 element
= elementUnderMouse
;
654 element
= this.treeElementFromPoint(x
, y
+ 2);
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
)
674 * @param {!WebInspector.DOMNode} node
675 * @param {function()} callback
677 _loadDimensionsForNode: function(node
, callback
)
679 if (!node
.nodeName() || node
.nodeName().toLowerCase() !== "img") {
684 node
.resolveToObject("", resolvedNode
);
686 function resolvedNode(object
)
693 object
.callFunctionJSON(features
, undefined, callback
);
697 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number, currentSrc: (string|undefined)}}
698 * @suppressReceiverCheck
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
)
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
))
740 _onmousemove: function(event
)
742 var element
= this._treeElementFromEvent(event
);
743 if (element
&& this._previousHoveredElement
=== element
)
746 if (this._previousHoveredElement
) {
747 this._previousHoveredElement
.hovered
= false;
748 delete this._previousHoveredElement
;
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
) });
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())
779 if (event
.target
.nodeName
=== "A")
782 var treeElement
= this._treeElementFromEvent(event
);
783 if (!this._isValidDragSourceOrTarget(treeElement
))
786 if (treeElement
.node().nodeName() === "BODY" || treeElement
.node().nodeName() === "HEAD")
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();
798 _ondragover: function(event
)
800 if (!this._treeElementBeingDragged
)
803 var treeElement
= this._treeElementFromEvent(event
);
804 if (!this._isValidDragSourceOrTarget(treeElement
))
807 var node
= treeElement
.node();
809 if (node
=== this._treeElementBeingDragged
._node
)
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';
822 _ondragleave: function(event
)
824 this._clearDragOverTreeElementMarker();
825 event
.preventDefault();
830 * @param {?TreeElement} treeElement
833 _isValidDragSourceOrTarget: function(treeElement
)
838 if (!(treeElement
instanceof WebInspector
.ElementsTreeElement
))
840 var elementsTreeElement
= /** @type {!WebInspector.ElementsTreeElement} */ (treeElement
);
842 var node
= elementsTreeElement
.node();
843 if (!node
.parentNode
|| node
.parentNode
.nodeType() !== Node
.ELEMENT_NODE
)
849 _ondrop: function(event
)
851 event
.preventDefault();
852 var treeElement
= this._treeElementFromEvent(event
);
854 this._doMove(treeElement
);
858 * @param {!TreeElement} treeElement
860 _doMove: function(treeElement
)
862 if (!this._treeElementBeingDragged
)
868 if (treeElement
.isClosingTag()) {
869 // Drop onto closing tag -> insert as last child.
870 parentNode
= treeElement
.node();
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())
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"))
911 var commentNode
= event
.target
.enclosingNodeOrSelfWithClass("webkit-html-comment");
912 contextMenu
.appendApplicableItems(event
.target
);
914 contextMenu
.appendSeparator();
915 treeElement
.populateTextContextMenu(contextMenu
, textNode
);
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());
930 runPendingUpdates: function()
932 this._updateModifiedNodes();
935 handleShortcut: function(event
)
937 var node
= this.selectedDOMNode();
940 var treeElement
= node
[this._treeElementSymbol
];
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;
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;
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())
969 if (node
.pseudoType())
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
)
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;
996 this.selectDOMNode(newNode
, true);
999 var newTreeItem
= this.findTreeElement(newNode
);
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
)
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;
1024 this.selectDOMNode(newNode
, true);
1026 var newTreeItem
= this.findTreeElement(newNode
);
1029 newTreeItem
.expand();
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
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
;
1050 function resolvedNode(object
)
1056 * @param {?string} pseudoType
1057 * @suppressGlobalPropertiesCheck
1058 * @suppressReceiverCheck
1061 function toggleClassAndInjectStyleRule(pseudoType
)
1063 const classNamePrefix
= "__web-inspector-hide";
1064 const classNameSuffix
= "-shortcut__";
1065 const styleTagId
= "__web-inspector-hide-shortcut-style__";
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
);
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
);
1099 effectiveNode
.resolveToObject("", resolvedNode
);
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
);
1144 record
= new WebInspector
.ElementsTreeOutline
.UpdateRecord();
1145 this._updateRecords
.set(node
, record
);
1151 * @param {!WebInspector.DOMNode} node
1152 * @return {?WebInspector.ElementsTreeOutline.UpdateRecord}
1154 _updateRecordForHighlight: function(node
)
1156 if (!WebInspector
.moduleSetting("highlightDOMUpdates").get() || !this._visible
)
1158 return this._updateRecords
.get(node
) || null;
1162 * @param {!WebInspector.Event} event
1164 _documentUpdated: function(event
)
1166 var inspectedRootDocument
= event
.data
;
1170 if (!inspectedRootDocument
)
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
)
1252 if (this._updateModifiedNodesTimeout
)
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.
1276 for (var node
of this._updateRecords
.keys()) {
1277 if (this._updateRecords
.get(node
).hasChangedChildren())
1278 this._updateModifiedParentNode(node
);
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
);
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())
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
;
1338 * @param {!WebInspector.ElementsTreeElement} treeElement
1339 * @param {!WebInspector.DOMNode} child
1340 * @return {?WebInspector.ElementsTreeElement}
1342 _showChild: function(treeElement
, child
)
1344 if (treeElement
.isClosingTag())
1347 var index
= this._visibleChildren(treeElement
.node()).indexOf(child
);
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
1388 _hasVisibleChildren: function(node
)
1390 if (WebInspector
.ElementsTreeElement
.canShowInlineText(node
))
1393 if (node
.importedDocument())
1395 if (node
.templateContent())
1397 if (node
.childNodeCount())
1399 if (WebInspector
.ElementsTreeElement
.visibleShadowRoots(node
).length
)
1401 if (node
.hasPseudoElements())
1403 if (node
.isInsertionPoint())
1409 * @param {!WebInspector.ElementsTreeElement} treeElement
1411 _createExpandAllButtonTreeElement: function(treeElement
)
1413 var button
= createTextButton("", handleLoadAllChildren
.bind(this));
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
));
1434 * @param {!WebInspector.ElementsTreeElement} treeElement
1435 * @param {number} expandedChildrenLimit
1437 setExpandedChildrenLimit: function(treeElement
, expandedChildrenLimit
)
1439 if (treeElement
.expandedChildrenLimit() === expandedChildrenLimit
)
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();
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.
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
);
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
)
1500 var wasSelected
= child
.selected
;
1502 child
.parent
.removeChild(child
);
1503 treeElement
.insertChild(child
, targetIndex
);
1509 * @param {!WebInspector.ElementsTreeElement} treeElement
1511 _innerUpdateChildren: function(treeElement
)
1513 if (this._treeElementsBeingUpdated
.has(treeElement
))
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
);
1532 var elementsTreeElement
= /** @type {!WebInspector.ElementsTreeElement} */ (existingTreeElement
);
1533 var existingNode
= elementsTreeElement
.node();
1535 if (visibleChildrenSet
.has(existingNode
)) {
1536 existingTreeElements
.set(existingNode
, existingTreeElement
);
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
);
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
1591 WebInspector
.ElementsTreeOutline
.ElementDecorator = function()
1595 WebInspector
.ElementsTreeOutline
.ElementDecorator
.prototype = {
1597 * @param {!WebInspector.DOMNode} node
1600 decorate: function(node
)
1605 * @param {!WebInspector.DOMNode} node
1608 decorateAncestor: function(node
)
1615 * @implements {WebInspector.ElementsTreeOutline.ElementDecorator}
1617 WebInspector
.ElementsTreeOutline
.PseudoStateDecorator = function()
1619 WebInspector
.ElementsTreeOutline
.ElementDecorator
.call(this);
1622 WebInspector
.ElementsTreeOutline
.PseudoStateDecorator
.prototype = {
1625 * @param {!WebInspector.DOMNode} node
1628 decorate: function(node
)
1630 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
1632 var propertyValue
= node
.getUserProperty(WebInspector
.CSSStyleModel
.PseudoStatePropertyName
);
1635 return WebInspector
.UIString("Element state: %s", ":" + propertyValue
.join(", :"));
1640 * @param {!WebInspector.DOMNode} node
1643 decorateAncestor: function(node
)
1645 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
1648 var descendantCount
= node
.descendantUserPropertyCount(WebInspector
.CSSStyleModel
.PseudoStatePropertyName
);
1649 if (!descendantCount
)
1651 if (descendantCount
=== 1)
1652 return WebInspector
.UIString("%d descendant with forced state", descendantCount
);
1653 return WebInspector
.UIString("%d descendants with forced state", descendantCount
);
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
1717 isAttributeModified: function(attributeName
)
1719 return this._modifiedAttributes
&& this._modifiedAttributes
.has(attributeName
);
1725 hasRemovedAttributes: function()
1727 return !!this._removedAttributes
&& !!this._removedAttributes
.size
;
1733 isCharDataModified: function()
1735 return !!this._charDataModified
;
1741 hasChangedChildren: function()
1743 return !!this._hasChangedChildren
;
1749 hasRemovedChildren: function()
1751 return !!this._hasRemovedChildren
;
1757 * @implements {WebInspector.Renderer}
1759 WebInspector
.ElementsTreeOutline
.Renderer = function()
1763 WebInspector
.ElementsTreeOutline
.Renderer
.prototype = {
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());
1786 domModel
.pushObjectAsNodeToFrontend(object
, onNodeResolved
);
1788 reject(new Error("No dom model for given JS object target found."));
1790 reject(new Error("Can't reveal not a node."));
1794 * @param {?WebInspector.DOMNode} node
1796 function onNodeResolved(node
)
1799 reject(new Error("Could not resolve node."));
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
);
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 = {
1843 return this._hovered
;
1848 if (this._hovered
=== x
)
1851 this.listItemElement
.classList
.toggle("hovered", x
);
1854 updateSelection: function()
1861 backendNodeId: function()
1863 return this._nodeShortcut
.deferredNode
.backendNodeId();
1868 * @param {boolean=} selectedByUser
1871 onselect: function(selectedByUser
)
1873 if (!selectedByUser
)
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
)
1884 this.treeOutline
._selectedDOMNode
= node
;
1885 this.treeOutline
._selectedNodeChanged();
1891 __proto__
: TreeElement
.prototype