summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js1669
1 files changed, 1669 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
new file mode 100644
index 000000000..bee7a1d67
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
@@ -0,0 +1,1669 @@
+/*
+ * Copyright (C) 2007, 2008, 2013, 2015, 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
+ * Copyright (C) 2009 Joseph Pecoraro
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.DOMTreeElement = class DOMTreeElement extends WebInspector.TreeElement
+{
+ constructor(node, elementCloseTag)
+ {
+ super("", node);
+
+ this._elementCloseTag = elementCloseTag;
+ this.hasChildren = !elementCloseTag && this._hasVisibleChildren();
+
+ if (this.representedObject.nodeType() === Node.ELEMENT_NODE && !elementCloseTag)
+ this._canAddAttributes = true;
+ this._searchQuery = null;
+ this._expandedChildrenLimit = WebInspector.DOMTreeElement.InitialChildrenLimit;
+
+ this._recentlyModifiedAttributes = [];
+ this._boundNodeChangedAnimationEnd = this._nodeChangedAnimationEnd.bind(this);
+
+ node.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this);
+ }
+
+ // Static
+
+ static shadowRootTypeDisplayName(type)
+ {
+ switch (type) {
+ case WebInspector.DOMNode.ShadowRootType.UserAgent:
+ return WebInspector.UIString("User Agent");
+ case WebInspector.DOMNode.ShadowRootType.Open:
+ return WebInspector.UIString("Open");
+ case WebInspector.DOMNode.ShadowRootType.Closed:
+ return WebInspector.UIString("Closed");
+ }
+ }
+
+ // Public
+
+ isCloseTag()
+ {
+ return this._elementCloseTag;
+ }
+
+ highlightSearchResults(searchQuery)
+ {
+ if (this._searchQuery !== searchQuery) {
+ this._updateSearchHighlight(false);
+ this._highlightResult = undefined; // A new search query.
+ }
+
+ this._searchQuery = searchQuery;
+ this._searchHighlightsVisible = true;
+ this.updateTitle(true);
+ }
+
+ hideSearchHighlights()
+ {
+ this._searchHighlightsVisible = false;
+ this._updateSearchHighlight(false);
+ }
+
+ emphasizeSearchHighlight()
+ {
+ var highlightElement = this.title.querySelector("." + WebInspector.DOMTreeElement.SearchHighlightStyleClassName);
+ console.assert(highlightElement);
+ if (!highlightElement)
+ return;
+
+ if (this._bouncyHighlightElement)
+ this._bouncyHighlightElement.remove();
+
+ this._bouncyHighlightElement = document.createElement("div");
+ this._bouncyHighlightElement.className = WebInspector.DOMTreeElement.BouncyHighlightStyleClassName;
+ this._bouncyHighlightElement.textContent = highlightElement.textContent;
+
+ // Position and show the bouncy highlight adjusting the coordinates to be inside the TreeOutline's space.
+ var highlightElementRect = highlightElement.getBoundingClientRect();
+ var treeOutlineRect = this.treeOutline.element.getBoundingClientRect();
+ this._bouncyHighlightElement.style.top = (highlightElementRect.top - treeOutlineRect.top) + "px";
+ this._bouncyHighlightElement.style.left = (highlightElementRect.left - treeOutlineRect.left) + "px";
+ this.title.appendChild(this._bouncyHighlightElement);
+
+ function animationEnded()
+ {
+ if (!this._bouncyHighlightElement)
+ return;
+
+ this._bouncyHighlightElement.remove();
+ this._bouncyHighlightElement = null;
+ }
+
+ this._bouncyHighlightElement.addEventListener("animationend", animationEnded.bind(this));
+ }
+
+ _updateSearchHighlight(show)
+ {
+ if (!this._highlightResult)
+ return;
+
+ function updateEntryShow(entry)
+ {
+ switch (entry.type) {
+ case "added":
+ entry.parent.insertBefore(entry.node, entry.nextSibling);
+ break;
+ case "changed":
+ entry.node.textContent = entry.newText;
+ break;
+ }
+ }
+
+ function updateEntryHide(entry)
+ {
+ switch (entry.type) {
+ case "added":
+ entry.node.remove();
+ break;
+ case "changed":
+ entry.node.textContent = entry.oldText;
+ break;
+ }
+ }
+
+ var updater = show ? updateEntryShow : updateEntryHide;
+
+ for (var i = 0, size = this._highlightResult.length; i < size; ++i)
+ updater(this._highlightResult[i]);
+ }
+
+ get hovered()
+ {
+ return this._hovered;
+ }
+
+ set hovered(value)
+ {
+ if (this._hovered === value)
+ return;
+
+ this._hovered = value;
+
+ if (this.listItemElement) {
+ this.listItemElement.classList.toggle("hovered", this._hovered);
+ this.updateSelectionArea();
+ }
+ }
+
+ get editable()
+ {
+ let node = this.representedObject;
+
+ if (node.isShadowRoot() || node.isInUserAgentShadowTree())
+ return false;
+
+ if (node.isPseudoElement())
+ return false;
+
+ return this.treeOutline.editable;
+ }
+
+ get expandedChildrenLimit()
+ {
+ return this._expandedChildrenLimit;
+ }
+
+ set expandedChildrenLimit(x)
+ {
+ if (this._expandedChildrenLimit === x)
+ return;
+
+ this._expandedChildrenLimit = x;
+ if (this.treeOutline && !this._updateChildrenInProgress)
+ this._updateChildren(true);
+ }
+
+ get expandedChildCount()
+ {
+ var count = this.children.length;
+ if (count && this.children[count - 1]._elementCloseTag)
+ count--;
+ if (count && this.children[count - 1].expandAllButton)
+ count--;
+ return count;
+ }
+
+ attributeDidChange(name)
+ {
+ this._recentlyModifiedAttributes.push({name});
+ }
+
+ showChildNode(node)
+ {
+ console.assert(!this._elementCloseTag);
+ if (this._elementCloseTag)
+ return null;
+
+ var index = this._visibleChildren().indexOf(node);
+ if (index === -1)
+ return null;
+
+ if (index >= this.expandedChildrenLimit) {
+ this._expandedChildrenLimit = index + 1;
+ this._updateChildren(true);
+ }
+
+ return this.children[index];
+ }
+
+ _createTooltipForNode()
+ {
+ var node = this.representedObject;
+ if (!node.nodeName() || node.nodeName().toLowerCase() !== "img")
+ return;
+
+ function setTooltip(error, result, wasThrown)
+ {
+ if (error || wasThrown || !result || result.type !== "string")
+ return;
+
+ try {
+ var properties = JSON.parse(result.description);
+ var offsetWidth = properties[0];
+ var offsetHeight = properties[1];
+ var naturalWidth = properties[2];
+ var naturalHeight = properties[3];
+ if (offsetHeight === naturalHeight && offsetWidth === naturalWidth)
+ this.tooltip = WebInspector.UIString("%d \xd7 %d pixels").format(offsetWidth, offsetHeight);
+ else
+ this.tooltip = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)").format(offsetWidth, offsetHeight, naturalWidth, naturalHeight);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ function resolvedNode(object)
+ {
+ if (!object)
+ return;
+
+ function dimensions()
+ {
+ return "[" + this.offsetWidth + "," + this.offsetHeight + "," + this.naturalWidth + "," + this.naturalHeight + "]";
+ }
+
+ object.callFunction(dimensions, undefined, false, setTooltip.bind(this));
+ object.release();
+ }
+ WebInspector.RemoteObject.resolveNode(node, "", resolvedNode.bind(this));
+ }
+
+ updateSelectionArea()
+ {
+ let listItemElement = this.listItemElement;
+ if (!listItemElement)
+ return;
+
+ // If there's no reason to have a selection area, remove the DOM element.
+ let indicatesTreeOutlineState = this.treeOutline && (this.treeOutline.dragOverTreeElement === this || this.treeOutline.selectedTreeElement === this);
+ if (!this.hovered && !this.pseudoClassesEnabled && !indicatesTreeOutlineState) {
+ if (this._selectionElement) {
+ this._selectionElement.remove();
+ this._selectionElement = null;
+ }
+
+ return;
+ }
+
+ if (!this._selectionElement) {
+ this._selectionElement = document.createElement("div");
+ this._selectionElement.className = "selection-area";
+ listItemElement.insertBefore(this._selectionElement, listItemElement.firstChild);
+ }
+
+ this._selectionElement.style.height = listItemElement.offsetHeight + "px";
+ }
+
+ onattach()
+ {
+ if (this.hovered)
+ this.listItemElement.classList.add("hovered");
+
+ this.updateTitle();
+
+ if (this.editable) {
+ this.listItemElement.draggable = true;
+ this.listItemElement.addEventListener("dragstart", this);
+ }
+ }
+
+ onpopulate()
+ {
+ if (this.children.length || !this._hasVisibleChildren() || this._elementCloseTag)
+ return;
+
+ this.updateChildren();
+ }
+
+ expandRecursively()
+ {
+ this.representedObject.getSubtree(-1, super.expandRecursively.bind(this, Number.MAX_VALUE));
+ }
+
+ updateChildren(fullRefresh)
+ {
+ if (this._elementCloseTag)
+ return;
+
+ this.representedObject.getChildNodes(this._updateChildren.bind(this, fullRefresh));
+ }
+
+ insertChildElement(child, index, closingTag)
+ {
+ var newElement = new WebInspector.DOMTreeElement(child, closingTag);
+ newElement.selectable = this.treeOutline._selectEnabled;
+ this.insertChild(newElement, index);
+ return newElement;
+ }
+
+ moveChild(child, targetIndex)
+ {
+ // No move needed if the child is already in the right place.
+ if (this.children[targetIndex] === child)
+ return;
+
+ var originalSelectedChild = this.treeOutline.selectedTreeElement;
+
+ this.removeChild(child);
+ this.insertChild(child, targetIndex);
+
+ if (originalSelectedChild !== this.treeOutline.selectedTreeElement)
+ originalSelectedChild.select();
+ }
+
+ _updateChildren(fullRefresh)
+ {
+ if (this._updateChildrenInProgress || !this.treeOutline._visible)
+ return;
+
+ this._updateChildrenInProgress = true;
+
+ var node = this.representedObject;
+ var selectedNode = this.treeOutline.selectedDOMNode();
+ var originalScrollTop = 0;
+
+ var hasVisibleChildren = this._hasVisibleChildren();
+
+ if (fullRefresh || !hasVisibleChildren) {
+ var treeOutlineContainerElement = this.treeOutline.element.parentNode;
+ originalScrollTop = treeOutlineContainerElement.scrollTop;
+ var selectedTreeElement = this.treeOutline.selectedTreeElement;
+ if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
+ this.select();
+ this.removeChildren();
+
+ // No longer have children.
+ if (!hasVisibleChildren) {
+ this.hasChildren = false;
+ this.updateTitle();
+ this._updateChildrenInProgress = false;
+ return;
+ }
+ }
+
+ // We now have children.
+ if (!this.hasChildren) {
+ this.hasChildren = true;
+ this.updateTitle();
+ }
+
+ // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
+ // Keep a list of existing tree elements for nodes that we can use later.
+ var existingChildTreeElements = new Map;
+ for (var i = this.children.length - 1; i >= 0; --i) {
+ var currentChildTreeElement = this.children[i];
+ var currentNode = currentChildTreeElement.representedObject;
+ var currentParentNode = currentNode.parentNode;
+ if (currentParentNode === node) {
+ existingChildTreeElements.set(currentNode, currentChildTreeElement);
+ continue;
+ }
+
+ this.removeChildAtIndex(i);
+ }
+
+ // Move / create TreeElements for our visible children.
+ var elementToSelect = null;
+ var visibleChildren = this._visibleChildren();
+ for (var i = 0; i < visibleChildren.length && i < this.expandedChildrenLimit; ++i) {
+ var childNode = visibleChildren[i];
+
+ // Already have a tree element for this child, just move it.
+ var existingChildTreeElement = existingChildTreeElements.get(childNode);
+ if (existingChildTreeElement) {
+ this.moveChild(existingChildTreeElement, i);
+ continue;
+ }
+
+ // No existing tree element for this child. Insert a new element.
+ var newChildTreeElement = this.insertChildElement(childNode, i);
+
+ // Update state.
+ if (childNode === selectedNode)
+ elementToSelect = newChildTreeElement;
+ if (this.expandedChildCount > this.expandedChildrenLimit)
+ this.expandedChildrenLimit++;
+ }
+
+ // Update expand all children button.
+ this.adjustCollapsedRange();
+
+ // Insert closing tag tree element.
+ var lastChild = this.children.lastValue;
+ if (node.nodeType() === Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
+ this.insertChildElement(this.representedObject, this.children.length, true);
+
+ // We want to restore the original selection and tree scroll position after a full refresh, if possible.
+ if (fullRefresh && elementToSelect) {
+ elementToSelect.select();
+ if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight)
+ treeOutlineContainerElement.scrollTop = originalScrollTop;
+ }
+
+ this._updateChildrenInProgress = false;
+ }
+
+ adjustCollapsedRange()
+ {
+ // Ensure precondition: only the tree elements for node children are found in the tree
+ // (not the Expand All button or the closing tag).
+ if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent)
+ this.removeChild(this.expandAllButtonElement.__treeElement);
+
+ if (!this._hasVisibleChildren())
+ return;
+
+ var visibleChildren = this._visibleChildren();
+ var totalChildrenCount = visibleChildren.length;
+
+ // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
+ for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, totalChildrenCount); i < limit; ++i)
+ this.insertChildElement(visibleChildren[i], i);
+
+ var expandedChildCount = this.expandedChildCount;
+ if (totalChildrenCount > this.expandedChildCount) {
+ var targetButtonIndex = expandedChildCount;
+ if (!this.expandAllButtonElement) {
+ var button = document.createElement("button");
+ button.className = "show-all-nodes";
+ button.value = "";
+
+ var item = new WebInspector.TreeElement(button, null, false);
+ item.selectable = false;
+ item.expandAllButton = true;
+
+ this.insertChild(item, targetButtonIndex);
+ this.expandAllButtonElement = button;
+ this.expandAllButtonElement.__treeElement = item;
+ this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
+ } else if (!this.expandAllButtonElement.__treeElement.parent)
+ this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
+
+ this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)").format(totalChildrenCount - expandedChildCount);
+ } else if (this.expandAllButtonElement)
+ this.expandAllButtonElement = null;
+ }
+
+ handleLoadAllChildren()
+ {
+ var visibleChildren = this._visibleChildren();
+ this.expandedChildrenLimit = Math.max(visibleChildren.length, this.expandedChildrenLimit + WebInspector.DOMTreeElement.InitialChildrenLimit);
+ }
+
+ onexpand()
+ {
+ if (this._elementCloseTag)
+ return;
+
+ if (!this.listItemElement)
+ return;
+
+ this.updateTitle();
+ }
+
+ oncollapse()
+ {
+ if (this._elementCloseTag)
+ return;
+
+ this.updateTitle();
+ }
+
+ onreveal()
+ {
+ if (this.listItemElement) {
+ var tagSpans = this.listItemElement.getElementsByClassName("html-tag-name");
+ if (tagSpans.length)
+ tagSpans[0].scrollIntoViewIfNeeded(false);
+ else
+ this.listItemElement.scrollIntoViewIfNeeded(false);
+ }
+ }
+
+ onselect(treeElement, selectedByUser)
+ {
+ this.treeOutline.suppressRevealAndSelect = true;
+ this.treeOutline.selectDOMNode(this.representedObject, selectedByUser);
+ if (selectedByUser)
+ WebInspector.domTreeManager.highlightDOMNode(this.representedObject.id);
+ this.treeOutline.updateSelection();
+ this.treeOutline.suppressRevealAndSelect = false;
+ }
+
+ ondeselect(treeElement)
+ {
+ this.treeOutline.selectDOMNode(null);
+ }
+
+ ondelete()
+ {
+ if (!this.editable)
+ return false;
+
+ var startTagTreeElement = this.treeOutline.findTreeElement(this.representedObject);
+ if (startTagTreeElement)
+ startTagTreeElement.remove();
+ else
+ this.remove();
+ return true;
+ }
+
+ onenter()
+ {
+ if (!this.editable)
+ return false;
+
+ // On Enter or Return start editing the first attribute
+ // or create a new attribute on the selected element.
+ if (this.treeOutline.editing)
+ return false;
+
+ this._startEditing();
+
+ // prevent a newline from being immediately inserted
+ return true;
+ }
+
+ selectOnMouseDown(event)
+ {
+ super.selectOnMouseDown(event);
+
+ if (this._editing)
+ return;
+
+ // Prevent selecting the nearest word on double click.
+ if (event.detail >= 2)
+ event.preventDefault();
+ }
+
+ ondblclick(event)
+ {
+ if (!this.editable)
+ return false;
+
+ if (this._editing || this._elementCloseTag)
+ return;
+
+ if (this._startEditingTarget(event.target))
+ return;
+
+ if (this.hasChildren && !this.expanded)
+ this.expand();
+ }
+
+ _insertInLastAttributePosition(tag, node)
+ {
+ if (tag.getElementsByClassName("html-attribute").length > 0)
+ tag.insertBefore(node, tag.lastChild);
+ else {
+ var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
+ tag.textContent = "";
+ tag.append("<" + nodeName, node, ">");
+ }
+
+ this.updateSelectionArea();
+ }
+
+ _startEditingTarget(eventTarget)
+ {
+ if (this.treeOutline.selectedDOMNode() !== this.representedObject)
+ return false;
+
+ if (this.representedObject.isShadowRoot() || this.representedObject.isInUserAgentShadowTree())
+ return false;
+
+ if (this.representedObject.isPseudoElement())
+ return false;
+
+ if (this.representedObject.nodeType() !== Node.ELEMENT_NODE && this.representedObject.nodeType() !== Node.TEXT_NODE)
+ return false;
+
+ var textNode = eventTarget.enclosingNodeOrSelfWithClass("html-text-node");
+ if (textNode)
+ return this._startEditingTextNode(textNode);
+
+ var attribute = eventTarget.enclosingNodeOrSelfWithClass("html-attribute");
+ if (attribute)
+ return this._startEditingAttribute(attribute, eventTarget);
+
+ var tagName = eventTarget.enclosingNodeOrSelfWithClass("html-tag-name");
+ if (tagName)
+ return this._startEditingTagName(tagName);
+
+ return false;
+ }
+
+ _populateTagContextMenu(contextMenu, event)
+ {
+ let node = this.representedObject;
+ if (!node.isInUserAgentShadowTree()) {
+ let attribute = event.target.enclosingNodeOrSelfWithClass("html-attribute");
+
+ if (event.target && event.target.tagName === "A") {
+ let url = event.target.href;
+
+ contextMenu.appendItem(WebInspector.UIString("Open in New Tab"), () => {
+ const frame = null;
+ const alwaysOpenExternally = true;
+ WebInspector.openURL(url, frame, alwaysOpenExternally);
+ });
+
+ if (WebInspector.frameResourceManager.resourceForURL(url)) {
+ contextMenu.appendItem(WebInspector.UIString("Reveal in Resources Tab"), () => {
+ let frame = WebInspector.frameResourceManager.frameForIdentifier(node.frameIdentifier);
+ WebInspector.openURL(url, frame);
+ });
+ }
+
+ contextMenu.appendItem(WebInspector.UIString("Copy Link Address"), () => {
+ InspectorFrontendHost.copyText(url);
+ });
+
+ contextMenu.appendSeparator();
+ }
+
+ // Add attribute-related actions.
+ if (this.editable) {
+ contextMenu.appendItem(WebInspector.UIString("Add Attribute"), this._addNewAttribute.bind(this));
+ if (attribute)
+ contextMenu.appendItem(WebInspector.UIString("Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
+ contextMenu.appendSeparator();
+ }
+
+ if (WebInspector.cssStyleManager.canForcePseudoClasses()) {
+ let pseudoSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Forced Pseudo-Classes"));
+ this._populateForcedPseudoStateItems(pseudoSubMenu);
+ contextMenu.appendSeparator();
+ }
+ }
+
+ this._populateNodeContextMenu(contextMenu);
+ this.treeOutline._populateContextMenu(contextMenu, this.representedObject);
+ }
+
+ _populateForcedPseudoStateItems(subMenu)
+ {
+ var node = this.representedObject;
+ var enabledPseudoClasses = node.enabledPseudoClasses;
+ // These strings don't need to be localized as they are CSS pseudo-classes.
+ WebInspector.CSSStyleManager.ForceablePseudoClasses.forEach(function(pseudoClass) {
+ var label = pseudoClass.capitalize();
+ var enabled = enabledPseudoClasses.includes(pseudoClass);
+ subMenu.appendCheckboxItem(label, function() {
+ node.setPseudoClassEnabled(pseudoClass, !enabled);
+ }, enabled, false);
+ });
+ }
+
+ _populateTextContextMenu(contextMenu, textNode)
+ {
+ if (this.editable)
+ contextMenu.appendItem(WebInspector.UIString("Edit Text"), this._startEditingTextNode.bind(this, textNode));
+
+ this._populateNodeContextMenu(contextMenu);
+ }
+
+ _populateNodeContextMenu(contextMenu)
+ {
+ let node = this.representedObject;
+
+ // Add free-form node-related actions.
+ if (this.editable)
+ contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
+ if (!node.isPseudoElement())
+ contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
+ if (this.editable)
+ contextMenu.appendItem(WebInspector.UIString("Delete Node"), this.remove.bind(this));
+ if (node.nodeType() === Node.ELEMENT_NODE)
+ contextMenu.appendItem(WebInspector.UIString("Scroll Into View"), this._scrollIntoView.bind(this));
+
+ contextMenu.appendSeparator();
+
+ if (node.nodeType() === Node.ELEMENT_NODE) {
+ contextMenu.appendItem(WebInspector.UIString("Copy Selector Path"), () => {
+ let cssPath = WebInspector.cssPath(this.representedObject);
+ InspectorFrontendHost.copyText(cssPath);
+ });
+ }
+
+ if (!node.isPseudoElement()) {
+ contextMenu.appendItem(WebInspector.UIString("Copy XPath"), () => {
+ let xpath = WebInspector.xpath(this.representedObject);
+ InspectorFrontendHost.copyText(xpath);
+ });
+ }
+
+ if (node.isCustomElement()) {
+ contextMenu.appendSeparator();
+ contextMenu.appendItem(WebInspector.UIString("Jump to Definition"), this._showCustomElementDefinition.bind(this));
+ }
+ }
+
+ _startEditing()
+ {
+ if (this.treeOutline.selectedDOMNode() !== this.representedObject)
+ return false;
+
+ if (!this.editable)
+ return false;
+
+ var listItem = this.listItemElement;
+
+ if (this._canAddAttributes) {
+ var attribute = listItem.getElementsByClassName("html-attribute")[0];
+ if (attribute)
+ return this._startEditingAttribute(attribute, attribute.getElementsByClassName("html-attribute-value")[0]);
+
+ return this._addNewAttribute();
+ }
+
+ if (this.representedObject.nodeType() === Node.TEXT_NODE) {
+ var textNode = listItem.getElementsByClassName("html-text-node")[0];
+ if (textNode)
+ return this._startEditingTextNode(textNode);
+ return false;
+ }
+ }
+
+ _addNewAttribute()
+ {
+ // Cannot just convert the textual html into an element without
+ // a parent node. Use a temporary span container for the HTML.
+ var container = document.createElement("span");
+ this._buildAttributeDOM(container, " ", "");
+ var attr = container.firstChild;
+ attr.style.marginLeft = "2px"; // overrides the .editing margin rule
+ attr.style.marginRight = "2px"; // overrides the .editing margin rule
+
+ var tag = this.listItemElement.getElementsByClassName("html-tag")[0];
+ this._insertInLastAttributePosition(tag, attr);
+ return this._startEditingAttribute(attr, attr);
+ }
+
+ _triggerEditAttribute(attributeName)
+ {
+ var attributeElements = this.listItemElement.getElementsByClassName("html-attribute-name");
+ for (var i = 0, len = attributeElements.length; i < len; ++i) {
+ if (attributeElements[i].textContent === attributeName) {
+ for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
+ if (elem.nodeType !== Node.ELEMENT_NODE)
+ continue;
+
+ if (elem.classList.contains("html-attribute-value"))
+ return this._startEditingAttribute(elem.parentNode, elem);
+ }
+ }
+ }
+ }
+
+ _startEditingAttribute(attribute, elementForSelection)
+ {
+ if (WebInspector.isBeingEdited(attribute))
+ return true;
+
+ var attributeNameElement = attribute.getElementsByClassName("html-attribute-name")[0];
+ if (!attributeNameElement)
+ return false;
+
+ var attributeName = attributeNameElement.textContent;
+
+ function removeZeroWidthSpaceRecursive(node)
+ {
+ if (node.nodeType === Node.TEXT_NODE) {
+ node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
+ return;
+ }
+
+ if (node.nodeType !== Node.ELEMENT_NODE)
+ return;
+
+ for (var child = node.firstChild; child; child = child.nextSibling)
+ removeZeroWidthSpaceRecursive(child);
+ }
+
+ // Remove zero-width spaces that were added by nodeTitleInfo.
+ removeZeroWidthSpaceRecursive(attribute);
+
+ var config = new WebInspector.EditingConfig(this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
+ config.setNumberCommitHandler(this._attributeNumberEditingCommitted.bind(this));
+ this._editing = WebInspector.startEditing(attribute, config);
+
+ window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
+
+ return true;
+ }
+
+ _startEditingTextNode(textNode)
+ {
+ if (WebInspector.isBeingEdited(textNode))
+ return true;
+
+ var config = new WebInspector.EditingConfig(this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
+ config.spellcheck = true;
+ this._editing = WebInspector.startEditing(textNode, config);
+ window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
+
+ return true;
+ }
+
+ _startEditingTagName(tagNameElement)
+ {
+ if (!tagNameElement) {
+ tagNameElement = this.listItemElement.getElementsByClassName("html-tag-name")[0];
+ if (!tagNameElement)
+ return false;
+ }
+
+ var tagName = tagNameElement.textContent;
+ if (WebInspector.DOMTreeElement.EditTagBlacklist[tagName.toLowerCase()])
+ return false;
+
+ if (WebInspector.isBeingEdited(tagNameElement))
+ return true;
+
+ let closingTagElement = this._distinctClosingTagElement();
+ let originalClosingTagTextContent = closingTagElement ? closingTagElement.textContent : "";
+
+ function keyupListener(event)
+ {
+ if (closingTagElement)
+ closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
+ }
+
+ function editingComitted(element, newTagName)
+ {
+ tagNameElement.removeEventListener("keyup", keyupListener, false);
+ this._tagNameEditingCommitted.apply(this, arguments);
+ }
+
+ function editingCancelled()
+ {
+ if (closingTagElement)
+ closingTagElement.textContent = originalClosingTagTextContent;
+
+ tagNameElement.removeEventListener("keyup", keyupListener, false);
+ this._editingCancelled.apply(this, arguments);
+ }
+
+ tagNameElement.addEventListener("keyup", keyupListener, false);
+
+ var config = new WebInspector.EditingConfig(editingComitted.bind(this), editingCancelled.bind(this), tagName);
+ this._editing = WebInspector.startEditing(tagNameElement, config);
+ window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
+ return true;
+ }
+
+ _startEditingAsHTML(commitCallback, error, initialValue)
+ {
+ if (error)
+ return;
+ if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement))
+ return;
+
+ this._htmlEditElement = document.createElement("div");
+ this._htmlEditElement.textContent = initialValue;
+
+ // Hide header items.
+ var child = this.listItemElement.firstChild;
+ while (child) {
+ child.style.display = "none";
+ child = child.nextSibling;
+ }
+ // Hide children item.
+ if (this._childrenListNode)
+ this._childrenListNode.style.display = "none";
+ // Append editor.
+ this.listItemElement.appendChild(this._htmlEditElement);
+
+ this.updateSelectionArea();
+
+ function commit()
+ {
+ commitCallback(this._htmlEditElement.textContent);
+ dispose.call(this);
+ }
+
+ function dispose()
+ {
+ this._editing = false;
+
+ // Remove editor.
+ this.listItemElement.removeChild(this._htmlEditElement);
+ this._htmlEditElement = null;
+ // Unhide children item.
+ if (this._childrenListNode)
+ this._childrenListNode.style.removeProperty("display");
+ // Unhide header items.
+ var child = this.listItemElement.firstChild;
+ while (child) {
+ child.style.removeProperty("display");
+ child = child.nextSibling;
+ }
+
+ this.updateSelectionArea();
+ }
+
+ var config = new WebInspector.EditingConfig(commit.bind(this), dispose.bind(this));
+ config.setMultiline(true);
+ this._editing = WebInspector.startEditing(this._htmlEditElement, config);
+ }
+
+ _attributeEditingCommitted(element, newText, oldText, attributeName, moveDirection)
+ {
+ this._editing = false;
+
+ if (!moveDirection && newText === oldText)
+ return;
+
+ var treeOutline = this.treeOutline;
+ function moveToNextAttributeIfNeeded(error)
+ {
+ if (error)
+ this._editingCancelled(element, attributeName);
+
+ if (!moveDirection)
+ return;
+
+ treeOutline._updateModifiedNodes();
+
+ // Search for the attribute's position, and then decide where to move to.
+ var attributes = this.representedObject.attributes();
+ for (var i = 0; i < attributes.length; ++i) {
+ if (attributes[i].name !== attributeName)
+ continue;
+
+ if (moveDirection === "backward") {
+ if (i === 0)
+ this._startEditingTagName();
+ else
+ this._triggerEditAttribute(attributes[i - 1].name);
+ } else {
+ if (i === attributes.length - 1)
+ this._addNewAttribute();
+ else
+ this._triggerEditAttribute(attributes[i + 1].name);
+ }
+ return;
+ }
+
+ // Moving From the "New Attribute" position.
+ if (moveDirection === "backward") {
+ if (newText === " ") {
+ // Moving from "New Attribute" that was not edited
+ if (attributes.length)
+ this._triggerEditAttribute(attributes.lastValue.name);
+ } else {
+ // Moving from "New Attribute" that holds new value
+ if (attributes.length > 1)
+ this._triggerEditAttribute(attributes[attributes.length - 2].name);
+ }
+ } else if (moveDirection === "forward") {
+ if (!/^\s*$/.test(newText))
+ this._addNewAttribute();
+ else
+ this._startEditingTagName();
+ }
+ }
+
+ this.representedObject.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this));
+ }
+
+ _attributeNumberEditingCommitted(element, newText, oldText, attributeName, moveDirection)
+ {
+ if (newText === oldText)
+ return;
+
+ this.representedObject.setAttribute(attributeName, newText);
+ }
+
+ _tagNameEditingCommitted(element, newText, oldText, tagName, moveDirection)
+ {
+ this._editing = false;
+ var self = this;
+
+ function cancel()
+ {
+ var closingTagElement = self._distinctClosingTagElement();
+ if (closingTagElement)
+ closingTagElement.textContent = "</" + tagName + ">";
+
+ self._editingCancelled(element, tagName);
+ moveToNextAttributeIfNeeded.call(self);
+ }
+
+ function moveToNextAttributeIfNeeded()
+ {
+ if (moveDirection !== "forward") {
+ this._addNewAttribute();
+ return;
+ }
+
+ var attributes = this.representedObject.attributes();
+ if (attributes.length > 0)
+ this._triggerEditAttribute(attributes[0].name);
+ else
+ this._addNewAttribute();
+ }
+
+ newText = newText.trim();
+ if (newText === oldText) {
+ cancel();
+ return;
+ }
+
+ var treeOutline = this.treeOutline;
+ var wasExpanded = this.expanded;
+
+ function changeTagNameCallback(error, nodeId)
+ {
+ if (error || !nodeId) {
+ cancel();
+ return;
+ }
+
+ var node = WebInspector.domTreeManager.nodeForId(nodeId);
+
+ // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
+ treeOutline._updateModifiedNodes();
+ treeOutline.selectDOMNode(node, true);
+
+ var newTreeItem = treeOutline.findTreeElement(node);
+ if (wasExpanded)
+ newTreeItem.expand();
+
+ moveToNextAttributeIfNeeded.call(newTreeItem);
+ }
+
+ this.representedObject.setNodeName(newText, changeTagNameCallback);
+ }
+
+ _textNodeEditingCommitted(element, newText)
+ {
+ this._editing = false;
+
+ var textNode;
+ if (this.representedObject.nodeType() === Node.ELEMENT_NODE) {
+ // We only show text nodes inline in elements if the element only
+ // has a single child, and that child is a text node.
+ textNode = this.representedObject.firstChild;
+ } else if (this.representedObject.nodeType() === Node.TEXT_NODE)
+ textNode = this.representedObject;
+
+ textNode.setNodeValue(newText, this.updateTitle.bind(this));
+ }
+
+ _editingCancelled(element, context)
+ {
+ this._editing = false;
+
+ // Need to restore attributes structure.
+ this.updateTitle();
+ }
+
+ _distinctClosingTagElement()
+ {
+ // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM
+
+ // For an expanded element, it will be the last element with class "close"
+ // in the child element list.
+ if (this.expanded) {
+ var closers = this._childrenListNode.querySelectorAll(".close");
+ return closers[closers.length - 1];
+ }
+
+ // Remaining cases are single line non-expanded elements with a closing
+ // tag, or HTML elements without a closing tag (such as <br>). Return
+ // null in the case where there isn't a closing tag.
+ var tags = this.listItemElement.getElementsByClassName("html-tag");
+ return tags.length === 1 ? null : tags[tags.length - 1];
+ }
+
+ updateTitle(onlySearchQueryChanged)
+ {
+ // If we are editing, return early to prevent canceling the edit.
+ // After editing is committed updateTitle will be called.
+ if (this._editing)
+ return;
+
+ if (onlySearchQueryChanged) {
+ if (this._highlightResult)
+ this._updateSearchHighlight(false);
+ } else {
+ this.title = document.createElement("span");
+ this.title.appendChild(this._nodeTitleInfo().titleDOM);
+ this._highlightResult = undefined;
+ }
+
+ // Setting this.title will implicitly remove all children. Clear the
+ // selection element so that we properly recreate it if necessary.
+ this._selectionElement = null;
+ this.updateSelectionArea();
+ this._highlightSearchResults();
+ }
+
+ _buildAttributeDOM(parentElement, name, value, node)
+ {
+ let hasText = value.length > 0;
+ let attrSpanElement = parentElement.createChild("span", "html-attribute");
+ let attrNameElement = attrSpanElement.createChild("span", "html-attribute-name");
+ attrNameElement.textContent = name;
+ let attrValueElement = null;
+ if (hasText)
+ attrSpanElement.append("=\u200B\"");
+
+ if (name === "src" || /\bhref\b/.test(name)) {
+ let baseURL = node.ownerDocument ? node.ownerDocument.documentURL : null;
+ let rewrittenURL = absoluteURL(value, baseURL);
+ value = value.insertWordBreakCharacters();
+ if (!rewrittenURL) {
+ attrValueElement = attrSpanElement.createChild("span", "html-attribute-value");
+ attrValueElement.textContent = value;
+ } else {
+ if (value.startsWith("data:"))
+ value = value.trimMiddle(60);
+
+ attrValueElement = document.createElement("a");
+ attrValueElement.href = rewrittenURL;
+ attrValueElement.textContent = value;
+ attrSpanElement.appendChild(attrValueElement);
+ }
+ } else if (name === "srcset") {
+ let baseURL = node.ownerDocument ? node.ownerDocument.documentURL : null;
+ attrValueElement = attrSpanElement.createChild("span", "html-attribute-value");
+
+ // Leading whitespace.
+ let groups = value.split(/\s*,\s*/);
+ for (let i = 0; i < groups.length; ++i) {
+ let string = groups[i].trim();
+ let spaceIndex = string.search(/\s/);
+
+ if (spaceIndex === -1) {
+ let linkText = string;
+ let rewrittenURL = absoluteURL(string, baseURL);
+ let linkElement = attrValueElement.appendChild(document.createElement("a"));
+ linkElement.href = rewrittenURL;
+ linkElement.textContent = linkText.insertWordBreakCharacters();
+ } else {
+ let linkText = string.substring(0, spaceIndex);
+ let descriptorText = string.substring(spaceIndex).insertWordBreakCharacters();
+ let rewrittenURL = absoluteURL(linkText, baseURL);
+ let linkElement = attrValueElement.appendChild(document.createElement("a"));
+ linkElement.href = rewrittenURL;
+ linkElement.textContent = linkText.insertWordBreakCharacters();
+ let descriptorElement = attrValueElement.appendChild(document.createElement("span"));
+ descriptorElement.textContent = descriptorText;
+ }
+
+ if (i < groups.length - 1) {
+ let commaElement = attrValueElement.appendChild(document.createElement("span"));
+ commaElement.textContent = ", ";
+ }
+ }
+ } else {
+ value = value.insertWordBreakCharacters();
+ attrValueElement = attrSpanElement.createChild("span", "html-attribute-value");
+ attrValueElement.textContent = value;
+ }
+
+ if (hasText)
+ attrSpanElement.append("\"");
+
+ for (let attribute of this._recentlyModifiedAttributes) {
+ if (attribute.name === name)
+ attribute.element = hasText ? attrValueElement : attrNameElement;
+ }
+ }
+
+ _buildTagDOM(parentElement, tagName, isClosingTag, isDistinctTreeElement)
+ {
+ var node = this.representedObject;
+ var classes = ["html-tag"];
+ if (isClosingTag && isDistinctTreeElement)
+ classes.push("close");
+ var tagElement = parentElement.createChild("span", classes.join(" "));
+ tagElement.append("<");
+ var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "html-tag-name");
+ tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName;
+ if (!isClosingTag && node.hasAttributes()) {
+ var attributes = node.attributes();
+ for (var i = 0; i < attributes.length; ++i) {
+ var attr = attributes[i];
+ tagElement.append(" ");
+ this._buildAttributeDOM(tagElement, attr.name, attr.value, node);
+ }
+ }
+ tagElement.append(">");
+ parentElement.append("\u200B");
+ }
+
+ _nodeTitleInfo()
+ {
+ var node = this.representedObject;
+ var info = {titleDOM: document.createDocumentFragment(), hasChildren: this.hasChildren};
+
+ function trimedNodeValue()
+ {
+ // Trim empty lines from the beginning and extra space at the end since most style and script tags begin with a newline
+ // and end with a newline and indentation for the end tag.
+ return node.nodeValue().replace(/^[\n\r]*/, "").replace(/\s*$/, "");
+ }
+
+ switch (node.nodeType()) {
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ var fragmentElement = info.titleDOM.createChild("span", "html-fragment");
+ if (node.shadowRootType()) {
+ fragmentElement.textContent = WebInspector.UIString("Shadow Content (%s)").format(WebInspector.DOMTreeElement.shadowRootTypeDisplayName(node.shadowRootType()));
+ this.listItemElement.classList.add("shadow");
+ } else if (node.parentNode && node.parentNode.templateContent() === node) {
+ fragmentElement.textContent = WebInspector.UIString("Template Content");
+ this.listItemElement.classList.add("template");
+ } else {
+ fragmentElement.textContent = WebInspector.UIString("Document Fragment");
+ this.listItemElement.classList.add("fragment");
+ }
+ break;
+
+ case Node.ATTRIBUTE_NODE:
+ var value = node.value || "\u200B"; // Zero width space to force showing an empty value.
+ this._buildAttributeDOM(info.titleDOM, node.name, value);
+ break;
+
+ case Node.ELEMENT_NODE:
+ if (node.isPseudoElement()) {
+ var pseudoElement = info.titleDOM.createChild("span", "html-pseudo-element");
+ pseudoElement.textContent = "::" + node.pseudoType();
+ info.titleDOM.appendChild(document.createTextNode("\u200B"));
+ info.hasChildren = false;
+ break;
+ }
+
+ var tagName = node.nodeNameInCorrectCase();
+ if (this._elementCloseTag) {
+ this._buildTagDOM(info.titleDOM, tagName, true, true);
+ info.hasChildren = false;
+ break;
+ }
+
+ this._buildTagDOM(info.titleDOM, tagName, false, false);
+
+ var textChild = this._singleTextChild(node);
+ var showInlineText = textChild && textChild.nodeValue().length < WebInspector.DOMTreeElement.MaximumInlineTextChildLength;
+
+ if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.DOMTreeElement.ForbiddenClosingTagElements[tagName]))) {
+ if (this.hasChildren) {
+ var textNodeElement = info.titleDOM.createChild("span", "html-text-node");
+ textNodeElement.textContent = ellipsis;
+ info.titleDOM.append("\u200B");
+ }
+ this._buildTagDOM(info.titleDOM, tagName, true, false);
+ }
+
+ // If this element only has a single child that is a text node,
+ // just show that text and the closing tag inline rather than
+ // create a subtree for them
+ if (showInlineText) {
+ var textNodeElement = info.titleDOM.createChild("span", "html-text-node");
+ var nodeNameLowerCase = node.nodeName().toLowerCase();
+
+ if (nodeNameLowerCase === "script")
+ textNodeElement.appendChild(WebInspector.syntaxHighlightStringAsDocumentFragment(textChild.nodeValue().trim(), "text/javascript"));
+ else if (nodeNameLowerCase === "style")
+ textNodeElement.appendChild(WebInspector.syntaxHighlightStringAsDocumentFragment(textChild.nodeValue().trim(), "text/css"));
+ else
+ textNodeElement.textContent = textChild.nodeValue();
+
+ info.titleDOM.append("\u200B");
+
+ this._buildTagDOM(info.titleDOM, tagName, true, false);
+ info.hasChildren = false;
+ }
+ break;
+
+ case Node.TEXT_NODE:
+ if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") {
+ var newNode = info.titleDOM.createChild("span", "html-text-node large");
+ newNode.appendChild(WebInspector.syntaxHighlightStringAsDocumentFragment(trimedNodeValue(), "text/javascript"));
+ } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "style") {
+ var newNode = info.titleDOM.createChild("span", "html-text-node large");
+ newNode.appendChild(WebInspector.syntaxHighlightStringAsDocumentFragment(trimedNodeValue(), "text/css"));
+ } else {
+ info.titleDOM.append("\"");
+ var textNodeElement = info.titleDOM.createChild("span", "html-text-node");
+ textNodeElement.textContent = node.nodeValue();
+ info.titleDOM.append("\"");
+ }
+ break;
+
+ case Node.COMMENT_NODE:
+ var commentElement = info.titleDOM.createChild("span", "html-comment");
+ commentElement.append("<!--" + node.nodeValue() + "-->");
+ break;
+
+ case Node.DOCUMENT_TYPE_NODE:
+ var docTypeElement = info.titleDOM.createChild("span", "html-doctype");
+ docTypeElement.append("<!DOCTYPE " + node.nodeName());
+ if (node.publicId) {
+ docTypeElement.append(" PUBLIC \"" + node.publicId + "\"");
+ if (node.systemId)
+ docTypeElement.append(" \"" + node.systemId + "\"");
+ } else if (node.systemId)
+ docTypeElement.append(" SYSTEM \"" + node.systemId + "\"");
+
+ docTypeElement.append(">");
+ break;
+
+ case Node.CDATA_SECTION_NODE:
+ var cdataElement = info.titleDOM.createChild("span", "html-text-node");
+ cdataElement.append("<![CDATA[" + node.nodeValue() + "]]>");
+ break;
+
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ var processingInstructionElement = info.titleDOM.createChild("span", "html-processing-instruction");
+ var data = node.nodeValue();
+ var dataString = data.length ? " " + data : "";
+ var title = "<?" + node.nodeNameInCorrectCase() + dataString + "?>";
+ processingInstructionElement.append(title);
+ break;
+
+ default:
+ info.titleDOM.append(node.nodeNameInCorrectCase().collapseWhitespace());
+ }
+
+ return info;
+ }
+
+ _singleTextChild(node)
+ {
+ if (!node)
+ return null;
+
+ var firstChild = node.firstChild;
+ if (!firstChild || firstChild.nodeType() !== Node.TEXT_NODE)
+ return null;
+
+ if (node.hasShadowRoots())
+ return null;
+ if (node.templateContent())
+ return null;
+ if (node.hasPseudoElements())
+ return null;
+
+ var sibling = firstChild.nextSibling;
+ return sibling ? null : firstChild;
+ }
+
+ _showInlineText(node)
+ {
+ if (node.nodeType() === Node.ELEMENT_NODE) {
+ var textChild = this._singleTextChild(node);
+ if (textChild && textChild.nodeValue().length < WebInspector.DOMTreeElement.MaximumInlineTextChildLength)
+ return true;
+ }
+ return false;
+ }
+
+ _hasVisibleChildren()
+ {
+ var node = this.representedObject;
+
+ if (this._showInlineText(node))
+ return false;
+
+ if (node.hasChildNodes())
+ return true;
+ if (node.templateContent())
+ return true;
+ if (node.hasPseudoElements())
+ return true;
+
+ return false;
+ }
+
+ _visibleChildren()
+ {
+ var node = this.representedObject;
+
+ var visibleChildren = [];
+
+ var templateContent = node.templateContent();
+ if (templateContent)
+ visibleChildren.push(templateContent);
+
+ var beforePseudoElement = node.beforePseudoElement();
+ if (beforePseudoElement)
+ visibleChildren.push(beforePseudoElement);
+
+ if (node.childNodeCount && node.children)
+ visibleChildren = visibleChildren.concat(node.children);
+
+ var afterPseudoElement = node.afterPseudoElement();
+ if (afterPseudoElement)
+ visibleChildren.push(afterPseudoElement);
+
+ return visibleChildren;
+ }
+
+ remove()
+ {
+ var parentElement = this.parent;
+ if (!parentElement)
+ return;
+
+ var self = this;
+ function removeNodeCallback(error, removedNodeId)
+ {
+ if (error)
+ return;
+
+ if (!self.parent)
+ return;
+
+ parentElement.removeChild(self);
+ parentElement.adjustCollapsedRange();
+ }
+
+ this.representedObject.removeNode(removeNodeCallback);
+ }
+
+ _scrollIntoView()
+ {
+ function resolvedNode(object)
+ {
+ if (!object)
+ return;
+
+ function scrollIntoView()
+ {
+ this.scrollIntoViewIfNeeded(true);
+ }
+
+ object.callFunction(scrollIntoView, undefined, false, function() {});
+ object.release();
+ }
+
+ let node = this.representedObject;
+ WebInspector.RemoteObject.resolveNode(node, "", resolvedNode);
+ }
+
+ _showCustomElementDefinition()
+ {
+ const node = this.representedObject;
+ WebInspector.RemoteObject.resolveNode(node, "", (remoteObject) => {
+ if (!remoteObject)
+ return;
+
+ remoteObject.getProperty("constructor", (error, result, wasThrown) => {
+ if (error || result.type !== "function")
+ return;
+
+ DebuggerAgent.getFunctionDetails(result.objectId, (error, response) => {
+ if (error)
+ return;
+
+ let location = response.location;
+ let sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId, WebInspector.mainTarget);
+ if (!sourceCode)
+ return;
+
+ let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0);
+ WebInspector.showSourceCodeLocation(sourceCodeLocation);
+ });
+ result.release();
+ });
+ remoteObject.release();
+ });
+ }
+
+ _editAsHTML()
+ {
+ var treeOutline = this.treeOutline;
+ var node = this.representedObject;
+ var parentNode = node.parentNode;
+ var index = node.index;
+ var wasExpanded = this.expanded;
+
+ function selectNode(error, nodeId)
+ {
+ if (error)
+ return;
+
+ // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
+ treeOutline._updateModifiedNodes();
+
+ var newNode = parentNode ? parentNode.children[index] || parentNode : null;
+ if (!newNode)
+ return;
+
+ treeOutline.selectDOMNode(newNode, true);
+
+ if (wasExpanded) {
+ var newTreeItem = treeOutline.findTreeElement(newNode);
+ if (newTreeItem)
+ newTreeItem.expand();
+ }
+ }
+
+ function commitChange(value)
+ {
+ node.setOuterHTML(value, selectNode);
+ }
+
+ node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange));
+ }
+
+ _copyHTML()
+ {
+ this.representedObject.copyNode();
+ }
+
+ _highlightSearchResults()
+ {
+ if (!this.title || !this._searchQuery || !this._searchHighlightsVisible)
+ return;
+
+ if (this._highlightResult) {
+ this._updateSearchHighlight(true);
+ return;
+ }
+
+ var text = this.title.textContent;
+ var searchRegex = new RegExp(this._searchQuery.escapeForRegExp(), "gi");
+
+ var match = searchRegex.exec(text);
+ var matchRanges = [];
+ while (match) {
+ matchRanges.push({offset: match.index, length: match[0].length});
+ match = searchRegex.exec(text);
+ }
+
+ // Fall back for XPath, etc. matches.
+ if (!matchRanges.length)
+ matchRanges.push({offset: 0, length: text.length});
+
+ this._highlightResult = [];
+ WebInspector.highlightRangesWithStyleClass(this.title, matchRanges, WebInspector.DOMTreeElement.SearchHighlightStyleClassName, this._highlightResult);
+ }
+
+ _markNodeChanged()
+ {
+ for (let attribute of this._recentlyModifiedAttributes) {
+ let element = attribute.element;
+ if (!element)
+ continue;
+
+ element.classList.remove("node-state-changed");
+ element.addEventListener("animationend", this._boundNodeChangedAnimationEnd);
+ element.classList.add("node-state-changed");
+ }
+ }
+
+ _nodeChangedAnimationEnd(event)
+ {
+ let element = event.target;
+ element.classList.remove("node-state-changed");
+ element.removeEventListener("animationend", this._boundNodeChangedAnimationEnd);
+
+ for (let i = this._recentlyModifiedAttributes.length - 1; i >= 0; --i) {
+ if (this._recentlyModifiedAttributes[i].element === element)
+ this._recentlyModifiedAttributes.splice(i, 1);
+ }
+ }
+
+ get pseudoClassesEnabled()
+ {
+ return !!this.representedObject.enabledPseudoClasses.length;
+ }
+
+ _nodePseudoClassesDidChange(event)
+ {
+ if (this._elementCloseTag)
+ return;
+
+ this.updateSelectionArea();
+ this.listItemElement.classList.toggle("pseudo-class-enabled", !!this.representedObject.enabledPseudoClasses.length);
+ }
+
+ _fireDidChange()
+ {
+ super._fireDidChange();
+
+ this._markNodeChanged();
+ }
+
+ handleEvent(event)
+ {
+ if (event.type === "dragstart" && this._editing)
+ event.preventDefault();
+ }
+};
+
+WebInspector.DOMTreeElement.InitialChildrenLimit = 500;
+WebInspector.DOMTreeElement.MaximumInlineTextChildLength = 80;
+
+// A union of HTML4 and HTML5-Draft elements that explicitly
+// or implicitly (for HTML5) forbid the closing tag.
+WebInspector.DOMTreeElement.ForbiddenClosingTagElements = [
+ "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
+ "hr", "img", "input", "keygen", "link", "meta", "param", "source",
+ "wbr", "track", "menuitem"
+].keySet();
+
+// These tags we do not allow editing their tag name.
+WebInspector.DOMTreeElement.EditTagBlacklist = [
+ "html", "head", "body"
+].keySet();
+
+WebInspector.DOMTreeElement.ChangeType = {
+ Attribute: "dom-tree-element-change-type-attribute"
+};
+
+WebInspector.DOMTreeElement.SearchHighlightStyleClassName = "search-highlight";
+WebInspector.DOMTreeElement.BouncyHighlightStyleClassName = "bouncy-highlight";