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 {boolean=} showInElementsPanelEnabled
37 * @param {function(WebInspector.ContextMenu, WebInspector.DOMNode)=} contextMenuCallback
39 WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, showInElementsPanelEnabled, contextMenuCallback)
41 this.element = document.createElement("ol");
42 this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
43 this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
44 this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
45 this.element.addEventListener("dragstart", this._ondragstart.bind(this), false);
46 this.element.addEventListener("dragover", this._ondragover.bind(this), false);
47 this.element.addEventListener("dragleave", this._ondragleave.bind(this), false);
48 this.element.addEventListener("drop", this._ondrop.bind(this), false);
49 this.element.addEventListener("dragend", this._ondragend.bind(this), false);
51 TreeOutline.call(this, this.element);
53 this._includeRootDOMNode = !omitRootDOMNode;
54 this._selectEnabled = selectEnabled;
55 this._showInElementsPanelEnabled = showInElementsPanelEnabled;
56 this._rootDOMNode = null;
57 this._selectDOMNode = null;
58 this._eventSupport = new WebInspector.Object();
59 this._editing = false;
61 this._visible = false;
63 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
64 this._contextMenuCallback = contextMenuCallback;
67 WebInspector.ElementsTreeOutline.Events = {
68 SelectedNodeChanged: "SelectedNodeChanged"
71 WebInspector.ElementsTreeOutline.prototype = {
72 wireToDomAgent: function()
74 this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this);
77 setVisible: function(visible)
79 this._visible = visible;
83 this._updateModifiedNodes();
84 if (this._selectedDOMNode)
85 this._revealAndSelectNode(this._selectedDOMNode, false);
88 addEventListener: function(eventType, listener, thisObject)
90 this._eventSupport.addEventListener(eventType, listener, thisObject);
93 removeEventListener: function(eventType, listener, thisObject)
95 this._eventSupport.removeEventListener(eventType, listener, thisObject);
100 return this._rootDOMNode;
105 if (this._rootDOMNode === x)
108 this._rootDOMNode = x;
110 this._isXMLMimeType = x && x.isXMLNode();
117 return this._isXMLMimeType;
120 selectedDOMNode: function()
122 return this._selectedDOMNode;
125 selectDOMNode: function(node, focus)
127 if (this._selectedDOMNode === node) {
128 this._revealAndSelectNode(node, !focus);
132 this._selectedDOMNode = node;
133 this._revealAndSelectNode(node, !focus);
135 // The _revealAndSelectNode() method might find a different element if there is inlined text,
136 // and the select() call would change the selectedDOMNode and reenter this setter. So to
137 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same
138 // node as the one passed in.
139 if (this._selectedDOMNode === node)
140 this._selectedNodeChanged();
145 return this._editing;
150 var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;
152 this.removeChildren();
154 if (!this.rootDOMNode)
158 if (this._includeRootDOMNode) {
159 treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
160 treeElement.selectable = this._selectEnabled;
161 this.appendChild(treeElement);
163 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
164 var node = this.rootDOMNode.firstChild;
166 treeElement = new WebInspector.ElementsTreeElement(node);
167 treeElement.selectable = this._selectEnabled;
168 this.appendChild(treeElement);
169 node = node.nextSibling;
174 this._revealAndSelectNode(selectedNode, true);
177 updateSelection: function()
179 if (!this.selectedTreeElement)
181 var element = this.treeOutline.selectedTreeElement;
182 element.updateSelection();
185 _selectedNodeChanged: function()
187 this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged);
190 findTreeElement: function(node)
192 function isAncestorNode(ancestor, node)
194 return ancestor.isAncestor(node);
197 function parentNode(node)
199 return node.parentNode;
202 var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
203 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
204 // The text node might have been inlined if it was short, so try to find the parent element.
205 treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
211 createTreeElementFor: function(node)
213 var treeElement = this.findTreeElement(node);
216 if (!node.parentNode)
219 treeElement = this.createTreeElementFor(node.parentNode);
220 if (treeElement && treeElement.showChild(node.index))
221 return treeElement.children[node.index];
226 set suppressRevealAndSelect(x)
228 if (this._suppressRevealAndSelect === x)
230 this._suppressRevealAndSelect = x;
233 _revealAndSelectNode: function(node, omitFocus)
235 if (!node || this._suppressRevealAndSelect)
238 var treeElement = this.createTreeElementFor(node);
242 treeElement.revealAndSelect(omitFocus);
245 _treeElementFromEvent: function(event)
247 var scrollContainer = this.element.parentElement;
249 // We choose this X coordinate based on the knowledge that our list
250 // items extend at least to the right edge of the outer <ol> container.
251 // In the no-word-wrap mode the outer <ol> may be wider than the tree container
252 // (and partially hidden), in which case we are left to use only its right boundary.
253 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36;
257 // Our list items have 1-pixel cracks between them vertically. We avoid
258 // the cracks by checking slightly above and slightly below the mouse
259 // and seeing if we hit the same element each time.
260 var elementUnderMouse = this.treeElementFromPoint(x, y);
261 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
263 if (elementUnderMouse === elementAboveMouse)
264 element = elementUnderMouse;
266 element = this.treeElementFromPoint(x, y + 2);
271 _onmousedown: function(event)
273 var element = this._treeElementFromEvent(event);
275 if (!element || element.isEventWithinDisclosureTriangle(event))
281 _onmousemove: function(event)
283 var element = this._treeElementFromEvent(event);
284 if (element && this._previousHoveredElement === element)
287 if (this._previousHoveredElement) {
288 this._previousHoveredElement.hovered = false;
289 delete this._previousHoveredElement;
293 element.hovered = true;
294 this._previousHoveredElement = element;
296 // Lazily compute tag-specific tooltips.
297 if (element.representedObject && !element.tooltip)
298 element._createTooltipForNode();
301 WebInspector.domAgent.highlightDOMNode(element ? element.representedObject.id : 0);
304 _onmouseout: function(event)
306 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
307 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
310 if (this._previousHoveredElement) {
311 this._previousHoveredElement.hovered = false;
312 delete this._previousHoveredElement;
315 WebInspector.domAgent.hideDOMNodeHighlight();
318 _ondragstart: function(event)
320 var treeElement = this._treeElementFromEvent(event);
324 if (!this._isValidDragSourceOrTarget(treeElement))
327 if (treeElement.representedObject.nodeName() === "BODY" || treeElement.representedObject.nodeName() === "HEAD")
330 event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent);
331 event.dataTransfer.effectAllowed = "copyMove";
332 this._nodeBeingDragged = treeElement.representedObject;
334 WebInspector.domAgent.hideDOMNodeHighlight();
339 _ondragover: function(event)
341 if (!this._nodeBeingDragged)
344 var treeElement = this._treeElementFromEvent(event);
345 if (!this._isValidDragSourceOrTarget(treeElement))
348 var node = treeElement.representedObject;
350 if (node === this._nodeBeingDragged)
352 node = node.parentNode;
355 treeElement.updateSelection();
356 treeElement.listItemElement.addStyleClass("elements-drag-over");
357 this._dragOverTreeElement = treeElement;
358 event.preventDefault();
359 event.dataTransfer.dropEffect = 'move';
363 _ondragleave: function(event)
365 this._clearDragOverTreeElementMarker();
366 event.preventDefault();
370 _isValidDragSourceOrTarget: function(treeElement)
375 var node = treeElement.representedObject;
376 if (!(node instanceof WebInspector.DOMNode))
379 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
385 _ondrop: function(event)
387 event.preventDefault();
388 var treeElement = this._treeElementFromEvent(event);
389 if (this._nodeBeingDragged && treeElement) {
393 if (treeElement._elementCloseTag) {
394 // Drop onto closing tag -> insert as last child.
395 parentNode = treeElement.representedObject;
397 var dragTargetNode = treeElement.representedObject;
398 parentNode = dragTargetNode.parentNode;
399 anchorNode = dragTargetNode;
402 function callback(error, newNodeId)
407 this._updateModifiedNodes();
408 var newNode = WebInspector.domAgent.nodeForId(newNodeId);
410 this.selectDOMNode(newNode, true);
412 this._nodeBeingDragged.moveTo(parentNode, anchorNode, callback.bind(this));
415 delete this._nodeBeingDragged;
418 _ondragend: function(event)
420 event.preventDefault();
421 this._clearDragOverTreeElementMarker();
422 delete this._nodeBeingDragged;
425 _clearDragOverTreeElementMarker: function()
427 if (this._dragOverTreeElement) {
428 this._dragOverTreeElement.updateSelection();
429 this._dragOverTreeElement.listItemElement.removeStyleClass("elements-drag-over");
430 delete this._dragOverTreeElement;
434 _contextMenuEventFired: function(event)
436 if (!this._showInElementsPanelEnabled)
439 var treeElement = this._treeElementFromEvent(event);
443 function focusElement()
445 WebInspector.domAgent.inspectElement(treeElement.representedObject.id);
447 var contextMenu = new WebInspector.ContextMenu();
448 contextMenu.appendItem(WebInspector.UIString("Reveal in Elements Panel"), focusElement.bind(this));
449 contextMenu.show(event);
452 populateContextMenu: function(contextMenu, event)
454 var treeElement = this._treeElementFromEvent(event);
458 var tag = event.target.enclosingNodeOrSelfWithClass("webkit-html-tag");
459 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
460 var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment");
461 var populated = WebInspector.populateHrefContextMenu(contextMenu, this.selectedDOMNode(), event);
462 if (tag && treeElement._populateTagContextMenu) {
464 contextMenu.appendSeparator();
465 treeElement._populateTagContextMenu(contextMenu, event);
467 } else if (textNode && treeElement._populateTextContextMenu) {
469 contextMenu.appendSeparator();
470 treeElement._populateTextContextMenu(contextMenu, textNode);
472 } else if (commentNode && treeElement._populateNodeContextMenu) {
474 contextMenu.appendSeparator();
475 treeElement._populateNodeContextMenu(contextMenu, textNode);
482 adjustCollapsedRange: function()
486 _updateModifiedNodes: function()
488 if (this._elementsTreeUpdater)
489 this._elementsTreeUpdater._updateModifiedNodes();
492 _populateContextMenu: function(contextMenu, node)
494 if (this._contextMenuCallback)
495 this._contextMenuCallback(contextMenu, node);
499 WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
503 * @extends {TreeElement}
504 * @param {boolean=} elementCloseTag
506 WebInspector.ElementsTreeElement = function(node, elementCloseTag)
508 this._elementCloseTag = elementCloseTag;
509 var hasChildrenOverride = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node);
511 // The title will be updated in onattach.
512 TreeElement.call(this, "", node, hasChildrenOverride);
514 if (this.representedObject.nodeType() == Node.ELEMENT_NODE && !elementCloseTag)
515 this._canAddAttributes = true;
516 this._searchQuery = null;
517 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit;
520 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;
522 // A union of HTML4 and HTML5-Draft elements that explicitly
523 // or implicitly (for HTML5) forbid the closing tag.
524 // FIXME: Revise once HTML5 Final is published.
525 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
526 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
527 "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source"
530 // These tags we do not allow editing their tag name.
531 WebInspector.ElementsTreeElement.EditTagBlacklist = [
532 "html", "head", "body"
535 WebInspector.ElementsTreeElement.prototype = {
536 highlightSearchResults: function(searchQuery)
538 if (this._searchQuery !== searchQuery) {
539 this._updateSearchHighlight(false);
540 delete this._highlightResult; // A new search query.
543 this._searchQuery = searchQuery;
544 this._searchHighlightsVisible = true;
545 this.updateTitle(true);
548 hideSearchHighlights: function()
550 delete this._searchHighlightsVisible;
551 this._updateSearchHighlight(false);
554 _updateSearchHighlight: function(show)
556 if (!this._highlightResult)
559 function updateEntryShow(entry)
561 switch (entry.type) {
563 entry.parent.insertBefore(entry.node, entry.nextSibling);
566 entry.node.textContent = entry.newText;
571 function updateEntryHide(entry)
573 switch (entry.type) {
575 if (entry.node.parentElement)
576 entry.node.parentElement.removeChild(entry.node);
579 entry.node.textContent = entry.oldText;
584 var updater = show ? updateEntryShow : updateEntryHide;
586 for (var i = 0, size = this._highlightResult.length; i < size; ++i)
587 updater(this._highlightResult[i]);
592 return this._hovered;
597 if (this._hovered === x)
602 if (this.listItemElement) {
604 this.updateSelection();
605 this.listItemElement.addStyleClass("hovered");
607 this.listItemElement.removeStyleClass("hovered");
612 get expandedChildrenLimit()
614 return this._expandedChildrenLimit;
617 set expandedChildrenLimit(x)
619 if (this._expandedChildrenLimit === x)
622 this._expandedChildrenLimit = x;
623 if (this.treeOutline && !this._updateChildrenInProgress)
624 this._updateChildren(true);
627 get expandedChildCount()
629 var count = this.children.length;
630 if (count && this.children[count - 1]._elementCloseTag)
632 if (count && this.children[count - 1].expandAllButton)
637 showChild: function(index)
639 if (this._elementCloseTag)
642 if (index >= this.expandedChildrenLimit) {
643 this._expandedChildrenLimit = index + 1;
644 this._updateChildren(true);
647 // Whether index-th child is visible in the children tree
648 return this.expandedChildCount > index;
651 _createTooltipForNode: function()
653 var node = this.representedObject;
654 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img")
657 function setTooltip(result)
659 if (!result || result.type !== "string")
663 var properties = JSON.parse(result.description);
664 var offsetWidth = properties[0];
665 var offsetHeight = properties[1];
666 var naturalWidth = properties[2];
667 var naturalHeight = properties[3];
668 if (offsetHeight === naturalHeight && offsetWidth === naturalWidth)
669 this.tooltip = WebInspector.UIString("%d \xd7 %d pixels", offsetWidth, offsetHeight);
671 this.tooltip = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth, offsetHeight, naturalWidth, naturalHeight);
677 function resolvedNode(object)
682 function dimensions()
684 return "[" + this.offsetWidth + "," + this.offsetHeight + "," + this.naturalWidth + "," + this.naturalHeight + "]";
687 object.callFunction(dimensions, setTooltip.bind(this));
690 WebInspector.RemoteObject.resolveNode(node, "", resolvedNode.bind(this));
693 updateSelection: function()
695 var listItemElement = this.listItemElement;
696 if (!listItemElement)
699 if (document.body.offsetWidth <= 0) {
700 // The stylesheet hasn't loaded yet or the window is closed,
701 // so we can't calculate what is need. Return early.
705 if (!this.selectionElement) {
706 this.selectionElement = document.createElement("div");
707 this.selectionElement.className = "selection selected";
708 listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
711 this.selectionElement.style.height = listItemElement.offsetHeight + "px";
717 this.updateSelection();
718 this.listItemElement.addStyleClass("hovered");
722 this._preventFollowingLinksOnDoubleClick();
723 this.listItemElement.draggable = true;
726 _preventFollowingLinksOnDoubleClick: function()
728 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");
732 for (var i = 0; i < links.length; ++i)
733 links[i].preventFollowOnDoubleClick = true;
736 onpopulate: function()
738 if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag)
741 this.updateChildren();
745 * @param {boolean=} fullRefresh
747 updateChildren: function(fullRefresh)
749 if (this._elementCloseTag)
751 this.representedObject.getChildNodes(this._updateChildren.bind(this, fullRefresh));
755 * @param {boolean=} closingTag
757 insertChildElement: function(child, index, closingTag)
759 var newElement = new WebInspector.ElementsTreeElement(child, closingTag);
760 newElement.selectable = this.treeOutline._selectEnabled;
761 this.insertChild(newElement, index);
765 moveChild: function(child, targetIndex)
767 var wasSelected = child.selected;
768 this.removeChild(child);
769 this.insertChild(child, targetIndex);
775 * @param {boolean=} fullRefresh
777 _updateChildren: function(fullRefresh)
779 if (this._updateChildrenInProgress || !this.treeOutline._visible)
782 this._updateChildrenInProgress = true;
783 var selectedNode = this.treeOutline.selectedDOMNode();
784 var originalScrollTop = 0;
786 var treeOutlineContainerElement = this.treeOutline.element.parentNode;
787 originalScrollTop = treeOutlineContainerElement.scrollTop;
788 var selectedTreeElement = this.treeOutline.selectedTreeElement;
789 if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
791 this.removeChildren();
794 var treeElement = this;
795 var treeChildIndex = 0;
798 function updateChildrenOfNode(node)
800 var treeOutline = treeElement.treeOutline;
801 var child = node.firstChild;
803 var currentTreeElement = treeElement.children[treeChildIndex];
804 if (!currentTreeElement || currentTreeElement.representedObject !== child) {
805 // Find any existing element that is later in the children list.
806 var existingTreeElement = null;
807 for (var i = (treeChildIndex + 1), size = treeElement.expandedChildCount; i < size; ++i) {
808 if (treeElement.children[i].representedObject === child) {
809 existingTreeElement = treeElement.children[i];
814 if (existingTreeElement && existingTreeElement.parent === treeElement) {
815 // If an existing element was found and it has the same parent, just move it.
816 treeElement.moveChild(existingTreeElement, treeChildIndex);
818 // No existing element found, insert a new element.
819 if (treeChildIndex < treeElement.expandedChildrenLimit) {
820 var newElement = treeElement.insertChildElement(child, treeChildIndex);
821 if (child === selectedNode)
822 elementToSelect = newElement;
823 if (treeElement.expandedChildCount > treeElement.expandedChildrenLimit)
824 treeElement.expandedChildrenLimit++;
829 child = child.nextSibling;
834 // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
835 for (var i = (this.children.length - 1); i >= 0; --i) {
836 var currentChild = this.children[i];
837 var currentNode = currentChild.representedObject;
838 var currentParentNode = currentNode.parentNode;
840 if (currentParentNode === this.representedObject)
843 var selectedTreeElement = this.treeOutline.selectedTreeElement;
844 if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
847 this.removeChildAtIndex(i);
850 updateChildrenOfNode(this.representedObject);
851 this.adjustCollapsedRange();
853 var lastChild = this.children[this.children.length - 1];
854 if (this.representedObject.nodeType() == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
855 this.insertChildElement(this.representedObject, this.children.length, true);
857 // We want to restore the original selection and tree scroll position after a full refresh, if possible.
858 if (fullRefresh && elementToSelect) {
859 elementToSelect.select();
860 if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight)
861 treeOutlineContainerElement.scrollTop = originalScrollTop;
864 delete this._updateChildrenInProgress;
867 adjustCollapsedRange: function()
869 // Ensure precondition: only the tree elements for node children are found in the tree
870 // (not the Expand All button or the closing tag).
871 if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent)
872 this.removeChild(this.expandAllButtonElement.__treeElement);
874 const node = this.representedObject;
877 const childNodeCount = node.children.length;
879 // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
880 for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i)
881 this.insertChildElement(node.children[i], i);
883 const expandedChildCount = this.expandedChildCount;
884 if (childNodeCount > this.expandedChildCount) {
885 var targetButtonIndex = expandedChildCount;
886 if (!this.expandAllButtonElement) {
887 var button = document.createElement("button");
888 button.className = "show-all-nodes";
890 var item = new TreeElement(button, null, false);
891 item.selectable = false;
892 item.expandAllButton = true;
893 this.insertChild(item, targetButtonIndex);
894 this.expandAllButtonElement = item.listItemElement.firstChild;
895 this.expandAllButtonElement.__treeElement = item;
896 this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
897 } else if (!this.expandAllButtonElement.__treeElement.parent)
898 this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
899 this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount);
900 } else if (this.expandAllButtonElement)
901 delete this.expandAllButtonElement;
904 handleLoadAllChildren: function()
906 this.expandedChildrenLimit = Math.max(this.representedObject._childNodeCount, this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit);
911 if (this._elementCloseTag)
915 this.treeOutline.updateSelection();
918 oncollapse: function()
920 if (this._elementCloseTag)
924 this.treeOutline.updateSelection();
929 if (this.listItemElement) {
930 var tagSpans = this.listItemElement.getElementsByClassName("webkit-html-tag-name");
932 tagSpans[0].scrollIntoViewIfNeeded(false);
934 this.listItemElement.scrollIntoViewIfNeeded(false);
938 onselect: function(treeElement, selectedByUser)
940 this.treeOutline.suppressRevealAndSelect = true;
941 this.treeOutline.selectDOMNode(this.representedObject, selectedByUser);
943 WebInspector.domAgent.highlightDOMNode(this.representedObject.id);
944 this.updateSelection();
945 this.treeOutline.suppressRevealAndSelect = false;
950 var startTagTreeElement = this.treeOutline.findTreeElement(this.representedObject);
951 startTagTreeElement ? startTagTreeElement.remove() : this.remove();
957 // On Enter or Return start editing the first attribute
958 // or create a new attribute on the selected element.
959 if (this.treeOutline.editing)
962 this._startEditing();
964 // prevent a newline from being immediately inserted
968 selectOnMouseDown: function(event)
970 TreeElement.prototype.selectOnMouseDown.call(this, event);
975 if (this.treeOutline._showInElementsPanelEnabled) {
976 WebInspector.showPanel("elements");
977 this.treeOutline.selectDOMNode(this.representedObject, true);
980 // Prevent selecting the nearest word on double click.
981 if (event.detail >= 2)
982 event.preventDefault();
985 ondblclick: function(event)
987 if (this._editing || this._elementCloseTag)
990 if (this._startEditingTarget(event.target))
993 if (this.hasChildren && !this.expanded)
997 _insertInLastAttributePosition: function(tag, node)
999 if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
1000 tag.insertBefore(node, tag.lastChild);
1002 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
1003 tag.textContent = '';
1004 tag.appendChild(document.createTextNode('<'+nodeName));
1005 tag.appendChild(node);
1006 tag.appendChild(document.createTextNode('>'));
1009 this.updateSelection();
1012 _startEditingTarget: function(eventTarget)
1014 if (this.treeOutline.selectedDOMNode() != this.representedObject)
1017 if (this.representedObject.nodeType() != Node.ELEMENT_NODE && this.representedObject.nodeType() != Node.TEXT_NODE)
1020 var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node");
1022 return this._startEditingTextNode(textNode);
1024 var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute");
1026 return this._startEditingAttribute(attribute, eventTarget);
1028 var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
1030 return this._startEditingTagName(tagName);
1032 var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute");
1034 return this._addNewAttribute();
1039 _populateTagContextMenu: function(contextMenu, event)
1041 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
1042 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
1044 // Add attribute-related actions.
1045 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add attribute" : "Add Attribute"), this._addNewAttribute.bind(this));
1046 if (attribute && !newAttribute)
1047 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit attribute" : "Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
1048 contextMenu.appendSeparator();
1050 this._populateNodeContextMenu(contextMenu);
1051 this.treeOutline._populateContextMenu(contextMenu, this.representedObject);
1054 _populateTextContextMenu: function(contextMenu, textNode)
1056 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode));
1057 this._populateNodeContextMenu(contextMenu);
1060 _populateNodeContextMenu: function(contextMenu)
1062 // Add free-form node-related actions.
1063 contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
1064 contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
1065 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete node" : "Delete Node"), this.remove.bind(this));
1068 _startEditing: function()
1070 if (this.treeOutline.selectedDOMNode() !== this.representedObject)
1073 var listItem = this._listItemNode;
1075 if (this._canAddAttributes) {
1076 var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
1078 return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);
1080 return this._addNewAttribute();
1083 if (this.representedObject.nodeType() === Node.TEXT_NODE) {
1084 var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
1086 return this._startEditingTextNode(textNode);
1091 _addNewAttribute: function()
1093 // Cannot just convert the textual html into an element without
1094 // a parent node. Use a temporary span container for the HTML.
1095 var container = document.createElement("span");
1096 this._buildAttributeDOM(container, " ", "");
1097 var attr = container.firstChild;
1098 attr.style.marginLeft = "2px"; // overrides the .editing margin rule
1099 attr.style.marginRight = "2px"; // overrides the .editing margin rule
1101 var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
1102 this._insertInLastAttributePosition(tag, attr);
1103 return this._startEditingAttribute(attr, attr);
1106 _triggerEditAttribute: function(attributeName)
1108 var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
1109 for (var i = 0, len = attributeElements.length; i < len; ++i) {
1110 if (attributeElements[i].textContent === attributeName) {
1111 for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
1112 if (elem.nodeType !== Node.ELEMENT_NODE)
1115 if (elem.hasStyleClass("webkit-html-attribute-value"))
1116 return this._startEditingAttribute(elem.parentNode, elem);
1122 _startEditingAttribute: function(attribute, elementForSelection)
1124 if (WebInspector.isBeingEdited(attribute))
1127 var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
1128 if (!attributeNameElement)
1131 var attributeName = attributeNameElement.textContent;
1133 function removeZeroWidthSpaceRecursive(node)
1135 if (node.nodeType === Node.TEXT_NODE) {
1136 node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
1140 if (node.nodeType !== Node.ELEMENT_NODE)
1143 for (var child = node.firstChild; child; child = child.nextSibling)
1144 removeZeroWidthSpaceRecursive(child);
1147 // Remove zero-width spaces that were added by nodeTitleInfo.
1148 removeZeroWidthSpaceRecursive(attribute);
1150 var config = new WebInspector.EditingConfig(this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
1151 this._editing = WebInspector.startEditing(attribute, config);
1153 window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
1158 _startEditingTextNode: function(textNode)
1160 if (WebInspector.isBeingEdited(textNode))
1163 var config = new WebInspector.EditingConfig(this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
1164 this._editing = WebInspector.startEditing(textNode, config);
1165 window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
1170 _startEditingTagName: function(tagNameElement)
1172 if (!tagNameElement) {
1173 tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0];
1174 if (!tagNameElement)
1178 var tagName = tagNameElement.textContent;
1179 if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()])
1182 if (WebInspector.isBeingEdited(tagNameElement))
1185 var closingTagElement = this._distinctClosingTagElement();
1187 function keyupListener(event)
1189 if (closingTagElement)
1190 closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
1193 function editingComitted(element, newTagName)
1195 tagNameElement.removeEventListener('keyup', keyupListener, false);
1196 this._tagNameEditingCommitted.apply(this, arguments);
1199 function editingCancelled()
1201 tagNameElement.removeEventListener('keyup', keyupListener, false);
1202 this._editingCancelled.apply(this, arguments);
1205 tagNameElement.addEventListener('keyup', keyupListener, false);
1207 var config = new WebInspector.EditingConfig(editingComitted.bind(this), editingCancelled.bind(this), tagName);
1208 this._editing = WebInspector.startEditing(tagNameElement, config);
1209 window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
1213 _startEditingAsHTML: function(commitCallback, error, initialValue)
1217 if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement))
1220 this._htmlEditElement = document.createElement("div");
1221 this._htmlEditElement.className = "source-code elements-tree-editor";
1222 this._htmlEditElement.textContent = initialValue;
1224 // Hide header items.
1225 var child = this.listItemElement.firstChild;
1227 child.style.display = "none";
1228 child = child.nextSibling;
1230 // Hide children item.
1231 if (this._childrenListNode)
1232 this._childrenListNode.style.display = "none";
1234 this.listItemElement.appendChild(this._htmlEditElement);
1236 this.updateSelection();
1240 commitCallback(this._htmlEditElement.textContent);
1246 this._editing = false;
1249 this.listItemElement.removeChild(this._htmlEditElement);
1250 delete this._htmlEditElement;
1251 // Unhide children item.
1252 if (this._childrenListNode)
1253 this._childrenListNode.style.removeProperty("display");
1254 // Unhide header items.
1255 var child = this.listItemElement.firstChild;
1257 child.style.removeProperty("display");
1258 child = child.nextSibling;
1261 this.updateSelection();
1264 var config = new WebInspector.EditingConfig(commit.bind(this), dispose.bind(this));
1265 config.setMultiline(true);
1266 this._editing = WebInspector.startEditing(this._htmlEditElement, config);
1269 _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
1271 this._editing = false;
1273 var treeOutline = this.treeOutline;
1274 function moveToNextAttributeIfNeeded(error)
1277 this._editingCancelled(element, attributeName);
1282 treeOutline._updateModifiedNodes();
1284 // Search for the attribute's position, and then decide where to move to.
1285 var attributes = this.representedObject.attributes();
1286 for (var i = 0; i < attributes.length; ++i) {
1287 if (attributes[i].name !== attributeName)
1290 if (moveDirection === "backward") {
1292 this._startEditingTagName();
1294 this._triggerEditAttribute(attributes[i - 1].name);
1296 if (i === attributes.length - 1)
1297 this._addNewAttribute();
1299 this._triggerEditAttribute(attributes[i + 1].name);
1304 // Moving From the "New Attribute" position.
1305 if (moveDirection === "backward") {
1306 if (newText === " ") {
1307 // Moving from "New Attribute" that was not edited
1308 if (attributes.length > 0)
1309 this._triggerEditAttribute(attributes[attributes.length - 1].name);
1311 // Moving from "New Attribute" that holds new value
1312 if (attributes.length > 1)
1313 this._triggerEditAttribute(attributes[attributes.length - 2].name);
1315 } else if (moveDirection === "forward") {
1316 if (!/^\s*$/.test(newText))
1317 this._addNewAttribute();
1319 this._startEditingTagName();
1323 this.representedObject.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this));
1326 _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection)
1328 this._editing = false;
1333 var closingTagElement = self._distinctClosingTagElement();
1334 if (closingTagElement)
1335 closingTagElement.textContent = "</" + tagName + ">";
1337 self._editingCancelled(element, tagName);
1338 moveToNextAttributeIfNeeded.call(self);
1341 function moveToNextAttributeIfNeeded()
1343 if (moveDirection !== "forward") {
1344 this._addNewAttribute();
1348 var attributes = this.representedObject.attributes();
1349 if (attributes.length > 0)
1350 this._triggerEditAttribute(attributes[0].name);
1352 this._addNewAttribute();
1355 newText = newText.trim();
1356 if (newText === oldText) {
1361 var treeOutline = this.treeOutline;
1362 var wasExpanded = this.expanded;
1364 function changeTagNameCallback(error, nodeId)
1366 if (error || !nodeId) {
1371 var node = WebInspector.domAgent.nodeForId(nodeId);
1373 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
1374 treeOutline._updateModifiedNodes();
1375 treeOutline.selectDOMNode(node, true);
1377 var newTreeItem = treeOutline.findTreeElement(node);
1379 newTreeItem.expand();
1381 moveToNextAttributeIfNeeded.call(newTreeItem);
1384 this.representedObject.setNodeName(newText, changeTagNameCallback);
1387 _textNodeEditingCommitted: function(element, newText)
1389 this._editing = false;
1392 if (this.representedObject.nodeType() === Node.ELEMENT_NODE) {
1393 // We only show text nodes inline in elements if the element only
1394 // has a single child, and that child is a text node.
1395 textNode = this.representedObject.firstChild;
1396 } else if (this.representedObject.nodeType() == Node.TEXT_NODE)
1397 textNode = this.representedObject;
1399 textNode.setNodeValue(newText, this.updateTitle.bind(this));
1402 _editingCancelled: function(element, context)
1404 this._editing = false;
1406 // Need to restore attributes structure.
1410 _distinctClosingTagElement: function()
1412 // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM
1414 // For an expanded element, it will be the last element with class "close"
1415 // in the child element list.
1416 if (this.expanded) {
1417 var closers = this._childrenListNode.querySelectorAll(".close");
1418 return closers[closers.length-1];
1421 // Remaining cases are single line non-expanded elements with a closing
1422 // tag, or HTML elements without a closing tag (such as <br>). Return
1423 // null in the case where there isn't a closing tag.
1424 var tags = this.listItemElement.getElementsByClassName("webkit-html-tag");
1425 return (tags.length === 1 ? null : tags[tags.length-1]);
1429 * @param {boolean=} onlySearchQueryChanged
1431 updateTitle: function(onlySearchQueryChanged)
1433 // If we are editing, return early to prevent canceling the edit.
1434 // After editing is committed updateTitle will be called.
1438 if (onlySearchQueryChanged) {
1439 if (this._highlightResult)
1440 this._updateSearchHighlight(false);
1442 var highlightElement = document.createElement("span");
1443 highlightElement.className = "highlight";
1444 highlightElement.appendChild(this._nodeTitleInfo(WebInspector.linkifyURLAsNode).titleDOM);
1445 this.title = highlightElement;
1446 delete this._highlightResult;
1449 delete this.selectionElement;
1450 this.updateSelection();
1451 this._preventFollowingLinksOnDoubleClick();
1452 this._highlightSearchResults();
1456 * @param {WebInspector.DOMNode=} node
1457 * @param {function(string, string, string, boolean=, string=)=} linkify
1459 _buildAttributeDOM: function(parentElement, name, value, node, linkify)
1461 var hasText = (value.length > 0);
1462 var attrSpanElement = parentElement.createChild("span", "webkit-html-attribute");
1463 var attrNameElement = attrSpanElement.createChild("span", "webkit-html-attribute-name");
1464 attrNameElement.textContent = name;
1467 attrSpanElement.appendChild(document.createTextNode("=\u200B\""));
1469 if (linkify && (name === "src" || name === "href")) {
1470 var rewrittenHref = WebInspector.resourceURLForRelatedNode(node, value);
1471 value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
1472 if (rewrittenHref === null) {
1473 var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value");
1474 attrValueElement.textContent = value;
1476 if (value.indexOf("data:") === 0)
1477 value = value.trimMiddle(60);
1478 attrSpanElement.appendChild(linkify(rewrittenHref, value, "webkit-html-attribute-value", node.nodeName().toLowerCase() === "a"));
1481 value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
1482 var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value");
1483 attrValueElement.textContent = value;
1487 attrSpanElement.appendChild(document.createTextNode("\""));
1491 * @param {function(string, string, string, boolean=, string=)=} linkify
1493 _buildTagDOM: function(parentElement, tagName, isClosingTag, isDistinctTreeElement, linkify)
1495 var node = /** @type WebInspector.DOMNode */ this.representedObject;
1496 var classes = [ "webkit-html-tag" ];
1497 if (isClosingTag && isDistinctTreeElement)
1498 classes.push("close");
1499 var tagElement = parentElement.createChild("span", classes.join(" "));
1500 tagElement.appendChild(document.createTextNode("<"));
1501 var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name");
1502 tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName;
1503 if (!isClosingTag && node.hasAttributes()) {
1504 var attributes = node.attributes();
1505 for (var i = 0; i < attributes.length; ++i) {
1506 var attr = attributes[i];
1507 tagElement.appendChild(document.createTextNode(" "));
1508 this._buildAttributeDOM(tagElement, attr.name, attr.value, node, linkify);
1511 tagElement.appendChild(document.createTextNode(">"));
1512 parentElement.appendChild(document.createTextNode("\u200B"));
1515 _nodeTitleInfo: function(linkify)
1517 var node = this.representedObject;
1518 var info = {titleDOM: document.createDocumentFragment(), hasChildren: this.hasChildren};
1520 switch (node.nodeType()) {
1521 case Node.DOCUMENT_NODE:
1522 info.titleDOM.appendChild(document.createTextNode("Document"));
1525 case Node.DOCUMENT_FRAGMENT_NODE:
1526 info.titleDOM.appendChild(document.createTextNode("Document Fragment"));
1529 case Node.ATTRIBUTE_NODE:
1530 var value = node.value || "\u200B"; // Zero width space to force showing an empty value.
1531 this._buildAttributeDOM(info.titleDOM, node.name, value);
1534 case Node.ELEMENT_NODE:
1535 var tagName = node.nodeNameInCorrectCase();
1536 if (this._elementCloseTag) {
1537 this._buildTagDOM(info.titleDOM, tagName, true, true);
1538 info.hasChildren = false;
1542 this._buildTagDOM(info.titleDOM, tagName, false, false, linkify);
1544 var textChild = this._singleTextChild(node);
1545 var showInlineText = textChild && textChild.nodeValue().length < Preferences.maxInlineTextChildLength;
1547 if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName]))) {
1548 if (this.hasChildren) {
1549 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
1550 textNodeElement.textContent = "\u2026";
1551 info.titleDOM.appendChild(document.createTextNode("\u200B"));
1553 this._buildTagDOM(info.titleDOM, tagName, true, false);
1556 // If this element only has a single child that is a text node,
1557 // just show that text and the closing tag inline rather than
1558 // create a subtree for them
1559 if (showInlineText) {
1560 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
1561 textNodeElement.textContent = textChild.nodeValue();
1562 info.titleDOM.appendChild(document.createTextNode("\u200B"));
1563 this._buildTagDOM(info.titleDOM, tagName, true, false);
1564 info.hasChildren = false;
1568 case Node.TEXT_NODE:
1569 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") {
1570 var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-js-node");
1571 newNode.textContent = node.nodeValue();
1573 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true);
1574 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);
1575 } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "style") {
1576 var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-css-node");
1577 newNode.textContent = node.nodeValue();
1579 var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css", true);
1580 cssSyntaxHighlighter.syntaxHighlightNode(newNode);
1582 info.titleDOM.appendChild(document.createTextNode("\""));
1583 var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
1584 textNodeElement.textContent = node.nodeValue();
1585 info.titleDOM.appendChild(document.createTextNode("\""));
1589 case Node.COMMENT_NODE:
1590 var commentElement = info.titleDOM.createChild("span", "webkit-html-comment");
1591 commentElement.appendChild(document.createTextNode("<!--" + node.nodeValue() + "-->"));
1594 case Node.DOCUMENT_TYPE_NODE:
1595 var docTypeElement = info.titleDOM.createChild("span", "webkit-html-doctype");
1596 docTypeElement.appendChild(document.createTextNode("<!DOCTYPE " + node.nodeName()));
1597 if (node.publicId) {
1598 docTypeElement.appendChild(document.createTextNode(" PUBLIC \"" + node.publicId + "\""));
1600 docTypeElement.appendChild(document.createTextNode(" \"" + node.systemId + "\""));
1601 } else if (node.systemId)
1602 docTypeElement.appendChild(document.createTextNode(" SYSTEM \"" + node.systemId + "\""));
1604 if (node.internalSubset)
1605 docTypeElement.appendChild(document.createTextNode(" [" + node.internalSubset + "]"));
1607 docTypeElement.appendChild(document.createTextNode(">"));
1610 case Node.CDATA_SECTION_NODE:
1611 var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node");
1612 cdataElement.appendChild(document.createTextNode("<![CDATA[" + node.nodeValue() + "]]>"));
1615 var defaultElement = info.titleDOM.appendChild(document.createTextNode(node.nodeNameInCorrectCase().collapseWhitespace()));
1621 _singleTextChild: function(node)
1626 var firstChild = node.firstChild;
1627 if (!firstChild || firstChild.nodeType() !== Node.TEXT_NODE)
1630 var sibling = firstChild.nextSibling;
1631 return sibling ? null : firstChild;
1634 _showInlineText: function(node)
1636 if (node.nodeType() === Node.ELEMENT_NODE) {
1637 var textChild = this._singleTextChild(node);
1638 if (textChild && textChild.nodeValue().length < Preferences.maxInlineTextChildLength)
1646 var parentElement = this.parent;
1651 function removeNodeCallback(error, removedNodeId)
1656 parentElement.removeChild(self);
1657 parentElement.adjustCollapsedRange();
1660 this.representedObject.removeNode(removeNodeCallback);
1663 _editAsHTML: function()
1665 var treeOutline = this.treeOutline;
1666 var node = this.representedObject;
1667 var wasExpanded = this.expanded;
1669 function selectNode(error, nodeId)
1671 if (error || !nodeId)
1674 var node = WebInspector.domAgent.nodeForId(nodeId);
1675 // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
1676 treeOutline._updateModifiedNodes();
1677 treeOutline.selectDOMNode(node, true);
1680 var newTreeItem = treeOutline.findTreeElement(node);
1682 newTreeItem.expand();
1686 function commitChange(value)
1688 node.setOuterHTML(value, selectNode);
1691 node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange));
1694 _copyHTML: function()
1696 this.representedObject.copyNode();
1699 _highlightSearchResults: function()
1701 if (!this._searchQuery || !this._searchHighlightsVisible)
1703 if (this._highlightResult) {
1704 this._updateSearchHighlight(true);
1708 var text = this.listItemElement.textContent;
1709 var regexObject = createPlainTextSearchRegex(this._searchQuery, "gi");
1712 var match = regexObject.exec(text);
1713 var matchRanges = [];
1715 matchRanges.push({ offset: match.index, length: match[0].length });
1716 match = regexObject.exec(text);
1719 // Fall back for XPath, etc. matches.
1720 if (!matchRanges.length)
1721 matchRanges.push({ offset: 0, length: text.length });
1723 this._highlightResult = [];
1724 highlightSearchResults(this.listItemElement, matchRanges, this._highlightResult);
1728 WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;
1733 WebInspector.ElementsTreeUpdater = function(treeOutline)
1735 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeInserted, this._nodeInserted, this);
1736 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this);
1737 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._attributesUpdated, this);
1738 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._attributesUpdated, this);
1739 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.CharacterDataModified, this._characterDataModified, this);
1740 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdated, this);
1741 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
1743 this._treeOutline = treeOutline;
1744 this._recentlyModifiedNodes = [];
1747 WebInspector.ElementsTreeUpdater.prototype = {
1748 _documentUpdated: function(event)
1750 var inspectedRootDocument = event.data;
1754 if (!inspectedRootDocument)
1757 this._treeOutline.rootDOMNode = inspectedRootDocument;
1760 _attributesUpdated: function(event)
1762 this._recentlyModifiedNodes.push({node: event.data.node, updated: true});
1763 if (this._treeOutline._visible)
1764 this._updateModifiedNodesSoon();
1767 _characterDataModified: function(event)
1769 this._recentlyModifiedNodes.push({node: event.data, updated: true});
1770 if (this._treeOutline._visible)
1771 this._updateModifiedNodesSoon();
1774 _nodeInserted: function(event)
1776 this._recentlyModifiedNodes.push({node: event.data, parent: event.data.parentNode, inserted: true});
1777 if (this._treeOutline._visible)
1778 this._updateModifiedNodesSoon();
1781 _nodeRemoved: function(event)
1783 this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, removed: true});
1784 if (this._treeOutline._visible)
1785 this._updateModifiedNodesSoon();
1788 _childNodeCountUpdated: function(event)
1790 var treeElement = this._treeOutline.findTreeElement(event.data);
1792 treeElement.hasChildren = event.data.hasChildNodes();
1795 _updateModifiedNodesSoon: function()
1797 if (this._updateModifiedNodesTimeout)
1799 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0);
1802 _updateModifiedNodes: function()
1804 if (this._updateModifiedNodesTimeout) {
1805 clearTimeout(this._updateModifiedNodesTimeout);
1806 delete this._updateModifiedNodesTimeout;
1809 var updatedParentTreeElements = [];
1811 for (var i = 0; i < this._recentlyModifiedNodes.length; ++i) {
1812 var parent = this._recentlyModifiedNodes[i].parent;
1813 var node = this._recentlyModifiedNodes[i].node;
1815 if (this._recentlyModifiedNodes[i].updated) {
1816 var nodeItem = this._treeOutline.findTreeElement(node);
1818 nodeItem.updateTitle();
1825 var parentNodeItem = this._treeOutline.findTreeElement(parent);
1826 if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
1827 parentNodeItem.updateChildren();
1828 parentNodeItem.alreadyUpdatedChildren = true;
1829 updatedParentTreeElements.push(parentNodeItem);
1833 for (var i = 0; i < updatedParentTreeElements.length; ++i)
1834 delete updatedParentTreeElements[i].alreadyUpdatedChildren;
1836 this._recentlyModifiedNodes = [];
1841 this._treeOutline.rootDOMNode = null;
1842 this._treeOutline.selectDOMNode(null, false);
1843 WebInspector.domAgent.hideDOMNodeHighlight();
1844 this._recentlyModifiedNodes = [];