Initial patch.
[vuplus_webkit] / Source / WebCore / inspector / front-end / treeoutline.js
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  */
32 function TreeOutline(listNode)
33 {
34     /**
35      * @type {Array.<TreeElement>}
36      */
37     this.children = [];
38     this.selectedTreeElement = null;
39     this._childrenListNode = listNode;
40     this._childrenListNode.removeChildren();
41     this._knownTreeElements = [];
42     this._treeElementsExpandedState = [];
43     this.expandTreeElementsWhenArrowing = false;
44     this.root = true;
45     this.hasChildren = false;
46     this.expanded = true;
47     this.selected = false;
48     this.treeOutline = this;
49
50     this._childrenListNode.tabIndex = 0;
51     this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
52 }
53
54 TreeOutline._knownTreeElementNextIdentifier = 1;
55
56 TreeOutline.prototype.appendChild = function(child)
57 {
58     if (!child)
59         throw("child can't be undefined or null");
60
61     var lastChild = this.children[this.children.length - 1];
62     if (lastChild) {
63         lastChild.nextSibling = child;
64         child.previousSibling = lastChild;
65     } else {
66         child.previousSibling = null;
67         child.nextSibling = null;
68     }
69
70     this.children.push(child);
71     this.hasChildren = true;
72     child.parent = this;
73     child.treeOutline = this.treeOutline;
74     child.treeOutline._rememberTreeElement(child);
75
76     var current = child.children[0];
77     while (current) {
78         current.treeOutline = this.treeOutline;
79         current.treeOutline._rememberTreeElement(current);
80         current = current.traverseNextTreeElement(false, child, true);
81     }
82
83     if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
84         child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
85
86     if (!this._childrenListNode) {
87         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
88         this._childrenListNode.parentTreeElement = this;
89         this._childrenListNode.addStyleClass("children");
90         if (this.hidden)
91             this._childrenListNode.addStyleClass("hidden");
92     }
93
94     child._attach();
95 }
96
97 TreeOutline.prototype.insertChild = function(child, index)
98 {
99     if (!child)
100         throw("child can't be undefined or null");
101
102     var previousChild = (index > 0 ? this.children[index - 1] : null);
103     if (previousChild) {
104         previousChild.nextSibling = child;
105         child.previousSibling = previousChild;
106     } else {
107         child.previousSibling = null;
108     }
109
110     var nextChild = this.children[index];
111     if (nextChild) {
112         nextChild.previousSibling = child;
113         child.nextSibling = nextChild;
114     } else {
115         child.nextSibling = null;
116     }
117
118     this.children.splice(index, 0, child);
119     this.hasChildren = true;
120     child.parent = this;
121     child.treeOutline = this.treeOutline;
122     child.treeOutline._rememberTreeElement(child);
123
124     var current = child.children[0];
125     while (current) {
126         current.treeOutline = this.treeOutline;
127         current.treeOutline._rememberTreeElement(current);
128         current = current.traverseNextTreeElement(false, child, true);
129     }
130
131     if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
132         child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
133
134     if (!this._childrenListNode) {
135         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
136         this._childrenListNode.parentTreeElement = this;
137         this._childrenListNode.addStyleClass("children");
138         if (this.hidden)
139             this._childrenListNode.addStyleClass("hidden");
140     }
141
142     child._attach();
143 }
144
145 TreeOutline.prototype.removeChildAtIndex = function(childIndex)
146 {
147     if (childIndex < 0 || childIndex >= this.children.length)
148         throw("childIndex out of range");
149
150     var child = this.children[childIndex];
151     this.children.splice(childIndex, 1);
152
153     var parent = child.parent;
154     if (child.deselect()) {
155         if (child.previousSibling)
156             child.previousSibling.select();
157         else if (child.nextSibling)
158             child.nextSibling.select();
159         else
160             parent.select();
161     }
162
163     if (child.previousSibling)
164         child.previousSibling.nextSibling = child.nextSibling;
165     if (child.nextSibling)
166         child.nextSibling.previousSibling = child.previousSibling;
167
168     if (child.treeOutline) {
169         child.treeOutline._forgetTreeElement(child);
170         child.treeOutline._forgetChildrenRecursive(child);
171     }
172
173     child._detach();
174     child.treeOutline = null;
175     child.parent = null;
176     child.nextSibling = null;
177     child.previousSibling = null;
178 }
179
180 TreeOutline.prototype.removeChild = function(child)
181 {
182     if (!child)
183         throw("child can't be undefined or null");
184
185     var childIndex = this.children.indexOf(child);
186     if (childIndex === -1)
187         throw("child not found in this node's children");
188
189     this.removeChildAtIndex.call(this, childIndex);
190 }
191
192 TreeOutline.prototype.removeChildren = function()
193 {
194     for (var i = 0; i < this.children.length; ++i) {
195         var child = this.children[i];
196         child.deselect();
197
198         if (child.treeOutline) {
199             child.treeOutline._forgetTreeElement(child);
200             child.treeOutline._forgetChildrenRecursive(child);
201         }
202
203         child._detach();
204         child.treeOutline = null;
205         child.parent = null;
206         child.nextSibling = null;
207         child.previousSibling = null;
208     }
209
210     this.children = [];
211 }
212
213 TreeOutline.prototype.removeChildrenRecursive = function()
214 {
215     var childrenToRemove = this.children;
216
217     var child = this.children[0];
218     while (child) {
219         if (child.children.length)
220             childrenToRemove = childrenToRemove.concat(child.children);
221         child = child.traverseNextTreeElement(false, this, true);
222     }
223
224     for (var i = 0; i < childrenToRemove.length; ++i) {
225         child = childrenToRemove[i];
226         child.deselect();
227         if (child.treeOutline)
228             child.treeOutline._forgetTreeElement(child);
229         child._detach();
230         child.children = [];
231         child.treeOutline = null;
232         child.parent = null;
233         child.nextSibling = null;
234         child.previousSibling = null;
235     }
236
237     this.children = [];
238 }
239
240 TreeOutline.prototype._rememberTreeElement = function(element)
241 {
242     if (!this._knownTreeElements[element.identifier])
243         this._knownTreeElements[element.identifier] = [];
244
245     // check if the element is already known
246     var elements = this._knownTreeElements[element.identifier];
247     if (elements.indexOf(element) !== -1)
248         return;
249
250     // add the element
251     elements.push(element);
252 }
253
254 TreeOutline.prototype._forgetTreeElement = function(element)
255 {
256     if (this._knownTreeElements[element.identifier])
257         this._knownTreeElements[element.identifier].remove(element, true);
258 }
259
260 TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
261 {
262     var child = parentElement.children[0];
263     while (child) {
264         this._forgetTreeElement(child);
265         child = child.traverseNextTreeElement(false, this, true);
266     }
267 }
268
269 TreeOutline.prototype.getCachedTreeElement = function(representedObject)
270 {
271     if (!representedObject)
272         return null;
273
274     if ("__treeElementIdentifier" in representedObject) {
275         // If this representedObject has a tree element identifier, and it is a known TreeElement
276         // in our tree we can just return that tree element.
277         var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
278         if (elements) {
279             for (var i = 0; i < elements.length; ++i)
280                 if (elements[i].representedObject === representedObject)
281                     return elements[i];
282         }
283     }
284     return null;
285 }
286
287 TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
288 {
289     if (!representedObject)
290         return null;
291
292     var cachedElement = this.getCachedTreeElement(representedObject);
293     if (cachedElement)
294         return cachedElement;
295
296     // The representedObject isn't known, so we start at the top of the tree and work down to find the first
297     // tree element that represents representedObject or one of its ancestors.
298     var item;
299     var found = false;
300     for (var i = 0; i < this.children.length; ++i) {
301         item = this.children[i];
302         if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) {
303             found = true;
304             break;
305         }
306     }
307
308     if (!found)
309         return null;
310
311     // Make sure the item that we found is connected to the root of the tree.
312     // Build up a list of representedObject's ancestors that aren't already in our tree.
313     var ancestors = [];
314     var currentObject = representedObject;
315     while (currentObject) {
316         ancestors.unshift(currentObject);
317         if (currentObject === item.representedObject)
318             break;
319         currentObject = getParent(currentObject);
320     }
321
322     // For each of those ancestors we populate them to fill in the tree.
323     for (var i = 0; i < ancestors.length; ++i) {
324         // Make sure we don't call findTreeElement with the same representedObject
325         // again, to prevent infinite recursion.
326         if (ancestors[i] === representedObject)
327             continue;
328         // FIXME: we could do something faster than findTreeElement since we will know the next
329         // ancestor exists in the tree.
330         item = this.findTreeElement(ancestors[i], isAncestor, getParent);
331         if (item)
332             item.onpopulate();
333     }
334
335     return this.getCachedTreeElement(representedObject);
336 }
337
338 TreeOutline.prototype.treeElementFromPoint = function(x, y)
339 {
340     var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
341     if (!node)
342         return null;
343
344     var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
345     if (listNode)
346         return listNode.parentTreeElement || listNode.treeElement;
347     return null;
348 }
349
350 TreeOutline.prototype._treeKeyDown = function(event)
351 {
352     if (event.target !== this._childrenListNode)
353         return;
354
355     if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
356         return;
357
358     var handled = false;
359     var nextSelectedElement;
360     if (event.keyIdentifier === "Up" && !event.altKey) {
361         nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
362         while (nextSelectedElement && !nextSelectedElement.selectable)
363             nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
364         handled = nextSelectedElement ? true : false;
365     } else if (event.keyIdentifier === "Down" && !event.altKey) {
366         nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
367         while (nextSelectedElement && !nextSelectedElement.selectable)
368             nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
369         handled = nextSelectedElement ? true : false;
370     } else if (event.keyIdentifier === "Left") {
371         if (this.selectedTreeElement.expanded) {
372             if (event.altKey)
373                 this.selectedTreeElement.collapseRecursively();
374             else
375                 this.selectedTreeElement.collapse();
376             handled = true;
377         } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
378             handled = true;
379             if (this.selectedTreeElement.parent.selectable) {
380                 nextSelectedElement = this.selectedTreeElement.parent;
381                 handled = nextSelectedElement ? true : false;
382             } else if (this.selectedTreeElement.parent)
383                 this.selectedTreeElement.parent.collapse();
384         }
385     } else if (event.keyIdentifier === "Right") {
386         if (!this.selectedTreeElement.revealed()) {
387             this.selectedTreeElement.reveal();
388             handled = true;
389         } else if (this.selectedTreeElement.hasChildren) {
390             handled = true;
391             if (this.selectedTreeElement.expanded) {
392                 nextSelectedElement = this.selectedTreeElement.children[0];
393                 handled = nextSelectedElement ? true : false;
394             } else {
395                 if (event.altKey)
396                     this.selectedTreeElement.expandRecursively();
397                 else
398                     this.selectedTreeElement.expand();
399             }
400         }
401     } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) {
402         if (this.selectedTreeElement.ondelete)
403             handled = this.selectedTreeElement.ondelete();
404     } else if (isEnterKey(event)) {
405         if (this.selectedTreeElement.onenter)
406             handled = this.selectedTreeElement.onenter();
407     }
408
409     if (nextSelectedElement) {
410         nextSelectedElement.reveal();
411         nextSelectedElement.select(false, true);
412     }
413
414     if (handled) {
415         event.preventDefault();
416         event.stopPropagation();
417     }
418 }
419
420 TreeOutline.prototype.expand = function()
421 {
422     // this is the root, do nothing
423 }
424
425 TreeOutline.prototype.collapse = function()
426 {
427     // this is the root, do nothing
428 }
429
430 TreeOutline.prototype.revealed = function()
431 {
432     return true;
433 }
434
435 TreeOutline.prototype.reveal = function()
436 {
437     // this is the root, do nothing
438 }
439
440 TreeOutline.prototype.select = function()
441 {
442     // this is the root, do nothing
443 }
444
445 TreeOutline.prototype.revealAndSelect = function(omitFocus)
446 {
447     // this is the root, do nothing
448 }
449
450 /**
451  * @constructor
452  * @param {Object=} representedObject
453  * @param {boolean=} hasChildren
454  */
455 function TreeElement(title, representedObject, hasChildren)
456 {
457     this._title = title;
458     this.representedObject = (representedObject || {});
459
460     if (this.representedObject.__treeElementIdentifier)
461         this.identifier = this.representedObject.__treeElementIdentifier;
462     else {
463         this.identifier = TreeOutline._knownTreeElementNextIdentifier++;
464         this.representedObject.__treeElementIdentifier = this.identifier;
465     }
466
467     this._hidden = false;
468     this.expanded = false;
469     this.selected = false;
470     this.hasChildren = hasChildren;
471     this.children = [];
472     this.treeOutline = null;
473     this.parent = null;
474     this.previousSibling = null;
475     this.nextSibling = null;
476     this._listItemNode = null;
477 }
478
479 TreeElement.prototype = {
480     selectable: true,
481     arrowToggleWidth: 10,
482
483     get listItemElement() {
484         return this._listItemNode;
485     },
486
487     get childrenListElement() {
488         return this._childrenListNode;
489     },
490
491     get title() {
492         return this._title;
493     },
494
495     set title(x) {
496         this._title = x;
497         this._setListItemNodeContent();
498     },
499
500     get titleHTML() {
501         return this._titleHTML;
502     },
503
504     set titleHTML(x) {
505         this._titleHTML = x;
506         this._setListItemNodeContent();
507     },
508
509     get tooltip() {
510         return this._tooltip;
511     },
512
513     set tooltip(x) {
514         this._tooltip = x;
515         if (this._listItemNode)
516             this._listItemNode.title = x ? x : "";
517     },
518
519     get hasChildren() {
520         return this._hasChildren;
521     },
522
523     set hasChildren(x) {
524         if (this._hasChildren === x)
525             return;
526
527         this._hasChildren = x;
528
529         if (!this._listItemNode)
530             return;
531
532         if (x)
533             this._listItemNode.addStyleClass("parent");
534         else {
535             this._listItemNode.removeStyleClass("parent");
536             this.collapse();
537         }
538     },
539
540     get hidden() {
541         return this._hidden;
542     },
543
544     set hidden(x) {
545         if (this._hidden === x)
546             return;
547
548         this._hidden = x;
549
550         if (x) {
551             if (this._listItemNode)
552                 this._listItemNode.addStyleClass("hidden");
553             if (this._childrenListNode)
554                 this._childrenListNode.addStyleClass("hidden");
555         } else {
556             if (this._listItemNode)
557                 this._listItemNode.removeStyleClass("hidden");
558             if (this._childrenListNode)
559                 this._childrenListNode.removeStyleClass("hidden");
560         }
561     },
562
563     get shouldRefreshChildren() {
564         return this._shouldRefreshChildren;
565     },
566
567     set shouldRefreshChildren(x) {
568         this._shouldRefreshChildren = x;
569         if (x && this.expanded)
570             this.expand();
571     },
572
573     _setListItemNodeContent: function()
574     {
575         if (!this._listItemNode)
576             return;
577
578         if (!this._titleHTML && !this._title)
579             this._listItemNode.removeChildren();
580         else if (typeof this._titleHTML === "string")
581             this._listItemNode.innerHTML = this._titleHTML;
582         else if (typeof this._title === "string")
583             this._listItemNode.textContent = this._title;
584         else {
585             this._listItemNode.removeChildren();
586             if (this._title.parentNode)
587                 this._title.parentNode.removeChild(this._title);
588             this._listItemNode.appendChild(this._title);
589         }
590     }
591 }
592
593 TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild;
594 TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild;
595 TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild;
596 TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex;
597 TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren;
598 TreeElement.prototype.removeChildrenRecursive = TreeOutline.prototype.removeChildrenRecursive;
599
600 TreeElement.prototype._attach = function()
601 {
602     if (!this._listItemNode || this.parent._shouldRefreshChildren) {
603         if (this._listItemNode && this._listItemNode.parentNode)
604             this._listItemNode.parentNode.removeChild(this._listItemNode);
605
606         this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
607         this._listItemNode.treeElement = this;
608         this._setListItemNodeContent();
609         this._listItemNode.title = this._tooltip ? this._tooltip : "";
610
611         if (this.hidden)
612             this._listItemNode.addStyleClass("hidden");
613         if (this.hasChildren)
614             this._listItemNode.addStyleClass("parent");
615         if (this.expanded)
616             this._listItemNode.addStyleClass("expanded");
617         if (this.selected)
618             this._listItemNode.addStyleClass("selected");
619
620         this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
621         this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
622         this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
623
624         if (this.onattach)
625             this.onattach(this);
626     }
627
628     var nextSibling = null;
629     if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
630         nextSibling = this.nextSibling._listItemNode;
631     this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
632     if (this._childrenListNode)
633         this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
634     if (this.selected)
635         this.select();
636     if (this.expanded)
637         this.expand();
638 }
639
640 TreeElement.prototype._detach = function()
641 {
642     if (this._listItemNode && this._listItemNode.parentNode)
643         this._listItemNode.parentNode.removeChild(this._listItemNode);
644     if (this._childrenListNode && this._childrenListNode.parentNode)
645         this._childrenListNode.parentNode.removeChild(this._childrenListNode);
646 }
647
648 TreeElement.treeElementMouseDown = function(event)
649 {
650     var element = event.currentTarget;
651     if (!element || !element.treeElement || !element.treeElement.selectable)
652         return;
653
654     if (element.treeElement.isEventWithinDisclosureTriangle(event))
655         return;
656
657     element.treeElement.selectOnMouseDown(event);
658 }
659
660 TreeElement.treeElementToggled = function(event)
661 {
662     var element = event.currentTarget;
663     if (!element || !element.treeElement)
664         return;
665
666     var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
667     var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
668     if (!toggleOnClick && !isInTriangle)
669         return;
670
671     if (element.treeElement.expanded) {
672         if (event.altKey)
673             element.treeElement.collapseRecursively();
674         else
675             element.treeElement.collapse();
676     } else {
677         if (event.altKey)
678             element.treeElement.expandRecursively();
679         else
680             element.treeElement.expand();
681     }
682     event.stopPropagation();
683 }
684
685 TreeElement.treeElementDoubleClicked = function(event)
686 {
687     var element = event.currentTarget;
688     if (!element || !element.treeElement)
689         return;
690
691     if (element.treeElement.ondblclick)
692         element.treeElement.ondblclick.call(element.treeElement, event);
693     else if (element.treeElement.hasChildren && !element.treeElement.expanded)
694         element.treeElement.expand();
695 }
696
697 TreeElement.prototype.collapse = function()
698 {
699     if (this._listItemNode)
700         this._listItemNode.removeStyleClass("expanded");
701     if (this._childrenListNode)
702         this._childrenListNode.removeStyleClass("expanded");
703
704     this.expanded = false;
705     if (this.treeOutline)
706         this.treeOutline._treeElementsExpandedState[this.identifier] = true;
707
708     if (this.oncollapse)
709         this.oncollapse(this);
710 }
711
712 TreeElement.prototype.collapseRecursively = function()
713 {
714     var item = this;
715     while (item) {
716         if (item.expanded)
717             item.collapse();
718         item = item.traverseNextTreeElement(false, this, true);
719     }
720 }
721
722 TreeElement.prototype.expand = function()
723 {
724     if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
725         return;
726
727     if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
728         if (this._childrenListNode && this._childrenListNode.parentNode)
729             this._childrenListNode.parentNode.removeChild(this._childrenListNode);
730
731         this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
732         this._childrenListNode.parentTreeElement = this;
733         this._childrenListNode.addStyleClass("children");
734
735         if (this.hidden)
736             this._childrenListNode.addStyleClass("hidden");
737
738         this.onpopulate();
739
740         for (var i = 0; i < this.children.length; ++i)
741             this.children[i]._attach();
742
743         delete this._shouldRefreshChildren;
744     }
745
746     if (this._listItemNode) {
747         this._listItemNode.addStyleClass("expanded");
748         if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
749             this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
750     }
751
752     if (this._childrenListNode)
753         this._childrenListNode.addStyleClass("expanded");
754
755     this.expanded = true;
756     if (this.treeOutline)
757         this.treeOutline._treeElementsExpandedState[this.identifier] = true;
758
759     if (this.onexpand)
760         this.onexpand(this);
761 }
762
763 TreeElement.prototype.expandRecursively = function(maxDepth)
764 {
765     var item = this;
766     var info = {};
767     var depth = 0;
768
769     // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
770     // in some case can be infinite, since JavaScript objects can hold circular references.
771     // So default to a recursion cap of 3 levels, since that gives fairly good results.
772     if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
773         maxDepth = 3;
774
775     while (item) {
776         if (depth < maxDepth)
777             item.expand();
778         item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
779         depth += info.depthChange;
780     }
781 }
782
783 TreeElement.prototype.hasAncestor = function(ancestor) {
784     if (!ancestor)
785         return false;
786
787     var currentNode = this.parent;
788     while (currentNode) {
789         if (ancestor === currentNode)
790             return true;
791         currentNode = currentNode.parent;
792     }
793
794     return false;
795 }
796
797 TreeElement.prototype.reveal = function()
798 {
799     var currentAncestor = this.parent;
800     while (currentAncestor && !currentAncestor.root) {
801         if (!currentAncestor.expanded)
802             currentAncestor.expand();
803         currentAncestor = currentAncestor.parent;
804     }
805
806     if (this.onreveal)
807         this.onreveal(this);
808 }
809
810 TreeElement.prototype.revealed = function()
811 {
812     var currentAncestor = this.parent;
813     while (currentAncestor && !currentAncestor.root) {
814         if (!currentAncestor.expanded)
815             return false;
816         currentAncestor = currentAncestor.parent;
817     }
818
819     return true;
820 }
821
822 TreeElement.prototype.selectOnMouseDown = function(event)
823 {
824     this.select(false, true);
825 }
826
827 /**
828  * @param {boolean=} omitFocus
829  * @param {boolean=} selectedByUser
830  */
831 TreeElement.prototype.select = function(omitFocus, selectedByUser)
832 {
833     if (!this.treeOutline || !this.selectable || this.selected)
834         return;
835
836     if (this.treeOutline.selectedTreeElement)
837         this.treeOutline.selectedTreeElement.deselect();
838
839     this.selected = true;
840
841     if(!omitFocus)
842         this.treeOutline._childrenListNode.focus();
843
844     // Focusing on another node may detach "this" from tree.
845     if (!this.treeOutline)
846         return;
847     this.treeOutline.selectedTreeElement = this;
848     if (this._listItemNode)
849         this._listItemNode.addStyleClass("selected");
850
851     if (this.onselect)
852         this.onselect(this, selectedByUser);
853 }
854
855 TreeElement.prototype.revealAndSelect = function(omitFocus)
856 {
857     this.reveal();
858     this.select(omitFocus);
859 }
860
861 /**
862  * @param {boolean=} supressOnDeselect
863  */
864 TreeElement.prototype.deselect = function(supressOnDeselect)
865 {
866     if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
867         return false;
868
869     this.selected = false;
870     this.treeOutline.selectedTreeElement = null;
871     if (this._listItemNode)
872         this._listItemNode.removeStyleClass("selected");
873
874     if (this.ondeselect && !supressOnDeselect)
875         this.ondeselect(this);
876     return true;
877 }
878
879 TreeElement.prototype.onpopulate = function()
880 {
881     // Overriden by subclasses.
882 }
883
884 TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
885 {
886     if (!dontPopulate && this.hasChildren)
887         this.onpopulate();
888
889     if (info)
890         info.depthChange = 0;
891
892     var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
893     if (element && (!skipHidden || (skipHidden && this.expanded))) {
894         if (info)
895             info.depthChange = 1;
896         return element;
897     }
898
899     if (this === stayWithin)
900         return null;
901
902     element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
903     if (element)
904         return element;
905
906     element = this;
907     while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
908         if (info)
909             info.depthChange -= 1;
910         element = element.parent;
911     }
912
913     if (!element)
914         return null;
915
916     return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
917 }
918
919 TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate)
920 {
921     var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
922     if (!dontPopulate && element && element.hasChildren)
923         element.onpopulate();
924
925     while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
926         if (!dontPopulate && element.hasChildren)
927             element.onpopulate();
928         element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
929     }
930
931     if (element)
932         return element;
933
934     if (!this.parent || this.parent.root)
935         return null;
936
937     return this.parent;
938 }
939
940 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
941 {
942     var left = this._listItemNode.totalOffsetLeft();
943     return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
944 }