2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
31 * @param {!Element} listNode
32 * @param {boolean=} nonFocusable
34 function TreeOutline(listNode, nonFocusable)
36 /** @type {!Array.<!TreeElement>} */
38 this.selectedTreeElement = null;
39 this._childrenListNode = listNode;
40 this.childrenListElement = this._childrenListNode;
41 this._childrenListNode.removeChildren();
42 this.expandTreeElementsWhenArrowing = false;
44 this.hasChildren = false;
46 this.selected = false;
47 this.treeOutline = this;
48 /** @type {?function(!TreeElement, !TreeElement):number} */
49 this.comparator = null;
51 this.setFocusable(!nonFocusable);
52 this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
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;
61 TreeOutline.prototype.setFocusable = function(focusable)
64 this._childrenListNode.setAttribute("tabIndex", 0);
66 this._childrenListNode.removeAttribute("tabIndex");
70 * @param {!TreeElement} child
72 TreeOutline.prototype.appendChild = function(child)
75 if (this.treeOutline.comparator)
76 insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator);
78 insertionIndex = this.children.length;
79 this.insertChild(child, insertionIndex);
83 * @param {!TreeElement} child
84 * @param {!TreeElement} beforeChild
86 TreeOutline.prototype.insertBeforeChild = function(child, beforeChild)
89 throw("child can't be undefined or null");
92 throw("beforeChild can't be undefined or null");
94 var childIndex = this.children.indexOf(beforeChild);
95 if (childIndex === -1)
96 throw("beforeChild not found in this node's children");
98 this.insertChild(child, childIndex);
102 * @param {!TreeElement} child
103 * @param {number} index
105 TreeOutline.prototype.insertChild = function(child, index)
108 throw("child can't be undefined or null");
110 var previousChild = (index > 0 ? this.children[index - 1] : null);
112 previousChild.nextSibling = child;
113 child.previousSibling = previousChild;
115 child.previousSibling = null;
118 var nextChild = this.children[index];
120 nextChild.previousSibling = child;
121 child.nextSibling = nextChild;
123 child.nextSibling = null;
126 this.children.splice(index, 0, child);
127 this.hasChildren = true;
129 child.treeOutline = this.treeOutline;
130 child.treeOutline._rememberTreeElement(child);
132 var current = child.children[0];
134 current.treeOutline = this.treeOutline;
135 current.treeOutline._rememberTreeElement(current);
136 current = current.traverseNextTreeElement(false, child, true);
139 if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined")
140 child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject);
142 if (!this._childrenListNode) {
143 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
144 this._childrenListNode.parentTreeElement = this;
145 this._childrenListNode.classList.add("children");
147 this._childrenListNode.classList.add("hidden");
154 * @param {number} childIndex
156 TreeOutline.prototype.removeChildAtIndex = function(childIndex)
158 if (childIndex < 0 || childIndex >= this.children.length)
159 throw("childIndex out of range");
161 var child = this.children[childIndex];
162 this.children.splice(childIndex, 1);
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();
174 if (child.previousSibling)
175 child.previousSibling.nextSibling = child.nextSibling;
176 if (child.nextSibling)
177 child.nextSibling.previousSibling = child.previousSibling;
179 if (child.treeOutline) {
180 child.treeOutline._forgetTreeElement(child);
181 child.treeOutline._forgetChildrenRecursive(child);
185 child.treeOutline = null;
187 child.nextSibling = null;
188 child.previousSibling = null;
192 * @param {!TreeElement} child
194 TreeOutline.prototype.removeChild = function(child)
197 throw("child can't be undefined or null");
199 var childIndex = this.children.indexOf(child);
200 if (childIndex === -1)
201 throw("child not found in this node's children");
203 this.removeChildAtIndex.call(this, childIndex);
206 TreeOutline.prototype.removeChildren = function()
208 for (var i = 0; i < this.children.length; ++i) {
209 var child = this.children[i];
212 if (child.treeOutline) {
213 child.treeOutline._forgetTreeElement(child);
214 child.treeOutline._forgetChildrenRecursive(child);
218 child.treeOutline = null;
220 child.nextSibling = null;
221 child.previousSibling = null;
228 * @param {!TreeElement} element
230 TreeOutline.prototype._rememberTreeElement = function(element)
232 if (!this._treeElementsMap.get(element.representedObject))
233 this._treeElementsMap.put(element.representedObject, []);
235 // check if the element is already known
236 var elements = this._treeElementsMap.get(element.representedObject);
237 if (elements.indexOf(element) !== -1)
241 elements.push(element);
245 * @param {!TreeElement} element
247 TreeOutline.prototype._forgetTreeElement = function(element)
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);
258 * @param {!TreeElement} parentElement
260 TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
262 var child = parentElement.children[0];
264 this._forgetTreeElement(child);
265 child = child.traverseNextTreeElement(false, parentElement, true);
270 * @param {?Object} representedObject
271 * @return {?TreeElement}
273 TreeOutline.prototype.getCachedTreeElement = function(representedObject)
275 if (!representedObject)
278 var elements = this._treeElementsMap.get(representedObject);
279 if (elements && elements.length)
285 * @param {?Object} representedObject
286 * @return {?TreeElement}
288 TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
290 if (!representedObject)
293 var cachedElement = this.getCachedTreeElement(representedObject);
295 return cachedElement;
297 // Walk up the parent pointers from the desired representedObject
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
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]);
312 treeElement.onpopulate(); // fill the cache with the children of treeElement
315 return this.getCachedTreeElement(representedObject);
321 * @return {?TreeElement}
323 TreeOutline.prototype.treeElementFromPoint = function(x, y)
325 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
329 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
331 return listNode.parentTreeElement || listNode.treeElement;
335 TreeOutline.prototype._treeKeyDown = function(event)
337 if (event.target !== this._childrenListNode)
340 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
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) {
358 this.selectedTreeElement.collapseRecursively();
360 this.selectedTreeElement.collapse();
362 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
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();
372 } else if (event.keyIdentifier === "Right") {
373 if (!this.selectedTreeElement.revealed()) {
374 this.selectedTreeElement.reveal();
376 } else if (this.selectedTreeElement.hasChildren) {
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;
385 this.selectedTreeElement.expandRecursively();
387 this.selectedTreeElement.expand();
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();
397 if (nextSelectedElement) {
398 nextSelectedElement.reveal();
399 nextSelectedElement.select(false, true);
406 TreeOutline.prototype.expand = function()
408 // this is the root, do nothing
411 TreeOutline.prototype.collapse = function()
413 // this is the root, do nothing
416 TreeOutline.prototype.revealed = function()
421 TreeOutline.prototype.reveal = function()
423 // this is the root, do nothing
426 TreeOutline.prototype.select = function()
428 // this is the root, do nothing
432 * @param {boolean=} omitFocus
434 TreeOutline.prototype.revealAndSelect = function(omitFocus)
436 // this is the root, do nothing
441 * @param {string|!Node} title
442 * @param {?Object=} representedObject
443 * @param {boolean=} hasChildren
445 function TreeElement(title, representedObject, hasChildren)
448 this.representedObject = (representedObject || {});
451 this._hidden = false;
452 this._selectable = true;
453 this.expanded = false;
454 this.selected = false;
455 this.hasChildren = hasChildren;
457 this.treeOutline = null;
459 this.previousSibling = null;
460 this.nextSibling = null;
461 this._listItemNode = null;
464 TreeElement.prototype = {
465 arrowToggleWidth: 10,
470 return this._selectable;
474 this._selectable = x;
477 get listItemElement() {
478 return this._listItemNode;
481 get childrenListElement() {
482 return this._childrenListNode;
491 this._setListItemNodeContent();
495 return this._tooltip;
500 if (this._listItemNode)
501 this._listItemNode.title = x ? x : "";
505 return this._hasChildren;
509 if (this._hasChildren === x)
512 this._hasChildren = x;
514 if (!this._listItemNode)
518 this._listItemNode.classList.add("parent");
520 this._listItemNode.classList.remove("parent");
530 if (this._hidden === x)
536 if (this._listItemNode)
537 this._listItemNode.classList.add("hidden");
538 if (this._childrenListNode)
539 this._childrenListNode.classList.add("hidden");
541 if (this._listItemNode)
542 this._listItemNode.classList.remove("hidden");
543 if (this._childrenListNode)
544 this._childrenListNode.classList.remove("hidden");
548 get shouldRefreshChildren() {
549 return this._shouldRefreshChildren;
552 set shouldRefreshChildren(x) {
553 this._shouldRefreshChildren = x;
554 if (x && this.expanded)
558 _setListItemNodeContent: function()
560 if (!this._listItemNode)
563 if (typeof this._title === "string")
564 this._listItemNode.textContent = this._title;
566 this._listItemNode.removeChildren();
568 this._listItemNode.appendChild(this._title);
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;
580 TreeElement.prototype._attach = function()
582 if (!this._listItemNode || this.parent._shouldRefreshChildren) {
583 if (this._listItemNode && this._listItemNode.parentNode)
584 this._listItemNode.parentNode.removeChild(this._listItemNode);
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 : "";
592 this._listItemNode.classList.add("hidden");
593 if (this.hasChildren)
594 this._listItemNode.classList.add("parent");
596 this._listItemNode.classList.add("expanded");
598 this._listItemNode.classList.add("selected");
600 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
601 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
602 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
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);
619 TreeElement.prototype._detach = function()
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);
627 TreeElement.treeElementMouseDown = function(event)
629 var element = event.currentTarget;
630 if (!element || !element.treeElement || !element.treeElement.selectable)
633 if (element.treeElement.isEventWithinDisclosureTriangle(event))
636 element.treeElement.selectOnMouseDown(event);
639 TreeElement.treeElementToggled = function(event)
641 var element = event.currentTarget;
642 if (!element || !element.treeElement)
645 var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
646 var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
647 if (!toggleOnClick && !isInTriangle)
650 if (element.treeElement.expanded) {
652 element.treeElement.collapseRecursively();
654 element.treeElement.collapse();
657 element.treeElement.expandRecursively();
659 element.treeElement.expand();
664 TreeElement.treeElementDoubleClicked = function(event)
666 var element = event.currentTarget;
667 if (!element || !element.treeElement)
670 var handled = element.treeElement.ondblclick.call(element.treeElement, event);
673 if (element.treeElement.hasChildren && !element.treeElement.expanded)
674 element.treeElement.expand();
677 TreeElement.prototype.collapse = function()
679 if (this._listItemNode)
680 this._listItemNode.classList.remove("expanded");
681 if (this._childrenListNode)
682 this._childrenListNode.classList.remove("expanded");
684 this.expanded = false;
686 if (this.treeOutline)
687 this.treeOutline._expandedStateMap.put(this.representedObject, false);
692 TreeElement.prototype.collapseRecursively = function()
698 item = item.traverseNextTreeElement(false, this, true);
702 TreeElement.prototype.expand = function()
704 if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
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.
711 this.expanded = true;
712 if (this.treeOutline)
713 this.treeOutline._expandedStateMap.put(this.representedObject, true);
715 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
716 if (this._childrenListNode && this._childrenListNode.parentNode)
717 this._childrenListNode.parentNode.removeChild(this._childrenListNode);
719 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
720 this._childrenListNode.parentTreeElement = this;
721 this._childrenListNode.classList.add("children");
724 this._childrenListNode.classList.add("hidden");
728 for (var i = 0; i < this.children.length; ++i)
729 this.children[i]._attach();
731 delete this._shouldRefreshChildren;
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);
740 if (this._childrenListNode)
741 this._childrenListNode.classList.add("expanded");
746 TreeElement.prototype.expandRecursively = function(maxDepth)
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.
759 if (depth < maxDepth)
761 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
762 depth += info.depthChange;
766 TreeElement.prototype.hasAncestor = function(ancestor) {
770 var currentNode = this.parent;
771 while (currentNode) {
772 if (ancestor === currentNode)
774 currentNode = currentNode.parent;
780 TreeElement.prototype.reveal = function()
782 var currentAncestor = this.parent;
783 while (currentAncestor && !currentAncestor.root) {
784 if (!currentAncestor.expanded)
785 currentAncestor.expand();
786 currentAncestor = currentAncestor.parent;
792 TreeElement.prototype.revealed = function()
794 var currentAncestor = this.parent;
795 while (currentAncestor && !currentAncestor.root) {
796 if (!currentAncestor.expanded)
798 currentAncestor = currentAncestor.parent;
804 TreeElement.prototype.selectOnMouseDown = function(event)
806 if (this.select(false, true))
811 * @param {boolean=} omitFocus
812 * @param {boolean=} selectedByUser
815 TreeElement.prototype.select = function(omitFocus, selectedByUser)
817 if (!this.treeOutline || !this.selectable || this.selected)
820 if (this.treeOutline.selectedTreeElement)
821 this.treeOutline.selectedTreeElement.deselect();
823 this.selected = true;
826 this.treeOutline._childrenListNode.focus();
828 // Focusing on another node may detach "this" from tree.
829 if (!this.treeOutline)
831 this.treeOutline.selectedTreeElement = this;
832 if (this._listItemNode)
833 this._listItemNode.classList.add("selected");
835 return this.onselect(selectedByUser);
839 * @param {boolean=} omitFocus
841 TreeElement.prototype.revealAndSelect = function(omitFocus)
844 this.select(omitFocus);
848 * @param {boolean=} supressOnDeselect
850 TreeElement.prototype.deselect = function(supressOnDeselect)
852 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
855 this.selected = false;
856 this.treeOutline.selectedTreeElement = null;
857 if (this._listItemNode)
858 this._listItemNode.classList.remove("selected");
862 // Overridden by subclasses.
863 TreeElement.prototype.onpopulate = function() { }
868 TreeElement.prototype.onenter = function() { return false; }
873 TreeElement.prototype.ondelete = function() { return false; }
878 TreeElement.prototype.onspace = function() { return false; }
880 TreeElement.prototype.onattach = function() { }
882 TreeElement.prototype.onexpand = function() { }
884 TreeElement.prototype.oncollapse = function() { }
887 * @param {!MouseEvent} e
890 TreeElement.prototype.ondblclick = function(e) { return false; }
892 TreeElement.prototype.onreveal = function() { }
895 * @param {boolean=} selectedByUser
898 TreeElement.prototype.onselect = function(selectedByUser) { return false; }
901 * @param {boolean} skipUnrevealed
902 * @param {(!TreeOutline|!TreeElement|null)=} stayWithin
903 * @param {boolean=} dontPopulate
904 * @param {!Object=} info
905 * @return {?TreeElement}
907 TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info)
909 if (!dontPopulate && this.hasChildren)
913 info.depthChange = 0;
915 var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0];
916 if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) {
918 info.depthChange = 1;
922 if (this === stayWithin)
925 element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
930 while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
932 info.depthChange -= 1;
933 element = element.parent;
939 return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
943 * @param {boolean} skipUnrevealed
944 * @param {boolean=} dontPopulate
945 * @return {?TreeElement}
947 TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate)
949 var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
950 if (!dontPopulate && element && element.hasChildren)
951 element.onpopulate();
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]);
962 if (!this.parent || this.parent.root)
968 TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
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;