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 {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);
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"
66 // These tags we do not allow editing their tag name.
67 WebInspector
.ElementsTreeElement
.EditTagBlacklist
= [
68 "html", "head", "body"
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");
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
);
91 * @param {!WebInspector.DOMNode} root
95 return root
.shadowRootType() !== WebInspector
.DOMNode
.ShadowRootTypes
.UserAgent
;
101 * @param {!WebInspector.DOMNode} node
104 WebInspector
.ElementsTreeElement
.canShowInlineText = function(node
)
106 if (node
.importedDocument() || node
.templateContent() || WebInspector
.ElementsTreeElement
.visibleShadowRoots(node
).length
|| node
.hasPseudoElements())
108 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
110 if (!node
.firstChild
|| node
.firstChild
!== node
.lastChild
|| node
.firstChild
.nodeType() !== Node
.TEXT_NODE
)
112 var textChild
= node
.firstChild
;
113 var maxInlineTextChildLength
= 80;
114 if (textChild
.nodeValue().length
< maxInlineTextChildLength
)
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 = {
146 isClosingTag: function()
148 return !!this._elementCloseTag
;
152 * @return {!WebInspector.DOMNode}
162 isEditing: function()
164 return !!this._editing
;
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
)
204 function updateEntryHide(entry
)
206 switch (entry
.type
) {
211 entry
.node
.textContent
= entry
.oldText
;
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
)
229 this._inClipboard
= inClipboard
;
230 this.listItemElement
.classList
.toggle("in-clipboard", inClipboard
);
235 return this._hovered
;
240 if (this._hovered
=== x
)
245 if (this.listItemElement
) {
247 this.updateSelection();
248 this.listItemElement
.classList
.add("hovered");
250 this.listItemElement
.classList
.remove("hovered");
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
)
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";
291 if (!this._elementCloseTag
)
292 this._node
[this.treeOutline
.treeElementSymbol()] = this;
300 if (this._node
[this.treeOutline
.treeElementSymbol()] === this)
301 this._node
[this.treeOutline
.treeElementSymbol()] = null;
310 this.updateSelection();
311 this.listItemElement
.classList
.add("hovered");
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");
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}
342 TreeElement
.prototype.expandRecursively
.call(this, Number
.MAX_VALUE
);
345 this._node
.getSubtree(-1, callback
.bind(this));
353 if (this._elementCloseTag
)
357 this.treeOutline
.updateSelection();
360 oncollapse: function()
362 if (this._elementCloseTag
)
366 this.treeOutline
.updateSelection();
374 if (this.listItemElement
) {
375 var tagSpans
= this.listItemElement
.getElementsByClassName("webkit-html-tag-name");
377 tagSpans
[0].scrollIntoViewIfNeeded(true);
379 this.listItemElement
.scrollIntoViewIfNeeded(true);
385 * @param {boolean=} omitFocus
386 * @param {boolean=} selectedByUser
389 select: function(omitFocus
, selectedByUser
)
393 if (selectedByUser
&& this.treeOutline
.handlePickNode(this.title
, this._node
))
395 return TreeElement
.prototype.select
.call(this, omitFocus
, selectedByUser
);
400 * @param {boolean=} selectedByUser
403 onselect: function(selectedByUser
)
405 this.treeOutline
.suppressRevealAndSelect
= true;
406 this.treeOutline
.selectDOMNode(this._node
, selectedByUser
);
408 this._node
.highlight();
409 this.updateSelection();
410 this.treeOutline
.suppressRevealAndSelect
= false;
420 var startTagTreeElement
= this.treeOutline
.findTreeElement(this._node
);
421 startTagTreeElement
? startTagTreeElement
.remove() : this.remove();
431 // On Enter or Return start editing the first attribute
432 // or create a new attribute on the selected element.
436 this._startEditing();
438 // prevent a newline from being immediately inserted
442 selectOnMouseDown: function(event
)
444 TreeElement
.prototype.selectOnMouseDown
.call(this, event
);
449 // Prevent selecting the nearest word on double click.
450 if (event
.detail
>= 2)
451 event
.preventDefault();
458 ondblclick: function(event
)
460 if (this._editing
|| this._elementCloseTag
)
463 if (this._startEditingTarget(/** @type {!Element} */(event
.target
)))
466 if (this.isExpandable() && !this.expanded
)
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
);
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
498 _startEditingTarget: function(eventTarget
)
500 if (this.treeOutline
.selectedDOMNode() != this._node
)
503 if (this._node
.nodeType() != Node
.ELEMENT_NODE
&& this._node
.nodeType() != Node
.TEXT_NODE
)
506 if (this.treeOutline
.pickNodeMode())
509 var textNode
= eventTarget
.enclosingNodeOrSelfWithClass("webkit-html-text-node");
511 return this._startEditingTextNode(textNode
);
513 var attribute
= eventTarget
.enclosingNodeOrSelfWithClass("webkit-html-attribute");
515 return this._startEditingAttribute(attribute
, eventTarget
);
517 var tagName
= eventTarget
.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
519 return this._startEditingTagName(tagName
);
521 var newAttribute
= eventTarget
.enclosingNodeOrSelfWithClass("add-attribute");
523 return this._addNewAttribute();
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
)
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));
579 contextMenu
.appendItem(WebInspector
.UIString("Copy XPath"), this._copyXPath
.bind(this));
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
));
589 contextMenu
.appendItem(WebInspector
.UIString("Delete"), this.remove
.bind(this));
590 contextMenu
.appendSeparator();
593 _startEditing: function()
595 if (this.treeOutline
.selectedDOMNode() !== this._node
)
598 var listItem
= this._listItemNode
;
600 if (this._canAddAttributes
) {
601 var attribute
= listItem
.getElementsByClassName("webkit-html-attribute")[0];
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];
611 return this._startEditingTextNode(textNode
);
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
)
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
))
655 var attributeNameElement
= attribute
.getElementsByClassName("webkit-html-attribute-name")[0];
656 if (!attributeNameElement
)
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, "");
672 if (node
.nodeType
!== Node
.ELEMENT_NODE
)
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
692 function postKeyDownFinishHandler(event
)
694 WebInspector
.handleElementValueModifications(event
, attribute
);
697 config
.setPostKeydownFinishHandler(postKeyDownFinishHandler
);
699 this._editing
= WebInspector
.InplaceEditor
.startEditing(attribute
, config
);
701 this.listItemElement
.getComponentSelection().setBaseAndExtent(elementForSelection
, 0, elementForSelection
, 1);
707 * @param {!Element} textNodeElement
709 _startEditingTextNode: function(textNodeElement
)
711 if (WebInspector
.isBeingEdited(textNodeElement
))
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");
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);
731 * @param {!Element=} tagNameElement
733 _startEditingTagName: function(tagNameElement
)
735 if (!tagNameElement
) {
736 tagNameElement
= this.listItemElement
.getElementsByClassName("webkit-html-tag-name")[0];
741 var tagName
= tagNameElement
.textContent
;
742 if (WebInspector
.ElementsTreeElement
.EditTagBlacklist
[tagName
.toLowerCase()])
745 if (WebInspector
.isBeingEdited(tagNameElement
))
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);
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
)
800 function consume(event
)
802 if (event
.eventPhase
=== Event
.AT_TARGET
)
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
;
814 child
.style
.display
= "none";
815 child
= child
.nextSibling
;
817 // Hide children item.
818 if (this._childrenListNode
)
819 this._childrenListNode
.style
.display
= "none";
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
);
838 * @this {WebInspector.ElementsTreeElement}
843 delete this._editing
;
844 this.treeOutline
.setMultilineEditing(null);
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
;
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
)
893 this._editingCancelled(element
, attributeName
);
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
)
906 if (moveDirection
=== "backward") {
908 this._startEditingTagName();
910 this._triggerEditAttribute(attributes
[i
- 1].name
);
912 if (i
=== attributes
.length
- 1)
913 this._addNewAttribute();
915 this._triggerEditAttribute(attributes
[i
+ 1].name
);
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
);
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();
935 this._startEditingTagName();
940 if ((attributeName
.trim() || newText
.trim()) && oldText
!== newText
) {
941 this._node
.setAttribute(attributeName
, newText
, moveToNextAttributeIfNeeded
.bind(this));
946 moveToNextAttributeIfNeeded
.call(this);
949 _tagNameEditingCommitted: function(element
, newText
, oldText
, tagName
, moveDirection
)
951 delete this._editing
;
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();
974 var attributes
= this._node
.attributes();
975 if (attributes
.length
> 0)
976 this._triggerEditAttribute(attributes
[0].name
);
978 this._addNewAttribute();
981 newText
= newText
.trim();
982 if (newText
=== oldText
) {
987 var treeOutline
= this.treeOutline
;
988 var wasExpanded
= this.expanded
;
990 function changeTagNameCallback(error
, nodeId
)
992 if (error
|| !nodeId
) {
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}
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.
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.
1065 if (onlySearchQueryChanged
) {
1066 this._hideSearchHighlight();
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");
1072 for (var node
= this._node
; depth
&& node
; node
= node
.parentNode
) {
1073 if (node
.nodeType() === Node
.DOCUMENT_FRAGMENT_NODE
)
1077 this.childrenListElement
.classList
.add("shadow-root-deep");
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
;
1092 this.updateSelection();
1093 this._preventFollowingLinksOnDoubleClick();
1094 this._highlightSearchResults();
1097 updateDecorations: function()
1099 if (this.isClosingTag())
1101 var node
= this._node
;
1102 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
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
);
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
);
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
);
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
)
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
)
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
);
1175 processColors
.call(this, colors
, "elements-gutter-decoration");
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
;
1195 child
.style
.marginLeft
= offset
+ "px";
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;
1215 var additionalHighlightOffset
= 0;
1219 * @param {string} match
1220 * @param {number} replaceOffset
1223 function replacer(match
, replaceOffset
) {
1224 while (highlightIndex
< highlightCount
&& result
.entityRanges
[highlightIndex
].offset
< replaceOffset
) {
1225 result
.entityRanges
[highlightIndex
].offset
+= additionalHighlightOffset
;
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
;
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
;
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
);
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;
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
) {
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
);
1299 setValueWithEntities
.call(this, attrValueElement
, value
);
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
);
1344 var hasUpdates
= updateRecord
.hasRemovedAttributes() || updateRecord
.hasRemovedChildren();
1345 hasUpdates
|= !this.expanded
&& updateRecord
.hasChangedChildren();
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
)
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;
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);
1394 case Node
.ELEMENT_NODE
:
1395 var pseudoType
= node
.pseudoType();
1397 this._buildPseudoElementDOM(titleDOM
, pseudoType
);
1401 var tagName
= node
.nodeNameInCorrectCase();
1402 if (this._elementCloseTag
) {
1403 this._buildTagDOM(titleDOM
, tagName
, true, true, updateRecord
);
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
);
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");
1433 if (this.treeOutline
.isXMLMimeType
|| !WebInspector
.ElementsTreeElement
.ForbiddenClosingTagElements
[tagName
])
1434 this._buildTagDOM(titleDOM
, tagName
, true, false, updateRecord
);
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));
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");
1462 case Node
.COMMENT_NODE
:
1463 var commentElement
= titleDOM
.createChild("span", "webkit-html-comment");
1464 commentElement
.createTextChild("<!--" + node
.nodeValue() + "-->");
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
+ "\"");
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(">");
1483 case Node
.CDATA_SECTION_NODE
:
1484 var cdataElement
= titleDOM
.createChild("span", "webkit-html-text-node");
1485 cdataElement
.createTextChild("<![CDATA[" + node
.nodeValue() + "]]>");
1488 case Node
.DOCUMENT_FRAGMENT_NODE
:
1489 var fragmentElement
= titleDOM
.createChild("span", "webkit-html-fragment");
1490 fragmentElement
.textContent
= node
.nodeNameInCorrectCase().collapseWhitespace();
1493 titleDOM
.createTextChild(node
.nodeNameInCorrectCase().collapseWhitespace());
1497 * @this {WebInspector.ElementsTreeElement}
1499 function updateSearchHighlight()
1501 delete this._highlightResult
;
1502 this._highlightSearchResults();
1510 if (this._node
.pseudoType())
1512 var parentElement
= this.parent
;
1516 if (!this._node
.parentNode
|| this._node
.parentNode
.nodeType() === Node
.DOCUMENT_NODE
)
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();
1532 if (startEditing
=== false)
1536 * @param {?Protocol.Error} error
1538 function selectNode(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()
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
)
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
= [];
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
1606 function scrollIntoView()
1608 this.scrollIntoViewIfNeeded(true);
1612 object
.callFunction(scrollIntoView
);
1615 this._node
.resolveToObject("", scrollIntoViewCallback
);
1618 __proto__
: TreeElement
.prototype