home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_ElementsTreeOutline.js < prev    next >
Encoding:
Text File  |  2010-06-03  |  51.6 KB  |  1,426 lines

  1. /*
  2.  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
  3.  * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
  4.  * Copyright (C) 2009 Joseph Pecoraro
  5.  *
  6.  * Redistribution and use in source and binary forms, with or without
  7.  * modification, are permitted provided that the following conditions
  8.  * are met:
  9.  *
  10.  * 1.  Redistributions of source code must retain the above copyright
  11.  *     notice, this list of conditions and the following disclaimer.
  12.  * 2.  Redistributions in binary form must reproduce the above copyright
  13.  *     notice, this list of conditions and the following disclaimer in the
  14.  *     documentation and/or other materials provided with the distribution.
  15.  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  16.  *     its contributors may be used to endorse or promote products derived
  17.  *     from this software without specific prior written permission.
  18.  *
  19.  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  20.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22.  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  23.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  28.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29.  */
  30.  
  31. WebInspector.ElementsTreeOutline = function() {
  32.     this.element = document.createElement("ol");
  33.     this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
  34.     this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
  35.     this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
  36.  
  37.     TreeOutline.call(this, this.element);
  38.  
  39.     this.includeRootDOMNode = true;
  40.     this.selectEnabled = false;
  41.     this.showInElementsPanelEnabled = false;
  42.     this.rootDOMNode = null;
  43.     this.focusedDOMNode = null;
  44.  
  45.     this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
  46.     this.element.addEventListener("keydown", this._keyDown.bind(this), true);
  47. }
  48.  
  49. WebInspector.ElementsTreeOutline.prototype = {
  50.     get rootDOMNode()
  51.     {
  52.         return this._rootDOMNode;
  53.     },
  54.  
  55.     set rootDOMNode(x)
  56.     {
  57.         if (this._rootDOMNode === x)
  58.             return;
  59.  
  60.         this._rootDOMNode = x;
  61.  
  62.         this._isXMLMimeType = !!(WebInspector.mainResource && WebInspector.mainResource.mimeType && WebInspector.mainResource.mimeType.match(/x(?:ht)?ml/i));
  63.  
  64.         this.update();
  65.     },
  66.  
  67.     get isXMLMimeType()
  68.     {
  69.         return this._isXMLMimeType;
  70.     },
  71.  
  72.     nodeNameToCorrectCase: function(nodeName)
  73.     {
  74.         return this.isXMLMimeType ? nodeName : nodeName.toLowerCase();
  75.     },
  76.  
  77.     get focusedDOMNode()
  78.     {
  79.         return this._focusedDOMNode;
  80.     },
  81.  
  82.     set focusedDOMNode(x)
  83.     {
  84.         if (this._focusedDOMNode === x) {
  85.             this.revealAndSelectNode(x);
  86.             return;
  87.         }
  88.  
  89.         this._focusedDOMNode = x;
  90.  
  91.         this.revealAndSelectNode(x);
  92.  
  93.         // The revealAndSelectNode() method might find a different element if there is inlined text,
  94.         // and the select() call would change the focusedDOMNode and reenter this setter. So to
  95.         // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same
  96.         // node as the one passed in.
  97.         if (this._focusedDOMNode === x) {
  98.             this.focusedNodeChanged();
  99.  
  100.             if (x && !this.suppressSelectHighlight) {
  101.                 InspectorBackend.highlightDOMNode(x.id);
  102.  
  103.                 if ("_restorePreviousHighlightNodeTimeout" in this)
  104.                     clearTimeout(this._restorePreviousHighlightNodeTimeout);
  105.  
  106.                 function restoreHighlightToHoveredNode()
  107.                 {
  108.                     var hoveredNode = WebInspector.hoveredDOMNode;
  109.                     if (hoveredNode)
  110.                         InspectorBackend.highlightDOMNode(hoveredNode.id);
  111.                     else
  112.                         InspectorBackend.hideDOMNodeHighlight();
  113.                 }
  114.  
  115.                 this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000);
  116.             }
  117.         }
  118.     },
  119.  
  120.     update: function()
  121.     {
  122.         var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;
  123.  
  124.         this.removeChildren();
  125.  
  126.         if (!this.rootDOMNode)
  127.             return;
  128.  
  129.         var treeElement;
  130.         if (this.includeRootDOMNode) {
  131.             treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
  132.             treeElement.selectable = this.selectEnabled;
  133.             this.appendChild(treeElement);
  134.         } else {
  135.             // FIXME: this could use findTreeElement to reuse a tree element if it already exists
  136.             var node = this.rootDOMNode.firstChild;
  137.             while (node) {
  138.                 treeElement = new WebInspector.ElementsTreeElement(node);
  139.                 treeElement.selectable = this.selectEnabled;
  140.                 this.appendChild(treeElement);
  141.                 node = node.nextSibling;
  142.             }
  143.         }
  144.  
  145.         if (selectedNode)
  146.             this.revealAndSelectNode(selectedNode);
  147.     },
  148.  
  149.     updateSelection: function()
  150.     {
  151.         if (!this.selectedTreeElement)
  152.             return;
  153.         var element = this.treeOutline.selectedTreeElement;
  154.         element.updateSelection();
  155.     },
  156.  
  157.     focusedNodeChanged: function(forceUpdate) {},
  158.  
  159.     findTreeElement: function(node)
  160.     {
  161.         var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode);
  162.         if (!treeElement && node.nodeType === Node.TEXT_NODE) {
  163.             // The text node might have been inlined if it was short, so try to find the parent element.
  164.             treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode);
  165.         }
  166.  
  167.         return treeElement;
  168.     },
  169.  
  170.     createTreeElementFor: function(node)
  171.     {
  172.         var treeElement = this.findTreeElement(node);
  173.         if (treeElement)
  174.             return treeElement;
  175.         if (!node.parentNode)
  176.             return null;
  177.  
  178.         var treeElement = this.createTreeElementFor(node.parentNode);
  179.         if (treeElement && treeElement.showChild(node.index))
  180.             return treeElement.children[node.index];
  181.  
  182.         return null;
  183.     },
  184.  
  185.     set suppressRevealAndSelect(x)
  186.     {
  187.         if (this._suppressRevealAndSelect === x)
  188.             return;
  189.         this._suppressRevealAndSelect = x;
  190.     },
  191.  
  192.     revealAndSelectNode: function(node)
  193.     {
  194.         if (!node || this._suppressRevealAndSelect)
  195.             return;
  196.  
  197.         var treeElement = this.createTreeElementFor(node);
  198.         if (!treeElement)
  199.             return;
  200.  
  201.         treeElement.reveal();
  202.         treeElement.select();
  203.     },
  204.  
  205.     _treeElementFromEvent: function(event)
  206.     {
  207.         var root = this.element;
  208.  
  209.         // We choose this X coordinate based on the knowledge that our list
  210.         // items extend nearly to the right edge of the outer <ol>.
  211.         var x = root.totalOffsetLeft + root.offsetWidth - 20;
  212.  
  213.         var y = event.pageY;
  214.  
  215.         // Our list items have 1-pixel cracks between them vertically. We avoid
  216.         // the cracks by checking slightly above and slightly below the mouse
  217.         // and seeing if we hit the same element each time.
  218.         var elementUnderMouse = this.treeElementFromPoint(x, y);
  219.         var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
  220.         var element;
  221.         if (elementUnderMouse === elementAboveMouse)
  222.             element = elementUnderMouse;
  223.         else
  224.             element = this.treeElementFromPoint(x, y + 2);
  225.  
  226.         return element;
  227.     },
  228.  
  229.     _keyDown: function(event)
  230.     {
  231.         if (event.target !== this.treeOutline.element)
  232.             return;
  233.  
  234.         var selectedElement = this.selectedTreeElement;
  235.         if (!selectedElement)
  236.             return;
  237.  
  238.         if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Backspace ||
  239.                 event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Delete) {
  240.             var startTagTreeElement = this.findTreeElement(selectedElement.representedObject);
  241.             if (selectedElement !== startTagTreeElement)
  242.                 selectedElement = startTagTreeElement;
  243.             selectedElement.remove();
  244.             event.preventDefault();
  245.             event.stopPropagation();
  246.             return;
  247.         }
  248.  
  249.         // On Enter or Return start editing the first attribute
  250.         // or create a new attribute on the selected element.
  251.         if (isEnterKey(event)) {
  252.             if (this._editing)
  253.                 return;
  254.  
  255.             selectedElement._startEditing();
  256.  
  257.             // prevent a newline from being immediately inserted
  258.             event.preventDefault();
  259.             event.stopPropagation();
  260.             return;
  261.         }
  262.     },
  263.  
  264.     _onmousedown: function(event)
  265.     {
  266.         var element = this._treeElementFromEvent(event);
  267.  
  268.         if (!element || element.isEventWithinDisclosureTriangle(event))
  269.             return;
  270.  
  271.         element.select();
  272.     },
  273.  
  274.     _onmousemove: function(event)
  275.     {
  276.         var element = this._treeElementFromEvent(event);
  277.         if (element && this._previousHoveredElement === element)
  278.             return;
  279.  
  280.         if (this._previousHoveredElement) {
  281.             this._previousHoveredElement.hovered = false;
  282.             delete this._previousHoveredElement;
  283.         }
  284.  
  285.         if (element) {
  286.             element.hovered = true;
  287.             this._previousHoveredElement = element;
  288.  
  289.             // Lazily compute tag-specific tooltips.
  290.             if (element.representedObject && !element.tooltip)
  291.                 element._createTooltipForNode();
  292.         }
  293.  
  294.         WebInspector.hoveredDOMNode = (element ? element.representedObject : null);
  295.     },
  296.  
  297.     _onmouseout: function(event)
  298.     {
  299.         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
  300.         if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
  301.             return;
  302.  
  303.         if (this._previousHoveredElement) {
  304.             this._previousHoveredElement.hovered = false;
  305.             delete this._previousHoveredElement;
  306.         }
  307.  
  308.         WebInspector.hoveredDOMNode = null;
  309.     },
  310.  
  311.     _contextMenuEventFired: function(event)
  312.     {
  313.         var listItem = event.target.enclosingNodeOrSelfWithNodeName("LI");
  314.         if (!listItem || !listItem.treeElement)
  315.             return;
  316.  
  317.         var contextMenu = new WebInspector.ContextMenu();
  318.  
  319.         var tag = event.target.enclosingNodeOrSelfWithClass("webkit-html-tag");
  320.         var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
  321.         if (tag && listItem.treeElement._populateTagContextMenu)
  322.             listItem.treeElement._populateTagContextMenu(contextMenu, event);
  323.         else if (textNode && listItem.treeElement._populateTextContextMenu)
  324.             listItem.treeElement._populateTextContextMenu(contextMenu, textNode);
  325.         contextMenu.show(event);
  326.     }
  327. }
  328.  
  329. WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
  330.  
  331. WebInspector.ElementsTreeElement = function(node, elementCloseTag)
  332. {
  333.     this._elementCloseTag = elementCloseTag;
  334.     var hasChildrenOverride = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node);
  335.  
  336.     // The title will be updated in onattach.
  337.     TreeElement.call(this, "", node, hasChildrenOverride);
  338.  
  339.     if (this.representedObject.nodeType == Node.ELEMENT_NODE && !elementCloseTag)
  340.         this._canAddAttributes = true;
  341.     this._searchQuery = null;
  342.     this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit;
  343. }
  344.  
  345. WebInspector.ElementsTreeElement.InitialChildrenLimit = 500;
  346.  
  347. // A union of HTML4 and HTML5-Draft elements that explicitly
  348. // or implicitly (for HTML5) forbid the closing tag.
  349. // FIXME: Revise once HTML5 Final is published.
  350. WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [
  351.     "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
  352.     "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source"
  353. ].keySet();
  354.  
  355. // These tags we do not allow editing their tag name.
  356. WebInspector.ElementsTreeElement.EditTagBlacklist = [
  357.     "html", "head", "body"
  358. ].keySet();
  359.  
  360. WebInspector.ElementsTreeElement.prototype = {
  361.     highlightSearchResults: function(searchQuery)
  362.     {
  363.         if (this._searchQuery === searchQuery)
  364.             return;
  365.  
  366.         this._searchQuery = searchQuery;
  367.         this.updateTitle();
  368.     },
  369.  
  370.     get hovered()
  371.     {
  372.         return this._hovered;
  373.     },
  374.  
  375.     set hovered(x)
  376.     {
  377.         if (this._hovered === x)
  378.             return;
  379.  
  380.         this._hovered = x;
  381.  
  382.         if (this.listItemElement) {
  383.             if (x) {
  384.                 this.updateSelection();
  385.                 this.listItemElement.addStyleClass("hovered");
  386.             } else {
  387.                 this.listItemElement.removeStyleClass("hovered");
  388.             }
  389.         }
  390.     },
  391.  
  392.     get expandedChildrenLimit()
  393.     {
  394.         return this._expandedChildrenLimit;
  395.     },
  396.  
  397.     set expandedChildrenLimit(x)
  398.     {
  399.         if (this._expandedChildrenLimit === x)
  400.             return;
  401.  
  402.         this._expandedChildrenLimit = x;
  403.         if (this.treeOutline && !this._updateChildrenInProgress)
  404.             this._updateChildren(true);
  405.     },
  406.  
  407.     get expandedChildCount()
  408.     {
  409.         var count = this.children.length;
  410.         if (count && this.children[count - 1]._elementCloseTag)
  411.             count--;
  412.         if (count && this.children[count - 1].expandAllButton)
  413.             count--;
  414.         return count;
  415.     },
  416.  
  417.     showChild: function(index)
  418.     {
  419.         if (this._elementCloseTag)
  420.             return;
  421.  
  422.         if (index >= this.expandedChildrenLimit) {
  423.             this._expandedChildrenLimit = index + 1;
  424.             this._updateChildren(true);
  425.         }
  426.  
  427.         // Whether index-th child is visible in the children tree
  428.         return this.expandedChildCount > index;
  429.     },
  430.  
  431.     _createTooltipForNode: function()
  432.     {
  433.         var node = this.representedObject;
  434.         if (!node.nodeName || node.nodeName.toLowerCase() !== "img")
  435.             return;
  436.         
  437.         function setTooltip(properties)
  438.         {
  439.             if (!properties)
  440.                 return;
  441.  
  442.             if (properties.offsetHeight === properties.naturalHeight && properties.offsetWidth === properties.naturalWidth)
  443.                 this.tooltip = WebInspector.UIString("%d ├ù %d pixels", properties.offsetWidth, properties.offsetHeight);
  444.             else
  445.                 this.tooltip = WebInspector.UIString("%d ├ù %d pixels (Natural: %d ├ù %d pixels)", properties.offsetWidth, properties.offsetHeight, properties.naturalWidth, properties.naturalHeight);
  446.         }
  447.         var objectProxy = new WebInspector.ObjectProxy(node.injectedScriptId, node.id);
  448.         WebInspector.ObjectProxy.getPropertiesAsync(objectProxy, ["naturalHeight", "naturalWidth", "offsetHeight", "offsetWidth"], setTooltip.bind(this));
  449.     },
  450.  
  451.     updateSelection: function()
  452.     {
  453.         var listItemElement = this.listItemElement;
  454.         if (!listItemElement)
  455.             return;
  456.  
  457.         if (document.body.offsetWidth <= 0) {
  458.             // The stylesheet hasn't loaded yet or the window is closed,
  459.             // so we can't calculate what is need. Return early.
  460.             return;
  461.         }
  462.  
  463.         if (!this.selectionElement) {
  464.             this.selectionElement = document.createElement("div");
  465.             this.selectionElement.className = "selection selected";
  466.             listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
  467.         }
  468.  
  469.         this.selectionElement.style.height = listItemElement.offsetHeight + "px";
  470.     },
  471.  
  472.     onattach: function()
  473.     {
  474.         if (this._hovered) {
  475.             this.updateSelection();
  476.             this.listItemElement.addStyleClass("hovered");
  477.         }
  478.  
  479.         this.updateTitle();
  480.  
  481.         this._preventFollowingLinksOnDoubleClick();
  482.     },
  483.  
  484.     _preventFollowingLinksOnDoubleClick: function()
  485.     {
  486.         var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
  487.         if (!links)
  488.             return;
  489.  
  490.         for (var i = 0; i < links.length; ++i)
  491.             links[i].preventFollowOnDoubleClick = true;
  492.     },
  493.  
  494.     onpopulate: function()
  495.     {
  496.         if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag)
  497.             return;
  498.  
  499.         this.updateChildren();
  500.     },
  501.  
  502.     updateChildren: function(fullRefresh)
  503.     {
  504.         if (this._elementCloseTag)
  505.             return;
  506.  
  507.         WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh));
  508.     },
  509.  
  510.     insertChildElement: function(child, index, closingTag)
  511.     {
  512.         var newElement = new WebInspector.ElementsTreeElement(child, closingTag);
  513.         newElement.selectable = this.treeOutline.selectEnabled;
  514.         this.insertChild(newElement, index);
  515.         return newElement;
  516.     },
  517.  
  518.     moveChild: function(child, targetIndex)
  519.     {
  520.         var wasSelected = child.selected;
  521.         this.removeChild(child);
  522.         this.insertChild(child, targetIndex);
  523.         if (wasSelected)
  524.             child.select();
  525.     },
  526.  
  527.     _updateChildren: function(fullRefresh)
  528.     {
  529.         if (this._updateChildrenInProgress)
  530.             return;
  531.  
  532.         this._updateChildrenInProgress = true;
  533.         var focusedNode = this.treeOutline.focusedDOMNode;
  534.         var originalScrollTop;
  535.         if (fullRefresh) {
  536.             var treeOutlineContainerElement = this.treeOutline.element.parentNode;
  537.             originalScrollTop = treeOutlineContainerElement.scrollTop;
  538.             var selectedTreeElement = this.treeOutline.selectedTreeElement;
  539.             if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
  540.                 this.select();
  541.             this.removeChildren();
  542.         }
  543.  
  544.         var treeElement = this;
  545.         var treeChildIndex = 0;
  546.         var elementToSelect;
  547.  
  548.         function updateChildrenOfNode(node)
  549.         {
  550.             var treeOutline = treeElement.treeOutline;
  551.             var child = node.firstChild;
  552.             while (child) {
  553.                 var currentTreeElement = treeElement.children[treeChildIndex];
  554.                 if (!currentTreeElement || currentTreeElement.representedObject !== child) {
  555.                     // Find any existing element that is later in the children list.
  556.                     var existingTreeElement = null;
  557.                     for (var i = (treeChildIndex + 1), size = treeElement.expandedChildCount; i < size; ++i) {
  558.                         if (treeElement.children[i].representedObject === child) {
  559.                             existingTreeElement = treeElement.children[i];
  560.                             break;
  561.                         }
  562.                     }
  563.  
  564.                     if (existingTreeElement && existingTreeElement.parent === treeElement) {
  565.                         // If an existing element was found and it has the same parent, just move it.
  566.                         treeElement.moveChild(existingTreeElement, treeChildIndex);
  567.                     } else {
  568.                         // No existing element found, insert a new element.
  569.                         if (treeChildIndex < treeElement.expandedChildrenLimit) {
  570.                             var newElement = treeElement.insertChildElement(child, treeChildIndex);
  571.                             if (child === focusedNode)
  572.                                 elementToSelect = newElement;
  573.                             if (treeElement.expandedChildCount > treeElement.expandedChildrenLimit)
  574.                                 treeElement.expandedChildrenLimit++;
  575.                         }
  576.                     }
  577.                 }
  578.  
  579.                 child = child.nextSibling;
  580.                 ++treeChildIndex;
  581.             }
  582.         }
  583.  
  584.         // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
  585.         for (var i = (this.children.length - 1); i >= 0; --i) {
  586.             var currentChild = this.children[i];
  587.             var currentNode = currentChild.representedObject;
  588.             var currentParentNode = currentNode.parentNode;
  589.  
  590.             if (currentParentNode === this.representedObject)
  591.                 continue;
  592.  
  593.             var selectedTreeElement = this.treeOutline.selectedTreeElement;
  594.             if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
  595.                 this.select();
  596.  
  597.             this.removeChildAtIndex(i);
  598.         }
  599.  
  600.         updateChildrenOfNode(this.representedObject);
  601.         this.adjustCollapsedRange(false);
  602.  
  603.         var lastChild = this.children[this.children.length - 1];
  604.         if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag))
  605.             this.insertChildElement(this.representedObject, this.children.length, true);
  606.  
  607.         // We want to restore the original selection and tree scroll position after a full refresh, if possible.
  608.         if (fullRefresh && elementToSelect) {
  609.             elementToSelect.select();
  610.             if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight)
  611.                 treeOutlineContainerElement.scrollTop = originalScrollTop;
  612.         }
  613.  
  614.         delete this._updateChildrenInProgress;
  615.     },
  616.  
  617.     adjustCollapsedRange: function()
  618.     {
  619.         // Ensure precondition: only the tree elements for node children are found in the tree
  620.         // (not the Expand All button or the closing tag).
  621.         if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent)
  622.             this.removeChild(this.expandAllButtonElement.__treeElement);
  623.  
  624.         const node = this.representedObject;
  625.         if (!node.children)
  626.             return;
  627.         const childNodeCount = node.children.length;
  628.  
  629.         // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom.
  630.         for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i)
  631.             this.insertChildElement(node.children[i], i);
  632.  
  633.         const expandedChildCount = this.expandedChildCount;
  634.         if (childNodeCount > this.expandedChildCount) {
  635.             var targetButtonIndex = expandedChildCount;
  636.             if (!this.expandAllButtonElement) {
  637.                 var title = "<button class=\"show-all-nodes\" value=\"\" />";
  638.                 var item = new TreeElement(title, null, false);
  639.                 item.selectable = false;
  640.                 item.expandAllButton = true;
  641.                 this.insertChild(item, targetButtonIndex);
  642.                 this.expandAllButtonElement = item.listItemElement.firstChild;
  643.                 this.expandAllButtonElement.__treeElement = item;
  644.                 this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false);
  645.             } else if (!this.expandAllButtonElement.__treeElement.parent)
  646.                 this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex);
  647.             this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount);
  648.         } else if (this.expandAllButtonElement)
  649.             delete this.expandAllButtonElement;
  650.     },
  651.  
  652.     handleLoadAllChildren: function()
  653.     {
  654.         this.expandedChildrenLimit = Math.max(this.representedObject._childNodeCount, this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit);
  655.     },
  656.  
  657.     onexpand: function()
  658.     {
  659.         if (this._elementCloseTag)
  660.             return;
  661.  
  662.         this.updateTitle();
  663.         this.treeOutline.updateSelection();
  664.     },
  665.  
  666.     oncollapse: function()
  667.     {
  668.         if (this._elementCloseTag)
  669.             return;
  670.  
  671.         this.updateTitle();
  672.         this.treeOutline.updateSelection();
  673.     },
  674.  
  675.     onreveal: function()
  676.     {
  677.         if (this.listItemElement)
  678.             this.listItemElement.scrollIntoViewIfNeeded(false);
  679.     },
  680.  
  681.     onselect: function()
  682.     {
  683.         this.treeOutline.suppressRevealAndSelect = true;
  684.         this.treeOutline.focusedDOMNode = this.representedObject;
  685.         this.updateSelection();
  686.         this.treeOutline.suppressRevealAndSelect = false;
  687.     },
  688.  
  689.     selectOnMouseDown: function(event)
  690.     {
  691.         TreeElement.prototype.selectOnMouseDown.call(this, event);
  692.  
  693.         if (this._editing)
  694.             return;
  695.  
  696.         if (this.treeOutline.showInElementsPanelEnabled) {
  697.             WebInspector.showElementsPanel();
  698.             WebInspector.panels.elements.focusedDOMNode = this.representedObject;
  699.         }
  700.  
  701.         // Prevent selecting the nearest word on double click.
  702.         if (event.detail >= 2)
  703.             event.preventDefault();
  704.     },
  705.  
  706.     ondblclick: function(event)
  707.     {
  708.         if (this._editing || this._elementCloseTag)
  709.             return;
  710.  
  711.         if (this._startEditingTarget(event.target))
  712.             return;
  713.  
  714.         if (this.hasChildren && !this.expanded)
  715.             this.expand();
  716.     },
  717.  
  718.     _insertInLastAttributePosition: function(tag, node)
  719.     {
  720.         if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
  721.             tag.insertBefore(node, tag.lastChild);
  722.         else {
  723.             var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
  724.             tag.textContent = '';
  725.             tag.appendChild(document.createTextNode('<'+nodeName));
  726.             tag.appendChild(node);
  727.             tag.appendChild(document.createTextNode('>'));
  728.         }
  729.  
  730.         this.updateSelection();
  731.     },
  732.  
  733.     _startEditingTarget: function(eventTarget)
  734.     {
  735.         if (this.treeOutline.focusedDOMNode != this.representedObject)
  736.             return;
  737.  
  738.         if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
  739.             return false;
  740.  
  741.         var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node");
  742.         if (textNode)
  743.             return this._startEditingTextNode(textNode);
  744.  
  745.         var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute");
  746.         if (attribute)
  747.             return this._startEditingAttribute(attribute, eventTarget);
  748.  
  749.         var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name");
  750.         if (tagName)
  751.             return this._startEditingTagName(tagName);
  752.  
  753.         var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute");
  754.         if (newAttribute)
  755.             return this._addNewAttribute();
  756.  
  757.         return false;
  758.     },
  759.  
  760.     _populateTagContextMenu: function(contextMenu, event)
  761.     {
  762.         var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
  763.         var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
  764.  
  765.         // Add attribute-related actions.
  766.         contextMenu.appendItem(WebInspector.UIString("Add Attribute"), this._addNewAttribute.bind(this));
  767.         if (attribute && !newAttribute)
  768.             contextMenu.appendItem(WebInspector.UIString("Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target));
  769.         contextMenu.appendSeparator();
  770.  
  771.         // Add free-form node-related actions.
  772.         contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
  773.         contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
  774.         contextMenu.appendItem(WebInspector.UIString("Delete Node"), this.remove.bind(this));
  775.     },
  776.  
  777.     _populateTextContextMenu: function(contextMenu, textNode)
  778.     {
  779.         contextMenu.appendItem(WebInspector.UIString("Edit Text"), this._startEditingTextNode.bind(this, textNode));
  780.     },
  781.  
  782.     _startEditing: function()
  783.     {
  784.         if (this.treeOutline.focusedDOMNode !== this.representedObject)
  785.             return;
  786.  
  787.         var listItem = this._listItemNode;
  788.  
  789.         if (this._canAddAttributes) {
  790.             var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0];
  791.             if (attribute)
  792.                 return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]);
  793.  
  794.             return this._addNewAttribute();
  795.         }
  796.  
  797.         if (this.representedObject.nodeType === Node.TEXT_NODE) {
  798.             var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0];
  799.             if (textNode)
  800.                 return this._startEditingTextNode(textNode);
  801.             return;
  802.         }
  803.     },
  804.  
  805.     _addNewAttribute: function()
  806.     {
  807.         // Cannot just convert the textual html into an element without
  808.         // a parent node. Use a temporary span container for the HTML.
  809.         var container = document.createElement("span");
  810.         container.innerHTML = this._attributeHTML(" ", "");
  811.         var attr = container.firstChild;
  812.         attr.style.marginLeft = "2px"; // overrides the .editing margin rule
  813.         attr.style.marginRight = "2px"; // overrides the .editing margin rule
  814.  
  815.         var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
  816.         this._insertInLastAttributePosition(tag, attr);
  817.         return this._startEditingAttribute(attr, attr);
  818.     },
  819.  
  820.     _triggerEditAttribute: function(attributeName)
  821.     {
  822.         var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
  823.         for (var i = 0, len = attributeElements.length; i < len; ++i) {
  824.             if (attributeElements[i].textContent === attributeName) {
  825.                 for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
  826.                     if (elem.nodeType !== Node.ELEMENT_NODE)
  827.                         continue;
  828.  
  829.                     if (elem.hasStyleClass("webkit-html-attribute-value"))
  830.                         return this._startEditingAttribute(elem.parentNode, elem);
  831.                 }
  832.             }
  833.         }
  834.     },
  835.  
  836.     _startEditingAttribute: function(attribute, elementForSelection)
  837.     {
  838.         if (WebInspector.isBeingEdited(attribute))
  839.             return true;
  840.  
  841.         var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
  842.         if (!attributeNameElement)
  843.             return false;
  844.  
  845.         var attributeName = attributeNameElement.innerText;
  846.  
  847.         function removeZeroWidthSpaceRecursive(node)
  848.         {
  849.             if (node.nodeType === Node.TEXT_NODE) {
  850.                 node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
  851.                 return;
  852.             }
  853.  
  854.             if (node.nodeType !== Node.ELEMENT_NODE)
  855.                 return;
  856.  
  857.             for (var child = node.firstChild; child; child = child.nextSibling)
  858.                 removeZeroWidthSpaceRecursive(child);
  859.         }
  860.  
  861.         // Remove zero-width spaces that were added by nodeTitleInfo.
  862.         removeZeroWidthSpaceRecursive(attribute);
  863.  
  864.         this._editing = WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
  865.         window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
  866.  
  867.         return true;
  868.     },
  869.  
  870.     _startEditingTextNode: function(textNode)
  871.     {
  872.         if (WebInspector.isBeingEdited(textNode))
  873.             return true;
  874.  
  875.         this._editing = WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
  876.         window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
  877.  
  878.         return true;
  879.     },
  880.  
  881.     _startEditingTagName: function(tagNameElement)
  882.     {
  883.         if (!tagNameElement) {
  884.             tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0];
  885.             if (!tagNameElement)
  886.                 return false;
  887.         }
  888.  
  889.         var tagName = tagNameElement.textContent;
  890.         if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()])
  891.             return false;
  892.  
  893.         if (WebInspector.isBeingEdited(tagNameElement))
  894.             return true;
  895.  
  896.         var closingTagElement = this._distinctClosingTagElement();
  897.  
  898.         function keyupListener(event)
  899.         {
  900.             if (closingTagElement)
  901.                 closingTagElement.textContent = "</" + tagNameElement.textContent + ">";
  902.         }
  903.  
  904.         function editingComitted(element, newTagName)
  905.         {
  906.             tagNameElement.removeEventListener('keyup', keyupListener, false);
  907.             this._tagNameEditingCommitted.apply(this, arguments);
  908.         }
  909.  
  910.         function editingCancelled()
  911.         {
  912.             tagNameElement.removeEventListener('keyup', keyupListener, false);
  913.             this._editingCancelled.apply(this, arguments);
  914.         }
  915.  
  916.         tagNameElement.addEventListener('keyup', keyupListener, false);
  917.  
  918.         this._editing = WebInspector.startEditing(tagNameElement, editingComitted.bind(this), editingCancelled.bind(this), tagName);
  919.         window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
  920.         return true;
  921.     },
  922.  
  923.     _startEditingAsHTML: function(commitCallback, initialValue)
  924.     {
  925.         if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement))
  926.             return true;
  927.  
  928.         this._htmlEditElement = document.createElement("div");
  929.         this._htmlEditElement.className = "source-code elements-tree-editor";
  930.         this._htmlEditElement.textContent = initialValue;
  931.  
  932.         // Hide header items.
  933.         var child = this.listItemElement.firstChild;
  934.         while (child) {
  935.             child.style.display = "none";
  936.             child = child.nextSibling;
  937.         }
  938.         // Hide children item.
  939.         if (this._childrenListNode)
  940.             this._childrenListNode.style.display = "none";
  941.         // Append editor.
  942.         this.listItemElement.appendChild(this._htmlEditElement);
  943.  
  944.         this.updateSelection();
  945.  
  946.         function commit()
  947.         {
  948.             commitCallback(this._htmlEditElement.textContent);
  949.             dispose.call(this);
  950.         }
  951.  
  952.         function dispose()
  953.         {
  954.             delete this._editing;
  955.  
  956.             // Remove editor.
  957.             this.listItemElement.removeChild(this._htmlEditElement);
  958.             delete this._htmlEditElement;
  959.             // Unhide children item.
  960.             if (this._childrenListNode)
  961.                 this._childrenListNode.style.removeProperty("display");
  962.             // Unhide header items.
  963.             var child = this.listItemElement.firstChild;
  964.             while (child) {
  965.                 child.style.removeProperty("display");
  966.                 child = child.nextSibling;
  967.             }
  968.  
  969.             this.updateSelection();
  970.         }
  971.  
  972.         this._editing = WebInspector.startEditing(this._htmlEditElement, commit.bind(this), dispose.bind(this), null, true);
  973.     },
  974.  
  975.     _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
  976.     {
  977.         delete this._editing;
  978.  
  979.         // Before we do anything, determine where we should move
  980.         // next based on the current element's settings
  981.         var moveToAttribute, moveToTagName, moveToNewAttribute;
  982.         if (moveDirection) {
  983.             var found = false;
  984.  
  985.             // Search for the attribute's position, and then decide where to move to.
  986.             var attributes = this.representedObject.attributes;
  987.             for (var i = 0; i < attributes.length; ++i) {
  988.                 if (attributes[i].name === attributeName) {
  989.                     found = true;
  990.                     if (moveDirection === "backward") {
  991.                         if (i === 0)
  992.                             moveToTagName = true;
  993.                         else
  994.                             moveToAttribute = attributes[i - 1].name;
  995.                     } else if (moveDirection === "forward") {
  996.                         if (i === attributes.length - 1)
  997.                             moveToNewAttribute = true;
  998.                         else
  999.                             moveToAttribute = attributes[i + 1].name;
  1000.                     }
  1001.                 }
  1002.             }
  1003.  
  1004.             // Moving From the "New Attribute" position.
  1005.             if (!found) {
  1006.                 if (moveDirection === "backward" && attributes.length > 0)
  1007.                     moveToAttribute = attributes[attributes.length - 1].name;
  1008.                 else if (moveDirection === "forward" && !/^\s*$/.test(newText))
  1009.                     moveToNewAttribute = true;
  1010.             }
  1011.         }
  1012.  
  1013.         function moveToNextAttributeIfNeeded()
  1014.         {
  1015.             // Cleanup empty new attribute sections.
  1016.             if (element.textContent.trim().length === 0)
  1017.                 element.parentNode.removeChild(element);
  1018.  
  1019.             // Make the move.
  1020.             if (moveToAttribute)
  1021.                 this._triggerEditAttribute(moveToAttribute);
  1022.             else if (moveToNewAttribute)
  1023.                 this._addNewAttribute();
  1024.             else if (moveToTagName)
  1025.                 this._startEditingTagName();
  1026.         }
  1027.  
  1028.         function regenerateStyledAttribute(name, value)
  1029.         {
  1030.             var previous = element.previousSibling;
  1031.             if (!previous || previous.nodeType !== Node.TEXT_NODE)
  1032.                 element.parentNode.insertBefore(document.createTextNode(" "), element);
  1033.             element.outerHTML = this._attributeHTML(name, value);
  1034.         }
  1035.  
  1036.         var parseContainerElement = document.createElement("span");
  1037.         parseContainerElement.innerHTML = "<span " + newText + "></span>";
  1038.         var parseElement = parseContainerElement.firstChild;
  1039.  
  1040.         if (!parseElement) {
  1041.             this._editingCancelled(element, attributeName);
  1042.             moveToNextAttributeIfNeeded.call(this);
  1043.             return;
  1044.         }
  1045.  
  1046.         if (!parseElement.hasAttributes()) {
  1047.             this.representedObject.removeAttribute(attributeName);
  1048.             moveToNextAttributeIfNeeded.call(this);
  1049.             return;
  1050.         }
  1051.  
  1052.         var foundOriginalAttribute = false;
  1053.         for (var i = 0; i < parseElement.attributes.length; ++i) {
  1054.             var attr = parseElement.attributes[i];
  1055.             foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName;
  1056.             try {
  1057.                 this.representedObject.setAttribute(attr.name, attr.value);
  1058.                 regenerateStyledAttribute.call(this, attr.name, attr.value);
  1059.             } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can)
  1060.         }
  1061.  
  1062.         if (!foundOriginalAttribute)
  1063.             this.representedObject.removeAttribute(attributeName);
  1064.  
  1065.         this.treeOutline.focusedNodeChanged(true);
  1066.  
  1067.         moveToNextAttributeIfNeeded.call(this);
  1068.     },
  1069.  
  1070.     _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection)
  1071.     {
  1072.         delete this._editing;
  1073.         var self = this;
  1074.  
  1075.         function cancel()
  1076.         {
  1077.             var closingTagElement = self._distinctClosingTagElement();
  1078.             if (closingTagElement)
  1079.                 closingTagElement.textContent = "</" + tagName + ">";
  1080.  
  1081.             self._editingCancelled(element, tagName);
  1082.             moveToNextAttributeIfNeeded.call(self);
  1083.         }
  1084.  
  1085.         function moveToNextAttributeIfNeeded()
  1086.         {
  1087.             if (moveDirection !== "forward")
  1088.                 return;
  1089.  
  1090.             var attributes = this.representedObject.attributes;
  1091.             if (attributes.length > 0)
  1092.                 this._triggerEditAttribute(attributes[0].name);
  1093.             else
  1094.                 this._addNewAttribute();
  1095.         }
  1096.  
  1097.         newText = newText.trim();
  1098.         if (newText === oldText) {
  1099.             cancel();
  1100.             return;
  1101.         }
  1102.  
  1103.         var treeOutline = this.treeOutline;
  1104.         var wasExpanded = this.expanded;
  1105.  
  1106.         function changeTagNameCallback(nodeId)
  1107.         {
  1108.             if (nodeId === -1) {
  1109.                 cancel();
  1110.                 return;
  1111.             }
  1112.  
  1113.             // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
  1114.             WebInspector.panels.elements.updateModifiedNodes();
  1115.  
  1116.             WebInspector.updateFocusedNode(nodeId);
  1117.             var newTreeItem = treeOutline.findTreeElement(WebInspector.domAgent.nodeForId(nodeId));
  1118.             if (wasExpanded)
  1119.                 newTreeItem.expand();
  1120.  
  1121.             moveToNextAttributeIfNeeded.call(newTreeItem);
  1122.         }
  1123.  
  1124.         var callId = WebInspector.Callback.wrap(changeTagNameCallback);
  1125.         InspectorBackend.changeTagName(callId, this.representedObject.id, newText, wasExpanded);
  1126.     },
  1127.  
  1128.     _textNodeEditingCommitted: function(element, newText)
  1129.     {
  1130.         delete this._editing;
  1131.  
  1132.         var textNode;
  1133.         if (this.representedObject.nodeType == Node.ELEMENT_NODE) {
  1134.             // We only show text nodes inline in elements if the element only
  1135.             // has a single child, and that child is a text node.
  1136.             textNode = this.representedObject.firstChild;
  1137.         } else if (this.representedObject.nodeType == Node.TEXT_NODE)
  1138.             textNode = this.representedObject;
  1139.  
  1140.         textNode.nodeValue = newText;
  1141.  
  1142.         // Need to restore attributes / node structure.
  1143.         this.updateTitle();
  1144.     },
  1145.  
  1146.     _editingCancelled: function(element, context)
  1147.     {
  1148.         delete this._editing;
  1149.  
  1150.         // Need to restore attributes structure.
  1151.         this.updateTitle();
  1152.     },
  1153.  
  1154.     _distinctClosingTagElement: function()
  1155.     {
  1156.         // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM
  1157.  
  1158.         // For an expanded element, it will be the last element with class "close"
  1159.         // in the child element list.
  1160.         if (this.expanded) {
  1161.             var closers = this._childrenListNode.querySelectorAll(".close");
  1162.             return closers[closers.length-1];
  1163.         }
  1164.  
  1165.         // Remaining cases are single line non-expanded elements with a closing
  1166.         // tag, or HTML elements without a closing tag (such as <br>). Return
  1167.         // null in the case where there isn't a closing tag.
  1168.         var tags = this.listItemElement.getElementsByClassName("webkit-html-tag");
  1169.         return (tags.length === 1 ? null : tags[tags.length-1]);
  1170.     },
  1171.  
  1172.     updateTitle: function()
  1173.     {
  1174.         // If we are editing, return early to prevent canceling the edit.
  1175.         // After editing is committed updateTitle will be called.
  1176.         if (this._editing)
  1177.             return;
  1178.  
  1179.         var title = this._nodeTitleInfo(WebInspector.linkifyURL).title;
  1180.         this.title = "<span class=\"highlight\">" + title + "</span>";
  1181.         delete this.selectionElement;
  1182.         this.updateSelection();
  1183.         this._preventFollowingLinksOnDoubleClick();
  1184.         this._highlightSearchResults();
  1185.     },
  1186.  
  1187.     _rewriteAttrHref: function(node, hrefValue)
  1188.     {
  1189.         if (!hrefValue || hrefValue.indexOf("://") > 0)
  1190.             return hrefValue;
  1191.  
  1192.         for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
  1193.             if (frameOwnerCandidate.documentURL) {
  1194.                 var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, hrefValue);
  1195.                 if (result)
  1196.                     return result;
  1197.                 break;
  1198.             }
  1199.         }
  1200.  
  1201.         // documentURL not found or has bad value
  1202.         for (var url in WebInspector.resourceURLMap) {
  1203.             var match = url.match(WebInspector.URLRegExp);
  1204.             if (match && match[4] === hrefValue)
  1205.                 return url;
  1206.         }
  1207.         return hrefValue;
  1208.     },
  1209.  
  1210.     _attributeHTML: function(name, value, node, linkify)
  1211.     {
  1212.         var hasText = (value.length > 0);
  1213.         var html = "<span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + name.escapeHTML() + "</span>";
  1214.  
  1215.         if (hasText)
  1216.             html += "=​\"";
  1217.  
  1218.         if (linkify && (name === "src" || name === "href")) {
  1219.             value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B");
  1220.             html += linkify(this._rewriteAttrHref(node, value), value, "webkit-html-attribute-value", node.nodeName.toLowerCase() === "a");
  1221.         } else {
  1222.             value = value.escapeHTML().replace(/([\/;:\)\]\}])/g, "$1​");
  1223.             html += "<span class=\"webkit-html-attribute-value\">" + value + "</span>";
  1224.         }
  1225.  
  1226.         if (hasText)
  1227.             html += "\"";
  1228.  
  1229.         html += "</span>";
  1230.         return html;
  1231.     },
  1232.  
  1233.     _tagHTML: function(tagName, isClosingTag, isDistinctTreeElement, linkify)
  1234.     {
  1235.         var node = this.representedObject;
  1236.         var result = "<span class=\"webkit-html-tag" + (isClosingTag && isDistinctTreeElement ? " close" : "")  + "\"><";
  1237.         result += "<span " + (isClosingTag ? "" : "class=\"webkit-html-tag-name\"") + ">" + (isClosingTag ? "/" : "") + tagName + "</span>";
  1238.         if (!isClosingTag && node.hasAttributes()) {
  1239.             for (var i = 0; i < node.attributes.length; ++i) {
  1240.                 var attr = node.attributes[i];
  1241.                 result += " " + this._attributeHTML(attr.name, attr.value, node, linkify);
  1242.             }
  1243.         }
  1244.         result += "></span>​";
  1245.  
  1246.         return result;
  1247.     },
  1248.  
  1249.     _nodeTitleInfo: function(linkify)
  1250.     {
  1251.         var node = this.representedObject;
  1252.         var info = {title: "", hasChildren: this.hasChildren};
  1253.  
  1254.         switch (node.nodeType) {
  1255.             case Node.DOCUMENT_NODE:
  1256.                 info.title = "Document";
  1257.                 break;
  1258.  
  1259.             case Node.DOCUMENT_FRAGMENT_NODE:
  1260.                 info.title = "Document Fragment";
  1261.                 break;
  1262.  
  1263.             case Node.ELEMENT_NODE:
  1264.                 var tagName = this.treeOutline.nodeNameToCorrectCase(node.nodeName).escapeHTML();
  1265.                 if (this._elementCloseTag) {
  1266.                     info.title = this._tagHTML(tagName, true, true);
  1267.                     info.hasChildren = false;
  1268.                     break;
  1269.                 }
  1270.  
  1271.                 info.title = this._tagHTML(tagName, false, false, linkify);
  1272.  
  1273.                 var textChild = onlyTextChild.call(node);
  1274.                 var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength;
  1275.  
  1276.                 if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName]))) {
  1277.                     if (this.hasChildren)
  1278.                         info.title += "<span class=\"webkit-html-text-node\">…</span>​";
  1279.                     info.title += this._tagHTML(tagName, true, false);
  1280.                 }
  1281.  
  1282.                 // If this element only has a single child that is a text node,
  1283.                 // just show that text and the closing tag inline rather than
  1284.                 // create a subtree for them
  1285.                 if (showInlineText) {
  1286.                     info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>​" + this._tagHTML(tagName, true, false);
  1287.                     info.hasChildren = false;
  1288.                 }
  1289.                 break;
  1290.  
  1291.             case Node.TEXT_NODE:
  1292.                 if (isNodeWhitespace.call(node))
  1293.                     info.title = "(whitespace)";
  1294.                 else {
  1295.                     if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "script") {
  1296.                         var newNode = document.createElement("span");
  1297.                         newNode.textContent = node.textContent;
  1298.  
  1299.                         var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript");
  1300.                         javascriptSyntaxHighlighter.syntaxHighlightNode(newNode);
  1301.  
  1302.                         info.title = "<span class=\"webkit-html-text-node webkit-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
  1303.                     } else if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "style") {
  1304.                         var newNode = document.createElement("span");
  1305.                         newNode.textContent = node.textContent;
  1306.  
  1307.                         var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css");
  1308.                         cssSyntaxHighlighter.syntaxHighlightNode(newNode);
  1309.  
  1310.                         info.title = "<span class=\"webkit-html-text-node webkit-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>";
  1311.                     } else {
  1312.                         info.title = "\"<span class=\"webkit-html-text-node\">" + node.nodeValue.escapeHTML() + "</span>\"";
  1313.                     }
  1314.                 }
  1315.                 break;
  1316.  
  1317.             case Node.COMMENT_NODE:
  1318.                 info.title = "<span class=\"webkit-html-comment\"><!--" + node.nodeValue.escapeHTML() + "--></span>";
  1319.                 break;
  1320.  
  1321.             case Node.DOCUMENT_TYPE_NODE:
  1322.                 info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE " + node.nodeName;
  1323.                 if (node.publicId) {
  1324.                     info.title += " PUBLIC \"" + node.publicId + "\"";
  1325.                     if (node.systemId)
  1326.                         info.title += " \"" + node.systemId + "\"";
  1327.                 } else if (node.systemId)
  1328.                     info.title += " SYSTEM \"" + node.systemId + "\"";
  1329.                 if (node.internalSubset)
  1330.                     info.title += " [" + node.internalSubset + "]";
  1331.                 info.title += "></span>";
  1332.                 break;
  1333.             default:
  1334.                 info.title = this.treeOutline.nodeNameToCorrectCase(node.nodeName).collapseWhitespace().escapeHTML();
  1335.         }
  1336.  
  1337.         return info;
  1338.     },
  1339.  
  1340.     _showInlineText: function(node)
  1341.     {
  1342.         if (node.nodeType === Node.ELEMENT_NODE) {
  1343.             var textChild = onlyTextChild.call(node);
  1344.             if (textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength)
  1345.                 return true;
  1346.         }
  1347.         return false;
  1348.     },
  1349.  
  1350.     remove: function()
  1351.     {
  1352.         var parentElement = this.parent;
  1353.         if (!parentElement)
  1354.             return;
  1355.  
  1356.         var self = this;
  1357.         function removeNodeCallback(removedNodeId)
  1358.         {
  1359.             // -1 is an error code, which means removing the node from the DOM failed,
  1360.             // so we shouldn't remove it from the tree.
  1361.             if (removedNodeId === -1)
  1362.                 return;
  1363.  
  1364.             parentElement.removeChild(self);
  1365.             parentElement.adjustCollapsedRange(true);
  1366.         }
  1367.  
  1368.         var callId = WebInspector.Callback.wrap(removeNodeCallback);
  1369.         InspectorBackend.removeNode(callId, this.representedObject.id);
  1370.     },
  1371.  
  1372.     _editAsHTML: function()
  1373.     {
  1374.         var treeOutline = this.treeOutline;
  1375.         var node = this.representedObject;
  1376.         var wasExpanded = this.expanded;
  1377.  
  1378.         function selectNode(nodeId)
  1379.         {
  1380.             if (!nodeId)
  1381.                 return;
  1382.  
  1383.             // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
  1384.             WebInspector.panels.elements.updateModifiedNodes();
  1385.  
  1386.             WebInspector.updateFocusedNode(nodeId);
  1387.             if (wasExpanded) {
  1388.                 var newTreeItem = treeOutline.findTreeElement(WebInspector.domAgent.nodeForId(nodeId));
  1389.                 if (newTreeItem)
  1390.                     newTreeItem.expand();
  1391.             }
  1392.         }
  1393.  
  1394.         function commitChange(value)
  1395.         {
  1396.             InjectedScriptAccess.get(node.injectedScriptId).setOuterHTML(node.id, value, wasExpanded, selectNode.bind(this));
  1397.         }
  1398.  
  1399.         InjectedScriptAccess.get(node.injectedScriptId).getNodePropertyValue(node.id, "outerHTML", this._startEditingAsHTML.bind(this, commitChange));
  1400.     },
  1401.  
  1402.     _copyHTML: function()
  1403.     {
  1404.         InspectorBackend.copyNode(this.representedObject.id);
  1405.     },
  1406.  
  1407.     _highlightSearchResults: function()
  1408.     {
  1409.         if (!this._searchQuery)
  1410.             return;
  1411.         var text = this.listItemElement.textContent;
  1412.         var regexObject = createSearchRegex(this._searchQuery);
  1413.  
  1414.         var offset = 0;
  1415.         var match = regexObject.exec(text);
  1416.         while (match) {
  1417.             highlightSearchResult(this.listItemElement, offset + match.index, match[0].length);
  1418.             offset += match.index + 1;
  1419.             text = text.substring(match.index + 1);
  1420.             match = regexObject.exec(text);
  1421.         }
  1422.     }
  1423. }
  1424.  
  1425. WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;
  1426.