Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     14  *     its contributors may be used to endorse or promote products derived
     15  *     from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /**
     30  * @constructor
     31  * @param {Element} listNode
     32  * @param {boolean=} nonFocusable
     33  */
     34 function TreeOutline(listNode, nonFocusable)
     35 {
     36     /** @type {!Array.<TreeElement>} */
     37     this.children = [];
     38     this.selectedTreeElement = null;
     39     this._childrenListNode = listNode;
     40     this.childrenListElement = this._childrenListNode;
     41     this._childrenListNode.removeChildren();
     42     this.expandTreeElementsWhenArrowing = false;
     43     this.root = true;
     44     this.hasChildren = false;
     45     this.expanded = true;
     46     this.selected = false;
     47     this.treeOutline = this;
     48     /** @type {function(TreeElement,TreeElement):number|null} */
     49     this.comparator = null;
     50 
     51     this.setFocusable(!nonFocusable);
     52     this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
     53 
     54     /** @type {!Map.<Object, !Array.<!TreeElement>>} */
     55     this._treeElementsMap = new Map();
     56     /** @type {!Map.<Object, boolean>} */
     57     this._expandedStateMap = new Map();
     58 }
     59 
     60 TreeOutline.prototype.setFocusable = function(focusable)
     61 {
     62     if (focusable)
     63         this._childrenListNode.setAttribute("tabIndex", 0);
     64     else
     65         this._childrenListNode.removeAttribute("tabIndex");
     66 }
     67 
     68 /**
     69  * @param {TreeElement} child
     70  */
     71 TreeOutline.prototype.appendChild = function(child)
     72 {
     73     var insertionIndex;
     74     if (this.treeOutline.comparator)
     75         insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator);
     76     else
     77         insertionIndex = this.children.length;
     78     this.insertChild(child, insertionIndex);
     79 }
     80 
     81 /**
     82  * @param {TreeElement} child
     83  * @param {TreeElement} beforeChild
     84  */
     85 TreeOutline.prototype.insertBeforeChild = function(child, beforeChild)
     86 {
     87     if (!child)
     88         throw("child can't be undefined or null");
     89 
     90     if (!beforeChild)
     91         throw("beforeChild can't be undefined or null");
     92 
     93     var childIndex = this.children.indexOf(beforeChild);
     94     if (childIndex === -1)
     95         throw("beforeChild not found in this node's children");
     96 
     97     this.insertChild(child, childIndex);
     98 }
     99 
    100 /**
    101  * @param {TreeElement} child
    102  * @param {number} index
    103  */
    104 TreeOutline.prototype.insertChild = function(child, index)
    105 {
    106     if (!child)
    107         throw("child can't be undefined or null");
    108 
    109     var previousChild = (index > 0 ? this.children[index - 1] : null);
    110     if (previousChild) {
    111         previousChild.nextSibling = child;
    112         child.previousSibling = previousChild;
    113     } else {
    114         child.previousSibling = null;
    115     }
    116 
    117     var nextChild = this.children[index];
    118     if (nextChild) {
    119         nextChild.previousSibling = child;
    120         child.nextSibling = nextChild;
    121     } else {
    122         child.nextSibling = null;
    123     }
    124 
    125     this.children.splice(index, 0, child);
    126     this.hasChildren = true;
    127     child.parent = this;
    128     child.treeOutline = this.treeOutline;
    129     child.treeOutline._rememberTreeElement(child);
    130 
    131     var current = child.children[0];
    132     while (current) {
    133         current.treeOutline = this.treeOutline;
    134         current.treeOutline._rememberTreeElement(current);
    135         current = current.traverseNextTreeElement(false, child, true);
    136     }
    137 
    138     if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined")
    139         child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject);
    140 
    141     if (!this._childrenListNode) {
    142         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
    143         this._childrenListNode.parentTreeElement = this;
    144         this._childrenListNode.classList.add("children");
    145         if (this.hidden)
    146             this._childrenListNode.classList.add("hidden");
    147     }
    148 
    149     child._attach();
    150 }
    151 
    152 /**
    153  * @param {number} childIndex
    154  */
    155 TreeOutline.prototype.removeChildAtIndex = function(childIndex)
    156 {
    157     if (childIndex < 0 || childIndex >= this.children.length)
    158         throw("childIndex out of range");
    159 
    160     var child = this.children[childIndex];
    161     this.children.splice(childIndex, 1);
    162 
    163     var parent = child.parent;
    164     if (child.deselect()) {
    165         if (child.previousSibling)
    166             child.previousSibling.select();
    167         else if (child.nextSibling)
    168             child.nextSibling.select();
    169         else
    170             parent.select();
    171     }
    172 
    173     if (child.previousSibling)
    174         child.previousSibling.nextSibling = child.nextSibling;
    175     if (child.nextSibling)
    176         child.nextSibling.previousSibling = child.previousSibling;
    177 
    178     if (child.treeOutline) {
    179         child.treeOutline._forgetTreeElement(child);
    180         child.treeOutline._forgetChildrenRecursive(child);
    181     }
    182 
    183     child._detach();
    184     child.treeOutline = null;
    185     child.parent = null;
    186     child.nextSibling = null;
    187     child.previousSibling = null;
    188 }
    189 
    190 /**
    191  * @param {TreeElement} child
    192  */
    193 TreeOutline.prototype.removeChild = function(child)
    194 {
    195     if (!child)
    196         throw("child can't be undefined or null");
    197 
    198     var childIndex = this.children.indexOf(child);
    199     if (childIndex === -1)
    200         throw("child not found in this node's children");
    201 
    202     this.removeChildAtIndex.call(this, childIndex);
    203 }
    204 
    205 TreeOutline.prototype.removeChildren = function()
    206 {
    207     for (var i = 0; i < this.children.length; ++i) {
    208         var child = this.children[i];
    209         child.deselect();
    210 
    211         if (child.treeOutline) {
    212             child.treeOutline._forgetTreeElement(child);
    213             child.treeOutline._forgetChildrenRecursive(child);
    214         }
    215 
    216         child._detach();
    217         child.treeOutline = null;
    218         child.parent = null;
    219         child.nextSibling = null;
    220         child.previousSibling = null;
    221     }
    222 
    223     this.children = [];
    224 }
    225 
    226 /**
    227  * @param {TreeElement} element
    228  */
    229 TreeOutline.prototype._rememberTreeElement = function(element)
    230 {
    231     if (!this._treeElementsMap.get(element.representedObject))
    232         this._treeElementsMap.put(element.representedObject, []);
    233 
    234     // check if the element is already known
    235     var elements = this._treeElementsMap.get(element.representedObject);
    236     if (elements.indexOf(element) !== -1)
    237         return;
    238 
    239     // add the element
    240     elements.push(element);
    241 }
    242 
    243 /**
    244  * @param {TreeElement} element
    245  */
    246 TreeOutline.prototype._forgetTreeElement = function(element)
    247 {
    248     if (this._treeElementsMap.get(element.representedObject)) {
    249         var elements = this._treeElementsMap.get(element.representedObject);
    250         elements.remove(element, true);
    251         if (!elements.length)
    252             this._treeElementsMap.remove(element.representedObject);
    253     }
    254 }
    255 
    256 /**
    257  * @param {TreeElement} parentElement
    258  */
    259 TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
    260 {
    261     var child = parentElement.children[0];
    262     while (child) {
    263         this._forgetTreeElement(child);
    264         child = child.traverseNextTreeElement(false, parentElement, true);
    265     }
    266 }
    267 
    268 TreeOutline.prototype.getCachedTreeElement = function(representedObject)
    269 {
    270     if (!representedObject)
    271         return null;
    272 
    273     var elements = this._treeElementsMap.get(representedObject);
    274     if (elements && elements.length)
    275         return elements[0];
    276     return null;
    277 }
    278 
    279 TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
    280 {
    281     if (!representedObject)
    282         return null;
    283 
    284     var cachedElement = this.getCachedTreeElement(representedObject);
    285     if (cachedElement)
    286         return cachedElement;
    287 
    288     // Walk up the parent pointers from the desired representedObject
    289     var ancestors = [];
    290     for (var currentObject = getParent(representedObject); currentObject;  currentObject = getParent(currentObject)) {
    291         ancestors.push(currentObject);
    292         if (this.getCachedTreeElement(currentObject))  // stop climbing as soon as we hit
    293             break;
    294     }
    295 
    296     if (!currentObject)
    297         return null;
    298 
    299     // Walk down to populate each ancestor's children, to fill in the tree and the cache.
    300     for (var i = ancestors.length - 1; i >= 0; --i) {
    301         var treeElement = this.getCachedTreeElement(ancestors[i]);
    302         if (treeElement)
    303             treeElement.onpopulate();  // fill the cache with the children of treeElement
    304     }
    305 
    306     return this.getCachedTreeElement(representedObject);
    307 }
    308 
    309 TreeOutline.prototype.treeElementFromPoint = function(x, y)
    310 {
    311     var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
    312     if (!node)
    313         return null;
    314 
    315     var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
    316     if (listNode)
    317         return listNode.parentTreeElement || listNode.treeElement;
    318     return null;
    319 }
    320 
    321 TreeOutline.prototype._treeKeyDown = function(event)
    322 {
    323     if (event.target !== this._childrenListNode)
    324         return;
    325 
    326     if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
    327         return;
    328 
    329     var handled = false;
    330     var nextSelectedElement;
    331     if (event.keyIdentifier === "Up" && !event.altKey) {
    332         nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
    333         while (nextSelectedElement && !nextSelectedElement.selectable)
    334             nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
    335         handled = nextSelectedElement ? true : false;
    336     } else if (event.keyIdentifier === "Down" && !event.altKey) {
    337         nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
    338         while (nextSelectedElement && !nextSelectedElement.selectable)
    339             nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
    340         handled = nextSelectedElement ? true : false;
    341     } else if (event.keyIdentifier === "Left") {
    342         if (this.selectedTreeElement.expanded) {
    343             if (event.altKey)
    344                 this.selectedTreeElement.collapseRecursively();
    345             else
    346                 this.selectedTreeElement.collapse();
    347             handled = true;
    348         } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
    349             handled = true;
    350             if (this.selectedTreeElement.parent.selectable) {
    351                 nextSelectedElement = this.selectedTreeElement.parent;
    352                 while (nextSelectedElement && !nextSelectedElement.selectable)
    353                     nextSelectedElement = nextSelectedElement.parent;
    354                 handled = nextSelectedElement ? true : false;
    355             } else if (this.selectedTreeElement.parent)
    356                 this.selectedTreeElement.parent.collapse();
    357         }
    358     } else if (event.keyIdentifier === "Right") {
    359         if (!this.selectedTreeElement.revealed()) {
    360             this.selectedTreeElement.reveal();
    361             handled = true;
    362         } else if (this.selectedTreeElement.hasChildren) {
    363             handled = true;
    364             if (this.selectedTreeElement.expanded) {
    365                 nextSelectedElement = this.selectedTreeElement.children[0];
    366                 while (nextSelectedElement && !nextSelectedElement.selectable)
    367                     nextSelectedElement = nextSelectedElement.nextSibling;
    368                 handled = nextSelectedElement ? true : false;
    369             } else {
    370                 if (event.altKey)
    371                     this.selectedTreeElement.expandRecursively();
    372                 else
    373                     this.selectedTreeElement.expand();
    374             }
    375         }
    376     } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */)
    377         handled = this.selectedTreeElement.ondelete();
    378     else if (isEnterKey(event))
    379         handled = this.selectedTreeElement.onenter();
    380     else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code)
    381         handled = this.selectedTreeElement.onspace();
    382 
    383     if (nextSelectedElement) {
    384         nextSelectedElement.reveal();
    385         nextSelectedElement.select(false, true);
    386     }
    387 
    388     if (handled)
    389         event.consume(true);
    390 }
    391 
    392 TreeOutline.prototype.expand = function()
    393 {
    394     // this is the root, do nothing
    395 }
    396 
    397 TreeOutline.prototype.collapse = function()
    398 {
    399     // this is the root, do nothing
    400 }
    401 
    402 TreeOutline.prototype.revealed = function()
    403 {
    404     return true;
    405 }
    406 
    407 TreeOutline.prototype.reveal = function()
    408 {
    409     // this is the root, do nothing
    410 }
    411 
    412 TreeOutline.prototype.select = function()
    413 {
    414     // this is the root, do nothing
    415 }
    416 
    417 /**
    418  * @param {boolean=} omitFocus
    419  */
    420 TreeOutline.prototype.revealAndSelect = function(omitFocus)
    421 {
    422     // this is the root, do nothing
    423 }
    424 
    425 /**
    426  * @constructor
    427  * @param {Object=} representedObject
    428  * @param {boolean=} hasChildren
    429  */
    430 function TreeElement(title, representedObject, hasChildren)
    431 {
    432     this._title = title;
    433     this.representedObject = (representedObject || {});
    434 
    435     this._hidden = false;
    436     this._selectable = true;
    437     this.expanded = false;
    438     this.selected = false;
    439     this.hasChildren = hasChildren;
    440     this.children = [];
    441     this.treeOutline = null;
    442     this.parent = null;
    443     this.previousSibling = null;
    444     this.nextSibling = null;
    445     this._listItemNode = null;
    446 }
    447 
    448 TreeElement.prototype = {
    449     arrowToggleWidth: 10,
    450 
    451     get selectable() {
    452         if (this._hidden)
    453             return false;
    454         return this._selectable;
    455     },
    456 
    457     set selectable(x) {
    458         this._selectable = x;
    459     },
    460 
    461     get listItemElement() {
    462         return this._listItemNode;
    463     },
    464 
    465     get childrenListElement() {
    466         return this._childrenListNode;
    467     },
    468 
    469     get title() {
    470         return this._title;
    471     },
    472 
    473     set title(x) {
    474         this._title = x;
    475         this._setListItemNodeContent();
    476     },
    477 
    478     get tooltip() {
    479         return this._tooltip;
    480     },
    481 
    482     set tooltip(x) {
    483         this._tooltip = x;
    484         if (this._listItemNode)
    485             this._listItemNode.title = x ? x : "";
    486     },
    487 
    488     get hasChildren() {
    489         return this._hasChildren;
    490     },
    491 
    492     set hasChildren(x) {
    493         if (this._hasChildren === x)
    494             return;
    495 
    496         this._hasChildren = x;
    497 
    498         if (!this._listItemNode)
    499             return;
    500 
    501         if (x)
    502             this._listItemNode.classList.add("parent");
    503         else {
    504             this._listItemNode.classList.remove("parent");
    505             this.collapse();
    506         }
    507     },
    508 
    509     get hidden() {
    510         return this._hidden;
    511     },
    512 
    513     set hidden(x) {
    514         if (this._hidden === x)
    515             return;
    516 
    517         this._hidden = x;
    518 
    519         if (x) {
    520             if (this._listItemNode)
    521                 this._listItemNode.classList.add("hidden");
    522             if (this._childrenListNode)
    523                 this._childrenListNode.classList.add("hidden");
    524         } else {
    525             if (this._listItemNode)
    526                 this._listItemNode.classList.remove("hidden");
    527             if (this._childrenListNode)
    528                 this._childrenListNode.classList.remove("hidden");
    529         }
    530     },
    531 
    532     get shouldRefreshChildren() {
    533         return this._shouldRefreshChildren;
    534     },
    535 
    536     set shouldRefreshChildren(x) {
    537         this._shouldRefreshChildren = x;
    538         if (x && this.expanded)
    539             this.expand();
    540     },
    541 
    542     _setListItemNodeContent: function()
    543     {
    544         if (!this._listItemNode)
    545             return;
    546 
    547         if (typeof this._title === "string")
    548             this._listItemNode.textContent = this._title;
    549         else {
    550             this._listItemNode.removeChildren();
    551             if (this._title)
    552                 this._listItemNode.appendChild(this._title);
    553         }
    554     }
    555 }
    556 
    557 TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild;
    558 TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild;
    559 TreeElement.prototype.insertBeforeChild = TreeOutline.prototype.insertBeforeChild;
    560 TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild;
    561 TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex;
    562 TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren;
    563 
    564 TreeElement.prototype._attach = function()
    565 {
    566     if (!this._listItemNode || this.parent._shouldRefreshChildren) {
    567         if (this._listItemNode && this._listItemNode.parentNode)
    568             this._listItemNode.parentNode.removeChild(this._listItemNode);
    569 
    570         this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
    571         this._listItemNode.treeElement = this;
    572         this._setListItemNodeContent();
    573         this._listItemNode.title = this._tooltip ? this._tooltip : "";
    574 
    575         if (this.hidden)
    576             this._listItemNode.classList.add("hidden");
    577         if (this.hasChildren)
    578             this._listItemNode.classList.add("parent");
    579         if (this.expanded)
    580             this._listItemNode.classList.add("expanded");
    581         if (this.selected)
    582             this._listItemNode.classList.add("selected");
    583 
    584         this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
    585         this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
    586         this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
    587 
    588         this.onattach();
    589     }
    590 
    591     var nextSibling = null;
    592     if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
    593         nextSibling = this.nextSibling._listItemNode;
    594     this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
    595     if (this._childrenListNode)
    596         this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    597     if (this.selected)
    598         this.select();
    599     if (this.expanded)
    600         this.expand();
    601 }
    602 
    603 TreeElement.prototype._detach = function()
    604 {
    605     if (this._listItemNode && this._listItemNode.parentNode)
    606         this._listItemNode.parentNode.removeChild(this._listItemNode);
    607     if (this._childrenListNode && this._childrenListNode.parentNode)
    608         this._childrenListNode.parentNode.removeChild(this._childrenListNode);
    609 }
    610 
    611 TreeElement.treeElementMouseDown = function(event)
    612 {
    613     var element = event.currentTarget;
    614     if (!element || !element.treeElement || !element.treeElement.selectable)
    615         return;
    616 
    617     if (element.treeElement.isEventWithinDisclosureTriangle(event))
    618         return;
    619 
    620     element.treeElement.selectOnMouseDown(event);
    621 }
    622 
    623 TreeElement.treeElementToggled = function(event)
    624 {
    625     var element = event.currentTarget;
    626     if (!element || !element.treeElement)
    627         return;
    628 
    629     var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
    630     var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
    631     if (!toggleOnClick && !isInTriangle)
    632         return;
    633 
    634     if (element.treeElement.expanded) {
    635         if (event.altKey)
    636             element.treeElement.collapseRecursively();
    637         else
    638             element.treeElement.collapse();
    639     } else {
    640         if (event.altKey)
    641             element.treeElement.expandRecursively();
    642         else
    643             element.treeElement.expand();
    644     }
    645     event.consume();
    646 }
    647 
    648 TreeElement.treeElementDoubleClicked = function(event)
    649 {
    650     var element = event.currentTarget;
    651     if (!element || !element.treeElement)
    652         return;
    653 
    654     var handled = element.treeElement.ondblclick.call(element.treeElement, event);
    655     if (handled)
    656         return;
    657     if (element.treeElement.hasChildren && !element.treeElement.expanded)
    658         element.treeElement.expand();
    659 }
    660 
    661 TreeElement.prototype.collapse = function()
    662 {
    663     if (this._listItemNode)
    664         this._listItemNode.classList.remove("expanded");
    665     if (this._childrenListNode)
    666         this._childrenListNode.classList.remove("expanded");
    667 
    668     this.expanded = false;
    669 
    670     if (this.treeOutline)
    671         this.treeOutline._expandedStateMap.put(this.representedObject, false);
    672 
    673     this.oncollapse();
    674 }
    675 
    676 TreeElement.prototype.collapseRecursively = function()
    677 {
    678     var item = this;
    679     while (item) {
    680         if (item.expanded)
    681             item.collapse();
    682         item = item.traverseNextTreeElement(false, this, true);
    683     }
    684 }
    685 
    686 TreeElement.prototype.expand = function()
    687 {
    688     if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
    689         return;
    690 
    691     // Set this before onpopulate. Since onpopulate can add elements, this makes
    692     // sure the expanded flag is true before calling those functions. This prevents the possibility
    693     // of an infinite loop if onpopulate were to call expand.
    694 
    695     this.expanded = true;
    696     if (this.treeOutline)
    697         this.treeOutline._expandedStateMap.put(this.representedObject, true);
    698 
    699     if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
    700         if (this._childrenListNode && this._childrenListNode.parentNode)
    701             this._childrenListNode.parentNode.removeChild(this._childrenListNode);
    702 
    703         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
    704         this._childrenListNode.parentTreeElement = this;
    705         this._childrenListNode.classList.add("children");
    706 
    707         if (this.hidden)
    708             this._childrenListNode.classList.add("hidden");
    709 
    710         this.onpopulate();
    711 
    712         for (var i = 0; i < this.children.length; ++i)
    713             this.children[i]._attach();
    714 
    715         delete this._shouldRefreshChildren;
    716     }
    717 
    718     if (this._listItemNode) {
    719         this._listItemNode.classList.add("expanded");
    720         if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
    721             this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
    722     }
    723 
    724     if (this._childrenListNode)
    725         this._childrenListNode.classList.add("expanded");
    726 
    727     this.onexpand();
    728 }
    729 
    730 TreeElement.prototype.expandRecursively = function(maxDepth)
    731 {
    732     var item = this;
    733     var info = {};
    734     var depth = 0;
    735 
    736     // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
    737     // in some case can be infinite, since JavaScript objects can hold circular references.
    738     // So default to a recursion cap of 3 levels, since that gives fairly good results.
    739     if (isNaN(maxDepth))
    740         maxDepth = 3;
    741 
    742     while (item) {
    743         if (depth < maxDepth)
    744             item.expand();
    745         item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
    746         depth += info.depthChange;
    747     }
    748 }
    749 
    750 TreeElement.prototype.hasAncestor = function(ancestor) {
    751     if (!ancestor)
    752         return false;
    753 
    754     var currentNode = this.parent;
    755     while (currentNode) {
    756         if (ancestor === currentNode)
    757             return true;
    758         currentNode = currentNode.parent;
    759     }
    760 
    761     return false;
    762 }
    763 
    764 TreeElement.prototype.reveal = function()
    765 {
    766     var currentAncestor = this.parent;
    767     while (currentAncestor && !currentAncestor.root) {
    768         if (!currentAncestor.expanded)
    769             currentAncestor.expand();
    770         currentAncestor = currentAncestor.parent;
    771     }
    772 
    773     this.onreveal(this);
    774 }
    775 
    776 TreeElement.prototype.revealed = function()
    777 {
    778     var currentAncestor = this.parent;
    779     while (currentAncestor && !currentAncestor.root) {
    780         if (!currentAncestor.expanded)
    781             return false;
    782         currentAncestor = currentAncestor.parent;
    783     }
    784 
    785     return true;
    786 }
    787 
    788 TreeElement.prototype.selectOnMouseDown = function(event)
    789 {
    790     if (this.select(false, true))
    791         event.consume(true);
    792 }
    793 
    794 /**
    795  * @param {boolean=} omitFocus
    796  * @param {boolean=} selectedByUser
    797  * @return {boolean}
    798  */
    799 TreeElement.prototype.select = function(omitFocus, selectedByUser)
    800 {
    801     if (!this.treeOutline || !this.selectable || this.selected)
    802         return false;
    803 
    804     if (this.treeOutline.selectedTreeElement)
    805         this.treeOutline.selectedTreeElement.deselect();
    806 
    807     this.selected = true;
    808 
    809     if(!omitFocus)
    810         this.treeOutline._childrenListNode.focus();
    811 
    812     // Focusing on another node may detach "this" from tree.
    813     if (!this.treeOutline)
    814         return false;
    815     this.treeOutline.selectedTreeElement = this;
    816     if (this._listItemNode)
    817         this._listItemNode.classList.add("selected");
    818 
    819     return this.onselect(selectedByUser);
    820 }
    821 
    822 /**
    823  * @param {boolean=} omitFocus
    824  */
    825 TreeElement.prototype.revealAndSelect = function(omitFocus)
    826 {
    827     this.reveal();
    828     this.select(omitFocus);
    829 }
    830 
    831 /**
    832  * @param {boolean=} supressOnDeselect
    833  */
    834 TreeElement.prototype.deselect = function(supressOnDeselect)
    835 {
    836     if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
    837         return false;
    838 
    839     this.selected = false;
    840     this.treeOutline.selectedTreeElement = null;
    841     if (this._listItemNode)
    842         this._listItemNode.classList.remove("selected");
    843     return true;
    844 }
    845 
    846 // Overridden by subclasses.
    847 TreeElement.prototype.onpopulate = function() { }
    848 TreeElement.prototype.onenter = function() { }
    849 TreeElement.prototype.ondelete = function() { }
    850 TreeElement.prototype.onspace = function() { }
    851 TreeElement.prototype.onattach = function() { }
    852 TreeElement.prototype.onexpand = function() { }
    853 TreeElement.prototype.oncollapse = function() { }
    854 TreeElement.prototype.ondblclick = function() { }
    855 TreeElement.prototype.onreveal = function() { }
    856 /** @param {boolean=} selectedByUser */
    857 TreeElement.prototype.onselect = function(selectedByUser) { }
    858 
    859 /**
    860  * @param {boolean} skipUnrevealed
    861  * @param {(TreeOutline|TreeElement)=} stayWithin
    862  * @param {boolean=} dontPopulate
    863  * @param {Object=} info
    864  * @return {TreeElement}
    865  */
    866 TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info)
    867 {
    868     if (!dontPopulate && this.hasChildren)
    869         this.onpopulate();
    870 
    871     if (info)
    872         info.depthChange = 0;
    873 
    874     var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0];
    875     if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) {
    876         if (info)
    877             info.depthChange = 1;
    878         return element;
    879     }
    880 
    881     if (this === stayWithin)
    882         return null;
    883 
    884     element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
    885     if (element)
    886         return element;
    887 
    888     element = this;
    889     while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
    890         if (info)
    891             info.depthChange -= 1;
    892         element = element.parent;
    893     }
    894 
    895     if (!element)
    896         return null;
    897 
    898     return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
    899 }
    900 
    901 /**
    902  * @param {boolean} skipUnrevealed
    903  * @param {boolean=} dontPopulate
    904  * @return {TreeElement}
    905  */
    906 TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate)
    907 {
    908     var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
    909     if (!dontPopulate && element && element.hasChildren)
    910         element.onpopulate();
    911 
    912     while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
    913         if (!dontPopulate && element.hasChildren)
    914             element.onpopulate();
    915         element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
    916     }
    917 
    918     if (element)
    919         return element;
    920 
    921     if (!this.parent || this.parent.root)
    922         return null;
    923 
    924     return this.parent;
    925 }
    926 
    927 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
    928 {
    929     // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446)
    930     var paddingLeftValue = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left");
    931     var computedLeftPadding = paddingLeftValue ? paddingLeftValue.getFloatValue(CSSPrimitiveValue.CSS_PX) : 0;
    932     var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding;
    933     return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
    934 }
    935