Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / elements / ElementsTreeOutline.js
index 0a4be13..b88807c 100644 (file)
@@ -40,21 +40,28 @@ WebInspector.ElementsTreeOutline = function(target, omitRootDOMNode, selectEnabl
 {
     this._target = target;
     this._domModel = target.domModel;
-    this.element = document.createElement("ol");
-    this.element.className = "elements-tree-outline";
-    this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
-    this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
-    this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
-    this.element.addEventListener("dragstart", this._ondragstart.bind(this), false);
-    this.element.addEventListener("dragover", this._ondragover.bind(this), false);
-    this.element.addEventListener("dragleave", this._ondragleave.bind(this), false);
-    this.element.addEventListener("drop", this._ondrop.bind(this), false);
-    this.element.addEventListener("dragend", this._ondragend.bind(this), false);
-    this.element.addEventListener("keydown", this._onkeydown.bind(this), false);
-    this.element.addEventListener("webkitAnimationEnd", this._onAnimationEnd.bind(this), false);
-    this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
-
-    TreeOutline.call(this, this.element);
+    var element = createElement("div");
+
+    this._shadowRoot = element.createShadowRoot();
+    this._shadowRoot.appendChild(WebInspector.View.createStyleElement("elements/elementsTreeOutline.css"));
+
+    var outlineDisclosureElement = this._shadowRoot.createChild("div", "outline-disclosure");
+    WebInspector.installComponentRootStyles(outlineDisclosureElement);
+    this._element = outlineDisclosureElement.createChild("ol", "elements-tree-outline source-code");
+    this._element.addEventListener("mousedown", this._onmousedown.bind(this), false);
+    this._element.addEventListener("mousemove", this._onmousemove.bind(this), false);
+    this._element.addEventListener("mouseleave", this._onmouseleave.bind(this), false);
+    this._element.addEventListener("dragstart", this._ondragstart.bind(this), false);
+    this._element.addEventListener("dragover", this._ondragover.bind(this), false);
+    this._element.addEventListener("dragleave", this._ondragleave.bind(this), false);
+    this._element.addEventListener("drop", this._ondrop.bind(this), false);
+    this._element.addEventListener("dragend", this._ondragend.bind(this), false);
+    this._element.addEventListener("keydown", this._onkeydown.bind(this), false);
+    this._element.addEventListener("webkitAnimationEnd", this._onAnimationEnd.bind(this), false);
+    this._element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
+
+    TreeOutline.call(this, this._element);
+    this.element = element;
 
     this._includeRootDOMNode = !omitRootDOMNode;
     this._selectEnabled = selectEnabled;
@@ -69,6 +76,9 @@ WebInspector.ElementsTreeOutline = function(target, omitRootDOMNode, selectEnabl
 
     this._setPseudoClassCallback = setPseudoClassCallback;
     this._createNodeDecorators();
+
+    this._popoverHelper = new WebInspector.PopoverHelper(this._element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
+    this._popoverHelper.setTimeout(0);
 }
 
 /** @typedef {{node: !WebInspector.DOMNode, isCut: boolean}} */
@@ -107,6 +117,14 @@ WebInspector.ElementsTreeOutline.MappedCharToEntity = {
 
 WebInspector.ElementsTreeOutline.prototype = {
     /**
+     * @param {boolean} wrap
+     */
+    setWordWrap: function(wrap)
+    {
+        this._element.classList.toggle("nowrap", !wrap);
+    },
+
+    /**
      * @param {!Event} event
      */
     _onAnimationEnd: function(event)
@@ -121,7 +139,7 @@ WebInspector.ElementsTreeOutline.prototype = {
     setPickNodeMode: function(value)
     {
         this._pickNodeMode = value;
-        this.element.classList.toggle("pick-node-mode", value);
+        this._element.classList.toggle("pick-node-mode", value);
     },
 
     /**
@@ -131,14 +149,14 @@ WebInspector.ElementsTreeOutline.prototype = {
     _handlePickNode: function(element, node)
     {
         if (!this._pickNodeMode)
-            return true;
+            return false;
 
         this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.NodePicked, node);
         var hasRunningAnimation = element.classList.contains("elements-tree-element-pick-node-1") || element.classList.contains("elements-tree-element-pick-node-2");
         element.classList.toggle("elements-tree-element-pick-node-1");
         if (hasRunningAnimation)
             element.classList.toggle("elements-tree-element-pick-node-2");
-        return false;
+        return true;
     },
 
     /**
@@ -226,8 +244,7 @@ WebInspector.ElementsTreeOutline.prototype = {
             return;
 
         // Do not interfere with text editing.
-        var currentFocusElement = WebInspector.currentFocusElement();
-        if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement))
+        if (WebInspector.isEditing())
             return;
 
         var targetNode = this.selectedDOMNode();
@@ -289,8 +306,7 @@ WebInspector.ElementsTreeOutline.prototype = {
     handlePasteKeyboardEvent: function(event)
     {
         // Do not interfere with text editing.
-        var currentFocusElement = WebInspector.currentFocusElement();
-        if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement))
+        if (WebInspector.isEditing())
             return;
 
         var targetNode = this.selectedDOMNode();
@@ -335,8 +351,10 @@ WebInspector.ElementsTreeOutline.prototype = {
     setVisible: function(visible)
     {
         this._visible = visible;
-        if (!this._visible)
+        if (!this._visible) {
+            this._popoverHelper.hidePopover();
             return;
+        }
 
         this._updateModifiedNodes();
         if (this._selectedDOMNode)
@@ -575,6 +593,77 @@ WebInspector.ElementsTreeOutline.prototype = {
         return element;
     },
 
+    /**
+     * @param {!Element} element
+     * @param {!Event} event
+     * @return {!Element|!AnchorBox|undefined}
+     */
+    _getPopoverAnchor: function(element, event)
+    {
+        var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
+        if (!anchor || !anchor.href)
+            return;
+
+        return anchor;
+    },
+
+    /**
+     * @param {!WebInspector.DOMNode} node
+     * @param {function()} callback
+     */
+    _loadDimensionsForNode: function(node, callback)
+    {
+        if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
+            callback();
+            return;
+        }
+
+        node.resolveToObject("", resolvedNode);
+
+        function resolvedNode(object)
+        {
+            if (!object) {
+                callback();
+                return;
+            }
+
+            object.callFunctionJSON(dimensions, undefined, callback);
+            object.release();
+
+            /**
+             * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}}
+             * @suppressReceiverCheck
+             * @this {!Element}
+             */
+            function dimensions()
+            {
+                return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight };
+            }
+        }
+    },
+
+    /**
+     * @param {!Element} anchor
+     * @param {!WebInspector.Popover} popover
+     */
+    _showPopover: function(anchor, popover)
+    {
+        var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
+        var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.treeElement).node();
+        this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
+
+        /**
+         * @param {!Element=} contents
+         */
+        function showPopover(contents)
+        {
+            if (!contents)
+                return;
+            popover.setCanShrink(false);
+            popover.show(contents, anchor);
+        }
+    },
+
     _onmousedown: function(event)
     {
         var element = this._treeElementFromEvent(event);
@@ -607,12 +696,8 @@ WebInspector.ElementsTreeOutline.prototype = {
             this._domModel.hideDOMNodeHighlight();
     },
 
-    _onmouseout: function(event)
+    _onmouseleave: function(event)
     {
-        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
-        if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
-            return;
-
         if (this._previousHoveredElement) {
             this._previousHoveredElement.hovered = false;
             delete this._previousHoveredElement;
@@ -629,9 +714,6 @@ WebInspector.ElementsTreeOutline.prototype = {
             return false;
 
         var treeElement = this._treeElementFromEvent(event);
-        if (!treeElement)
-            return false;
-
         if (!this._isValidDragSourceOrTarget(treeElement))
             return false;
 
@@ -687,10 +769,11 @@ WebInspector.ElementsTreeOutline.prototype = {
         if (!treeElement)
             return false;
 
-        var node = treeElement.representedObject;
-        if (!(node instanceof WebInspector.DOMNode))
+        if (!(treeElement instanceof WebInspector.ElementsTreeElement))
             return false;
+        var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (treeElement);
 
+        var node = elementsTreeElement._node;
         if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
             return false;
 
@@ -773,7 +856,6 @@ WebInspector.ElementsTreeOutline.prototype = {
             return;
 
         var contextMenu = new WebInspector.ContextMenu(event);
-
         var isPseudoElement = !!treeElement._node.pseudoType();
         var isTag = treeElement._node.nodeType() === Node.ELEMENT_NODE && !isPseudoElement;
         var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
@@ -896,6 +978,7 @@ WebInspector.ElementsTreeOutline.prototype = {
 
             /**
              * @param {?string} pseudoType
+             * @suppressGlobalPropertiesCheck
              * @suppressReceiverCheck
              * @this {!Element}
              */
@@ -904,7 +987,14 @@ WebInspector.ElementsTreeOutline.prototype = {
                 const classNamePrefix = "__web-inspector-hide";
                 const classNameSuffix = "-shortcut__";
                 const styleTagId = "__web-inspector-hide-shortcut-style__";
-                const styleRules = ".__web-inspector-hide-shortcut__, .__web-inspector-hide-shortcut__ * { visibility: hidden !important; } .__web-inspector-hidebefore-shortcut__::before { visibility: hidden !important; } .__web-inspector-hideafter-shortcut__::after { visibility: hidden !important; }";
+                var selectors = [];
+                selectors.push("html /deep/ .__web-inspector-hide-shortcut__");
+                selectors.push("html /deep/ .__web-inspector-hide-shortcut__ /deep/ *");
+                selectors.push("html /deep/ .__web-inspector-hidebefore-shortcut__::before");
+                selectors.push("html /deep/ .__web-inspector-hideafter-shortcut__::after");
+                var selector = selectors.join(", ");
+                var ruleBody = "    visibility: hidden !important;";
+                var rule = "\n" + selector + "\n{\n" + ruleBody + "\n}\n";
 
                 var className = classNamePrefix + (pseudoType || "") + classNameSuffix;
                 this.classList.toggle(className);
@@ -916,7 +1006,7 @@ WebInspector.ElementsTreeOutline.prototype = {
                 style = document.createElement("style");
                 style.id = styleTagId;
                 style.type = "text/css";
-                style.textContent = styleRules;
+                style.textContent = rule;
                 document.head.appendChild(style);
             }
 
@@ -1010,7 +1100,7 @@ WebInspector.ElementsTreeElement = function(node, elementCloseTag)
     this._node = node;
 
     this._elementCloseTag = elementCloseTag;
-    this._updateHasChildren();
+    this._updateChildrenDisplayMode();
 
     if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag)
         this._canAddAttributes = true;
@@ -1032,7 +1122,25 @@ WebInspector.ElementsTreeElement.EditTagBlacklist = [
     "html", "head", "body"
 ].keySet();
 
+/** @enum {number} */
+WebInspector.ElementsTreeElement.ChildrenDisplayMode = {
+    NoChildren: 0,
+    InlineText: 1,
+    HasChildren: 2
+}
+
 WebInspector.ElementsTreeElement.prototype = {
+    /**
+     * @return {!WebInspector.DOMNode}
+     */
+    node: function()
+    {
+        return this._node;
+    },
+
+    /**
+     * @param {string} searchQuery
+     */
     highlightSearchResults: function(searchQuery)
     {
         if (this._searchQuery !== searchQuery) {
@@ -1051,6 +1159,9 @@ WebInspector.ElementsTreeElement.prototype = {
         this._updateSearchHighlight(false);
     },
 
+    /**
+     * @param {boolean} show
+     */
     _updateSearchHighlight: function(show)
     {
         if (!this._highlightResult)
@@ -1135,7 +1246,7 @@ WebInspector.ElementsTreeElement.prototype = {
 
         this._expandedChildrenLimit = x;
         if (this.treeOutline && !this._updateChildrenInProgress)
-            this._updateChildren(true);
+            this._updateChildren(true, this.children);
     },
 
     get expandedChildCount()
@@ -1163,7 +1274,7 @@ WebInspector.ElementsTreeElement.prototype = {
 
         if (index >= this.expandedChildrenLimit) {
             this._expandedChildrenLimit = index + 1;
-            this._updateChildren(true);
+            this._updateChildren(true, this.children);
         }
 
         // Whether index-th child is visible in the children tree
@@ -1177,7 +1288,7 @@ WebInspector.ElementsTreeElement.prototype = {
             return;
 
         if (!this._readyToUpdateSelection) {
-            if (document.body.offsetWidth > 0)
+            if (listItemElement.ownerDocument.body.offsetWidth > 0)
                 this._readyToUpdateSelection = true;
             else {
                 // The stylesheet hasn't loaded yet or the window is closed,
@@ -1187,7 +1298,7 @@ WebInspector.ElementsTreeElement.prototype = {
         }
 
         if (!this.selectionElement) {
-            this.selectionElement = document.createElement("div");
+            this.selectionElement = createElement("div");
             this.selectionElement.className = "selection selected";
             listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
         }
@@ -1220,7 +1331,7 @@ WebInspector.ElementsTreeElement.prototype = {
     onpopulate: function()
     {
         this.populated = true;
-        if (this.children.length || !this.hasChildren)
+        if (this.children.length || !this._hasChildTreeElements())
             return;
 
         this.updateChildren();
@@ -1231,10 +1342,10 @@ WebInspector.ElementsTreeElement.prototype = {
      */
     updateChildren: function(fullRefresh)
     {
-        if (!this.hasChildren)
+        if (!this._hasChildTreeElements())
             return;
         console.assert(!this._elementCloseTag);
-        this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh));
+        this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh || false));
     },
 
     /**
@@ -1261,11 +1372,12 @@ WebInspector.ElementsTreeElement.prototype = {
     },
 
     /**
-     * @param {boolean=} fullRefresh
+     * @param {boolean} fullRefresh
+     * @param {?Array.<!WebInspector.DOMNode>} children
      */
-    _updateChildren: function(fullRefresh)
+    _updateChildren: function(fullRefresh, children)
     {
-        if (this._updateChildrenInProgress || !this.treeOutline._visible)
+        if (!children || this._updateChildrenInProgress || !this.treeOutline._visible)
             return;
 
         this._updateChildrenInProgress = true;
@@ -1280,74 +1392,50 @@ WebInspector.ElementsTreeElement.prototype = {
             this.removeChildren();
         }
 
-        /**
-         * @this {WebInspector.ElementsTreeElement}
-         * @return {?WebInspector.ElementsTreeElement}
-         */
-        function updateChildrenOfNode()
-        {
-            var treeOutline = this.treeOutline;
-            var visibleChildren = this._visibleChildren();
-            var treeChildIndex = 0;
-            var elementToSelect = null;
-
-            for (var i = 0; i < visibleChildren.length; ++i) {
-                var child = visibleChildren[i];
-                var currentTreeElement = this.children[treeChildIndex];
-                if (!currentTreeElement || currentTreeElement._node !== child) {
-                    // Find any existing element that is later in the children list.
-                    var existingTreeElement = null;
-                    for (var j = (treeChildIndex + 1), size = this.expandedChildCount; j < size; ++j) {
-                        if (this.children[j]._node === child) {
-                            existingTreeElement = this.children[j];
-                            break;
-                        }
-                    }
-
-                    if (existingTreeElement && existingTreeElement.parent === this) {
-                        // If an existing element was found and it has the same parent, just move it.
-                        this.moveChild(existingTreeElement, treeChildIndex);
-                    } else {
-                        // No existing element found, insert a new element.
-                        if (treeChildIndex < this.expandedChildrenLimit) {
-                            var newElement = this.insertChildElement(child, treeChildIndex);
-                            if (child === selectedNode)
-                                elementToSelect = newElement;
-                            if (this.expandedChildCount > this.expandedChildrenLimit)
-                                this.expandedChildrenLimit++;
-                        }
-                    }
-                }
-
-                ++treeChildIndex;
-            }
-            return elementToSelect;
-        }
-
-        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
-        for (var i = (this.children.length - 1); i >= 0; --i) {
-            var currentChild = this.children[i];
-            var currentNode = currentChild._node;
-            if (!currentNode)
+        // Remove any tree elements that no longer have this node as their parent and save
+        // all existing elements that could be reused. This also removes closing tag element.
+        var existingTreeElements = new Map();
+        for (var i = this.children.length - 1; i >= 0; --i) {
+            var existingTreeElement = this.children[i];
+            var existingNode = existingTreeElement._node;
+            // Skip expand all button.
+            if (!existingNode)
                 continue;
-            var currentParentNode = currentNode.parentNode;
 
-            if (currentParentNode === this._node)
+            if (existingNode.parentNode === this._node) {
+                existingTreeElements.set(existingNode, existingTreeElement);
                 continue;
+            }
 
             var selectedTreeElement = this.treeOutline.selectedTreeElement;
-            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
+            if (selectedTreeElement && (selectedTreeElement === existingTreeElement || selectedTreeElement.hasAncestor(existingTreeElement)))
                 this.select();
 
             this.removeChildAtIndex(i);
         }
 
-        var elementToSelect = updateChildrenOfNode.call(this);
+        var elementToSelect;
+        var visibleChildren = this._visibleChildren();
+        for (var i = 0; i < visibleChildren.length && i < this.expandedChildrenLimit; ++i) {
+            var child = visibleChildren[i];
+            if (existingTreeElements.has(child)) {
+                // If an existing element was found, just move it.
+                this.moveChild(existingTreeElements.get(child), i);
+            } else {
+                // No existing element found, insert a new element.
+                var newElement = this.insertChildElement(child, i);
+                if (child === selectedNode)
+                    elementToSelect = newElement;
+                // If a node was inserted in the middle of existing list dynamically we might need to increase the limit.
+                if (this.expandedChildCount > this.expandedChildrenLimit)
+                    this.expandedChildrenLimit++;
+            }
+        }
+
         this.updateTitle();
         this._adjustCollapsedRange();
 
-        var lastChild = this.children[this.children.length - 1];
-        if (this._node.nodeType() === Node.ELEMENT_NODE && this.hasChildren)
+        if (this._node.nodeType() === Node.ELEMENT_NODE && this._hasChildTreeElements())
             this.insertChildElement(this._node, this.children.length, true);
 
         // We want to restore the original selection and tree scroll position after a full refresh, if possible.
@@ -1365,8 +1453,8 @@ WebInspector.ElementsTreeElement.prototype = {
         var visibleChildren = this._visibleChildren();
         // 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.expandAllButtonElement && this.expandAllButtonElement.parent)
+            this.removeChild(this.expandAllButtonElement);
 
         const childNodeCount = visibleChildren.length;
 
@@ -1378,19 +1466,18 @@ WebInspector.ElementsTreeElement.prototype = {
         if (childNodeCount > this.expandedChildCount) {
             var targetButtonIndex = expandedChildCount;
             if (!this.expandAllButtonElement) {
-                var button = document.createElement("button");
+                var button = createElement("button");
                 button.className = "text-button";
                 button.value = "";
-                var item = new TreeElement(button, null, false);
-                item.selectable = false;
-                item.expandAllButton = true;
-                this.insertChild(item, targetButtonIndex);
-                this.expandAllButtonElement = item.listItemElement.firstChild;
-                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)", childNodeCount - expandedChildCount);
+                button.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
+                this.expandAllButtonElement = new TreeElement(button, null, false);
+                this.expandAllButtonElement.selectable = false;
+                this.expandAllButtonElement.expandAllButton = true;
+                this.expandAllButtonElement._button = button;
+                this.insertChild(this.expandAllButtonElement, targetButtonIndex);
+            } else if (!this.expandAllButtonElement.parent)
+                this.insertChild(this.expandAllButtonElement, targetButtonIndex);
+            this.expandAllButtonElement._button.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount);
         } else if (this.expandAllButtonElement)
             delete this.expandAllButtonElement;
     },
@@ -1455,8 +1542,10 @@ WebInspector.ElementsTreeElement.prototype = {
      */
     select: function(omitFocus, selectedByUser)
     {
-        if (!this.treeOutline._handlePickNode(this.title, this._node))
+        if (this._editing)
             return false;
+        if (this.treeOutline._handlePickNode(this.title, this._node))
+            return true;
         return TreeElement.prototype.select.call(this, omitFocus, selectedByUser);
     },
 
@@ -1512,7 +1601,8 @@ WebInspector.ElementsTreeElement.prototype = {
             return;
 
         if (this.treeOutline._showInElementsPanelEnabled) {
-            WebInspector.inspectorView.showPanel("elements");
+            var panel = WebInspector.ElementsPanel.instance();
+            WebInspector.inspectorView.setCurrentPanel(panel);
             this.treeOutline.selectDOMNode(this._node, true);
         }
 
@@ -1533,7 +1623,7 @@ WebInspector.ElementsTreeElement.prototype = {
         if (this._startEditingTarget(/** @type {!Element} */(event.target)))
             return false;
 
-        if (this.hasChildren && !this.expanded)
+        if (this._hasChildTreeElements() && !this.expanded)
             this.expand();
         return false;
     },
@@ -1543,7 +1633,7 @@ WebInspector.ElementsTreeElement.prototype = {
      */
     hasEditableNode: function()
     {
-        return !this.representedObject.isShadowRoot() && !this.representedObject.ancestorUserAgentShadowRoot();
+        return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadowRoot();
     },
 
     _insertInLastAttributePosition: function(tag, node)
@@ -1649,26 +1739,28 @@ WebInspector.ElementsTreeElement.prototype = {
     _populateNodeContextMenu: function(contextMenu)
     {
         // Add free-form node-related actions.
-        var openTagElement = this.treeOutline.getCachedTreeElement(this.representedObject) || this;
+        var openTagElement = this.treeOutline.getCachedTreeElement(this._node) || this;
         var isEditable = this.hasEditableNode();
         if (isEditable && !this._editing)
             contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), openTagElement._editAsHTML.bind(openTagElement));
-        var isShadowRoot = this.representedObject.isShadowRoot();
+        var isShadowRoot = this._node.isShadowRoot();
 
         // Place it here so that all "Copy"-ing items stick together.
-        if (this.representedObject.nodeType() === Node.ELEMENT_NODE)
+        if (this._node.nodeType() === Node.ELEMENT_NODE)
             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy CSS path" : "Copy CSS Path"), this._copyCSSPath.bind(this));
         if (!isShadowRoot)
             contextMenu.appendItem(WebInspector.UIString("Copy XPath"), this._copyXPath.bind(this));
         if (!isShadowRoot) {
             var treeOutline = this.treeOutline;
-            contextMenu.appendItem(WebInspector.UIString("Copy"), treeOutline._performCopyOrCut.bind(treeOutline, false, this.representedObject));
-            contextMenu.appendItem(WebInspector.UIString("Cut"), treeOutline._performCopyOrCut.bind(treeOutline, true, this.representedObject), !this.hasEditableNode());
-            contextMenu.appendItem(WebInspector.UIString("Paste"), treeOutline._pasteNode.bind(treeOutline, this.representedObject), !treeOutline._canPaste(this.representedObject));
+            contextMenu.appendSeparator();
+            contextMenu.appendItem(WebInspector.UIString("Cut"), treeOutline._performCopyOrCut.bind(treeOutline, true, this._node), !this.hasEditableNode());
+            contextMenu.appendItem(WebInspector.UIString("Copy"), treeOutline._performCopyOrCut.bind(treeOutline, false, this._node));
+            contextMenu.appendItem(WebInspector.UIString("Paste"), treeOutline._pasteNode.bind(treeOutline, this._node), !treeOutline._canPaste(this._node));
         }
 
         if (isEditable)
-            contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete node" : "Delete Node"), this.remove.bind(this));
+            contextMenu.appendItem(WebInspector.UIString("Delete"), this.remove.bind(this));
+        contextMenu.appendSeparator();
     },
 
     _startEditing: function()
@@ -1698,7 +1790,7 @@ WebInspector.ElementsTreeElement.prototype = {
     {
         // 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");
+        var container = createElement("span");
         this._buildAttributeDOM(container, " ", "");
         var attr = container.firstElementChild;
         attr.style.marginLeft = "2px"; // overrides the .editing margin rule
@@ -1728,6 +1820,8 @@ WebInspector.ElementsTreeElement.prototype = {
 
     _startEditingAttribute: function(attribute, elementForSelection)
     {
+        console.assert(this.listItemElement.isAncestor(attribute));
+
         if (WebInspector.isBeingEdited(attribute))
             return true;
 
@@ -1752,12 +1846,8 @@ WebInspector.ElementsTreeElement.prototype = {
                 removeZeroWidthSpaceRecursive(child);
         }
 
-        var domNode;
-        var listItemElement = attribute.enclosingNodeOrSelfWithNodeName("li");
-        if (attributeName && attributeValueElement && listItemElement && listItemElement.treeElement)
-            domNode = listItemElement.treeElement.representedObject;
-        var attributeValue = domNode ? domNode.getAttribute(attributeName) : undefined;
-        if (typeof attributeValue !== "undefined")
+        var attributeValue = attributeName && attributeValueElement ? this._node.getAttribute(attributeName) : undefined;
+        if (attributeValue !== undefined)
             attributeValueElement.textContent = attributeValue;
 
         // Remove zero-width spaces that were added by nodeTitleInfo.
@@ -1892,7 +1982,7 @@ WebInspector.ElementsTreeElement.prototype = {
 
         initialValue = this._convertWhitespaceToEntities(initialValue).text;
 
-        this._htmlEditElement = document.createElement("div");
+        this._htmlEditElement = createElement("div");
         this._htmlEditElement.className = "source-code elements-tree-editor";
 
         // Hide header items.
@@ -1944,14 +2034,23 @@ WebInspector.ElementsTreeElement.prototype = {
 
             this.treeOutline.childrenListElement.parentElement.removeEventListener("mousedown", consume, false);
             this.updateSelection();
-            this.treeOutline.element.focus();
+            this.treeOutline._element.focus();
         }
 
         var config = new WebInspector.InplaceEditor.Config(commit.bind(this), dispose.bind(this));
         config.setMultilineOptions(initialValue, { name: "xml", htmlMode: true }, "web-inspector-html", WebInspector.settings.domWordWrap.get(), true);
-        this._editing = WebInspector.InplaceEditor.startEditing(this._htmlEditElement, config);
-        this._editing.setWidth(this.treeOutline._visibleWidth);
-        this.treeOutline._multilineEditing = this._editing;
+        WebInspector.InplaceEditor.startMultilineEditing(this._htmlEditElement, config).then(markAsBeingEdited.bind(this)).done();
+
+        /**
+         * @param {!Object} controller
+         * @this {WebInspector.ElementsTreeElement}
+         */
+        function markAsBeingEdited(controller)
+        {
+            this._editing = /** @type {!WebInspector.InplaceEditor.Controller} */ (controller);
+            this._editing.setWidth(this.treeOutline._visibleWidth);
+            this.treeOutline._multilineEditing = this._editing;
+        }
     },
 
     _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
@@ -2146,7 +2245,7 @@ WebInspector.ElementsTreeElement.prototype = {
             var nodeInfo = this._nodeTitleInfo(WebInspector.linkifyURLAsNode);
             if (nodeInfo.shadowRoot)
                 this.listItemElement.classList.add("shadow-root");
-            var highlightElement = document.createElement("span");
+            var highlightElement = createElement("span");
             highlightElement.className = "highlight";
             highlightElement.appendChild(nodeInfo.titleDOM);
             this.title = highlightElement;
@@ -2187,7 +2286,7 @@ WebInspector.ElementsTreeElement.prototype = {
         if (!decoratorMessages.length && !parentDecoratorMessages.length)
             return null;
 
-        var decoratorElement = document.createElement("div");
+        var decoratorElement = createElement("div");
         decoratorElement.classList.add("elements-gutter-decoration");
         if (!decoratorMessages.length)
             decoratorElement.classList.add("elements-has-decorated-children");
@@ -2271,14 +2370,16 @@ WebInspector.ElementsTreeElement.prototype = {
         {
             var rewrittenHref = node.resolveURL(value);
             if (rewrittenHref === null) {
-                var span = document.createElement("span");
+                var span = createElement("span");
                 setValueWithEntities.call(this, span, value);
                 return span;
             }
             value = value.replace(closingPunctuationRegex, "$&\u200B");
             if (value.startsWith("data:"))
                 value = value.trimMiddle(60);
-            return linkify(rewrittenHref, value, "", node.nodeName().toLowerCase() === "a");
+            var anchor = linkify(rewrittenHref, value, "", node.nodeName().toLowerCase() === "a");
+            anchor.preventFollow = true;
+            return anchor;
         }
 
         if (linkify && (name === "src" || name === "href")) {
@@ -2375,7 +2476,7 @@ WebInspector.ElementsTreeElement.prototype = {
     _nodeTitleInfo: function(linkify)
     {
         var node = this._node;
-        var info = {titleDOM: document.createDocumentFragment(), hasChildren: this.hasChildren};
+        var info = {titleDOM: createDocumentFragment(), hasChildren: this._hasChildTreeElements()};
 
         switch (node.nodeType()) {
             case Node.ATTRIBUTE_NODE:
@@ -2399,28 +2500,30 @@ WebInspector.ElementsTreeElement.prototype = {
 
                 this._buildTagDOM(info.titleDOM, tagName, false, false, linkify);
 
-                var showInlineText = this._showInlineText() && !this.hasChildren;
-                if (!this.expanded && !showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName])) {
-                    if (this.hasChildren) {
+                switch (this._childrenDisplayMode) {
+                case WebInspector.ElementsTreeElement.ChildrenDisplayMode.HasChildren:
+                    if (!this.expanded) {
                         var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node bogus");
                         textNodeElement.textContent = "\u2026";
                         info.titleDOM.createTextChild("\u200B");
+                        this._buildTagDOM(info.titleDOM, tagName, true, false);
                     }
-                    this._buildTagDOM(info.titleDOM, tagName, true, false);
-                }
+                    break;
 
-                // 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) {
-                    console.assert(!this.hasChildren);
+                case WebInspector.ElementsTreeElement.ChildrenDisplayMode.InlineText:
                     var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node");
                     var result = this._convertWhitespaceToEntities(node.firstChild.nodeValue());
                     textNodeElement.textContent = result.text;
                     WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
                     info.titleDOM.createTextChild("\u200B");
-                    this._buildTagDOM(info.titleDOM, tagName, true, false);
                     info.hasChildren = false;
+                    this._buildTagDOM(info.titleDOM, tagName, true, false);
+                    break;
+
+                case WebInspector.ElementsTreeElement.ChildrenDisplayMode.NoChildren:
+                    if (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName])
+                        this._buildTagDOM(info.titleDOM, tagName, true, false);
+                    break;
                 }
                 break;
 
@@ -2489,24 +2592,6 @@ WebInspector.ElementsTreeElement.prototype = {
         return info;
     },
 
-    /**
-     * @return {boolean}
-     */
-    _showInlineText: function()
-    {
-        if (this._node.importedDocument() || this._node.templateContent() || this._visibleShadowRoots().length > 0 || this._node.hasPseudoElements())
-            return false;
-        if (this._node.nodeType() !== Node.ELEMENT_NODE)
-            return false;
-        if (!this._node.firstChild || this._node.firstChild !== this._node.lastChild || this._node.firstChild.nodeType() !== Node.TEXT_NODE)
-            return false;
-        var textChild = this._node.firstChild;
-        var maxInlineTextChildLength = 80;
-        if (textChild.nodeValue().length < maxInlineTextChildLength)
-            return true;
-        return false;
-    },
-
     remove: function()
     {
         if (this._node.pseudoType())
@@ -2685,9 +2770,45 @@ WebInspector.ElementsTreeElement.prototype = {
         return childCount;
     },
 
-    _updateHasChildren: function()
+    /**
+     * @return {boolean}
+     */
+    _hasChildTreeElements: function()
+    {
+        return this._childrenDisplayMode === WebInspector.ElementsTreeElement.ChildrenDisplayMode.HasChildren;
+    },
+
+    /**
+     * @return {boolean}
+     */
+    _canShowInlineText: function()
+    {
+        if (this._node.importedDocument() || this._node.templateContent() || this._visibleShadowRoots().length > 0 || this._node.hasPseudoElements())
+            return false;
+        if (this._node.nodeType() !== Node.ELEMENT_NODE)
+            return false;
+        if (!this._node.firstChild || this._node.firstChild !== this._node.lastChild || this._node.firstChild.nodeType() !== Node.TEXT_NODE)
+            return false;
+        var textChild = this._node.firstChild;
+        var maxInlineTextChildLength = 80;
+        if (textChild.nodeValue().length < maxInlineTextChildLength)
+            return true;
+        return false;
+    },
+
+    _updateChildrenDisplayMode: function()
     {
-        this.hasChildren = !this._elementCloseTag && !this._showInlineText() && this._visibleChildCount() > 0;
+        var showInlineText = this._canShowInlineText();
+        var hasChildren = !this._elementCloseTag && this._visibleChildCount() > 0;
+
+        if (showInlineText)
+            this._childrenDisplayMode = WebInspector.ElementsTreeElement.ChildrenDisplayMode.InlineText;
+        else if (hasChildren)
+            this._childrenDisplayMode = WebInspector.ElementsTreeElement.ChildrenDisplayMode.HasChildren;
+        else
+            this._childrenDisplayMode = WebInspector.ElementsTreeElement.ChildrenDisplayMode.NoChildren;
+
+        this.setHasChildren(this._childrenDisplayMode === WebInspector.ElementsTreeElement.ChildrenDisplayMode.HasChildren);
     },
 
     __proto__: TreeElement.prototype
@@ -2739,10 +2860,9 @@ WebInspector.ElementsTreeUpdater.prototype = {
 
         var treeElement = this._treeOutline.findTreeElement(parentNode);
         if (treeElement) {
-            var oldHasChildren = treeElement.hasChildren;
-            var oldShowInlineText = treeElement._showInlineText();
-            treeElement._updateHasChildren();
-            if (treeElement.hasChildren !== oldHasChildren || oldShowInlineText || treeElement._showInlineText())
+            var oldDisplayMode = treeElement._childrenDisplayMode;
+            treeElement._updateChildrenDisplayMode();
+            if (treeElement._childrenDisplayMode !== oldDisplayMode)
                 this._nodeModified(parentNode);
         }
 
@@ -2837,26 +2957,26 @@ WebInspector.ElementsTreeUpdater.prototype = {
             delete this._updateModifiedNodesTimeout;
         }
 
-        var updatedNodes = this._recentlyModifiedNodes.values().concat(this._recentlyModifiedParentNodes.values());
+        var updatedNodes = this._recentlyModifiedNodes.valuesArray().concat(this._recentlyModifiedParentNodes.valuesArray());
         var hidePanelWhileUpdating = updatedNodes.length > 10;
         if (hidePanelWhileUpdating) {
             var treeOutlineContainerElement = this._treeOutline.element.parentNode;
             var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainerElement.scrollTop : 0;
-            this._treeOutline.element.classList.add("hidden");
+            this._treeOutline._element.classList.add("hidden");
         }
 
-        if (this._treeOutline._rootDOMNode && this._recentlyModifiedParentNodes.contains(this._treeOutline._rootDOMNode)) {
+        if (this._treeOutline._rootDOMNode && this._recentlyModifiedParentNodes.has(this._treeOutline._rootDOMNode)) {
             // Document's children have changed, perform total update.
             this._treeOutline.update();
         } else {
-            var nodes = this._recentlyModifiedNodes.values();
+            var nodes = this._recentlyModifiedNodes.valuesArray();
             for (var i = 0, size = nodes.length; i < size; ++i) {
                 var nodeItem = this._treeOutline.findTreeElement(nodes[i]);
                 if (nodeItem)
                     nodeItem.updateTitle();
             }
 
-            var parentNodes = this._recentlyModifiedParentNodes.values();
+            var parentNodes = this._recentlyModifiedParentNodes.valuesArray();
             for (var i = 0, size = parentNodes.length; i < size; ++i) {
                 var parentNodeItem = this._treeOutline.findTreeElement(parentNodes[i]);
                 if (parentNodeItem && parentNodeItem.populated)
@@ -2865,7 +2985,7 @@ WebInspector.ElementsTreeUpdater.prototype = {
         }
 
         if (hidePanelWhileUpdating) {
-            this._treeOutline.element.classList.remove("hidden");
+            this._treeOutline._element.classList.remove("hidden");
             if (originalScrollTop)
                 treeOutlineContainerElement.scrollTop = originalScrollTop;
             this._treeOutline.updateSelection();
@@ -2897,20 +3017,44 @@ WebInspector.ElementsTreeOutline.Renderer = function()
 WebInspector.ElementsTreeOutline.Renderer.prototype = {
     /**
      * @param {!Object} object
-     * @return {?Element}
+     * @return {!Promise.<!Element>}
      */
     render: function(object)
     {
-        if (!(object instanceof WebInspector.DOMNode))
-            return null;
-        var node = /** @type {!WebInspector.DOMNode} */ (object);
-        var treeOutline = new WebInspector.ElementsTreeOutline(node.target(), false, false);
-        treeOutline.rootDOMNode = node;
-        treeOutline.element.classList.add("outline-disclosure");
-        if (!treeOutline.children[0].hasChildren)
-            treeOutline.element.classList.add("single-node");
-        treeOutline.setVisible(true);
-        treeOutline.element.treeElementForTest = treeOutline.children[0];
-        return treeOutline.element;
+        return new Promise(renderPromise);
+
+        /**
+         * @param {function(!Element)} resolve
+         * @param {function(!Error)} reject
+         */
+        function renderPromise(resolve, reject)
+        {
+            if (object instanceof WebInspector.DOMNode)
+                onNodeResolved(/** @type {!WebInspector.DOMNode} */ (object));
+            else if (object instanceof WebInspector.DeferredDOMNode)
+                (/** @type {!WebInspector.DeferredDOMNode} */ (object)).resolve(onNodeResolved);
+            else if (object instanceof WebInspector.RemoteObject)
+                (/** @type {!WebInspector.RemoteObject} */ (object)).pushNodeToFrontend(onNodeResolved);
+            else
+                reject(new Error("Can't reveal not a node."));
+
+            /**
+             * @param {?WebInspector.DOMNode} node
+             */
+            function onNodeResolved(node)
+            {
+                if (!node) {
+                    reject(new Error("Could not resolve node."));
+                    return;
+                }
+                var treeOutline = new WebInspector.ElementsTreeOutline(node.target(), false, false);
+                treeOutline.rootDOMNode = node;
+                if (!treeOutline.children[0].hasChildren)
+                    treeOutline._element.classList.add("single-node");
+                treeOutline.setVisible(true);
+                treeOutline.element.treeElementForTest = treeOutline.children[0];
+                resolve(treeOutline.element);
+            }
+        }
     }
 }