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 {boolean=} omitRootDOMNode
35 * @param {boolean=} selectEnabled
36 * @param {function(!WebInspector.ContextMenu, !WebInspector.DOMNode)=} contextMenuCallback
37 * @param {function(!DOMAgent.NodeId, string, boolean)=} setPseudoClassCallback
39 WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, contextMenuCallback, setPseudoClassCallback)
41 this.element = document.createElement("ol");
42 this.element.className = "elements-tree-outline";
43 this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
44 this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
45 this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
46 this.element.addEventListener("dragstart", this._ondragstart.bind(this), false);
47 this.element.addEventListener("dragover", this._ondragover.bind(this), false);
48 this.element.addEventListener("dragleave", this._ondragleave.bind(this), false);
49 this.element.addEventListener("drop", this._ondrop.bind(this), false);
50 this.element.addEventListener("dragend", this._ondragend.bind(this), false);
51 this.element.addEventListener("keydown", this._onkeydown.bind(this), false);
53 TreeOutline.call(this, this.element);
55 this._includeRootDOMNode = !omitRootDOMNode;
56 this._selectEnabled = selectEnabled;
57 /** @type {?WebInspector.DOMNode} */
58 this._rootDOMNode = null;
59 /** @type {?WebInspector.DOMNode} */
60 this._selectedDOMNode = null;
61 this._eventSupport = new WebInspector.Object();
63 this._visible = false;
65 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
66 this._contextMenuCallback = contextMenuCallback;
67 this._setPseudoClassCallback = setPseudoClassCallback;
68 this._createNodeDecorators();
74 WebInspector.ElementsTreeOutline.Events = {
75 SelectedNodeChanged: "SelectedNodeChanged",
76 ElementsTreeUpdated: "ElementsTreeUpdated"
81 * @type {!Object.<string, string>}
83 WebInspector.ElementsTreeOutline.MappedCharToEntity = {
88 "\u200a": "#8202", // Hairspace
89 "\u200b": "#8203", // ZWSP
94 "\u202a": "#8234", // LRE
95 "\u202b": "#8235", // RLE
96 "\u202c": "#8236", // PDF
97 "\u202d": "#8237", // LRO
98 "\u202e": "#8238" // RLO
101 WebInspector.ElementsTreeOutline.prototype = {
103 * @param {number} width
105 setVisibleWidth: function(width)
107 this._visibleWidth = width;
108 if (this._multilineEditing)
109 this._multilineEditing.setWidth(this._visibleWidth);
112 _createNodeDecorators: function()
114 this._nodeDecorators = [];
115 this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator());
118 wireToDomAgent: function()
120 this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this);
124 * @param {boolean} visible
126 setVisible: function(visible)
128 this._visible = visible;
132 this._updateModifiedNodes();
133 if (this._selectedDOMNode)
134 this._revealAndSelectNode(this._selectedDOMNode, false);
137 addEventListener: function(eventType, listener, thisObject)
139 this._eventSupport.addEventListener(eventType, listener, thisObject);
142 removeEventListener: function(eventType, listener, thisObject)
144 this._eventSupport.removeEventListener(eventType, listener, thisObject);
149 return this._rootDOMNode;
154 if (this._rootDOMNode === x)
157 this._rootDOMNode = x;
159 this._isXMLMimeType = x && x.isXMLNode();
166 return this._isXMLMimeType;
170 * @return {?WebInspector.DOMNode}
172 selectedDOMNode: function()
174 return this._selectedDOMNode;
178 * @param {?WebInspector.DOMNode} node
179 * @param {boolean=} focus
181 selectDOMNode: function(node, focus)
183 if (this._selectedDOMNode === node) {
184 this._revealAndSelectNode(node, !focus);
188 this._selectedDOMNode = node;
189 this._revealAndSelectNode(node, !focus);
191 // The _revealAndSelectNode() method might find a different element if there is inlined text,
192 // and the select() call would change the selectedDOMNode and reenter this setter. So to
193 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same
194 // node as the one passed in.
195 if (this._selectedDOMNode === node)
196 this._selectedNodeChanged();
204 var node = this.selectedDOMNode();
207 var treeElement = this.findTreeElement(node);
210 return treeElement._editing || false;
215 var selectedNode = this.selectedTreeElement ? this.selectedTreeElement._node : null;
217 this.removeChildren();
219 if (!this.rootDOMNode)
223 if (this._includeRootDOMNode) {
224 treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
225 treeElement.selectable = this._selectEnabled;
226 this.appendChild(treeElement);
228 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
229 var node = this.rootDOMNode.firstChild;
231 treeElement = new WebInspector.ElementsTreeElement(node);
232 treeElement.selectable = this._selectEnabled;
233 this.appendChild(treeElement);
234 node = node.nextSibling;
239 this._revealAndSelectNode(selectedNode, true);
242 updateSelection: function()
244 if (!this.selectedTreeElement)
246 var element = this.treeOutline.selectedTreeElement;
247 element.updateSelection();
251 * @param {!WebInspector.DOMNode} node
253 updateOpenCloseTags: function(node)
255 var treeElement = this.findTreeElement(node);
257 treeElement.updateTitle();
258 var children = treeElement.children;
259 var closingTagElement = children[children.length - 1];
260 if (closingTagElement && closingTagElement._elementCloseTag)
261 closingTagElement.updateTitle();
264 _selectedNodeChanged: function()
266 this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode);
270 * @param {!Array.<!WebInspector.DOMNode>} nodes
272 _fireElementsTreeUpdated: function(nodes)
274 this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, nodes);
278 * @param {!WebInspector.DOMNode} node
279 * @return {?TreeElement}
281 findTreeElement: function(node)
283 function isAncestorNode(ancestor, node)
285 return ancestor.isAncestor(node);
288 function parentNode(node)
290 return node.parentNode;
293 var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
294 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
295 // The text node might have been inlined if it was short, so try to find the parent element.
296 treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
303 * @param {!WebInspector.DOMNode} node
304 * @return {?TreeElement}
306 createTreeElementFor: function(node)
308 var treeElement = this.findTreeElement(node);
311 if (!node.parentNode)
314 treeElement = this.createTreeElementFor(node.parentNode);
315 return treeElement ? treeElement._showChild(node) : null;
318 set suppressRevealAndSelect(x)
320 if (this._suppressRevealAndSelect === x)
322 this._suppressRevealAndSelect = x;
326 * @param {?WebInspector.DOMNode} node
327 * @param {boolean} omitFocus
329 _revealAndSelectNode: function(node, omitFocus)
331 if (this._suppressRevealAndSelect)
334 if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootDOMNode)
335 node = this.rootDOMNode.firstChild;
338 var treeElement = this.createTreeElementFor(node);
342 treeElement.revealAndSelect(omitFocus);
346 * @return {?TreeElement}
348 _treeElementFromEvent: function(event)
350 var scrollContainer = this.element.parentElement;
352 // We choose this X coordinate based on the knowledge that our list
353 // items extend at least to the right edge of the outer <ol> container.
354 // In the no-word-wrap mode the outer <ol> may be wider than the tree container
355 // (and partially hidden), in which case we are left to use only its right boundary.
356 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36;
360 // Our list items have 1-pixel cracks between them vertically. We avoid
361 // the cracks by checking slightly above and slightly below the mouse
362 // and seeing if we hit the same element each time.
363 var elementUnderMouse = this.treeElementFromPoint(x, y);
364 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
366 if (elementUnderMouse === elementAboveMouse)
367 element = elementUnderMouse;
369 element = this.treeElementFromPoint(x, y + 2);
374 _onmousedown: function(event)
376 var element = this._treeElementFromEvent(event);
378 if (!element || element.isEventWithinDisclosureTriangle(event))
384 _onmousemove: function(event)
386 var element = this._treeElementFromEvent(event);
387 if (element && this._previousHoveredElement === element)
390 if (this._previousHoveredElement) {
391 this._previousHoveredElement.hovered = false;
392 delete this._previousHoveredElement;
396 element.hovered = true;
397 this._previousHoveredElement = element;
400 WebInspector.domModel.highlightDOMNode(element && element._node ? element._node.id : 0);
403 _onmouseout: function(event)
405 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
406 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
409 if (this._previousHoveredElement) {
410 this._previousHoveredElement.hovered = false;
411 delete this._previousHoveredElement;
414 WebInspector.domModel.hideDOMNodeHighlight();
417 _ondragstart: function(event)
419 if (!window.getSelection().isCollapsed)
421 if (event.target.nodeName === "A")
424 var treeElement = this._treeElementFromEvent(event);
428 if (!this._isValidDragSourceOrTarget(treeElement))
431 if (treeElement._node.nodeName() === "BODY" || treeElement._node.nodeName() === "HEAD")
434 event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent);
435 event.dataTransfer.effectAllowed = "copyMove";
436 this._treeElementBeingDragged = treeElement;
438 WebInspector.domModel.hideDOMNodeHighlight();
443 _ondragover: function(event)
445 if (!this._treeElementBeingDragged)
448 var treeElement = this._treeElementFromEvent(event);
449 if (!this._isValidDragSourceOrTarget(treeElement))
452 var node = treeElement._node;
454 if (node === this._treeElementBeingDragged._node)
456 node = node.parentNode;
459 treeElement.updateSelection();
460 treeElement.listItemElement.classList.add("elements-drag-over");
461 this._dragOverTreeElement = treeElement;
462 event.preventDefault();
463 event.dataTransfer.dropEffect = 'move';
467 _ondragleave: function(event)
469 this._clearDragOverTreeElementMarker();
470 event.preventDefault();
475 * @param {?TreeElement} treeElement
478 _isValidDragSourceOrTarget: function(treeElement)
483 var node = treeElement.representedObject;
484 if (!(node instanceof WebInspector.DOMNode))
487 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
493 _ondrop: function(event)
495 event.preventDefault();
496 var treeElement = this._treeElementFromEvent(event);
498 this._doMove(treeElement);
502 * @param {!TreeElement} treeElement
504 _doMove: function(treeElement)
506 if (!this._treeElementBeingDragged)
512 if (treeElement._elementCloseTag) {
513 // Drop onto closing tag -> insert as last child.
514 parentNode = treeElement._node;
516 var dragTargetNode = treeElement._node;
517 parentNode = dragTargetNode.parentNode;
518 anchorNode = dragTargetNode;
521 var wasExpanded = this._treeElementBeingDragged.expanded;
522 this._treeElementBeingDragged._node.moveTo(parentNode, anchorNode, this._selectNodeAfterEdit.bind(this, wasExpanded));
524 delete this._treeElementBeingDragged;
527 _ondragend: function(event)
529 event.preventDefault();
530 this._clearDragOverTreeElementMarker();
531 delete this._treeElementBeingDragged;
534 _clearDragOverTreeElementMarker: function()
536 if (this._dragOverTreeElement) {
537 this._dragOverTreeElement.updateSelection();
538 this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over");
539 delete this._dragOverTreeElement;
544 * @param {?Event} event
546 _onkeydown: function(event)
548 var keyboardEvent = /** @type {!KeyboardEvent} */ (event);
549 var node = /** @type {!WebInspector.DOMNode} */ (this.selectedDOMNode());
550 console.assert(node);
551 var treeElement = this.getCachedTreeElement(node);
555 if (!treeElement._editing && WebInspector.KeyboardShortcut.hasNoModifiers(keyboardEvent) && keyboardEvent.keyCode === WebInspector.KeyboardShortcut.Keys.H.code) {
556 this._toggleHideShortcut(node);
562 _contextMenuEventFired: function(event)
564 var treeElement = this._treeElementFromEvent(event);
568 var contextMenu = new WebInspector.ContextMenu(event);
569 contextMenu.appendApplicableItems(treeElement._node);
573 populateContextMenu: function(contextMenu, event)
575 var treeElement = this._treeElementFromEvent(event);
579 var isPseudoElement = !!treeElement._node.pseudoType();
580 var isTag = treeElement._node.nodeType() === Node.ELEMENT_NODE && !isPseudoElement;
581 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
582 if (textNode && textNode.classList.contains("bogus"))
584 var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment");
585 contextMenu.appendApplicableItems(event.target);
587 contextMenu.appendSeparator();
588 treeElement._populateTextContextMenu(contextMenu, textNode);
590 contextMenu.appendSeparator();
591 treeElement._populateTagContextMenu(contextMenu, event);
592 } else if (commentNode) {
593 contextMenu.appendSeparator();
594 treeElement._populateNodeContextMenu(contextMenu, textNode);
595 } else if (isPseudoElement) {
596 treeElement._populateScrollIntoView(contextMenu);
597 } else if (treeElement._node.isShadowRoot()) {
598 this.treeOutline._populateContextMenu(contextMenu, treeElement._node);
602 _updateModifiedNodes: function()
604 if (this._elementsTreeUpdater)
605 this._elementsTreeUpdater._updateModifiedNodes();
608 _populateContextMenu: function(contextMenu, node)
610 if (this._contextMenuCallback)
611 this._contextMenuCallback(contextMenu, node);
614 handleShortcut: function(event)
616 var node = this.selectedDOMNode();
617 var treeElement = this.getCachedTreeElement(node);
618 if (!node || !treeElement)
621 if (event.keyIdentifier === "F2" && treeElement.hasEditableNode()) {
622 this._toggleEditAsHTML(node);
623 event.handled = true;
627 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.parentNode) {
628 if (event.keyIdentifier === "Up" && node.previousSibling) {
629 node.moveTo(node.parentNode, node.previousSibling, this._selectNodeAfterEdit.bind(this, treeElement.expanded));
630 event.handled = true;
633 if (event.keyIdentifier === "Down" && node.nextSibling) {
634 node.moveTo(node.parentNode, node.nextSibling.nextSibling, this._selectNodeAfterEdit.bind(this, treeElement.expanded));
635 event.handled = true;
642 * @param {!WebInspector.DOMNode} node
644 _toggleEditAsHTML: function(node)
646 var treeElement = this.getCachedTreeElement(node);
650 if (treeElement._editing && treeElement._htmlEditElement && WebInspector.isBeingEdited(treeElement._htmlEditElement))
651 treeElement._editing.commit();
653 treeElement._editAsHTML();
657 * @param {boolean} wasExpanded
658 * @param {?Protocol.Error} error
659 * @param {!DOMAgent.NodeId=} nodeId
661 _selectNodeAfterEdit: function(wasExpanded, error, nodeId)
666 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
667 this._updateModifiedNodes();
669 var newNode = nodeId ? WebInspector.domModel.nodeForId(nodeId) : null;
673 this.selectDOMNode(newNode, true);
675 var newTreeItem = this.findTreeElement(newNode);
678 newTreeItem.expand();
684 * Runs a script on the node's remote object that toggles a class name on
685 * the node and injects a stylesheet into the head of the node's document
686 * containing a rule to set "visibility: hidden" on the class and all it's
689 * @param {!WebInspector.DOMNode} node
690 * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback
692 _toggleHideShortcut: function(node, userCallback)
694 var pseudoType = node.pseudoType();
695 var effectiveNode = pseudoType ? node.parentNode : node;
699 function resolvedNode(object)
705 * @param {?string} pseudoType
706 * @suppressReceiverCheck
709 function toggleClassAndInjectStyleRule(pseudoType)
711 const classNamePrefix = "__web-inspector-hide";
712 const classNameSuffix = "-shortcut__";
713 const styleTagId = "__web-inspector-hide-shortcut-style__";
714 const styleRules = ".__web-inspector-hide-shortcut__, .__web-inspector-hide-shortcut__ * { visibility: hidden !important; } .__web-inspector-hidebefore-shortcut__::before { visibility: hidden !important; } .__web-inspector-hideafter-shortcut__::after { visibility: hidden !important; }";
716 var className = classNamePrefix + (pseudoType || "") + classNameSuffix;
717 this.classList.toggle(className);
719 var style = document.head.querySelector("style#" + styleTagId);
723 style = document.createElement("style");
724 style.id = styleTagId;
725 style.type = "text/css";
726 style.textContent = styleRules;
727 document.head.appendChild(style);
730 object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoType }], userCallback);
734 WebInspector.RemoteObject.resolveNode(effectiveNode, "", resolvedNode);
737 __proto__: TreeOutline.prototype
743 WebInspector.ElementsTreeOutline.ElementDecorator = function()
747 WebInspector.ElementsTreeOutline.ElementDecorator.prototype = {
749 * @param {!WebInspector.DOMNode} node
752 decorate: function(node)
757 * @param {!WebInspector.DOMNode} node
760 decorateAncestor: function(node)
767 * @implements {WebInspector.ElementsTreeOutline.ElementDecorator}
769 WebInspector.ElementsTreeOutline.PseudoStateDecorator = function()
771 WebInspector.ElementsTreeOutline.ElementDecorator.call(this);
774 WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName = "pseudoState";
776 WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = {
778 * @param {!WebInspector.DOMNode} node
781 decorate: function(node)
783 if (node.nodeType() !== Node.ELEMENT_NODE)
785 var propertyValue = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
788 return WebInspector.UIString("Element state: %s", ":" + propertyValue.join(", :"));
792 * @param {!WebInspector.DOMNode} node
795 decorateAncestor: function(node)
797 if (node.nodeType() !== Node.ELEMENT_NODE)
800 var descendantCount = node.descendantUserPropertyCount(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
801 if (!descendantCount)
803 if (descendantCount === 1)
804 return WebInspector.UIString("%d descendant with forced state", descendantCount);
805 return WebInspector.UIString("%d descendants with forced state", descendantCount);
811 * @extends {TreeElement}
812 * @param {boolean=} elementCloseTag
814 WebInspector.ElementsTreeElement = function(node, elementCloseTag)
816 // The title will be updated in onattach.
817 TreeElement.call(this, "", node);
820 this._elementCloseTag = elementCloseTag;
821 this._updateHasChildren();
823 if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag)
824 this._canAddAttributes = true;
825 this._searchQuery = null;
826 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit;
829 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;
831 // A union of HTML4 and HTML5-Draft elements that explicitly
832 // or implicitly (for HTML5) forbid the closing tag.
833 // FIXME: Revise once HTML5 Final is published.
834 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
835 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
836 "hr", "img", "input", "keygen", "link", "meta", "param", "source"
839 // These tags we do not allow editing their tag name.
840 WebInspector.ElementsTreeElement.EditTagBlacklist = [
841 "html", "head", "body"
844 WebInspector.ElementsTreeElement.prototype = {
845 highlightSearchResults: function(searchQuery)
847 if (this._searchQuery !== searchQuery) {
848 this._updateSearchHighlight(false);
849 delete this._highlightResult; // A new search query.
852 this._searchQuery = searchQuery;
853 this._searchHighlightsVisible = true;
854 this.updateTitle(true);
857 hideSearchHighlights: function()
859 delete this._searchHighlightsVisible;
860 this._updateSearchHighlight(false);
863 _updateSearchHighlight: function(show)
865 if (!this._highlightResult)
868 function updateEntryShow(entry)
870 switch (entry.type) {
872 entry.parent.insertBefore(entry.node, entry.nextSibling);
875 entry.node.textContent = entry.newText;
880 function updateEntryHide(entry)
882 switch (entry.type) {
887 entry.node.textContent = entry.oldText;
892 // Preserve the semantic of node by following the order of updates for hide and show.
894 for (var i = 0, size = this._highlightResult.length; i < size; ++i)
895 updateEntryShow(this._highlightResult[i]);
897 for (var i = (this._highlightResult.length - 1); i >= 0; --i)
898 updateEntryHide(this._highlightResult[i]);
904 return this._hovered;
909 if (this._hovered === x)
914 if (this.listItemElement) {
916 this.updateSelection();
917 this.listItemElement.classList.add("hovered");
919 this.listItemElement.classList.remove("hovered");
924 get expandedChildrenLimit()
926 return this._expandedChildrenLimit;
929 set expandedChildrenLimit(x)
931 if (this._expandedChildrenLimit === x)
934 this._expandedChildrenLimit = x;
935 if (this.treeOutline && !this._updateChildrenInProgress)
936 this._updateChildren(true);
939 get expandedChildCount()
941 var count = this.children.length;
942 if (count && this.children[count - 1]._elementCloseTag)
944 if (count && this.children[count - 1].expandAllButton)
950 * @param {!WebInspector.DOMNode} child
951 * @return {?WebInspector.ElementsTreeElement}
953 _showChild: function(child)
955 if (this._elementCloseTag)
958 var index = this._visibleChildren().indexOf(child);
962 if (index >= this.expandedChildrenLimit) {
963 this._expandedChildrenLimit = index + 1;
964 this._updateChildren(true);
967 // Whether index-th child is visible in the children tree
968 return this.expandedChildCount > index ? this.children[index] : null;
971 updateSelection: function()
973 var listItemElement = this.listItemElement;
974 if (!listItemElement)
977 if (!this._readyToUpdateSelection) {
978 if (document.body.offsetWidth > 0)
979 this._readyToUpdateSelection = true;
981 // The stylesheet hasn't loaded yet or the window is closed,
982 // so we can't calculate what we need. Return early.
987 if (!this.selectionElement) {
988 this.selectionElement = document.createElement("div");
989 this.selectionElement.className = "selection selected";
990 listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
993 this.selectionElement.style.height = listItemElement.offsetHeight + "px";
999 this.updateSelection();
1000 this.listItemElement.classList.add("hovered");
1004 this._preventFollowingLinksOnDoubleClick();
1005 this.listItemElement.draggable = true;
1008 _preventFollowingLinksOnDoubleClick: function()
1010 var links = this.listItemElement.querySelectorAll("li .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
1014 for (var i = 0; i < links.length; ++i)
1015 links[i].preventFollowOnDoubleClick = true;
1018 onpopulate: function()
1020 if (this.children.length || this._showInlineText() || this._elementCloseTag)
1023 this.updateChildren();
1027 * @param {boolean=} fullRefresh
1029 updateChildren: function(fullRefresh)
1031 if (this._elementCloseTag)
1033 this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh));
1037 * @param {boolean=} closingTag
1038 * @return {!WebInspector.ElementsTreeElement}
1040 insertChildElement: function(child, index, closingTag)
1042 var newElement = new WebInspector.ElementsTreeElement(child, closingTag);
1043 newElement.selectable = this.treeOutline._selectEnabled;
1044 this.insertChild(newElement, index);
1048 moveChild: function(child, targetIndex)
1050 var wasSelected = child.selected;
1051 this.removeChild(child);
1052 this.insertChild(child, targetIndex);
1058 * @param {boolean=} fullRefresh
1060 _updateChildren: function(fullRefresh)
1062 if (this._updateChildrenInProgress || !this.treeOutline._visible)
1065 this._updateChildrenInProgress = true;
1066 var selectedNode = this.treeOutline.selectedDOMNode();
1067 var originalScrollTop = 0;
1069 var treeOutlineContainerElement = this.treeOutline.element.parentNode;
1070 originalScrollTop = treeOutlineContainerElement.scrollTop;
1071 var selectedTreeElement = this.treeOutline.selectedTreeElement;
1072 if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
1074 this.removeChildren();
1078 * @this {WebInspector.ElementsTreeElement}
1079 * @return {?WebInspector.ElementsTreeElement}
1081 function updateChildrenOfNode()
1083 var treeOutline = this.treeOutline;
1084 var visibleChildren = this._visibleChildren();
1085 var treeChildIndex = 0;
1086 var elementToSelect = null;
1088 for (var i = 0; i < visibleChildren.length; ++i) {
1089 var child = visibleChildren[i];
1090 var currentTreeElement = this.children[treeChildIndex];
1091 if (!currentTreeElement || currentTreeElement._node !== child) {
1092 // Find any existing element that is later in the children list.
1093 var existingTreeElement = null;
1094 for (var j = (treeChildIndex + 1), size = this.expandedChildCount; j < size; ++j) {
1095 if (this.children[j]._node === child) {
1096 existingTreeElement = this.children[j];
1101 if (existingTreeElement && existingTreeElement.parent === this) {
1102 // If an existing element was found and it has the same parent, just move it.
1103 this.moveChild(existingTreeElement, treeChildIndex);
1105 // No existing element found, insert a new element.
1106 if (treeChildIndex < this.expandedChildrenLimit) {
1107 var newElement = this.insertChildElement(child, treeChildIndex);
1108 if (child === selectedNode)
1109 elementToSelect = newElement;
1110 if (this.expandedChildCount > this.expandedChildrenLimit)
1111 this.expandedChildrenLimit++;
1118 return elementToSelect;
1121 // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
1122 for (var i = (this.children.length - 1); i >= 0; --i) {
1123 var currentChild = this.children[i];
1124 var currentNode = currentChild._node;
1127 var currentParentNode = currentNode.parentNode;
1129 if (currentParentNode === this._node)
1132 var selectedTreeElement = this.treeOutline.selectedTreeElement;
1133 if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
1136 this.removeChildAtIndex(i);
1139 var elementToSelect = updateChildrenOfNode.call(this);
1141 this._adjustCollapsedRange();
1143 var lastChild = this.children[this.children.length - 1];
1144 if (this._node.nodeType() == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
1145 this.insertChildElement(this._node, this.children.length, true);
1147 // We want to restore the original selection and tree scroll position after a full refresh, if possible.
1148 if (fullRefresh && elementToSelect) {
1149 elementToSelect.select();
1150 if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight)
1151 treeOutlineContainerElement.scrollTop = originalScrollTop;
1154 delete this._updateChildrenInProgress;
1157 _adjustCollapsedRange: function()
1159 var visibleChildren = this._visibleChildren();
1160 // Ensure precondition: only the tree elements for node children are found in the tree
1161 // (not the Expand All button or the closing tag).
1162 if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent)
1163 this.removeChild(this.expandAllButtonElement.__treeElement);
1165 const childNodeCount = visibleChildren.length;
1167 // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
1168 for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i)
1169 this.insertChildElement(visibleChildren[i], i);
1171 const expandedChildCount = this.expandedChildCount;
1172 if (childNodeCount > this.expandedChildCount) {
1173 var targetButtonIndex = expandedChildCount;
1174 if (!this.expandAllButtonElement) {
1175 var button = document.createElement("button");
1176 button.className = "show-all-nodes";
1178 var item = new TreeElement(button, null, false);
1179 item.selectable = false;
1180 item.expandAllButton = true;
1181 this.insertChild(item, targetButtonIndex);
1182 this.expandAllButtonElement = item.listItemElement.firstChild;
1183 this.expandAllButtonElement.__treeElement = item;
1184 this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
1185 } else if (!this.expandAllButtonElement.__treeElement.parent)
1186 this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
1187 this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount);
1188 } else if (this.expandAllButtonElement)
1189 delete this.expandAllButtonElement;
1192 handleLoadAllChildren: function()
1194 this.expandedChildrenLimit = Math.max(this._visibleChildCount(), this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit);
1197 expandRecursively: function()
1200 * @this {WebInspector.ElementsTreeElement}
1204 TreeElement.prototype.expandRecursively.call(this, Number.MAX_VALUE);
1207 this._node.getSubtree(-1, callback.bind(this));
1213 onexpand: function()
1215 if (this._elementCloseTag)
1219 this.treeOutline.updateSelection();
1222 oncollapse: function()
1224 if (this._elementCloseTag)
1228 this.treeOutline.updateSelection();
1234 onreveal: function()
1236 if (this.listItemElement) {
1237 var tagSpans = this.listItemElement.getElementsByClassName("webkit-html-tag-name");
1238 if (tagSpans.length)
1239 tagSpans[0].scrollIntoViewIfNeeded(true);
1241 this.listItemElement.scrollIntoViewIfNeeded(true);
1247 * @param {boolean=} selectedByUser
1250 onselect: function(selectedByUser)
1252 this.treeOutline.suppressRevealAndSelect = true;
1253 this.treeOutline.selectDOMNode(this._node, selectedByUser);
1255 WebInspector.domModel.highlightDOMNode(this._node.id);
1256 this.updateSelection();
1257 this.treeOutline.suppressRevealAndSelect = false;
1265 ondelete: function()
1267 var startTagTreeElement = this.treeOutline.findTreeElement(this._node);
1268 startTagTreeElement ? startTagTreeElement.remove() : this.remove();
1278 // On Enter or Return start editing the first attribute
1279 // or create a new attribute on the selected element.
1283 this._startEditing();
1285 // prevent a newline from being immediately inserted
1289 selectOnMouseDown: function(event)
1291 TreeElement.prototype.selectOnMouseDown.call(this, event);
1296 if (this.treeOutline._showInElementsPanelEnabled) {
1297 WebInspector.inspectorView.showPanel("elements");
1298 this.treeOutline.selectDOMNode(this._node, true);
1301 // Prevent selecting the nearest word on double click.
1302 if (event.detail >= 2)
1303 event.preventDefault();
1310 ondblclick: function(event)
1312 if (this._editing || this._elementCloseTag)
1315 if (this._startEditingTarget(event.target))
1318 if (this.hasChildren && !this.expanded)
1326 hasEditableNode: function()
1328 return !this.representedObject.isShadowRoot() && !this.representedObject.ancestorUserAgentShadowRoot();
1331 _insertInLastAttributePosition: function(tag, node)
1333 if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
1334 tag.insertBefore(node, tag.lastChild);
1336 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
1337 tag.textContent = '';
1338 tag.appendChild(document.createTextNode('<'+nodeName));
1339 tag.appendChild(node);
1340 tag.appendChild(document.createTextNode('>'));
1343 this.updateSelection();
1346 _startEditingTarget: function(eventTarget)
1348 if (this.treeOutline.selectedDOMNode() != this._node)
1351 if (this._node.nodeType() != Node.ELEMENT_NODE && this._node.nodeType() != Node.TEXT_NODE)
1354 var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node");
1356 return this._startEditingTextNode(textNode);
1358 var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute");
1360 return this._startEditingAttribute(attribute, eventTarget);
1362 var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
1364 return this._startEditingTagName(tagName);
1366 var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute");
1368 return this._addNewAttribute();
1374 * @param {!WebInspector.ContextMenu} contextMenu
1375 * @param {?Event} event
1377 _populateTagContextMenu: function(contextMenu, event)
1379 // Add attribute-related actions.
1380 var treeElement = this._elementCloseTag ? this.treeOutline.findTreeElement(this._node) : this;
1381 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add attribute" : "Add Attribute"), treeElement._addNewAttribute.bind(treeElement));
1383 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
1384 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
1385 if (attribute && !newAttribute)
1386 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit attribute" : "Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
1387 contextMenu.appendSeparator();
1388 if (this.treeOutline._setPseudoClassCallback) {
1389 var pseudoSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Force element state" : "Force Element State"));
1390 this._populateForcedPseudoStateItems(pseudoSubMenu);
1391 contextMenu.appendSeparator();
1393 this._populateNodeContextMenu(contextMenu);
1394 this.treeOutline._populateContextMenu(contextMenu, this._node);
1395 this._populateScrollIntoView(contextMenu);
1399 * @param {!WebInspector.ContextMenu} contextMenu
1401 _populateScrollIntoView: function(contextMenu)
1403 contextMenu.appendSeparator();
1404 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Scroll into view" : "Scroll into View"), this._scrollIntoView.bind(this));
1407 _populateForcedPseudoStateItems: function(subMenu)
1409 const pseudoClasses = ["active", "hover", "focus", "visited"];
1410 var node = this._node;
1411 var forcedPseudoState = (node ? node.getUserProperty("pseudoState") : null) || [];
1412 for (var i = 0; i < pseudoClasses.length; ++i) {
1413 var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0;
1414 subMenu.appendCheckboxItem(":" + pseudoClasses[i], this.treeOutline._setPseudoClassCallback.bind(null, node.id, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false);
1418 _populateTextContextMenu: function(contextMenu, textNode)
1420 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode));
1421 this._populateNodeContextMenu(contextMenu);
1424 _populateNodeContextMenu: function(contextMenu)
1426 // Add free-form node-related actions.
1427 var openTagElement = this.treeOutline.getCachedTreeElement(this.representedObject) || this;
1428 var isEditable = this.hasEditableNode();
1430 contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), openTagElement._editAsHTML.bind(openTagElement));
1431 var isShadowRoot = this.representedObject.isShadowRoot();
1433 contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
1435 // Place it here so that all "Copy"-ing items stick together.
1436 if (this.representedObject.nodeType() === Node.ELEMENT_NODE)
1437 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy CSS path" : "Copy CSS Path"), this._copyCSSPath.bind(this));
1439 contextMenu.appendItem(WebInspector.UIString("Copy XPath"), this._copyXPath.bind(this));
1441 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete node" : "Delete Node"), this.remove.bind(this));
1444 _startEditing: function()
1446 if (this.treeOutline.selectedDOMNode() !== this._node)
1449 var listItem = this._listItemNode;
1451 if (this._canAddAttributes) {
1452 var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
1454 return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);
1456 return this._addNewAttribute();
1459 if (this._node.nodeType() === Node.TEXT_NODE) {
1460 var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
1462 return this._startEditingTextNode(textNode);
1467 _addNewAttribute: function()
1469 // Cannot just convert the textual html into an element without
1470 // a parent node. Use a temporary span container for the HTML.
1471 var container = document.createElement("span");
1472 this._buildAttributeDOM(container, " ", "");
1473 var attr = container.firstElementChild;
1474 attr.style.marginLeft = "2px"; // overrides the .editing margin rule
1475 attr.style.marginRight = "2px"; // overrides the .editing margin rule
1477 var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
1478 this._insertInLastAttributePosition(tag, attr);
1479 attr.scrollIntoViewIfNeeded(true);
1480 return this._startEditingAttribute(attr, attr);
1483 _triggerEditAttribute: function(attributeName)
1485 var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
1486 for (var i = 0, len = attributeElements.length; i < len; ++i) {
1487 if (attributeElements[i].textContent === attributeName) {
1488 for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
1489 if (elem.nodeType !== Node.ELEMENT_NODE)
1492 if (elem.classList.contains("webkit-html-attribute-value"))
1493 return this._startEditingAttribute(elem.parentNode, elem);
1499 _startEditingAttribute: function(attribute, elementForSelection)
1501 if (WebInspector.isBeingEdited(attribute))
1504 var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
1505 if (!attributeNameElement)
1508 var attributeName = attributeNameElement.textContent;
1509 var attributeValueElement = attribute.getElementsByClassName("webkit-html-attribute-value")[0];
1511 function removeZeroWidthSpaceRecursive(node)
1513 if (node.nodeType === Node.TEXT_NODE) {
1514 node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
1518 if (node.nodeType !== Node.ELEMENT_NODE)
1521 for (var child = node.firstChild; child; child = child.nextSibling)
1522 removeZeroWidthSpaceRecursive(child);
1526 var listItemElement = attribute.enclosingNodeOrSelfWithNodeName("li");
1527 if (attributeName && attributeValueElement && listItemElement && listItemElement.treeElement)
1528 domNode = listItemElement.treeElement.representedObject;
1529 var attributeValue = domNode ? domNode.getAttribute(attributeName) : undefined;
1530 if (typeof attributeValue !== "undefined")
1531 attributeValueElement.textContent = attributeValue;
1533 // Remove zero-width spaces that were added by nodeTitleInfo.
1534 removeZeroWidthSpaceRecursive(attribute);
1536 var config = new WebInspector.InplaceEditor.Config(this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
1538 function handleKeyDownEvents(event)
1540 var isMetaOrCtrl = WebInspector.isMac() ?
1541 event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
1542 event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
1543 if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl))
1545 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
1547 else if (event.keyIdentifier === "U+0009") // Tab key
1548 return "move-" + (event.shiftKey ? "backward" : "forward");
1550 WebInspector.handleElementValueModifications(event, attribute);
1555 config.customFinishHandler = handleKeyDownEvents;
1557 this._editing = WebInspector.InplaceEditor.startEditing(attribute, config);
1559 window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
1565 * @param {!Element} textNodeElement
1567 _startEditingTextNode: function(textNodeElement)
1569 if (WebInspector.isBeingEdited(textNodeElement))
1572 var textNode = this._node;
1573 // We only show text nodes inline in elements if the element only
1574 // has a single child, and that child is a text node.
1575 if (textNode.nodeType() === Node.ELEMENT_NODE && textNode.firstChild)
1576 textNode = textNode.firstChild;
1578 var container = textNodeElement.enclosingNodeOrSelfWithClass("webkit-html-text-node");
1580 container.textContent = textNode.nodeValue(); // Strip the CSS or JS highlighting if present.
1581 var config = new WebInspector.InplaceEditor.Config(this._textNodeEditingCommitted.bind(this, textNode), this._editingCancelled.bind(this));
1582 this._editing = WebInspector.InplaceEditor.startEditing(textNodeElement, config);
1583 window.getSelection().setBaseAndExtent(textNodeElement, 0, textNodeElement, 1);
1589 * @param {!Element=} tagNameElement
1591 _startEditingTagName: function(tagNameElement)
1593 if (!tagNameElement) {
1594 tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0];
1595 if (!tagNameElement)
1599 var tagName = tagNameElement.textContent;
1600 if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()])
1603 if (WebInspector.isBeingEdited(tagNameElement))
1606 var closingTagElement = this._distinctClosingTagElement();
1609 * @param {?Event} event
1611 function keyupListener(event)
1613 if (closingTagElement)
1614 closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
1618 * @param {!Element} element
1619 * @param {string} newTagName
1620 * @this {WebInspector.ElementsTreeElement}
1622 function editingComitted(element, newTagName)
1624 tagNameElement.removeEventListener('keyup', keyupListener, false);
1625 this._tagNameEditingCommitted.apply(this, arguments);
1629 * @this {WebInspector.ElementsTreeElement}
1631 function editingCancelled()
1633 tagNameElement.removeEventListener('keyup', keyupListener, false);
1634 this._editingCancelled.apply(this, arguments);
1637 tagNameElement.addEventListener('keyup', keyupListener, false);
1639 var config = new WebInspector.InplaceEditor.Config(editingComitted.bind(this), editingCancelled.bind(this), tagName);
1640 this._editing = WebInspector.InplaceEditor.startEditing(tagNameElement, config);
1641 window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
1645 _startEditingAsHTML: function(commitCallback, error, initialValue)
1652 function consume(event)
1654 if (event.eventPhase === Event.AT_TARGET)
1655 event.consume(true);
1658 initialValue = this._convertWhitespaceToEntities(initialValue).text;
1660 this._htmlEditElement = document.createElement("div");
1661 this._htmlEditElement.className = "source-code elements-tree-editor";
1663 // Hide header items.
1664 var child = this.listItemElement.firstChild;
1666 child.style.display = "none";
1667 child = child.nextSibling;
1669 // Hide children item.
1670 if (this._childrenListNode)
1671 this._childrenListNode.style.display = "none";
1673 this.listItemElement.appendChild(this._htmlEditElement);
1674 this.treeOutline.childrenListElement.parentElement.addEventListener("mousedown", consume, false);
1676 this.updateSelection();
1679 * @param {!Element} element
1680 * @param {string} newValue
1681 * @this {WebInspector.ElementsTreeElement}
1683 function commit(element, newValue)
1685 commitCallback(initialValue, newValue);
1690 * @this {WebInspector.ElementsTreeElement}
1694 delete this._editing;
1695 delete this.treeOutline._multilineEditing;
1698 this.listItemElement.removeChild(this._htmlEditElement);
1699 delete this._htmlEditElement;
1700 // Unhide children item.
1701 if (this._childrenListNode)
1702 this._childrenListNode.style.removeProperty("display");
1703 // Unhide header items.
1704 var child = this.listItemElement.firstChild;
1706 child.style.removeProperty("display");
1707 child = child.nextSibling;
1710 this.treeOutline.childrenListElement.parentElement.removeEventListener("mousedown", consume, false);
1711 this.updateSelection();
1712 this.treeOutline.element.focus();
1715 var config = new WebInspector.InplaceEditor.Config(commit.bind(this), dispose.bind(this));
1716 config.setMultilineOptions(initialValue, { name: "xml", htmlMode: true }, "web-inspector-html", WebInspector.settings.domWordWrap.get(), true);
1717 this._editing = WebInspector.InplaceEditor.startEditing(this._htmlEditElement, config);
1718 this._editing.setWidth(this.treeOutline._visibleWidth);
1719 this.treeOutline._multilineEditing = this._editing;
1722 _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
1724 delete this._editing;
1726 var treeOutline = this.treeOutline;
1729 * @param {?Protocol.Error=} error
1730 * @this {WebInspector.ElementsTreeElement}
1732 function moveToNextAttributeIfNeeded(error)
1735 this._editingCancelled(element, attributeName);
1740 treeOutline._updateModifiedNodes();
1742 // Search for the attribute's position, and then decide where to move to.
1743 var attributes = this._node.attributes();
1744 for (var i = 0; i < attributes.length; ++i) {
1745 if (attributes[i].name !== attributeName)
1748 if (moveDirection === "backward") {
1750 this._startEditingTagName();
1752 this._triggerEditAttribute(attributes[i - 1].name);
1754 if (i === attributes.length - 1)
1755 this._addNewAttribute();
1757 this._triggerEditAttribute(attributes[i + 1].name);
1762 // Moving From the "New Attribute" position.
1763 if (moveDirection === "backward") {
1764 if (newText === " ") {
1765 // Moving from "New Attribute" that was not edited
1766 if (attributes.length > 0)
1767 this._triggerEditAttribute(attributes[attributes.length - 1].name);
1769 // Moving from "New Attribute" that holds new value
1770 if (attributes.length > 1)
1771 this._triggerEditAttribute(attributes[attributes.length - 2].name);
1773 } else if (moveDirection === "forward") {
1774 if (!/^\s*$/.test(newText))
1775 this._addNewAttribute();
1777 this._startEditingTagName();
1781 if (!attributeName.trim() && !newText.trim()) {
1783 moveToNextAttributeIfNeeded.call(this);
1787 if (oldText !== newText) {
1788 this._node.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this));
1793 moveToNextAttributeIfNeeded.call(this);
1796 _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection)
1798 delete this._editing;
1803 var closingTagElement = self._distinctClosingTagElement();
1804 if (closingTagElement)
1805 closingTagElement.textContent = "</" + tagName + ">";
1807 self._editingCancelled(element, tagName);
1808 moveToNextAttributeIfNeeded.call(self);
1812 * @this {WebInspector.ElementsTreeElement}
1814 function moveToNextAttributeIfNeeded()
1816 if (moveDirection !== "forward") {
1817 this._addNewAttribute();
1821 var attributes = this._node.attributes();
1822 if (attributes.length > 0)
1823 this._triggerEditAttribute(attributes[0].name);
1825 this._addNewAttribute();
1828 newText = newText.trim();
1829 if (newText === oldText) {
1834 var treeOutline = this.treeOutline;
1835 var wasExpanded = this.expanded;
1837 function changeTagNameCallback(error, nodeId)
1839 if (error || !nodeId) {
1843 var newTreeItem = treeOutline._selectNodeAfterEdit(wasExpanded, error, nodeId);
1844 moveToNextAttributeIfNeeded.call(newTreeItem);
1847 this._node.setNodeName(newText, changeTagNameCallback);
1851 * @param {!WebInspector.DOMNode} textNode
1852 * @param {!Element} element
1853 * @param {string} newText
1855 _textNodeEditingCommitted: function(textNode, element, newText)
1857 delete this._editing;
1860 * @this {WebInspector.ElementsTreeElement}
1866 textNode.setNodeValue(newText, callback.bind(this));
1870 * @param {!Element} element
1871 * @param {*} context
1873 _editingCancelled: function(element, context)
1875 delete this._editing;
1877 // Need to restore attributes structure.
1882 * @return {!Element}
1884 _distinctClosingTagElement: function()
1886 // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM
1888 // For an expanded element, it will be the last element with class "close"
1889 // in the child element list.
1890 if (this.expanded) {
1891 var closers = this._childrenListNode.querySelectorAll(".close");
1892 return closers[closers.length-1];
1895 // Remaining cases are single line non-expanded elements with a closing
1896 // tag, or HTML elements without a closing tag (such as <br>). Return
1897 // null in the case where there isn't a closing tag.
1898 var tags = this.listItemElement.getElementsByClassName("webkit-html-tag");
1899 return (tags.length === 1 ? null : tags[tags.length-1]);
1903 * @param {boolean=} onlySearchQueryChanged
1905 updateTitle: function(onlySearchQueryChanged)
1907 // If we are editing, return early to prevent canceling the edit.
1908 // After editing is committed updateTitle will be called.
1912 if (onlySearchQueryChanged) {
1913 if (this._highlightResult)
1914 this._updateSearchHighlight(false);
1916 var nodeInfo = this._nodeTitleInfo(WebInspector.linkifyURLAsNode);
1917 if (nodeInfo.shadowRoot)
1918 this.listItemElement.classList.add("shadow-root");
1919 var highlightElement = document.createElement("span");
1920 highlightElement.className = "highlight";
1921 highlightElement.appendChild(nodeInfo.titleDOM);
1922 this.title = highlightElement;
1923 this._updateDecorations();
1924 delete this._highlightResult;
1927 delete this.selectionElement;
1929 this.updateSelection();
1930 this._preventFollowingLinksOnDoubleClick();
1931 this._highlightSearchResults();
1935 * @return {?Element}
1937 _createDecoratorElement: function()
1939 var node = this._node;
1940 var decoratorMessages = [];
1941 var parentDecoratorMessages = [];
1942 for (var i = 0; i < this.treeOutline._nodeDecorators.length; ++i) {
1943 var decorator = this.treeOutline._nodeDecorators[i];
1944 var message = decorator.decorate(node);
1946 decoratorMessages.push(message);
1950 if (this.expanded || this._elementCloseTag)
1953 message = decorator.decorateAncestor(node);
1955 parentDecoratorMessages.push(message)
1957 if (!decoratorMessages.length && !parentDecoratorMessages.length)
1960 var decoratorElement = document.createElement("div");
1961 decoratorElement.classList.add("elements-gutter-decoration");
1962 if (!decoratorMessages.length)
1963 decoratorElement.classList.add("elements-has-decorated-children");
1964 decoratorElement.title = decoratorMessages.concat(parentDecoratorMessages).join("\n");
1965 return decoratorElement;
1968 _updateDecorations: function()
1970 if (this._decoratorElement)
1971 this._decoratorElement.remove();
1972 this._decoratorElement = this._createDecoratorElement();
1973 if (this._decoratorElement && this.listItemElement)
1974 this.listItemElement.insertBefore(this._decoratorElement, this.listItemElement.firstChild);
1978 * @param {!Node} parentElement
1979 * @param {string} name
1980 * @param {string} value
1981 * @param {boolean=} forceValue
1982 * @param {!WebInspector.DOMNode=} node
1983 * @param {function(string, string, string, boolean=, string=)=} linkify
1985 _buildAttributeDOM: function(parentElement, name, value, forceValue, node, linkify)
1987 var closingPunctuationRegex = /[\/;:\)\]\}]/g;
1988 var highlightIndex = 0;
1990 var additionalHighlightOffset = 0;
1994 * @param {string} match
1995 * @param {number} replaceOffset
1998 function replacer(match, replaceOffset) {
1999 while (highlightIndex < highlightCount && result.entityRanges[highlightIndex].offset < replaceOffset) {
2000 result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
2003 additionalHighlightOffset += 1;
2004 return match + "\u200B";
2008 * @param {!Element} element
2009 * @param {string} value
2010 * @this {WebInspector.ElementsTreeElement}
2012 function setValueWithEntities(element, value)
2014 var attrValueElement = element.createChild("span", "webkit-html-attribute-value");
2015 result = this._convertWhitespaceToEntities(value);
2016 highlightCount = result.entityRanges.length;
2017 value = result.text.replace(closingPunctuationRegex, replacer);
2018 while (highlightIndex < highlightCount) {
2019 result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
2022 attrValueElement.textContent = value;
2023 WebInspector.highlightRangesWithStyleClass(attrValueElement, result.entityRanges, "webkit-html-entity-value");
2026 var hasText = (forceValue || value.length > 0);
2027 var attrSpanElement = parentElement.createChild("span", "webkit-html-attribute");
2028 var attrNameElement = attrSpanElement.createChild("span", "webkit-html-attribute-name");
2029 attrNameElement.textContent = name;
2032 attrSpanElement.appendChild(document.createTextNode("=\u200B\""));
2034 if (linkify && (name === "src" || name === "href")) {
2035 var rewrittenHref = node.resolveURL(value);
2036 if (rewrittenHref === null) {
2037 setValueWithEntities.call(this, attrSpanElement, value);
2039 value = value.replace(closingPunctuationRegex, "$&\u200B");
2040 if (value.startsWith("data:"))
2041 value = value.trimMiddle(60);
2042 attrSpanElement.appendChild(linkify(rewrittenHref, value, "webkit-html-attribute-value", node.nodeName().toLowerCase() === "a"));
2045 setValueWithEntities.call(this, attrSpanElement, value);
2049 attrSpanElement.appendChild(document.createTextNode("\""));
2053 * @param {!Node} parentElement
2054 * @param {string} pseudoElementName
2056 _buildPseudoElementDOM: function(parentElement, pseudoElementName)
2058 var pseudoElement = parentElement.createChild("span", "webkit-html-pseudo-element");
2059 pseudoElement.textContent = "::" + pseudoElementName;
2060 parentElement.appendChild(document.createTextNode("\u200B"));
2064 * @param {!Node} parentElement
2065 * @param {string} tagName
2066 * @param {boolean} isClosingTag
2067 * @param {boolean} isDistinctTreeElement
2068 * @param {function(string, string, string, boolean=, string=)=} linkify
2070 _buildTagDOM: function(parentElement, tagName, isClosingTag, isDistinctTreeElement, linkify)
2072 var node = this._node;
2073 var classes = [ "webkit-html-tag" ];
2074 if (isClosingTag && isDistinctTreeElement)
2075 classes.push("close");
2076 var tagElement = parentElement.createChild("span", classes.join(" "));
2077 tagElement.appendChild(document.createTextNode("<"));
2078 var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name");
2079 tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName;
2080 if (!isClosingTag && node.hasAttributes()) {
2081 var attributes = node.attributes();
2082 for (var i = 0; i < attributes.length; ++i) {
2083 var attr = attributes[i];
2084 tagElement.appendChild(document.createTextNode(" "));
2085 this._buildAttributeDOM(tagElement, attr.name, attr.value, false, node, linkify);
2088 tagElement.appendChild(document.createTextNode(">"));
2089 parentElement.appendChild(document.createTextNode("\u200B"));
2093 * @param {string} text
2094 * @return {!{text: string, entityRanges: !Array.<!WebInspector.SourceRange>}}
2096 _convertWhitespaceToEntities: function(text)
2099 var resultLength = 0;
2100 var lastIndexAfterEntity = 0;
2101 var entityRanges = [];
2102 var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity;
2103 for (var i = 0, size = text.length; i < size; ++i) {
2104 var char = text.charAt(i);
2105 if (charToEntity[char]) {
2106 result += text.substring(lastIndexAfterEntity, i);
2107 var entityValue = "&" + charToEntity[char] + ";";
2108 entityRanges.push({offset: result.length, length: entityValue.length});
2109 result += entityValue;
2110 lastIndexAfterEntity = i + 1;
2114 result += text.substring(lastIndexAfterEntity);
2115 return {text: result || text, entityRanges: entityRanges};
2119 * @param {function(string, string, string, boolean=, string=)=} linkify
2121 _nodeTitleInfo: function(linkify)
2123 var node = this._node;
2124 var info = {titleDOM: document.createDocumentFragment(), hasChildren: this.hasChildren};
2126 switch (node.nodeType()) {
2127 case Node.ATTRIBUTE_NODE:
2128 this._buildAttributeDOM(info.titleDOM, node.name, node.value, true);
2131 case Node.ELEMENT_NODE:
2132 if (node.pseudoType()) {
2133 this._buildPseudoElementDOM(info.titleDOM, node.pseudoType());
2134 info.hasChildren = false;
2138 var tagName = node.nodeNameInCorrectCase();
2139 if (this._elementCloseTag) {
2140 this._buildTagDOM(info.titleDOM, tagName, true, true);
2141 info.hasChildren = false;
2145 this._buildTagDOM(info.titleDOM, tagName, false, false, linkify);
2147 var showInlineText = this._showInlineText() && !this.hasChildren;
2148 if (!this.expanded && !showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName])) {
2149 if (this.hasChildren) {
2150 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node bogus");
2151 textNodeElement.textContent = "\u2026";
2152 info.titleDOM.appendChild(document.createTextNode("\u200B"));
2154 this._buildTagDOM(info.titleDOM, tagName, true, false);
2157 // If this element only has a single child that is a text node,
2158 // just show that text and the closing tag inline rather than
2159 // create a subtree for them
2160 if (showInlineText) {
2161 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
2162 var result = this._convertWhitespaceToEntities(node.firstChild.nodeValue());
2163 textNodeElement.textContent = result.text;
2164 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
2165 info.titleDOM.appendChild(document.createTextNode("\u200B"));
2166 this._buildTagDOM(info.titleDOM, tagName, true, false);
2167 info.hasChildren = false;
2171 case Node.TEXT_NODE:
2172 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") {
2173 var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-js-node");
2174 newNode.textContent = node.nodeValue();
2176 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true);
2177 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);
2178 } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "style") {
2179 var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-css-node");
2180 newNode.textContent = node.nodeValue();
2182 var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css", true);
2183 cssSyntaxHighlighter.syntaxHighlightNode(newNode);
2185 info.titleDOM.appendChild(document.createTextNode("\""));
2186 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
2187 var result = this._convertWhitespaceToEntities(node.nodeValue());
2188 textNodeElement.textContent = result.text;
2189 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
2190 info.titleDOM.appendChild(document.createTextNode("\""));
2194 case Node.COMMENT_NODE:
2195 var commentElement = info.titleDOM.createChild("span", "webkit-html-comment");
2196 commentElement.appendChild(document.createTextNode("<!--" + node.nodeValue() + "-->"));
2199 case Node.DOCUMENT_TYPE_NODE:
2200 var docTypeElement = info.titleDOM.createChild("span", "webkit-html-doctype");
2201 docTypeElement.appendChild(document.createTextNode("<!DOCTYPE " + node.nodeName()));
2202 if (node.publicId) {
2203 docTypeElement.appendChild(document.createTextNode(" PUBLIC \"" + node.publicId + "\""));
2205 docTypeElement.appendChild(document.createTextNode(" \"" + node.systemId + "\""));
2206 } else if (node.systemId)
2207 docTypeElement.appendChild(document.createTextNode(" SYSTEM \"" + node.systemId + "\""));
2209 if (node.internalSubset)
2210 docTypeElement.appendChild(document.createTextNode(" [" + node.internalSubset + "]"));
2212 docTypeElement.appendChild(document.createTextNode(">"));
2215 case Node.CDATA_SECTION_NODE:
2216 var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node");
2217 cdataElement.appendChild(document.createTextNode("<![CDATA[" + node.nodeValue() + "]]>"));
2219 case Node.DOCUMENT_FRAGMENT_NODE:
2220 var fragmentElement = info.titleDOM.createChild("span", "webkit-html-fragment");
2221 if (node.isInShadowTree()) {
2222 var shadowRootType = node.shadowRootType();
2223 if (shadowRootType) {
2224 info.shadowRoot = true;
2225 fragmentElement.classList.add("shadow-root");
2228 fragmentElement.textContent = node.nodeNameInCorrectCase().collapseWhitespace();
2231 info.titleDOM.appendChild(document.createTextNode(node.nodeNameInCorrectCase().collapseWhitespace()));
2239 _showInlineText: function()
2241 if (this._node.importedDocument() || this._node.templateContent() || this._visibleShadowRoots().length > 0 || this._node.hasPseudoElements())
2243 if (this._node.nodeType() !== Node.ELEMENT_NODE)
2245 if (!this._node.firstChild || this._node.firstChild !== this._node.lastChild || this._node.firstChild.nodeType() !== Node.TEXT_NODE)
2247 var textChild = this._node.firstChild;
2248 if (textChild.nodeValue().length < Preferences.maxInlineTextChildLength)
2255 if (this._node.pseudoType())
2257 var parentElement = this.parent;
2262 function removeNodeCallback(error, removedNodeId)
2267 parentElement.removeChild(self);
2268 parentElement._adjustCollapsedRange();
2271 if (!this._node.parentNode || this._node.parentNode.nodeType() === Node.DOCUMENT_NODE)
2273 this._node.removeNode(removeNodeCallback);
2276 _editAsHTML: function()
2278 var node = this._node;
2279 if (node.pseudoType())
2282 var treeOutline = this.treeOutline;
2283 var parentNode = node.parentNode;
2284 var index = node.index;
2285 var wasExpanded = this.expanded;
2287 function selectNode(error, nodeId)
2292 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
2293 treeOutline._updateModifiedNodes();
2295 var newNode = parentNode ? parentNode.children()[index] || parentNode : null;
2299 treeOutline.selectDOMNode(newNode, true);
2302 var newTreeItem = treeOutline.findTreeElement(newNode);
2304 newTreeItem.expand();
2308 function commitChange(initialValue, value)
2310 if (initialValue !== value)
2311 node.setOuterHTML(value, selectNode);
2316 node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange));
2319 _copyHTML: function()
2321 this._node.copyNode();
2324 _copyCSSPath: function()
2326 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.cssPath(this._node, true));
2329 _copyXPath: function()
2331 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.xPath(this._node, true));
2334 _highlightSearchResults: function()
2336 if (!this._searchQuery || !this._searchHighlightsVisible)
2338 if (this._highlightResult) {
2339 this._updateSearchHighlight(true);
2343 var text = this.listItemElement.textContent;
2344 var regexObject = createPlainTextSearchRegex(this._searchQuery, "gi");
2347 var match = regexObject.exec(text);
2348 var matchRanges = [];
2350 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length));
2351 match = regexObject.exec(text);
2354 // Fall back for XPath, etc. matches.
2355 if (!matchRanges.length)
2356 matchRanges.push(new WebInspector.SourceRange(0, text.length));
2358 this._highlightResult = [];
2359 WebInspector.highlightSearchResults(this.listItemElement, matchRanges, this._highlightResult);
2362 _scrollIntoView: function()
2364 function scrollIntoViewCallback(object)
2367 * @suppressReceiverCheck
2370 function scrollIntoView()
2372 this.scrollIntoViewIfNeeded(true);
2376 object.callFunction(scrollIntoView);
2379 WebInspector.RemoteObject.resolveNode(this._node, "", scrollIntoViewCallback);
2383 * @return {!Array.<!WebInspector.DOMModel>}
2385 _visibleShadowRoots: function()
2387 var roots = this._node.shadowRoots();
2388 if (roots.length && !WebInspector.settings.showUAShadowDOM.get()) {
2389 roots = roots.filter(function(root) {
2390 return root.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.Author;
2397 * @return {!Array.<!WebInspector.DOMNode>} visibleChildren
2399 _visibleChildren: function()
2401 var visibleChildren = this._visibleShadowRoots();
2402 if (this._node.importedDocument())
2403 visibleChildren.push(this._node.importedDocument());
2404 if (this._node.templateContent())
2405 visibleChildren.push(this._node.templateContent());
2406 var pseudoElements = this._node.pseudoElements();
2407 if (pseudoElements[WebInspector.DOMNode.PseudoElementNames.Before])
2408 visibleChildren.push(pseudoElements[WebInspector.DOMNode.PseudoElementNames.Before]);
2409 if (this._node.childNodeCount())
2410 visibleChildren = visibleChildren.concat(this._node.children());
2411 if (pseudoElements[WebInspector.DOMNode.PseudoElementNames.After])
2412 visibleChildren.push(pseudoElements[WebInspector.DOMNode.PseudoElementNames.After]);
2413 return visibleChildren;
2419 _visibleChildCount: function()
2421 var childCount = this._node.childNodeCount() + this._visibleShadowRoots().length;
2422 if (this._node.importedDocument())
2424 if (this._node.templateContent())
2426 for (var pseudoType in this._node.pseudoElements())
2431 _updateHasChildren: function()
2433 this.hasChildren = !this._elementCloseTag && !this._showInlineText() && this._visibleChildCount() > 0;
2436 __proto__: TreeElement.prototype
2441 * @param {!WebInspector.ElementsTreeOutline} treeOutline
2443 WebInspector.ElementsTreeUpdater = function(treeOutline)
2445 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
2446 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
2447 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
2448 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
2449 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
2450 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
2451 WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
2453 this._treeOutline = treeOutline;
2454 /** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.UpdateEntry>} */
2455 this._recentlyModifiedNodes = new Map();
2458 WebInspector.ElementsTreeUpdater.prototype = {
2460 * @param {!WebInspector.DOMNode} node
2461 * @param {boolean} isUpdated
2462 * @param {!WebInspector.DOMNode=} parentNode
2464 _nodeModified: function(node, isUpdated, parentNode)
2466 if (this._treeOutline._visible)
2467 this._updateModifiedNodesSoon();
2469 var entry = this._recentlyModifiedNodes.get(node);
2471 entry = new WebInspector.ElementsTreeUpdater.UpdateEntry(isUpdated, parentNode);
2472 this._recentlyModifiedNodes.put(node, entry);
2476 entry.isUpdated |= isUpdated;
2478 entry.parent = parentNode;
2481 _documentUpdated: function(event)
2483 var inspectedRootDocument = event.data;
2487 if (!inspectedRootDocument)
2490 this._treeOutline.rootDOMNode = inspectedRootDocument;
2493 _attributesUpdated: function(event)
2495 this._nodeModified(event.data.node, true);
2498 _characterDataModified: function(event)
2500 this._nodeModified(event.data, true);
2503 _nodeInserted: function(event)
2505 this._nodeModified(event.data, false, event.data.parentNode);
2508 _nodeRemoved: function(event)
2510 this._nodeModified(event.data.node, false, event.data.parent);
2513 _childNodeCountUpdated: function(event)
2515 var treeElement = this._treeOutline.findTreeElement(event.data);
2517 var oldHasChildren = treeElement.hasChildren;
2518 treeElement._updateHasChildren();
2519 if (treeElement.hasChildren !== oldHasChildren)
2520 treeElement.updateTitle();
2524 _updateModifiedNodesSoon: function()
2526 if (this._updateModifiedNodesTimeout)
2528 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 50);
2531 _updateModifiedNodes: function()
2533 if (this._updateModifiedNodesTimeout) {
2534 clearTimeout(this._updateModifiedNodesTimeout);
2535 delete this._updateModifiedNodesTimeout;
2538 var updatedParentTreeElements = [];
2540 var hidePanelWhileUpdating = this._recentlyModifiedNodes.size() > 10;
2541 if (hidePanelWhileUpdating) {
2542 var treeOutlineContainerElement = this._treeOutline.element.parentNode;
2543 var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainerElement.scrollTop : 0;
2544 this._treeOutline.element.classList.add("hidden");
2547 var nodes = this._recentlyModifiedNodes.keys();
2548 for (var i = 0, size = nodes.length; i < size; ++i) {
2549 var node = nodes[i];
2550 var entry = this._recentlyModifiedNodes.get(node);
2551 var parent = entry.parent;
2553 if (parent === this._treeOutline._rootDOMNode) {
2554 // Document's children have changed, perform total update.
2555 this._treeOutline.update();
2556 this._treeOutline.element.classList.remove("hidden");
2560 if (entry.isUpdated) {
2561 var nodeItem = this._treeOutline.findTreeElement(node);
2563 nodeItem.updateTitle();
2566 var parentNodeItem = parent ? this._treeOutline.findTreeElement(parent) : null;
2567 if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
2568 parentNodeItem.updateChildren();
2569 parentNodeItem.alreadyUpdatedChildren = true;
2570 updatedParentTreeElements.push(parentNodeItem);
2574 for (var i = 0; i < updatedParentTreeElements.length; ++i)
2575 delete updatedParentTreeElements[i].alreadyUpdatedChildren;
2577 if (hidePanelWhileUpdating) {
2578 this._treeOutline.element.classList.remove("hidden");
2579 if (originalScrollTop)
2580 treeOutlineContainerElement.scrollTop = originalScrollTop;
2581 this._treeOutline.updateSelection();
2583 this._recentlyModifiedNodes.clear();
2585 this._treeOutline._fireElementsTreeUpdated(nodes);
2590 this._treeOutline.rootDOMNode = null;
2591 this._treeOutline.selectDOMNode(null, false);
2592 WebInspector.domModel.hideDOMNodeHighlight();
2593 this._recentlyModifiedNodes.clear();
2599 * @param {boolean} isUpdated
2600 * @param {!WebInspector.DOMNode=} parent
2602 WebInspector.ElementsTreeUpdater.UpdateEntry = function(isUpdated, parent)
2604 this.isUpdated = isUpdated;
2606 this.parent = parent;
2611 * @implements {WebInspector.Renderer}
2613 WebInspector.ElementsTreeOutline.Renderer = function()
2617 WebInspector.ElementsTreeOutline.Renderer.prototype = {
2619 * @param {!Object} object
2620 * @return {?Element}
2622 render: function(object)
2624 if (!(object instanceof WebInspector.DOMNode))
2626 var treeOutline = new WebInspector.ElementsTreeOutline(false, false);
2627 treeOutline.rootDOMNode = /** @type {!WebInspector.DOMNode} */ (object);
2628 treeOutline.element.classList.add("outline-disclosure");
2629 if (!treeOutline.children[0].hasChildren)
2630 treeOutline.element.classList.add("single-node");
2631 treeOutline.setVisible(true);
2632 treeOutline.element.treeElementForTest = treeOutline.children[0];
2633 return treeOutline.element;