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