Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / elements / ElementsTreeElement.js
blobec4d823a166df6d6c65aa7f66490c3f6ef561306
1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 /**
32 * @constructor
33 * @extends {TreeElement}
34 * @param {!WebInspector.DOMNode} node
35 * @param {boolean=} elementCloseTag
37 WebInspector.ElementsTreeElement = function(node, elementCloseTag)
39 // The title will be updated in onattach.
40 TreeElement.call(this);
41 this._node = node;
43 this._gutterContainer = this.listItemElement.createChild("div", "gutter-container");
44 this._decorationsElement = this._gutterContainer.createChild("div", "hidden");
45 this._decorationsElement.addEventListener("mousedown", this._decorationsClicked.bind(this));
47 this._elementCloseTag = elementCloseTag;
49 if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag)
50 this._canAddAttributes = true;
51 this._searchQuery = null;
52 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit;
53 if (this._node.nodeType() === Node.ELEMENT_NODE && this._node.parentNode && this._node.parentNode.nodeType() === Node.DOCUMENT_NODE && !this._node.parentNode.parentNode)
54 this.setCollapsible(false);
57 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;
59 // A union of HTML4 and HTML5-Draft elements that explicitly
60 // or implicitly (for HTML5) forbid the closing tag.
61 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
62 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
63 "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"
64 ].keySet();
66 // These tags we do not allow editing their tag name.
67 WebInspector.ElementsTreeElement.EditTagBlacklist = [
68 "html", "head", "body"
69 ].keySet();
71 /**
72 * @param {!WebInspector.ElementsTreeElement} treeElement
74 WebInspector.ElementsTreeElement.animateOnDOMUpdate = function(treeElement)
76 var tagName = treeElement.listItemElement.querySelector(".webkit-html-tag-name");
77 WebInspector.runCSSAnimationOnce(tagName || treeElement.listItemElement, "dom-update-highlight");
80 /**
81 * @param {!WebInspector.DOMNode} node
82 * @return {!Array<!WebInspector.DOMNode>}
84 WebInspector.ElementsTreeElement.visibleShadowRoots = function(node)
86 var roots = node.shadowRoots();
87 if (roots.length && !WebInspector.moduleSetting("showUAShadowDOM").get())
88 roots = roots.filter(filter);
90 /**
91 * @param {!WebInspector.DOMNode} root
93 function filter(root)
95 return root.shadowRootType() !== WebInspector.DOMNode.ShadowRootTypes.UserAgent;
97 return roots;
101 * @param {!WebInspector.DOMNode} node
102 * @return {boolean}
104 WebInspector.ElementsTreeElement.canShowInlineText = function(node)
106 if (node.importedDocument() || node.templateContent() || WebInspector.ElementsTreeElement.visibleShadowRoots(node).length || node.hasPseudoElements())
107 return false;
108 if (node.nodeType() !== Node.ELEMENT_NODE)
109 return false;
110 if (!node.firstChild || node.firstChild !== node.lastChild || node.firstChild.nodeType() !== Node.TEXT_NODE)
111 return false;
112 var textChild = node.firstChild;
113 var maxInlineTextChildLength = 80;
114 if (textChild.nodeValue().length < maxInlineTextChildLength)
115 return true;
116 return false;
120 * @param {!WebInspector.ContextSubMenuItem} subMenu
121 * @param {!WebInspector.DOMNode} node
123 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems = function(subMenu, node)
125 const pseudoClasses = ["active", "hover", "focus", "visited"];
126 var forcedPseudoState = WebInspector.CSSStyleModel.fromNode(node).pseudoState(node);
127 for (var i = 0; i < pseudoClasses.length; ++i) {
128 var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0;
129 subMenu.appendCheckboxItem(":" + pseudoClasses[i], setPseudoStateCallback.bind(null, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false);
133 * @param {string} pseudoState
134 * @param {boolean} enabled
136 function setPseudoStateCallback(pseudoState, enabled)
138 WebInspector.CSSStyleModel.fromNode(node).forcePseudoState(node, pseudoState, enabled);
142 WebInspector.ElementsTreeElement.prototype = {
144 * @return {boolean}
146 isClosingTag: function()
148 return !!this._elementCloseTag;
152 * @return {!WebInspector.DOMNode}
154 node: function()
156 return this._node;
160 * @return {boolean}
162 isEditing: function()
164 return !!this._editing;
168 * @return {!Element}
170 gutterElement: function()
172 return this._gutterContainer;
175 _decorationsClicked: function()
177 this.treeOutline.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.DecorationsClicked, this._node);
181 * @param {string} searchQuery
183 highlightSearchResults: function(searchQuery)
185 if (this._searchQuery !== searchQuery)
186 this._hideSearchHighlight();
188 this._searchQuery = searchQuery;
189 this._searchHighlightsVisible = true;
190 this.updateTitle(null, true);
193 hideSearchHighlights: function()
195 delete this._searchHighlightsVisible;
196 this._hideSearchHighlight();
199 _hideSearchHighlight: function()
201 if (!this._highlightResult)
202 return;
204 function updateEntryHide(entry)
206 switch (entry.type) {
207 case "added":
208 entry.node.remove();
209 break;
210 case "changed":
211 entry.node.textContent = entry.oldText;
212 break;
216 for (var i = (this._highlightResult.length - 1); i >= 0; --i)
217 updateEntryHide(this._highlightResult[i]);
219 delete this._highlightResult;
223 * @param {boolean} inClipboard
225 setInClipboard: function(inClipboard)
227 if (this._inClipboard === inClipboard)
228 return;
229 this._inClipboard = inClipboard;
230 this.listItemElement.classList.toggle("in-clipboard", inClipboard);
233 get hovered()
235 return this._hovered;
238 set hovered(x)
240 if (this._hovered === x)
241 return;
243 this._hovered = x;
245 if (this.listItemElement) {
246 if (x) {
247 this.updateSelection();
248 this.listItemElement.classList.add("hovered");
249 } else {
250 this.listItemElement.classList.remove("hovered");
256 * @return {number}
258 expandedChildrenLimit: function()
260 return this._expandedChildrenLimit;
264 * @param {number} expandedChildrenLimit
266 setExpandedChildrenLimit: function(expandedChildrenLimit)
268 this._expandedChildrenLimit = expandedChildrenLimit;
271 updateSelection: function()
273 var listItemElement = this.listItemElement;
274 if (!listItemElement)
275 return;
277 if (!this.selectionElement) {
278 this.selectionElement = createElement("div");
279 this.selectionElement.className = "selection selected";
280 listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
283 this.selectionElement.style.height = listItemElement.offsetHeight + "px";
287 * @override
289 onbind: function()
291 if (!this._elementCloseTag)
292 this._node[this.treeOutline.treeElementSymbol()] = this;
296 * @override
298 onunbind: function()
300 if (this._node[this.treeOutline.treeElementSymbol()] === this)
301 this._node[this.treeOutline.treeElementSymbol()] = null;
305 * @override
307 onattach: function()
309 if (this._hovered) {
310 this.updateSelection();
311 this.listItemElement.classList.add("hovered");
314 this.updateTitle();
315 this._preventFollowingLinksOnDoubleClick();
316 this.listItemElement.draggable = true;
319 _preventFollowingLinksOnDoubleClick: function()
321 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");
322 if (!links)
323 return;
325 for (var i = 0; i < links.length; ++i)
326 links[i].preventFollowOnDoubleClick = true;
329 onpopulate: function()
331 this.populated = true;
332 this.treeOutline.populateTreeElement(this);
335 expandRecursively: function()
338 * @this {WebInspector.ElementsTreeElement}
340 function callback()
342 TreeElement.prototype.expandRecursively.call(this, Number.MAX_VALUE);
345 this._node.getSubtree(-1, callback.bind(this));
349 * @override
351 onexpand: function()
353 if (this._elementCloseTag)
354 return;
356 this.updateTitle();
357 this.treeOutline.updateSelection();
360 oncollapse: function()
362 if (this._elementCloseTag)
363 return;
365 this.updateTitle();
366 this.treeOutline.updateSelection();
370 * @override
372 onreveal: function()
374 if (this.listItemElement) {
375 var tagSpans = this.listItemElement.getElementsByClassName("webkit-html-tag-name");
376 if (tagSpans.length)
377 tagSpans[0].scrollIntoViewIfNeeded(true);
378 else
379 this.listItemElement.scrollIntoViewIfNeeded(true);
384 * @override
385 * @param {boolean=} omitFocus
386 * @param {boolean=} selectedByUser
387 * @return {boolean}
389 select: function(omitFocus, selectedByUser)
391 if (this._editing)
392 return false;
393 if (selectedByUser && this.treeOutline.handlePickNode(this.title, this._node))
394 return true;
395 return TreeElement.prototype.select.call(this, omitFocus, selectedByUser);
399 * @override
400 * @param {boolean=} selectedByUser
401 * @return {boolean}
403 onselect: function(selectedByUser)
405 this.treeOutline.suppressRevealAndSelect = true;
406 this.treeOutline.selectDOMNode(this._node, selectedByUser);
407 if (selectedByUser)
408 this._node.highlight();
409 this.updateSelection();
410 this.treeOutline.suppressRevealAndSelect = false;
411 return true;
415 * @override
416 * @return {boolean}
418 ondelete: function()
420 var startTagTreeElement = this.treeOutline.findTreeElement(this._node);
421 startTagTreeElement ? startTagTreeElement.remove() : this.remove();
422 return true;
426 * @override
427 * @return {boolean}
429 onenter: function()
431 // On Enter or Return start editing the first attribute
432 // or create a new attribute on the selected element.
433 if (this._editing)
434 return false;
436 this._startEditing();
438 // prevent a newline from being immediately inserted
439 return true;
442 selectOnMouseDown: function(event)
444 TreeElement.prototype.selectOnMouseDown.call(this, event);
446 if (this._editing)
447 return;
449 // Prevent selecting the nearest word on double click.
450 if (event.detail >= 2)
451 event.preventDefault();
455 * @override
456 * @return {boolean}
458 ondblclick: function(event)
460 if (this._editing || this._elementCloseTag)
461 return false;
463 if (this._startEditingTarget(/** @type {!Element} */(event.target)))
464 return false;
466 if (this.isExpandable() && !this.expanded)
467 this.expand();
468 return false;
472 * @return {boolean}
474 hasEditableNode: function()
476 return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadowRoot();
479 _insertInLastAttributePosition: function(tag, node)
481 if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
482 tag.insertBefore(node, tag.lastChild);
483 else {
484 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
485 tag.textContent = '';
486 tag.createTextChild('<' + nodeName);
487 tag.appendChild(node);
488 tag.createTextChild('>');
491 this.updateSelection();
495 * @param {!Element} eventTarget
496 * @return {boolean}
498 _startEditingTarget: function(eventTarget)
500 if (this.treeOutline.selectedDOMNode() != this._node)
501 return false;
503 if (this._node.nodeType() != Node.ELEMENT_NODE && this._node.nodeType() != Node.TEXT_NODE)
504 return false;
506 if (this.treeOutline.pickNodeMode())
507 return false;
509 var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node");
510 if (textNode)
511 return this._startEditingTextNode(textNode);
513 var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute");
514 if (attribute)
515 return this._startEditingAttribute(attribute, eventTarget);
517 var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
518 if (tagName)
519 return this._startEditingTagName(tagName);
521 var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute");
522 if (newAttribute)
523 return this._addNewAttribute();
525 return false;
529 * @param {!WebInspector.ContextMenu} contextMenu
530 * @param {!Event} event
532 populateTagContextMenu: function(contextMenu, event)
534 // Add attribute-related actions.
535 var treeElement = this._elementCloseTag ? this.treeOutline.findTreeElement(this._node) : this;
536 contextMenu.appendItem(WebInspector.UIString.capitalize("Add ^attribute"), treeElement._addNewAttribute.bind(treeElement));
538 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
539 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
540 if (attribute && !newAttribute)
541 contextMenu.appendItem(WebInspector.UIString.capitalize("Edit ^attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
542 contextMenu.appendSeparator();
543 var pseudoSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString.capitalize("Force ^element ^state"));
544 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems(pseudoSubMenu, treeElement.node());
545 contextMenu.appendSeparator();
546 this.populateNodeContextMenu(contextMenu);
547 this.populateScrollIntoView(contextMenu);
551 * @param {!WebInspector.ContextMenu} contextMenu
553 populateScrollIntoView: function(contextMenu)
555 contextMenu.appendSeparator();
556 contextMenu.appendItem(WebInspector.UIString.capitalize("Scroll into ^view"), this._scrollIntoView.bind(this));
559 populateTextContextMenu: function(contextMenu, textNode)
561 if (!this._editing)
562 contextMenu.appendItem(WebInspector.UIString.capitalize("Edit ^text"), this._startEditingTextNode.bind(this, textNode));
563 this.populateNodeContextMenu(contextMenu);
566 populateNodeContextMenu: function(contextMenu)
568 // Add free-form node-related actions.
569 var openTagElement = this._node[this.treeOutline.treeElementSymbol()] || this;
570 var isEditable = this.hasEditableNode();
571 if (isEditable && !this._editing)
572 contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), openTagElement.toggleEditAsHTML.bind(openTagElement));
573 var isShadowRoot = this._node.isShadowRoot();
575 // Place it here so that all "Copy"-ing items stick together.
576 if (this._node.nodeType() === Node.ELEMENT_NODE)
577 contextMenu.appendItem(WebInspector.UIString.capitalize("Copy CSS ^path"), this._copyCSSPath.bind(this));
578 if (!isShadowRoot)
579 contextMenu.appendItem(WebInspector.UIString("Copy XPath"), this._copyXPath.bind(this));
580 if (!isShadowRoot) {
581 var treeOutline = this.treeOutline;
582 contextMenu.appendSeparator();
583 contextMenu.appendItem(WebInspector.UIString("Cut"), treeOutline.performCopyOrCut.bind(treeOutline, true, this._node), !this.hasEditableNode());
584 contextMenu.appendItem(WebInspector.UIString("Copy"), treeOutline.performCopyOrCut.bind(treeOutline, false, this._node));
585 contextMenu.appendItem(WebInspector.UIString("Paste"), treeOutline.pasteNode.bind(treeOutline, this._node), !treeOutline.canPaste(this._node));
588 if (isEditable)
589 contextMenu.appendItem(WebInspector.UIString("Delete"), this.remove.bind(this));
590 contextMenu.appendSeparator();
593 _startEditing: function()
595 if (this.treeOutline.selectedDOMNode() !== this._node)
596 return;
598 var listItem = this._listItemNode;
600 if (this._canAddAttributes) {
601 var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
602 if (attribute)
603 return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);
605 return this._addNewAttribute();
608 if (this._node.nodeType() === Node.TEXT_NODE) {
609 var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
610 if (textNode)
611 return this._startEditingTextNode(textNode);
612 return;
616 _addNewAttribute: function()
618 // Cannot just convert the textual html into an element without
619 // a parent node. Use a temporary span container for the HTML.
620 var container = createElement("span");
621 this._buildAttributeDOM(container, " ", "", null);
622 var attr = container.firstElementChild;
623 attr.style.marginLeft = "2px"; // overrides the .editing margin rule
624 attr.style.marginRight = "2px"; // overrides the .editing margin rule
626 var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
627 this._insertInLastAttributePosition(tag, attr);
628 attr.scrollIntoViewIfNeeded(true);
629 return this._startEditingAttribute(attr, attr);
632 _triggerEditAttribute: function(attributeName)
634 var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
635 for (var i = 0, len = attributeElements.length; i < len; ++i) {
636 if (attributeElements[i].textContent === attributeName) {
637 for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
638 if (elem.nodeType !== Node.ELEMENT_NODE)
639 continue;
641 if (elem.classList.contains("webkit-html-attribute-value"))
642 return this._startEditingAttribute(elem.parentNode, elem);
648 _startEditingAttribute: function(attribute, elementForSelection)
650 console.assert(this.listItemElement.isAncestor(attribute));
652 if (WebInspector.isBeingEdited(attribute))
653 return true;
655 var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
656 if (!attributeNameElement)
657 return false;
659 var attributeName = attributeNameElement.textContent;
660 var attributeValueElement = attribute.getElementsByClassName("webkit-html-attribute-value")[0];
662 // Make sure elementForSelection is not a child of attributeValueElement.
663 elementForSelection = attributeValueElement.isAncestor(elementForSelection) ? attributeValueElement : elementForSelection;
665 function removeZeroWidthSpaceRecursive(node)
667 if (node.nodeType === Node.TEXT_NODE) {
668 node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
669 return;
672 if (node.nodeType !== Node.ELEMENT_NODE)
673 return;
675 for (var child = node.firstChild; child; child = child.nextSibling)
676 removeZeroWidthSpaceRecursive(child);
679 var attributeValue = attributeName && attributeValueElement ? this._node.getAttribute(attributeName) : undefined;
680 if (attributeValue !== undefined)
681 attributeValueElement.setTextContentTruncatedIfNeeded(attributeValue, WebInspector.UIString("<value is too large to edit>"));
683 // Remove zero-width spaces that were added by nodeTitleInfo.
684 removeZeroWidthSpaceRecursive(attribute);
686 var config = new WebInspector.InplaceEditor.Config(this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
689 * @param {!Event} event
690 * @return {string}
692 function postKeyDownFinishHandler(event)
694 WebInspector.handleElementValueModifications(event, attribute);
695 return "";
697 config.setPostKeydownFinishHandler(postKeyDownFinishHandler);
699 this._editing = WebInspector.InplaceEditor.startEditing(attribute, config);
701 this.listItemElement.getComponentSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
703 return true;
707 * @param {!Element} textNodeElement
709 _startEditingTextNode: function(textNodeElement)
711 if (WebInspector.isBeingEdited(textNodeElement))
712 return true;
714 var textNode = this._node;
715 // We only show text nodes inline in elements if the element only
716 // has a single child, and that child is a text node.
717 if (textNode.nodeType() === Node.ELEMENT_NODE && textNode.firstChild)
718 textNode = textNode.firstChild;
720 var container = textNodeElement.enclosingNodeOrSelfWithClass("webkit-html-text-node");
721 if (container)
722 container.textContent = textNode.nodeValue(); // Strip the CSS or JS highlighting if present.
723 var config = new WebInspector.InplaceEditor.Config(this._textNodeEditingCommitted.bind(this, textNode), this._editingCancelled.bind(this));
724 this._editing = WebInspector.InplaceEditor.startEditing(textNodeElement, config);
725 this.listItemElement.getComponentSelection().setBaseAndExtent(textNodeElement, 0, textNodeElement, 1);
727 return true;
731 * @param {!Element=} tagNameElement
733 _startEditingTagName: function(tagNameElement)
735 if (!tagNameElement) {
736 tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0];
737 if (!tagNameElement)
738 return false;
741 var tagName = tagNameElement.textContent;
742 if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()])
743 return false;
745 if (WebInspector.isBeingEdited(tagNameElement))
746 return true;
748 var closingTagElement = this._distinctClosingTagElement();
751 * @param {!Event} event
753 function keyupListener(event)
755 if (closingTagElement)
756 closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
760 * @param {!Element} element
761 * @param {string} newTagName
762 * @this {WebInspector.ElementsTreeElement}
764 function editingComitted(element, newTagName)
766 tagNameElement.removeEventListener('keyup', keyupListener, false);
767 this._tagNameEditingCommitted.apply(this, arguments);
771 * @this {WebInspector.ElementsTreeElement}
773 function editingCancelled()
775 tagNameElement.removeEventListener('keyup', keyupListener, false);
776 this._editingCancelled.apply(this, arguments);
779 tagNameElement.addEventListener('keyup', keyupListener, false);
781 var config = new WebInspector.InplaceEditor.Config(editingComitted.bind(this), editingCancelled.bind(this), tagName);
782 this._editing = WebInspector.InplaceEditor.startEditing(tagNameElement, config);
783 this.listItemElement.getComponentSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
784 return true;
788 * @param {function(string, string)} commitCallback
789 * @param {function()} disposeCallback
790 * @param {?Protocol.Error} error
791 * @param {string} initialValue
793 _startEditingAsHTML: function(commitCallback, disposeCallback, error, initialValue)
795 if (error)
796 return;
797 if (this._editing)
798 return;
800 function consume(event)
802 if (event.eventPhase === Event.AT_TARGET)
803 event.consume(true);
806 initialValue = this._convertWhitespaceToEntities(initialValue).text;
808 this._htmlEditElement = createElement("div");
809 this._htmlEditElement.className = "source-code elements-tree-editor";
811 // Hide header items.
812 var child = this.listItemElement.firstChild;
813 while (child) {
814 child.style.display = "none";
815 child = child.nextSibling;
817 // Hide children item.
818 if (this._childrenListNode)
819 this._childrenListNode.style.display = "none";
820 // Append editor.
821 this.listItemElement.appendChild(this._htmlEditElement);
822 this.treeOutline.element.addEventListener("mousedown", consume, false);
824 this.updateSelection();
827 * @param {!Element} element
828 * @param {string} newValue
829 * @this {WebInspector.ElementsTreeElement}
831 function commit(element, newValue)
833 commitCallback(initialValue, newValue);
834 dispose.call(this);
838 * @this {WebInspector.ElementsTreeElement}
840 function dispose()
842 disposeCallback();
843 delete this._editing;
844 this.treeOutline.setMultilineEditing(null);
846 // Remove editor.
847 this.listItemElement.removeChild(this._htmlEditElement);
848 delete this._htmlEditElement;
849 // Unhide children item.
850 if (this._childrenListNode)
851 this._childrenListNode.style.removeProperty("display");
852 // Unhide header items.
853 var child = this.listItemElement.firstChild;
854 while (child) {
855 child.style.removeProperty("display");
856 child = child.nextSibling;
859 this.treeOutline.element.removeEventListener("mousedown", consume, false);
860 this.updateSelection();
861 this.treeOutline.focus();
864 var config = new WebInspector.InplaceEditor.Config(commit.bind(this), dispose.bind(this));
865 config.setMultilineOptions(initialValue, { name: "xml", htmlMode: true }, "web-inspector-html", WebInspector.moduleSetting("domWordWrap").get(), true);
866 WebInspector.InplaceEditor.startMultilineEditing(this._htmlEditElement, config).then(markAsBeingEdited.bind(this));
869 * @param {!Object} controller
870 * @this {WebInspector.ElementsTreeElement}
872 function markAsBeingEdited(controller)
874 this._editing = /** @type {!WebInspector.InplaceEditor.Controller} */ (controller);
875 this._editing.setWidth(this.treeOutline.visibleWidth());
876 this.treeOutline.setMultilineEditing(this._editing);
880 _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
882 delete this._editing;
884 var treeOutline = this.treeOutline;
887 * @param {?Protocol.Error=} error
888 * @this {WebInspector.ElementsTreeElement}
890 function moveToNextAttributeIfNeeded(error)
892 if (error)
893 this._editingCancelled(element, attributeName);
895 if (!moveDirection)
896 return;
898 treeOutline.runPendingUpdates();
900 // Search for the attribute's position, and then decide where to move to.
901 var attributes = this._node.attributes();
902 for (var i = 0; i < attributes.length; ++i) {
903 if (attributes[i].name !== attributeName)
904 continue;
906 if (moveDirection === "backward") {
907 if (i === 0)
908 this._startEditingTagName();
909 else
910 this._triggerEditAttribute(attributes[i - 1].name);
911 } else {
912 if (i === attributes.length - 1)
913 this._addNewAttribute();
914 else
915 this._triggerEditAttribute(attributes[i + 1].name);
917 return;
920 // Moving From the "New Attribute" position.
921 if (moveDirection === "backward") {
922 if (newText === " ") {
923 // Moving from "New Attribute" that was not edited
924 if (attributes.length > 0)
925 this._triggerEditAttribute(attributes[attributes.length - 1].name);
926 } else {
927 // Moving from "New Attribute" that holds new value
928 if (attributes.length > 1)
929 this._triggerEditAttribute(attributes[attributes.length - 2].name);
931 } else if (moveDirection === "forward") {
932 if (!/^\s*$/.test(newText))
933 this._addNewAttribute();
934 else
935 this._startEditingTagName();
940 if ((attributeName.trim() || newText.trim()) && oldText !== newText) {
941 this._node.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this));
942 return;
945 this.updateTitle();
946 moveToNextAttributeIfNeeded.call(this);
949 _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection)
951 delete this._editing;
952 var self = this;
954 function cancel()
956 var closingTagElement = self._distinctClosingTagElement();
957 if (closingTagElement)
958 closingTagElement.textContent = "</" + tagName + ">";
960 self._editingCancelled(element, tagName);
961 moveToNextAttributeIfNeeded.call(self);
965 * @this {WebInspector.ElementsTreeElement}
967 function moveToNextAttributeIfNeeded()
969 if (moveDirection !== "forward") {
970 this._addNewAttribute();
971 return;
974 var attributes = this._node.attributes();
975 if (attributes.length > 0)
976 this._triggerEditAttribute(attributes[0].name);
977 else
978 this._addNewAttribute();
981 newText = newText.trim();
982 if (newText === oldText) {
983 cancel();
984 return;
987 var treeOutline = this.treeOutline;
988 var wasExpanded = this.expanded;
990 function changeTagNameCallback(error, nodeId)
992 if (error || !nodeId) {
993 cancel();
994 return;
996 var newTreeItem = treeOutline.selectNodeAfterEdit(wasExpanded, error, nodeId);
997 moveToNextAttributeIfNeeded.call(newTreeItem);
999 this._node.setNodeName(newText, changeTagNameCallback);
1003 * @param {!WebInspector.DOMNode} textNode
1004 * @param {!Element} element
1005 * @param {string} newText
1007 _textNodeEditingCommitted: function(textNode, element, newText)
1009 delete this._editing;
1012 * @this {WebInspector.ElementsTreeElement}
1014 function callback()
1016 this.updateTitle();
1018 textNode.setNodeValue(newText, callback.bind(this));
1022 * @param {!Element} element
1023 * @param {*} context
1025 _editingCancelled: function(element, context)
1027 delete this._editing;
1029 // Need to restore attributes structure.
1030 this.updateTitle();
1034 * @return {!Element}
1036 _distinctClosingTagElement: function()
1038 // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM
1040 // For an expanded element, it will be the last element with class "close"
1041 // in the child element list.
1042 if (this.expanded) {
1043 var closers = this._childrenListNode.querySelectorAll(".close");
1044 return closers[closers.length-1];
1047 // Remaining cases are single line non-expanded elements with a closing
1048 // tag, or HTML elements without a closing tag (such as <br>). Return
1049 // null in the case where there isn't a closing tag.
1050 var tags = this.listItemElement.getElementsByClassName("webkit-html-tag");
1051 return (tags.length === 1 ? null : tags[tags.length-1]);
1055 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord=} updateRecord
1056 * @param {boolean=} onlySearchQueryChanged
1058 updateTitle: function(updateRecord, onlySearchQueryChanged)
1060 // If we are editing, return early to prevent canceling the edit.
1061 // After editing is committed updateTitle will be called.
1062 if (this._editing)
1063 return;
1065 if (onlySearchQueryChanged) {
1066 this._hideSearchHighlight();
1067 } else {
1068 var nodeInfo = this._nodeTitleInfo(updateRecord || null);
1069 if (this._node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE && this._node.isInShadowTree() && this._node.shadowRootType()) {
1070 this.childrenListElement.classList.add("shadow-root");
1071 var depth = 4;
1072 for (var node = this._node; depth && node; node = node.parentNode) {
1073 if (node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE)
1074 depth--;
1076 if (!depth)
1077 this.childrenListElement.classList.add("shadow-root-deep");
1078 else
1079 this.childrenListElement.classList.add("shadow-root-depth-" + depth);
1081 var highlightElement = createElement("span");
1082 highlightElement.className = "highlight";
1083 highlightElement.appendChild(nodeInfo);
1084 this.title = highlightElement;
1085 this.updateDecorations();
1086 this.listItemElement.insertBefore(this._gutterContainer, this.listItemElement.firstChild);
1087 delete this._highlightResult;
1090 delete this.selectionElement;
1091 if (this.selected)
1092 this.updateSelection();
1093 this._preventFollowingLinksOnDoubleClick();
1094 this._highlightSearchResults();
1097 updateDecorations: function()
1099 if (this.isClosingTag())
1100 return;
1101 var node = this._node;
1102 if (node.nodeType() !== Node.ELEMENT_NODE)
1103 return;
1105 var extensions = runtime.extensions(WebInspector.DOMPresentationUtils.MarkerDecorator);
1106 var markerToExtension = new Map();
1107 for (var extension of extensions)
1108 markerToExtension.set(extension.descriptor()["marker"], extension);
1110 var promises = [];
1111 var decorations = [];
1112 var descendantDecorations = [];
1113 node.traverseMarkers(visitor);
1116 * @param {!WebInspector.DOMNode} n
1117 * @param {string} marker
1119 function visitor(n, marker)
1121 var extension = markerToExtension.get(marker);
1122 if (!extension)
1123 return;
1124 promises.push(extension.instancePromise().then(collectDecoration.bind(null, n)));
1128 * @param {!WebInspector.DOMNode} n
1129 * @param {!WebInspector.DOMPresentationUtils.MarkerDecorator} decorator
1131 function collectDecoration(n, decorator)
1133 var decoration = decorator.decorate(n);
1134 if (!decoration)
1135 return;
1136 (n === node ? decorations : descendantDecorations).push(decoration);
1139 Promise.all(promises).then(updateDecorationsUI.bind(this));
1142 * @this {WebInspector.ElementsTreeElement}
1144 function updateDecorationsUI()
1146 this._decorationsElement.removeChildren();
1147 this._decorationsElement.classList.add("hidden");
1148 if (!decorations.length && !descendantDecorations.length)
1149 return;
1151 var colors = new Set();
1152 var titles = createElement("div");
1154 for (var decoration of decorations) {
1155 var titleElement = titles.createChild("div");
1156 titleElement.textContent = decoration.title;
1157 colors.add(decoration.color);
1159 if (this.expanded && !decorations.length)
1160 return;
1162 var descendantColors = new Set();
1163 if (descendantDecorations.length) {
1164 var element = titles.createChild("div");
1165 element.textContent = WebInspector.UIString("Children:");
1166 for (var decoration of descendantDecorations) {
1167 element = titles.createChild("div");
1168 element.style.marginLeft = "15px";
1169 element.textContent = decoration.title;
1170 descendantColors.add(decoration.color);
1174 var offset = 0;
1175 processColors.call(this, colors, "elements-gutter-decoration");
1176 if (!this.expanded)
1177 processColors.call(this, descendantColors, "elements-gutter-decoration elements-has-decorated-children");
1178 WebInspector.Tooltip.install(this._decorationsElement, titles);
1180 this._gutterContainer.classList.toggle("has-decorations", this._decorationsElement.hasChildNodes());
1183 * @param {!Set<string>} colors
1184 * @param {string} className
1185 * @this {WebInspector.ElementsTreeElement}
1187 function processColors(colors, className)
1189 for (var color of colors) {
1190 var child = this._decorationsElement.createChild("div", className);
1191 this._decorationsElement.classList.remove("hidden");
1192 child.style.backgroundColor = color;
1193 child.style.borderColor = color;
1194 if (offset)
1195 child.style.marginLeft = offset + "px";
1196 offset += 3;
1203 * @param {!Node} parentElement
1204 * @param {string} name
1205 * @param {string} value
1206 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord
1207 * @param {boolean=} forceValue
1208 * @param {!WebInspector.DOMNode=} node
1210 _buildAttributeDOM: function(parentElement, name, value, updateRecord, forceValue, node)
1212 var closingPunctuationRegex = /[\/;:\)\]\}]/g;
1213 var highlightIndex = 0;
1214 var highlightCount;
1215 var additionalHighlightOffset = 0;
1216 var result;
1219 * @param {string} match
1220 * @param {number} replaceOffset
1221 * @return {string}
1223 function replacer(match, replaceOffset) {
1224 while (highlightIndex < highlightCount && result.entityRanges[highlightIndex].offset < replaceOffset) {
1225 result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
1226 ++highlightIndex;
1228 additionalHighlightOffset += 1;
1229 return match + "\u200B";
1233 * @param {!Element} element
1234 * @param {string} value
1235 * @this {WebInspector.ElementsTreeElement}
1237 function setValueWithEntities(element, value)
1239 result = this._convertWhitespaceToEntities(value);
1240 highlightCount = result.entityRanges.length;
1241 value = result.text.replace(closingPunctuationRegex, replacer);
1242 while (highlightIndex < highlightCount) {
1243 result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
1244 ++highlightIndex;
1246 element.setTextContentTruncatedIfNeeded(value);
1247 WebInspector.highlightRangesWithStyleClass(element, result.entityRanges, "webkit-html-entity-value");
1250 var hasText = (forceValue || value.length > 0);
1251 var attrSpanElement = parentElement.createChild("span", "webkit-html-attribute");
1252 var attrNameElement = attrSpanElement.createChild("span", "webkit-html-attribute-name");
1253 attrNameElement.textContent = name;
1255 if (hasText)
1256 attrSpanElement.createTextChild("=\u200B\"");
1258 var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value");
1260 if (updateRecord && updateRecord.isAttributeModified(name))
1261 WebInspector.runCSSAnimationOnce(hasText ? attrValueElement : attrNameElement, "dom-update-highlight");
1264 * @this {WebInspector.ElementsTreeElement}
1265 * @param {string} value
1266 * @return {!Element}
1268 function linkifyValue(value)
1270 var rewrittenHref = node.resolveURL(value);
1271 if (rewrittenHref === null) {
1272 var span = createElement("span");
1273 setValueWithEntities.call(this, span, value);
1274 return span;
1276 value = value.replace(closingPunctuationRegex, "$&\u200B");
1277 if (value.startsWith("data:"))
1278 value = value.trimMiddle(60);
1279 var anchor = WebInspector.linkifyURLAsNode(rewrittenHref, value, "", node.nodeName().toLowerCase() === "a");
1280 anchor.preventFollow = true;
1281 return anchor;
1284 if (node && name === "src" || name === "href") {
1285 attrValueElement.appendChild(linkifyValue.call(this, value));
1286 } else if (node && node.nodeName().toLowerCase() === "img" && name === "srcset") {
1287 var sources = value.split(",");
1288 for (var i = 0; i < sources.length; ++i) {
1289 if (i > 0)
1290 attrValueElement.createTextChild(", ");
1291 var source = sources[i].trim();
1292 var indexOfSpace = source.indexOf(" ");
1293 var url = source.substring(0, indexOfSpace);
1294 var tail = source.substring(indexOfSpace);
1295 attrValueElement.appendChild(linkifyValue.call(this, url));
1296 attrValueElement.createTextChild(tail);
1298 } else {
1299 setValueWithEntities.call(this, attrValueElement, value);
1302 if (hasText)
1303 attrSpanElement.createTextChild("\"");
1307 * @param {!Node} parentElement
1308 * @param {string} pseudoElementName
1310 _buildPseudoElementDOM: function(parentElement, pseudoElementName)
1312 var pseudoElement = parentElement.createChild("span", "webkit-html-pseudo-element");
1313 pseudoElement.textContent = "::" + pseudoElementName;
1314 parentElement.createTextChild("\u200B");
1318 * @param {!Node} parentElement
1319 * @param {string} tagName
1320 * @param {boolean} isClosingTag
1321 * @param {boolean} isDistinctTreeElement
1322 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord
1324 _buildTagDOM: function(parentElement, tagName, isClosingTag, isDistinctTreeElement, updateRecord)
1326 var node = this._node;
1327 var classes = [ "webkit-html-tag" ];
1328 if (isClosingTag && isDistinctTreeElement)
1329 classes.push("close");
1330 var tagElement = parentElement.createChild("span", classes.join(" "));
1331 tagElement.createTextChild("<");
1332 var tagNameElement = tagElement.createChild("span", isClosingTag ? "webkit-html-close-tag-name" : "webkit-html-tag-name");
1333 tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName;
1334 if (!isClosingTag) {
1335 if (node.hasAttributes()) {
1336 var attributes = node.attributes();
1337 for (var i = 0; i < attributes.length; ++i) {
1338 var attr = attributes[i];
1339 tagElement.createTextChild(" ");
1340 this._buildAttributeDOM(tagElement, attr.name, attr.value, updateRecord, false, node);
1343 if (updateRecord) {
1344 var hasUpdates = updateRecord.hasRemovedAttributes() || updateRecord.hasRemovedChildren();
1345 hasUpdates |= !this.expanded && updateRecord.hasChangedChildren();
1346 if (hasUpdates)
1347 WebInspector.runCSSAnimationOnce(tagNameElement, "dom-update-highlight");
1351 tagElement.createTextChild(">");
1352 parentElement.createTextChild("\u200B");
1356 * @param {string} text
1357 * @return {!{text: string, entityRanges: !Array.<!WebInspector.SourceRange>}}
1359 _convertWhitespaceToEntities: function(text)
1361 var result = "";
1362 var lastIndexAfterEntity = 0;
1363 var entityRanges = [];
1364 var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity;
1365 for (var i = 0, size = text.length; i < size; ++i) {
1366 var char = text.charAt(i);
1367 if (charToEntity[char]) {
1368 result += text.substring(lastIndexAfterEntity, i);
1369 var entityValue = "&" + charToEntity[char] + ";";
1370 entityRanges.push({offset: result.length, length: entityValue.length});
1371 result += entityValue;
1372 lastIndexAfterEntity = i + 1;
1375 if (result)
1376 result += text.substring(lastIndexAfterEntity);
1377 return {text: result || text, entityRanges: entityRanges};
1381 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord
1382 * @return {!DocumentFragment} result
1384 _nodeTitleInfo: function(updateRecord)
1386 var node = this._node;
1387 var titleDOM = createDocumentFragment();
1389 switch (node.nodeType()) {
1390 case Node.ATTRIBUTE_NODE:
1391 this._buildAttributeDOM(titleDOM, /** @type {string} */ (node.name), /** @type {string} */ (node.value), updateRecord, true);
1392 break;
1394 case Node.ELEMENT_NODE:
1395 var pseudoType = node.pseudoType();
1396 if (pseudoType) {
1397 this._buildPseudoElementDOM(titleDOM, pseudoType);
1398 break;
1401 var tagName = node.nodeNameInCorrectCase();
1402 if (this._elementCloseTag) {
1403 this._buildTagDOM(titleDOM, tagName, true, true, updateRecord);
1404 break;
1407 this._buildTagDOM(titleDOM, tagName, false, false, updateRecord);
1409 if (this.isExpandable()) {
1410 if (!this.expanded) {
1411 var textNodeElement = titleDOM.createChild("span", "webkit-html-text-node bogus");
1412 textNodeElement.textContent = "\u2026";
1413 titleDOM.createTextChild("\u200B");
1414 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord);
1416 break;
1419 if (WebInspector.ElementsTreeElement.canShowInlineText(node)) {
1420 var textNodeElement = titleDOM.createChild("span", "webkit-html-text-node");
1421 var result = this._convertWhitespaceToEntities(node.firstChild.nodeValue());
1422 textNodeElement.textContent = result.text;
1423 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
1424 titleDOM.createTextChild("\u200B");
1425 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord);
1426 if (updateRecord && updateRecord.hasChangedChildren())
1427 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
1428 if (updateRecord && updateRecord.isCharDataModified())
1429 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
1430 break;
1433 if (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName])
1434 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord);
1435 break;
1437 case Node.TEXT_NODE:
1438 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") {
1439 var newNode = titleDOM.createChild("span", "webkit-html-text-node webkit-html-js-node");
1440 newNode.textContent = node.nodeValue();
1442 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true);
1443 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSearchHighlight.bind(this));
1444 } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "style") {
1445 var newNode = titleDOM.createChild("span", "webkit-html-text-node webkit-html-css-node");
1446 newNode.textContent = node.nodeValue();
1448 var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css", true);
1449 cssSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSearchHighlight.bind(this));
1450 } else {
1451 titleDOM.createTextChild("\"");
1452 var textNodeElement = titleDOM.createChild("span", "webkit-html-text-node");
1453 var result = this._convertWhitespaceToEntities(node.nodeValue());
1454 textNodeElement.textContent = result.text;
1455 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
1456 titleDOM.createTextChild("\"");
1457 if (updateRecord && updateRecord.isCharDataModified())
1458 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
1460 break;
1462 case Node.COMMENT_NODE:
1463 var commentElement = titleDOM.createChild("span", "webkit-html-comment");
1464 commentElement.createTextChild("<!--" + node.nodeValue() + "-->");
1465 break;
1467 case Node.DOCUMENT_TYPE_NODE:
1468 var docTypeElement = titleDOM.createChild("span", "webkit-html-doctype");
1469 docTypeElement.createTextChild("<!DOCTYPE " + node.nodeName());
1470 if (node.publicId) {
1471 docTypeElement.createTextChild(" PUBLIC \"" + node.publicId + "\"");
1472 if (node.systemId)
1473 docTypeElement.createTextChild(" \"" + node.systemId + "\"");
1474 } else if (node.systemId)
1475 docTypeElement.createTextChild(" SYSTEM \"" + node.systemId + "\"");
1477 if (node.internalSubset)
1478 docTypeElement.createTextChild(" [" + node.internalSubset + "]");
1480 docTypeElement.createTextChild(">");
1481 break;
1483 case Node.CDATA_SECTION_NODE:
1484 var cdataElement = titleDOM.createChild("span", "webkit-html-text-node");
1485 cdataElement.createTextChild("<![CDATA[" + node.nodeValue() + "]]>");
1486 break;
1488 case Node.DOCUMENT_FRAGMENT_NODE:
1489 var fragmentElement = titleDOM.createChild("span", "webkit-html-fragment");
1490 fragmentElement.textContent = node.nodeNameInCorrectCase().collapseWhitespace();
1491 break;
1492 default:
1493 titleDOM.createTextChild(node.nodeNameInCorrectCase().collapseWhitespace());
1497 * @this {WebInspector.ElementsTreeElement}
1499 function updateSearchHighlight()
1501 delete this._highlightResult;
1502 this._highlightSearchResults();
1505 return titleDOM;
1508 remove: function()
1510 if (this._node.pseudoType())
1511 return;
1512 var parentElement = this.parent;
1513 if (!parentElement)
1514 return;
1516 if (!this._node.parentNode || this._node.parentNode.nodeType() === Node.DOCUMENT_NODE)
1517 return;
1518 this._node.removeNode();
1522 * @param {function(boolean)=} callback
1523 * @param {boolean=} startEditing
1525 toggleEditAsHTML: function(callback, startEditing)
1527 if (this._editing && this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement)) {
1528 this._editing.commit();
1529 return;
1532 if (startEditing === false)
1533 return;
1536 * @param {?Protocol.Error} error
1538 function selectNode(error)
1540 if (callback)
1541 callback(!error);
1545 * @param {string} initialValue
1546 * @param {string} value
1548 function commitChange(initialValue, value)
1550 if (initialValue !== value)
1551 node.setOuterHTML(value, selectNode);
1554 function disposeCallback()
1556 if (callback)
1557 callback(false);
1560 var node = this._node;
1561 node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange, disposeCallback));
1564 _copyCSSPath: function()
1566 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.cssPath(this._node, true));
1569 _copyXPath: function()
1571 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.xPath(this._node, true));
1574 _highlightSearchResults: function()
1576 if (!this._searchQuery || !this._searchHighlightsVisible)
1577 return;
1578 this._hideSearchHighlight();
1580 var text = this.listItemElement.textContent;
1581 var regexObject = createPlainTextSearchRegex(this._searchQuery, "gi");
1583 var match = regexObject.exec(text);
1584 var matchRanges = [];
1585 while (match) {
1586 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length));
1587 match = regexObject.exec(text);
1590 // Fall back for XPath, etc. matches.
1591 if (!matchRanges.length)
1592 matchRanges.push(new WebInspector.SourceRange(0, text.length));
1594 this._highlightResult = [];
1595 WebInspector.highlightSearchResults(this.listItemElement, matchRanges, this._highlightResult);
1598 _scrollIntoView: function()
1600 function scrollIntoViewCallback(object)
1603 * @suppressReceiverCheck
1604 * @this {!Element}
1606 function scrollIntoView()
1608 this.scrollIntoViewIfNeeded(true);
1611 if (object)
1612 object.callFunction(scrollIntoView);
1615 this._node.resolveToObject("", scrollIntoViewCallback);
1618 __proto__: TreeElement.prototype