Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / ui / treeoutline.js
blob9896b2c97b2dd381432af852449eb3cb8178cddb
1 /*
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
6 * are met:
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.
29 /**
30 * @constructor
31 * @extends {WebInspector.Object}
32 * @param {boolean=} nonFocusable
34 function TreeOutline(nonFocusable)
36 this._createRootElement();
38 this.selectedTreeElement = null;
39 this.expandTreeElementsWhenArrowing = false;
40 /** @type {?function(!TreeElement, !TreeElement):number} */
41 this._comparator = null;
43 this._contentElement = this._rootElement._childrenListNode;
44 this._contentElement.addEventListener("keydown", this._treeKeyDown.bind(this), true);
46 this.setFocusable(!nonFocusable);
48 this.element = this._contentElement;
51 TreeOutline.Events = {
52 ElementAttached: "ElementAttached",
53 ElementExpanded: "ElementExpanded",
54 ElementCollapsed: "ElementCollapsed"
57 TreeOutline.prototype = {
58 _createRootElement: function()
60 this._rootElement = new TreeElement();
61 this._rootElement.treeOutline = this;
62 this._rootElement.root = true;
63 this._rootElement.selectable = false;
64 this._rootElement.expanded = true;
65 this._rootElement._childrenListNode.classList.remove("children");
68 /**
69 * @return {!TreeElement}
71 rootElement: function()
73 return this._rootElement;
76 /**
77 * @return {?TreeElement}
79 firstChild: function()
81 return this._rootElement.firstChild();
84 /**
85 * @param {!TreeElement} child
87 appendChild: function(child)
89 this._rootElement.appendChild(child);
92 /**
93 * @param {!TreeElement} child
94 * @param {number} index
96 insertChild: function(child, index)
98 this._rootElement.insertChild(child, index);
102 * @param {!TreeElement} child
104 removeChild: function(child)
106 this._rootElement.removeChild(child);
109 removeChildren: function()
111 this._rootElement.removeChildren();
115 * @param {number} x
116 * @param {number} y
117 * @return {?TreeElement}
119 treeElementFromPoint: function(x, y)
121 var node = this._contentElement.ownerDocument.deepElementFromPoint(x, y);
122 if (!node)
123 return null;
125 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
126 if (listNode)
127 return listNode.parentTreeElement || listNode.treeElement;
128 return null;
132 * @param {?Event} event
133 * @return {?TreeElement}
135 treeElementFromEvent: function(event)
137 return event ? this.treeElementFromPoint(event.pageX, event.pageY) : null;
141 * @param {?function(!TreeElement, !TreeElement):number} comparator
143 setComparator: function(comparator)
145 this._comparator = comparator;
149 * @param {boolean} focusable
151 setFocusable: function(focusable)
153 if (focusable)
154 this._contentElement.setAttribute("tabIndex", 0);
155 else
156 this._contentElement.removeAttribute("tabIndex");
159 focus: function()
161 this._contentElement.focus();
165 * @param {!TreeElement} element
167 _bindTreeElement: function(element)
169 if (element.treeOutline)
170 console.error("Binding element for the second time: " + new Error().stack);
171 element.treeOutline = this;
172 element.onbind();
176 * @param {!TreeElement} element
178 _unbindTreeElement: function(element)
180 if (!element.treeOutline)
181 console.error("Unbinding element that was not bound: " + new Error().stack);
183 element.deselect();
184 element.onunbind();
185 element.treeOutline = null;
189 * @return {boolean}
191 selectPrevious: function()
193 var nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
194 while (nextSelectedElement && !nextSelectedElement.selectable)
195 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
196 if (nextSelectedElement) {
197 nextSelectedElement.reveal();
198 nextSelectedElement.select(false, true);
199 return true;
201 return false;
205 * @return {boolean}
207 selectNext: function()
209 var nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
210 while (nextSelectedElement && !nextSelectedElement.selectable)
211 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
212 if (nextSelectedElement) {
213 nextSelectedElement.reveal();
214 nextSelectedElement.select(false, true);
215 return true;
217 return false;
221 * @param {!Event} event
223 _treeKeyDown: function(event)
225 if (event.target !== this._contentElement)
226 return;
228 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
229 return;
231 var handled = false;
232 var nextSelectedElement;
233 if (event.keyIdentifier === "Up" && !event.altKey) {
234 handled = this.selectPrevious();
235 } else if (event.keyIdentifier === "Down" && !event.altKey) {
236 handled = this.selectNext();
237 } else if (event.keyIdentifier === "Left") {
238 if (this.selectedTreeElement.expanded) {
239 if (event.altKey)
240 this.selectedTreeElement.collapseRecursively();
241 else
242 this.selectedTreeElement.collapse();
243 handled = true;
244 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
245 handled = true;
246 if (this.selectedTreeElement.parent.selectable) {
247 nextSelectedElement = this.selectedTreeElement.parent;
248 while (nextSelectedElement && !nextSelectedElement.selectable)
249 nextSelectedElement = nextSelectedElement.parent;
250 handled = nextSelectedElement ? true : false;
251 } else if (this.selectedTreeElement.parent)
252 this.selectedTreeElement.parent.collapse();
254 } else if (event.keyIdentifier === "Right") {
255 if (!this.selectedTreeElement.revealed()) {
256 this.selectedTreeElement.reveal();
257 handled = true;
258 } else if (this.selectedTreeElement._expandable) {
259 handled = true;
260 if (this.selectedTreeElement.expanded) {
261 nextSelectedElement = this.selectedTreeElement.firstChild();
262 while (nextSelectedElement && !nextSelectedElement.selectable)
263 nextSelectedElement = nextSelectedElement.nextSibling;
264 handled = nextSelectedElement ? true : false;
265 } else {
266 if (event.altKey)
267 this.selectedTreeElement.expandRecursively();
268 else
269 this.selectedTreeElement.expand();
272 } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */)
273 handled = this.selectedTreeElement.ondelete();
274 else if (isEnterKey(event))
275 handled = this.selectedTreeElement.onenter();
276 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code)
277 handled = this.selectedTreeElement.onspace();
279 if (nextSelectedElement) {
280 nextSelectedElement.reveal();
281 nextSelectedElement.select(false, true);
284 if (handled)
285 event.consume(true);
288 __proto__: WebInspector.Object.prototype
292 * @constructor
293 * @extends {TreeOutline}
294 * @param {string=} className
296 function TreeOutlineInShadow(className)
298 TreeOutline.call(this);
299 var innerElement = this.element;
300 innerElement.classList.add("tree-outline");
301 if (className)
302 innerElement.classList.add(className);
304 // Redefine element to the external one.
305 this.element = createElement("div");
306 this._shadowRoot = WebInspector.createShadowRootWithCoreStyles(this.element);
307 this._shadowRoot.appendChild(WebInspector.Widget.createStyleElement("ui/treeoutline.css"));
308 this._shadowRoot.appendChild(innerElement);
309 this._renderSelection = true;
312 TreeOutlineInShadow.prototype = {
314 * @param {string} cssFile
316 registerRequiredCSS: function(cssFile)
318 this._shadowRoot.appendChild(WebInspector.Widget.createStyleElement(cssFile));
321 __proto__: TreeOutline.prototype
325 * @constructor
326 * @param {(string|!Node)=} title
327 * @param {boolean=} expandable
329 function TreeElement(title, expandable)
331 /** @type {?TreeOutline} */
332 this.treeOutline = null;
333 this.parent = null;
334 this.previousSibling = null;
335 this.nextSibling = null;
337 this._listItemNode = createElement("li");
338 this._listItemNode.treeElement = this;
339 if (title)
340 this.title = title;
341 this._listItemNode.addEventListener("mousedown", this._handleMouseDown.bind(this), false);
342 this._listItemNode.addEventListener("selectstart", this._treeElementSelectStart.bind(this), false);
343 this._listItemNode.addEventListener("click", this._treeElementToggled.bind(this), false);
344 this._listItemNode.addEventListener("dblclick", this._handleDoubleClick.bind(this), false);
346 this._childrenListNode = createElement("ol");
347 this._childrenListNode.parentTreeElement = this;
348 this._childrenListNode.classList.add("children");
350 this._hidden = false;
351 this._selectable = true;
352 this.expanded = false;
353 this.selected = false;
354 this.setExpandable(expandable || false);
355 this._collapsible = true;
358 /** @const */
359 TreeElement._ArrowToggleWidth = 10;
361 TreeElement.prototype = {
363 * @param {?TreeElement} ancestor
364 * @return {boolean}
366 hasAncestor: function(ancestor)
368 if (!ancestor)
369 return false;
371 var currentNode = this.parent;
372 while (currentNode) {
373 if (ancestor === currentNode)
374 return true;
375 currentNode = currentNode.parent;
378 return false;
382 * @param {?TreeElement} ancestor
383 * @return {boolean}
385 hasAncestorOrSelf: function(ancestor)
387 return this === ancestor || this.hasAncestor(ancestor);
391 * @return {!Array.<!TreeElement>}
393 children: function()
395 return this._children || [];
399 * @return {number}
401 childCount: function()
403 return this._children ? this._children.length : 0;
407 * @return {?TreeElement}
409 firstChild: function()
411 return this._children ? this._children[0] : null;
415 * @return {?TreeElement}
417 lastChild: function()
419 return this._children ? this._children[this._children.length - 1] : null;
423 * @param {number} index
424 * @return {?TreeElement}
426 childAt: function(index)
428 return this._children ? this._children[index] : null;
432 * @param {!TreeElement} child
433 * @return {number}
435 indexOfChild: function(child)
437 return this._children ? this._children.indexOf(child) : -1;
441 * @param {!TreeElement} child
443 appendChild: function(child)
445 if (!this._children)
446 this._children = [];
448 var insertionIndex;
449 if (this.treeOutline && this.treeOutline._comparator)
450 insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this._children, this.treeOutline._comparator);
451 else
452 insertionIndex = this._children.length;
453 this.insertChild(child, insertionIndex);
457 * @param {!TreeElement} child
458 * @param {number} index
460 insertChild: function(child, index)
462 if (!this._children)
463 this._children = [];
465 if (!child)
466 throw("child can't be undefined or null");
468 console.assert(!child.parent, "Attempting to insert a child that is already in the tree, reparenting is not supported.");
470 var previousChild = (index > 0 ? this._children[index - 1] : null);
471 if (previousChild) {
472 previousChild.nextSibling = child;
473 child.previousSibling = previousChild;
474 } else {
475 child.previousSibling = null;
478 var nextChild = this._children[index];
479 if (nextChild) {
480 nextChild.previousSibling = child;
481 child.nextSibling = nextChild;
482 } else {
483 child.nextSibling = null;
486 this._children.splice(index, 0, child);
488 this.setExpandable(true);
489 child.parent = this;
491 if (this.treeOutline)
492 this.treeOutline._bindTreeElement(child);
493 for (var current = child.firstChild(); this.treeOutline && current; current = current.traverseNextTreeElement(false, child, true))
494 this.treeOutline._bindTreeElement(current);
495 child.onattach();
496 child._ensureSelection();
497 if (this.treeOutline)
498 this.treeOutline.dispatchEventToListeners(TreeOutline.Events.ElementAttached, child);
499 var nextSibling = child.nextSibling ? child.nextSibling._listItemNode : null;
500 this._childrenListNode.insertBefore(child._listItemNode, nextSibling);
501 this._childrenListNode.insertBefore(child._childrenListNode, nextSibling);
502 if (child.selected)
503 child.select();
504 if (child.expanded)
505 child.expand();
509 * @param {number} childIndex
511 removeChildAtIndex: function(childIndex)
513 if (childIndex < 0 || childIndex >= this._children.length)
514 throw("childIndex out of range");
516 var child = this._children[childIndex];
517 this._children.splice(childIndex, 1);
519 var parent = child.parent;
520 if (this.treeOutline && this.treeOutline.selectedTreeElement && this.treeOutline.selectedTreeElement.hasAncestorOrSelf(child)) {
521 if (child.nextSibling)
522 child.nextSibling.select(true);
523 else if (child.previousSibling)
524 child.previousSibling.select(true);
525 else if (parent)
526 parent.select(true);
529 if (child.previousSibling)
530 child.previousSibling.nextSibling = child.nextSibling;
531 if (child.nextSibling)
532 child.nextSibling.previousSibling = child.previousSibling;
533 child.parent = null;
535 if (this.treeOutline)
536 this.treeOutline._unbindTreeElement(child);
537 for (var current = child.firstChild(); this.treeOutline && current; current = current.traverseNextTreeElement(false, child, true))
538 this.treeOutline._unbindTreeElement(current);
540 child._detach();
544 * @param {!TreeElement} child
546 removeChild: function(child)
548 if (!child)
549 throw("child can't be undefined or null");
550 if (child.parent !== this)
551 return;
553 var childIndex = this._children.indexOf(child);
554 if (childIndex === -1)
555 throw("child not found in this node's children");
557 this.removeChildAtIndex(childIndex);
560 removeChildren: function()
562 if (!this.root && this.treeOutline && this.treeOutline.selectedTreeElement && this.treeOutline.selectedTreeElement.hasAncestorOrSelf(this))
563 this.select(true);
565 for (var i = 0; this._children && i < this._children.length; ++i) {
566 var child = this._children[i];
567 child.previousSibling = null
568 child.nextSibling = null;
569 child.parent = null;
571 if (this.treeOutline)
572 this.treeOutline._unbindTreeElement(child);
573 for (var current = child.firstChild(); this.treeOutline && current; current = current.traverseNextTreeElement(false, child, true))
574 this.treeOutline._unbindTreeElement(current);
575 child._detach();
577 this._children = [];
580 get selectable()
582 if (this._hidden)
583 return false;
584 return this._selectable;
587 set selectable(x)
589 this._selectable = x;
592 get listItemElement()
594 return this._listItemNode;
597 get childrenListElement()
599 return this._childrenListNode;
602 get title()
604 return this._title;
607 set title(x)
609 this._title = x;
610 if (typeof this._title === "string")
611 this._listItemNode.textContent = this._title;
612 else {
613 this._listItemNode.removeChildren();
614 if (this._title)
615 this._listItemNode.appendChild(this._title);
616 this._ensureSelection();
621 * @param {string} x
623 set tooltip(x)
625 this._listItemNode.title = x;
629 * @return {boolean}
631 isExpandable: function()
633 return this._expandable;
637 * @param {boolean} expandable
639 setExpandable: function(expandable)
641 if (this._expandable === expandable)
642 return;
644 this._expandable = expandable;
646 this._listItemNode.classList.toggle("parent", expandable);
647 if (!expandable)
648 this.collapse();
652 * @param {boolean} collapsible
654 setCollapsible: function(collapsible)
656 if (this._collapsible === collapsible)
657 return;
659 this._collapsible = collapsible;
661 this._listItemNode.classList.toggle("always-parent", !collapsible);
662 if (!collapsible)
663 this.expand();
666 get hidden()
668 return this._hidden;
671 set hidden(x)
673 if (this._hidden === x)
674 return;
676 this._hidden = x;
678 this._listItemNode.classList.toggle("hidden", x);
679 this._childrenListNode.classList.toggle("hidden", x);
682 invalidateChildren: function()
684 if (this._children) {
685 this.removeChildren();
686 this._children = null;
690 _ensureSelection: function()
692 if (!this.treeOutline || !this.treeOutline._renderSelection)
693 return;
694 if (!this._selectionElement)
695 this._selectionElement = createElementWithClass("div", "selection");
696 this._listItemNode.insertBefore(this._selectionElement, this.listItemElement.firstChild);
700 * @param {!Event} event
702 _treeElementSelectStart: function(event)
704 event.currentTarget._selectionStarted = true;
708 * @param {!Event} event
710 _treeElementToggled: function(event)
712 var element = event.currentTarget;
713 if (element._selectionStarted) {
714 delete element._selectionStarted;
715 var selection = element.getComponentSelection();
716 if (selection && !selection.isCollapsed && element.isSelfOrAncestor(selection.anchorNode) && element.isSelfOrAncestor(selection.focusNode))
717 return;
720 if (element.treeElement !== this)
721 return;
723 var toggleOnClick = this.toggleOnClick && !this.selectable;
724 var isInTriangle = this.isEventWithinDisclosureTriangle(event);
725 if (!toggleOnClick && !isInTriangle)
726 return;
728 if (event.target && event.target.enclosingNodeOrSelfWithNodeName("a"))
729 return;
731 if (this.expanded) {
732 if (event.altKey)
733 this.collapseRecursively();
734 else
735 this.collapse();
736 } else {
737 if (event.altKey)
738 this.expandRecursively();
739 else
740 this.expand();
742 event.consume();
746 * @param {!Event} event
748 _handleMouseDown: function(event)
750 var element = event.currentTarget;
751 if (!element)
752 return;
753 delete element._selectionStarted;
755 if (!this.selectable)
756 return;
757 if (element.treeElement !== this)
758 return;
760 if (this.isEventWithinDisclosureTriangle(event))
761 return;
763 this.selectOnMouseDown(event);
767 * @param {!Event} event
769 _handleDoubleClick: function(event)
771 var element = event.currentTarget;
772 if (!element || element.treeElement !== this)
773 return;
775 var handled = this.ondblclick(event);
776 if (handled)
777 return;
778 if (this._expandable && !this.expanded)
779 this.expand();
782 _detach: function()
784 this._listItemNode.remove();
785 this._childrenListNode.remove();
788 collapse: function()
790 if (!this.expanded || !this._collapsible)
791 return;
792 this._listItemNode.classList.remove("expanded");
793 this._childrenListNode.classList.remove("expanded");
794 this.expanded = false;
795 this.oncollapse();
796 if (this.treeOutline)
797 this.treeOutline.dispatchEventToListeners(TreeOutline.Events.ElementCollapsed, this);
800 collapseRecursively: function()
802 var item = this;
803 while (item) {
804 if (item.expanded)
805 item.collapse();
806 item = item.traverseNextTreeElement(false, this, true);
810 expand: function()
812 if (!this._expandable || (this.expanded && this._children))
813 return;
815 // Set this before onpopulate. Since onpopulate can add elements, this makes
816 // sure the expanded flag is true before calling those functions. This prevents the possibility
817 // of an infinite loop if onpopulate were to call expand.
819 this.expanded = true;
821 this._populateIfNeeded();
822 this._listItemNode.classList.add("expanded");
823 this._childrenListNode.classList.add("expanded");
825 this.onexpand();
826 if (this.treeOutline)
827 this.treeOutline.dispatchEventToListeners(TreeOutline.Events.ElementExpanded, this);
831 * @param {number=} maxDepth
833 expandRecursively: function(maxDepth)
835 var item = this;
836 var info = {};
837 var depth = 0;
839 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
840 // in some case can be infinite, since JavaScript objects can hold circular references.
841 // So default to a recursion cap of 3 levels, since that gives fairly good results.
842 if (isNaN(maxDepth))
843 maxDepth = 3;
845 while (item) {
846 if (depth < maxDepth)
847 item.expand();
848 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
849 depth += info.depthChange;
853 reveal: function()
855 var currentAncestor = this.parent;
856 while (currentAncestor && !currentAncestor.root) {
857 if (!currentAncestor.expanded)
858 currentAncestor.expand();
859 currentAncestor = currentAncestor.parent;
862 this.listItemElement.scrollIntoViewIfNeeded();
864 this.onreveal();
868 * @return {boolean}
870 revealed: function()
872 var currentAncestor = this.parent;
873 while (currentAncestor && !currentAncestor.root) {
874 if (!currentAncestor.expanded)
875 return false;
876 currentAncestor = currentAncestor.parent;
879 return true;
882 selectOnMouseDown: function(event)
884 if (this.select(false, true))
885 event.consume(true);
889 * @param {boolean=} omitFocus
890 * @param {boolean=} selectedByUser
891 * @return {boolean}
893 select: function(omitFocus, selectedByUser)
895 if (!this.treeOutline || !this.selectable || this.selected)
896 return false;
898 if (this.treeOutline.selectedTreeElement)
899 this.treeOutline.selectedTreeElement.deselect();
900 this.treeOutline.selectedTreeElement = null;
902 if (this.treeOutline._rootElement === this)
903 return false;
905 this.selected = true;
907 if (!omitFocus)
908 this.treeOutline.focus();
910 // Focusing on another node may detach "this" from tree.
911 if (!this.treeOutline)
912 return false;
913 this.treeOutline.selectedTreeElement = this;
914 this._listItemNode.classList.add("selected");
915 if (this._selectionElement)
916 this._selectionElement.style.height = this._listItemNode.offsetHeight + "px";
917 return this.onselect(selectedByUser);
921 * @param {boolean=} omitFocus
923 revealAndSelect: function(omitFocus)
925 this.reveal();
926 this.select(omitFocus);
930 * @param {boolean=} supressOnDeselect
932 deselect: function(supressOnDeselect)
934 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
935 return;
937 this.selected = false;
938 this.treeOutline.selectedTreeElement = null;
939 this._listItemNode.classList.remove("selected");
942 _populateIfNeeded: function()
944 if (this._expandable && !this._children) {
945 this._children = [];
946 this.onpopulate();
950 onpopulate: function()
952 // Overridden by subclasses.
956 * @return {boolean}
958 onenter: function()
960 return false;
964 * @return {boolean}
966 ondelete: function()
968 return false;
972 * @return {boolean}
974 onspace: function()
976 return false;
979 onbind: function()
983 onunbind: function()
987 onattach: function()
991 onexpand: function()
995 oncollapse: function()
1000 * @param {!Event} e
1001 * @return {boolean}
1003 ondblclick: function(e)
1005 return false;
1008 onreveal: function()
1013 * @param {boolean=} selectedByUser
1014 * @return {boolean}
1016 onselect: function(selectedByUser)
1018 return false;
1022 * @param {boolean} skipUnrevealed
1023 * @param {?TreeElement=} stayWithin
1024 * @param {boolean=} dontPopulate
1025 * @param {!Object=} info
1026 * @return {?TreeElement}
1028 traverseNextTreeElement: function(skipUnrevealed, stayWithin, dontPopulate, info)
1030 if (!dontPopulate)
1031 this._populateIfNeeded();
1033 if (info)
1034 info.depthChange = 0;
1036 var element = skipUnrevealed ? (this.revealed() ? this.firstChild() : null) : this.firstChild();
1037 if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) {
1038 if (info)
1039 info.depthChange = 1;
1040 return element;
1043 if (this === stayWithin)
1044 return null;
1046 element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
1047 if (element)
1048 return element;
1050 element = this;
1051 while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
1052 if (info)
1053 info.depthChange -= 1;
1054 element = element.parent;
1057 if (!element || element.root)
1058 return null;
1060 return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
1064 * @param {boolean} skipUnrevealed
1065 * @param {boolean=} dontPopulate
1066 * @return {?TreeElement}
1068 traversePreviousTreeElement: function(skipUnrevealed, dontPopulate)
1070 var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
1071 if (!dontPopulate && element)
1072 element._populateIfNeeded();
1074 while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.lastChild() : null) : element.lastChild())) {
1075 if (!dontPopulate)
1076 element._populateIfNeeded();
1077 element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.lastChild() : null) : element.lastChild());
1080 if (element)
1081 return element;
1083 if (!this.parent || this.parent.root)
1084 return null;
1086 return this.parent;
1090 * @return {boolean}
1092 isEventWithinDisclosureTriangle: function(event)
1094 // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446)
1095 var paddingLeftValue = window.getComputedStyle(this._listItemNode).paddingLeft;
1096 console.assert(paddingLeftValue.endsWith("px"));
1097 var computedLeftPadding = parseFloat(paddingLeftValue);
1098 var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding;
1099 return event.pageX >= left && event.pageX <= left + TreeElement._ArrowToggleWidth && this._expandable;