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._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
;
93 WebInspector
.ElementsTreeOutline
.Events
= {
94 NodePicked
: "NodePicked",
95 SelectedNodeChanged
: "SelectedNodeChanged",
96 ElementsTreeUpdated
: "ElementsTreeUpdated",
97 DecorationsClicked
: "DecorationsClicked"
102 * @type {!Object.<string, string>}
104 WebInspector
.ElementsTreeOutline
.MappedCharToEntity
= {
106 "\u0093": "#147", // <control>
111 "\u200a": "#8202", // Hairspace
112 "\u200b": "#8203", // ZWSP
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 = {
129 treeElementSymbol: function()
131 return this._treeElementSymbol
;
136 this._element
.focus();
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");
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
186 handlePickNode: function(element
, node
)
188 if (!this._pickNodeMode
)
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");
200 * @return {!WebInspector.DOMModel}
204 return this._domModel
;
208 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
210 setMultilineEditing: function(multilineEditing
)
212 this._multilineEditing
= multilineEditing
;
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
);
241 treeElement
.setInClipboard(false);
242 delete this._clipboardNodeData
;
246 var treeElement
= this.findTreeElement(data
.node
);
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())
274 // Do not interfere with text editing.
275 if (WebInspector
.isEditing())
278 var targetNode
= this.selectedDOMNode();
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()))
298 this._setClipboardData({ node
: node
, isCut
: isCut
});
302 * @param {!WebInspector.DOMNode} targetNode
305 canPaste: function(targetNode
)
307 if (targetNode
.isShadowRoot() || targetNode
.ancestorUserAgentShadowRoot())
310 if (!this._clipboardNodeData
)
313 var node
= this._clipboardNodeData
.node
;
314 if (this._clipboardNodeData
.isCut
&& (node
=== targetNode
|| node
.isAncestor(targetNode
)))
317 if (targetNode
.target() !== node
.target())
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())
340 var targetNode
= this.selectedDOMNode();
341 if (!targetNode
|| !this.canPaste(targetNode
))
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);
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
)
369 var pastedNode
= this._domModel
.nodeForId(nodeId
);
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();
389 this.runPendingUpdates();
390 if (this._selectedDOMNode
)
391 this._revealAndSelectNode(this._selectedDOMNode
, false);
396 return this._rootDOMNode
;
401 if (this._rootDOMNode
=== x
)
404 this._rootDOMNode
= x
;
406 this._isXMLMimeType
= x
&& x
.isXMLNode();
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
);
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();
451 var node
= this.selectedDOMNode();
454 var treeElement
= this.findTreeElement(node
);
457 return treeElement
.isEditing() || false;
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
)
474 if (this._includeRootDOMNode
) {
475 treeElement
= this._createElementTreeElement(this.rootDOMNode
);
476 this.appendChild(treeElement
);
478 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
479 var node
= this.rootDOMNode
.firstChild
;
481 treeElement
= this._createElementTreeElement(node
);
482 this.appendChild(treeElement
);
483 node
= node
.nextSibling
;
488 this._revealAndSelectNode(selectedNode
, true);
491 updateSelection: function()
493 if (!this.selectedTreeElement
)
495 var element
= this.selectedTreeElement
;
496 element
.updateSelection();
500 * @param {!WebInspector.DOMNode} node
502 updateOpenCloseTags: function(node
)
504 var treeElement
= this.findTreeElement(node
);
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
)
549 var cachedElement
= node
[this._treeElementSymbol
];
551 return cachedElement
;
553 // Walk up the parent pointers from the desired node
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
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
];
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
);
583 if (!node
.parentNode
)
586 treeElement
= this.createTreeElementFor(node
.parentNode
);
587 return treeElement
? this._showChild(treeElement
, node
) : null;
590 set suppressRevealAndSelect(x
)
592 if (this._suppressRevealAndSelect
=== x
)
594 this._suppressRevealAndSelect
= x
;
598 * @param {?WebInspector.DOMNode} node
599 * @param {boolean} omitFocus
601 _revealAndSelectNode: function(node
, omitFocus
)
603 if (this._suppressRevealAndSelect
)
606 if (!this._includeRootDOMNode
&& node
=== this.rootDOMNode
&& this.rootDOMNode
)
607 node
= this.rootDOMNode
.firstChild
;
610 var treeElement
= this.createTreeElementFor(node
);
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;
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);
638 if (elementUnderMouse
=== elementAboveMouse
)
639 element
= elementUnderMouse
;
641 element
= this.treeElementFromPoint(x
, y
+ 2);
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
)
661 * @param {!WebInspector.DOMNode} node
662 * @param {function()} callback
664 _loadDimensionsForNode: function(node
, callback
)
666 if (!node
.nodeName() || node
.nodeName().toLowerCase() !== "img") {
671 node
.resolveToObject("", resolvedNode
);
673 function resolvedNode(object
)
680 object
.callFunctionJSON(features
, undefined, callback
);
684 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number, currentSrc: (string|undefined)}}
685 * @suppressReceiverCheck
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
)
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
))
727 _onmousemove: function(event
)
729 var element
= this._treeElementFromEvent(event
);
730 if (element
&& this._previousHoveredElement
=== element
)
733 if (this._previousHoveredElement
) {
734 this._previousHoveredElement
.hovered
= false;
735 delete this._previousHoveredElement
;
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
) });
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())
766 if (event
.target
.nodeName
=== "A")
769 var treeElement
= this._treeElementFromEvent(event
);
770 if (!this._isValidDragSourceOrTarget(treeElement
))
773 if (treeElement
.node().nodeName() === "BODY" || treeElement
.node().nodeName() === "HEAD")
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();
785 _ondragover: function(event
)
787 if (!this._treeElementBeingDragged
)
790 var treeElement
= this._treeElementFromEvent(event
);
791 if (!this._isValidDragSourceOrTarget(treeElement
))
794 var node
= treeElement
.node();
796 if (node
=== this._treeElementBeingDragged
._node
)
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';
809 _ondragleave: function(event
)
811 this._clearDragOverTreeElementMarker();
812 event
.preventDefault();
817 * @param {?TreeElement} treeElement
820 _isValidDragSourceOrTarget: function(treeElement
)
825 if (!(treeElement
instanceof WebInspector
.ElementsTreeElement
))
827 var elementsTreeElement
= /** @type {!WebInspector.ElementsTreeElement} */ (treeElement
);
829 var node
= elementsTreeElement
.node();
830 if (!node
.parentNode
|| node
.parentNode
.nodeType() !== Node
.ELEMENT_NODE
)
836 _ondrop: function(event
)
838 event
.preventDefault();
839 var treeElement
= this._treeElementFromEvent(event
);
841 this._doMove(treeElement
);
845 * @param {!TreeElement} treeElement
847 _doMove: function(treeElement
)
849 if (!this._treeElementBeingDragged
)
855 if (treeElement
.isClosingTag()) {
856 // Drop onto closing tag -> insert as last child.
857 parentNode
= treeElement
.node();
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())
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"))
898 var commentNode
= event
.target
.enclosingNodeOrSelfWithClass("webkit-html-comment");
899 contextMenu
.appendApplicableItems(event
.target
);
901 contextMenu
.appendSeparator();
902 treeElement
.populateTextContextMenu(contextMenu
, textNode
);
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());
917 runPendingUpdates: function()
919 this._updateModifiedNodes();
922 handleShortcut: function(event
)
924 var node
= this.selectedDOMNode();
927 var treeElement
= node
[this._treeElementSymbol
];
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;
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;
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())
956 if (node
.pseudoType())
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
)
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;
983 this.selectDOMNode(newNode
, true);
986 var newTreeItem
= this.findTreeElement(newNode
);
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
)
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;
1011 this.selectDOMNode(newNode
, true);
1013 var newTreeItem
= this.findTreeElement(newNode
);
1016 newTreeItem
.expand();
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
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
;
1037 var hidden
= node
.marker("hidden-marker");
1039 function resolvedNode(object
)
1045 * @param {?string} pseudoType
1046 * @param {boolean} hidden
1047 * @suppressGlobalPropertiesCheck
1048 * @suppressReceiverCheck
1051 function toggleClassAndInjectStyleRule(pseudoType
, hidden
)
1053 const classNamePrefix
= "__web-inspector-hide";
1054 const classNameSuffix
= "-shortcut__";
1055 const styleTagId
= "__web-inspector-hide-shortcut-style__";
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
);
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
);
1087 node
.setMarker("hidden-marker", hidden
? null : true);
1090 effectiveNode
.resolveToObject("", resolvedNode
);
1094 * @param {!WebInspector.DOMNode} node
1097 isToggledToHidden: function(node
)
1099 return !!node
.marker("hidden-marker");
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
)
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 // 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
)
1255 if (this._updateModifiedNodesTimeout
)
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.
1279 for (var node
of this._updateRecords
.keys()) {
1280 if (this._updateRecords
.get(node
).hasChangedChildren())
1281 this._updateModifiedParentNode(node
);
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
);
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())
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
;
1341 * @param {!WebInspector.ElementsTreeElement} treeElement
1342 * @param {!WebInspector.DOMNode} child
1343 * @return {?WebInspector.ElementsTreeElement}
1345 _showChild: function(treeElement
, child
)
1347 if (treeElement
.isClosingTag())
1350 var index
= this._visibleChildren(treeElement
.node()).indexOf(child
);
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
1391 _hasVisibleChildren: function(node
)
1393 if (node
.importedDocument())
1395 if (node
.templateContent())
1397 if (WebInspector
.ElementsTreeElement
.visibleShadowRoots(node
).length
)
1399 if (node
.hasPseudoElements())
1401 if (node
.isInsertionPoint())
1403 return !!node
.childNodeCount() && !WebInspector
.ElementsTreeElement
.canShowInlineText(node
);
1407 * @param {!WebInspector.ElementsTreeElement} treeElement
1409 _createExpandAllButtonTreeElement: function(treeElement
)
1411 var button
= createTextButton("", handleLoadAllChildren
.bind(this));
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
));
1432 * @param {!WebInspector.ElementsTreeElement} treeElement
1433 * @param {number} expandedChildrenLimit
1435 setExpandedChildrenLimit: function(treeElement
, expandedChildrenLimit
)
1437 if (treeElement
.expandedChildrenLimit() === expandedChildrenLimit
)
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();
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.
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
);
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
)
1498 var wasSelected
= child
.selected
;
1500 child
.parent
.removeChild(child
);
1501 treeElement
.insertChild(child
, targetIndex
);
1507 * @param {!WebInspector.ElementsTreeElement} treeElement
1509 _innerUpdateChildren: function(treeElement
)
1511 if (this._treeElementsBeingUpdated
.has(treeElement
))
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
);
1530 var elementsTreeElement
= /** @type {!WebInspector.ElementsTreeElement} */ (existingTreeElement
);
1531 var existingNode
= elementsTreeElement
.node();
1533 if (visibleChildrenSet
.has(existingNode
)) {
1534 existingTreeElements
.set(existingNode
, existingTreeElement
);
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
);
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
];
1591 treeElement
.updateDecorations();
1594 __proto__
: TreeOutline
.prototype
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
1657 isAttributeModified: function(attributeName
)
1659 return this._modifiedAttributes
&& this._modifiedAttributes
.has(attributeName
);
1665 hasRemovedAttributes: function()
1667 return !!this._removedAttributes
&& !!this._removedAttributes
.size
;
1673 isCharDataModified: function()
1675 return !!this._charDataModified
;
1681 hasChangedChildren: function()
1683 return !!this._hasChangedChildren
;
1689 hasRemovedChildren: function()
1691 return !!this._hasRemovedChildren
;
1697 * @implements {WebInspector.Renderer}
1699 WebInspector
.ElementsTreeOutline
.Renderer = function()
1703 WebInspector
.ElementsTreeOutline
.Renderer
.prototype = {
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());
1726 domModel
.pushObjectAsNodeToFrontend(object
, onNodeResolved
);
1728 reject(new Error("No dom model for given JS object target found."));
1730 reject(new Error("Can't reveal not a node."));
1734 * @param {?WebInspector.DOMNode} node
1736 function onNodeResolved(node
)
1739 reject(new Error("Could not resolve node."));
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
);
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 = {
1783 return this._hovered
;
1788 if (this._hovered
=== x
)
1791 this.listItemElement
.classList
.toggle("hovered", x
);
1794 updateSelection: function()
1801 backendNodeId: function()
1803 return this._nodeShortcut
.deferredNode
.backendNodeId();
1808 * @param {boolean=} selectedByUser
1811 onselect: function(selectedByUser
)
1813 if (!selectedByUser
)
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
)
1824 this.treeOutline
._selectedDOMNode
= node
;
1825 this.treeOutline
._selectedNodeChanged();
1831 __proto__
: TreeElement
.prototype