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 * @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");
69 * @return {!TreeElement}
71 rootElement: function()
73 return this._rootElement
;
77 * @return {?TreeElement}
79 firstChild: function()
81 return this._rootElement
.firstChild();
85 * @param {!TreeElement} child
87 appendChild: function(child
)
89 this._rootElement
.appendChild(child
);
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();
117 * @return {?TreeElement}
119 treeElementFromPoint: function(x
, y
)
121 var node
= this._contentElement
.ownerDocument
.deepElementFromPoint(x
, y
);
125 var listNode
= node
.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
127 return listNode
.parentTreeElement
|| listNode
.treeElement
;
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
)
154 this._contentElement
.setAttribute("tabIndex", 0);
156 this._contentElement
.removeAttribute("tabIndex");
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;
176 * @param {!TreeElement} element
178 _unbindTreeElement: function(element
)
180 if (!element
.treeOutline
)
181 console
.error("Unbinding element that was not bound: " + new Error().stack
);
185 element
.treeOutline
= null;
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);
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);
221 * @param {!Event} event
223 _treeKeyDown: function(event
)
225 if (event
.target
!== this._contentElement
)
228 if (!this.selectedTreeElement
|| event
.shiftKey
|| event
.metaKey
|| event
.ctrlKey
)
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
) {
240 this.selectedTreeElement
.collapseRecursively();
242 this.selectedTreeElement
.collapse();
244 } else if (this.selectedTreeElement
.parent
&& !this.selectedTreeElement
.parent
.root
) {
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();
258 } else if (this.selectedTreeElement
._expandable
) {
260 if (this.selectedTreeElement
.expanded
) {
261 nextSelectedElement
= this.selectedTreeElement
.firstChild();
262 while (nextSelectedElement
&& !nextSelectedElement
.selectable
)
263 nextSelectedElement
= nextSelectedElement
.nextSibling
;
264 handled
= nextSelectedElement
? true : false;
267 this.selectedTreeElement
.expandRecursively();
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);
288 __proto__
: WebInspector
.Object
.prototype
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");
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
326 * @param {(string|!Node)=} title
327 * @param {boolean=} expandable
329 function TreeElement(title
, expandable
)
331 /** @type {?TreeOutline} */
332 this.treeOutline
= null;
334 this.previousSibling
= null;
335 this.nextSibling
= null;
337 this._listItemNode
= createElement("li");
338 this._listItemNode
.treeElement
= this;
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;
359 TreeElement
._ArrowToggleWidth
= 10;
361 TreeElement
.prototype = {
363 * @param {?TreeElement} ancestor
366 hasAncestor: function(ancestor
)
371 var currentNode
= this.parent
;
372 while (currentNode
) {
373 if (ancestor
=== currentNode
)
375 currentNode
= currentNode
.parent
;
382 * @param {?TreeElement} ancestor
385 hasAncestorOrSelf: function(ancestor
)
387 return this === ancestor
|| this.hasAncestor(ancestor
);
391 * @return {!Array.<!TreeElement>}
395 return this._children
|| [];
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
435 indexOfChild: function(child
)
437 return this._children
? this._children
.indexOf(child
) : -1;
441 * @param {!TreeElement} child
443 appendChild: function(child
)
449 if (this.treeOutline
&& this.treeOutline
._comparator
)
450 insertionIndex
= insertionIndexForObjectInListSortedByFunction(child
, this._children
, this.treeOutline
._comparator
);
452 insertionIndex
= this._children
.length
;
453 this.insertChild(child
, insertionIndex
);
457 * @param {!TreeElement} child
458 * @param {number} index
460 insertChild: function(child
, index
)
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);
472 previousChild
.nextSibling
= child
;
473 child
.previousSibling
= previousChild
;
475 child
.previousSibling
= null;
478 var nextChild
= this._children
[index
];
480 nextChild
.previousSibling
= child
;
481 child
.nextSibling
= nextChild
;
483 child
.nextSibling
= null;
486 this._children
.splice(index
, 0, child
);
488 this.setExpandable(true);
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
);
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
);
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);
529 if (child
.previousSibling
)
530 child
.previousSibling
.nextSibling
= child
.nextSibling
;
531 if (child
.nextSibling
)
532 child
.nextSibling
.previousSibling
= child
.previousSibling
;
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
);
544 * @param {!TreeElement} child
546 removeChild: function(child
)
549 throw("child can't be undefined or null");
550 if (child
.parent
!== this)
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))
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;
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
);
584 return this._selectable
;
589 this._selectable
= x
;
592 get listItemElement()
594 return this._listItemNode
;
597 get childrenListElement()
599 return this._childrenListNode
;
610 if (typeof this._title
=== "string")
611 this._listItemNode
.textContent
= this._title
;
613 this._listItemNode
.removeChildren();
615 this._listItemNode
.appendChild(this._title
);
616 this._ensureSelection();
625 this._listItemNode
.title
= x
;
631 isExpandable: function()
633 return this._expandable
;
637 * @param {boolean} expandable
639 setExpandable: function(expandable
)
641 if (this._expandable
=== expandable
)
644 this._expandable
= expandable
;
646 this._listItemNode
.classList
.toggle("parent", expandable
);
652 * @param {boolean} collapsible
654 setCollapsible: function(collapsible
)
656 if (this._collapsible
=== collapsible
)
659 this._collapsible
= collapsible
;
661 this._listItemNode
.classList
.toggle("always-parent", !collapsible
);
673 if (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
)
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
))
720 if (element
.treeElement
!== this)
723 var toggleOnClick
= this.toggleOnClick
&& !this.selectable
;
724 var isInTriangle
= this.isEventWithinDisclosureTriangle(event
);
725 if (!toggleOnClick
&& !isInTriangle
)
728 if (event
.target
&& event
.target
.enclosingNodeOrSelfWithNodeName("a"))
733 this.collapseRecursively();
738 this.expandRecursively();
746 * @param {!Event} event
748 _handleMouseDown: function(event
)
750 var element
= event
.currentTarget
;
753 delete element
._selectionStarted
;
755 if (!this.selectable
)
757 if (element
.treeElement
!== this)
760 if (this.isEventWithinDisclosureTriangle(event
))
763 this.selectOnMouseDown(event
);
767 * @param {!Event} event
769 _handleDoubleClick: function(event
)
771 var element
= event
.currentTarget
;
772 if (!element
|| element
.treeElement
!== this)
775 var handled
= this.ondblclick(event
);
778 if (this._expandable
&& !this.expanded
)
784 this._listItemNode
.remove();
785 this._childrenListNode
.remove();
790 if (!this.expanded
|| !this._collapsible
)
792 this._listItemNode
.classList
.remove("expanded");
793 this._childrenListNode
.classList
.remove("expanded");
794 this.expanded
= false;
796 if (this.treeOutline
)
797 this.treeOutline
.dispatchEventToListeners(TreeOutline
.Events
.ElementCollapsed
, this);
800 collapseRecursively: function()
806 item
= item
.traverseNextTreeElement(false, this, true);
812 if (!this._expandable
|| (this.expanded
&& this._children
))
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");
826 if (this.treeOutline
)
827 this.treeOutline
.dispatchEventToListeners(TreeOutline
.Events
.ElementExpanded
, this);
831 * @param {number=} maxDepth
833 expandRecursively: function(maxDepth
)
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.
846 if (depth
< maxDepth
)
848 item
= item
.traverseNextTreeElement(false, this, (depth
>= maxDepth
), info
);
849 depth
+= info
.depthChange
;
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();
872 var currentAncestor
= this.parent
;
873 while (currentAncestor
&& !currentAncestor
.root
) {
874 if (!currentAncestor
.expanded
)
876 currentAncestor
= currentAncestor
.parent
;
882 selectOnMouseDown: function(event
)
884 if (this.select(false, true))
889 * @param {boolean=} omitFocus
890 * @param {boolean=} selectedByUser
893 select: function(omitFocus
, selectedByUser
)
895 if (!this.treeOutline
|| !this.selectable
|| this.selected
)
898 if (this.treeOutline
.selectedTreeElement
)
899 this.treeOutline
.selectedTreeElement
.deselect();
900 this.treeOutline
.selectedTreeElement
= null;
902 if (this.treeOutline
._rootElement
=== this)
905 this.selected
= true;
908 this.treeOutline
.focus();
910 // Focusing on another node may detach "this" from tree.
911 if (!this.treeOutline
)
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
)
926 this.select(omitFocus
);
930 * @param {boolean=} supressOnDeselect
932 deselect: function(supressOnDeselect
)
934 if (!this.treeOutline
|| this.treeOutline
.selectedTreeElement
!== this || !this.selected
)
937 this.selected
= false;
938 this.treeOutline
.selectedTreeElement
= null;
939 this._listItemNode
.classList
.remove("selected");
942 _populateIfNeeded: function()
944 if (this._expandable
&& !this._children
) {
950 onpopulate: function()
952 // Overridden by subclasses.
995 oncollapse: function()
1003 ondblclick: function(e
)
1008 onreveal: function()
1013 * @param {boolean=} selectedByUser
1016 onselect: function(selectedByUser
)
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
)
1031 this._populateIfNeeded();
1034 info
.depthChange
= 0;
1036 var element
= skipUnrevealed
? (this.revealed() ? this.firstChild() : null) : this.firstChild();
1037 if (element
&& (!skipUnrevealed
|| (skipUnrevealed
&& this.expanded
))) {
1039 info
.depthChange
= 1;
1043 if (this === stayWithin
)
1046 element
= skipUnrevealed
? (this.revealed() ? this.nextSibling
: null) : this.nextSibling
;
1051 while (element
&& !element
.root
&& !(skipUnrevealed
? (element
.revealed() ? element
.nextSibling
: null) : element
.nextSibling
) && element
.parent
!== stayWithin
) {
1053 info
.depthChange
-= 1;
1054 element
= element
.parent
;
1057 if (!element
|| element
.root
)
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())) {
1076 element
._populateIfNeeded();
1077 element
= (skipUnrevealed
? (element
.revealed() && element
.expanded
? element
.lastChild() : null) : element
.lastChild());
1083 if (!this.parent
|| this.parent
.root
)
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
;