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