browser/devtools/profiler/cleopatra/js/tree.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:247d5c4c3289
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 "use strict";
6
7 var kMaxChunkDuration = 30; // ms
8
9 function escapeHTML(html) {
10 var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
11 var text = document.createTextNode(html);
12 pre.appendChild(text);
13 return pre.innerHTML;
14 }
15
16 RegExp.escape = function(text) {
17 return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
18 }
19
20 var requestAnimationFrame = window.webkitRequestAnimationFrame ||
21 window.mozRequestAnimationFrame ||
22 window.oRequestAnimationFrame ||
23 window.msRequestAnimationFrame ||
24 function(callback, element) {
25 return window.setTimeout(callback, 1000 / 60);
26 };
27
28 var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
29 window.mozCancelAnimationFrame ||
30 window.oCancelAnimationFrame ||
31 window.msCancelAnimationFrame ||
32 function(req) {
33 window.clearTimeout(req);
34 };
35
36 function TreeView() {
37 this._eventListeners = {};
38 this._pendingActions = [];
39 this._pendingActionsProcessingCallback = null;
40
41 this._container = document.createElement("div");
42 this._container.className = "treeViewContainer";
43 this._container.setAttribute("tabindex", "0"); // make it focusable
44
45 this._header = document.createElement("ul");
46 this._header.className = "treeHeader";
47 this._container.appendChild(this._header);
48
49 this._verticalScrollbox = document.createElement("div");
50 this._verticalScrollbox.className = "treeViewVerticalScrollbox";
51 this._container.appendChild(this._verticalScrollbox);
52
53 this._leftColumnBackground = document.createElement("div");
54 this._leftColumnBackground.className = "leftColumnBackground";
55 this._verticalScrollbox.appendChild(this._leftColumnBackground);
56
57 this._horizontalScrollbox = document.createElement("div");
58 this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
59 this._verticalScrollbox.appendChild(this._horizontalScrollbox);
60
61 this._styleElement = document.createElement("style");
62 this._styleElement.setAttribute("type", "text/css");
63 this._container.appendChild(this._styleElement);
64
65 this._contextMenu = document.createElement("menu");
66 this._contextMenu.setAttribute("type", "context");
67 this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
68 this._container.appendChild(this._contextMenu);
69
70 this._busyCover = document.createElement("div");
71 this._busyCover.className = "busyCover";
72 this._container.appendChild(this._busyCover);
73 this._abortToggleAll = false;
74 this.initSelection = true;
75
76 var self = this;
77 this._container.onkeydown = function (e) {
78 self._onkeypress(e);
79 };
80 this._container.onkeypress = function (e) {
81 // on key down gives us '8' and mapping shift+8='*' may not be portable.
82 if (String.fromCharCode(e.charCode) == '*')
83 self._onkeypress(e);
84 };
85 this._container.onclick = function (e) {
86 self._onclick(e);
87 };
88 this._verticalScrollbox.addEventListener("contextmenu", function(event) {
89 self._populateContextMenu(event);
90 }, true);
91 this._setUpScrolling();
92 };
93 TreeView.instanceCounter = 0;
94
95 TreeView.prototype = {
96 getContainer: function TreeView_getContainer() {
97 return this._container;
98 },
99 setColumns: function TreeView_setColumns(columns) {
100 this._header.innerHTML = "";
101 for (var i = 0; i < columns.length; i++) {
102 var li = document.createElement("li");
103 li.className = "treeColumnHeader treeColumnHeader" + i;
104 li.id = columns[i].name + "Header";
105 li.textContent = columns[i].title;
106 this._header.appendChild(li);
107 }
108 },
109 dataIsOutdated: function TreeView_dataIsOutdated() {
110 this._busyCover.classList.add("busy");
111 },
112 display: function TreeView_display(data, resources, filterByName) {
113 this._busyCover.classList.remove("busy");
114 this._filterByName = filterByName;
115 this._resources = resources;
116 this._addResourceIconStyles();
117 this._filterByNameReg = null; // lazy init
118 if (this._filterByName === "")
119 this._filterByName = null;
120 this._horizontalScrollbox.innerHTML = "";
121 this._horizontalScrollbox.data = data[0].getData();
122 if (this._pendingActionsProcessingCallback) {
123 cancelAnimationFrame(this._pendingActionsProcessingCallback);
124 this._pendingActionsProcessingCallback = 0;
125 }
126 this._pendingActions = [];
127
128 this._pendingActions.push({
129 parentElement: this._horizontalScrollbox,
130 parentNode: null,
131 data: data[0].getData()
132 });
133 this._processPendingActionsChunk();
134 changeFocus(this._container);
135 },
136 // Provide a snapshot of the reverse selection to restore with 'invert callback'
137 getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
138 var snapshot = [];
139
140 if (!this._selectedNode) {
141 return snapshot;
142 }
143
144 var curr = this._selectedNode.data;
145
146 while(curr) {
147 if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
148 snapshot.push(curr.name);
149 //dump(JSON.stringify(curr.name) + "\n");
150 }
151 if (curr.treeChildren && curr.treeChildren.length >= 1) {
152 curr = curr.treeChildren[0].getData();
153 } else {
154 break;
155 }
156 }
157
158 return snapshot.reverse();
159 },
160 // Provide a snapshot of the current selection to restore
161 getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
162 var snapshot = [];
163 var curr = this._selectedNode;
164
165 while(curr) {
166 if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
167 snapshot.push(curr.data.name);
168 //dump(JSON.stringify(curr.data.name) + "\n");
169 }
170 curr = curr.treeParent;
171 }
172
173 return snapshot.reverse();
174 },
175 setSelection: function TreeView_setSelection(frames) {
176 this.restoreSelectionSnapshot(frames, false);
177 },
178 // Take a selection snapshot and restore the selection
179 restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
180 var currNode = this._horizontalScrollbox.firstChild;
181 if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
182 snapshot.shift();
183 }
184 //dump("len: " + snapshot.length + "\n");
185 next_level: while (currNode && snapshot.length > 0) {
186 this._toggle(currNode, false, true);
187 this._syncProcessPendingActionProcessing();
188 for (var i = 0; i < currNode.treeChildren.length; i++) {
189 if (currNode.treeChildren[i].data.name == snapshot[0]) {
190 snapshot.shift();
191 this._toggle(currNode, false, true);
192 currNode = currNode.treeChildren[i];
193 continue next_level;
194 }
195 }
196 if (allowNonContiguous) {
197 // We need to do a Breadth-first search to find a match
198 var pendingSearch = [currNode.data];
199 while (pendingSearch.length > 0) {
200 var node = pendingSearch.shift();
201 if (!node.treeChildren)
202 continue;
203 for (var i = 0; i < node.treeChildren.length; i++) {
204 var childNode = node.treeChildren[i].getData();
205 if (childNode.name == snapshot[0]) {
206 //dump("found: " + childNode.name + "\n");
207 snapshot.shift();
208 var nodesToToggle = [childNode];
209 while (nodesToToggle[0].name != currNode.data.name) {
210 nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
211 }
212 var lastToggle = currNode;
213 for (var j = 0; j < nodesToToggle.length; j++) {
214 for (var k = 0; k < lastToggle.treeChildren.length; k++) {
215 if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
216 //dump("Expend: " + nodesToToggle[j].name + "\n");
217 this._toggle(lastToggle.treeChildren[k], false, true);
218 lastToggle = lastToggle.treeChildren[k];
219 this._syncProcessPendingActionProcessing();
220 }
221 }
222 }
223 currNode = lastToggle;
224 continue next_level;
225 }
226 //dump("pending: " + childNode.name + "\n");
227 pendingSearch.push(childNode);
228 }
229 }
230 }
231 break; // Didn't find child node matching
232 }
233
234 if (currNode == this._horizontalScrollbox) {
235 PROFILERERROR("Failed to restore selection, could not find root.\n");
236 return;
237 }
238
239 this._toggle(currNode, true, true);
240 this._select(currNode);
241 },
242 _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
243 this._pendingActionsProcessingCallback = 0;
244
245 var startTime = Date.now();
246 var endTime = startTime + kMaxChunkDuration;
247 while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
248 this._processOneAction(this._pendingActions.shift());
249 }
250 this._scrollHeightChanged();
251
252 this._schedulePendingActionProcessing();
253 },
254 _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
255 if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
256 var self = this;
257 this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
258 self._processPendingActionsChunk();
259 });
260 }
261 },
262 _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
263 this._processPendingActionsChunk(true);
264 },
265 _processOneAction: function TreeView__processOneAction(action) {
266 var li = this._createTree(action.parentElement, action.parentNode, action.data);
267 if ("allChildrenCollapsedValue" in action) {
268 if (this._abortToggleAll)
269 return;
270 this._toggleAll(li, action.allChildrenCollapsedValue, true);
271 }
272 },
273 addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
274 if (!(eventName in this._eventListeners))
275 this._eventListeners[eventName] = [];
276 if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
277 return;
278 this._eventListeners[eventName].push(callbackFunction);
279 },
280 removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
281 if (!(eventName in this._eventListeners))
282 return;
283 var index = this._eventListeners[eventName].indexOf(callbackFunction);
284 if (index == -1)
285 return;
286 this._eventListeners[eventName].splice(index, 1);
287 },
288 _fireEvent: function TreeView__fireEvent(eventName, eventObject) {
289 if (!(eventName in this._eventListeners))
290 return;
291 this._eventListeners[eventName].forEach(function (callbackFunction) {
292 callbackFunction(eventObject);
293 });
294 },
295 _setUpScrolling: function TreeView__setUpScrolling() {
296 var waitingForPaint = false;
297 var accumulatedDeltaX = 0;
298 var accumulatedDeltaY = 0;
299 var self = this;
300 function scrollListener(e) {
301 if (!waitingForPaint) {
302 requestAnimationFrame(function () {
303 self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
304 self._verticalScrollbox.scrollTop += accumulatedDeltaY;
305 accumulatedDeltaX = 0;
306 accumulatedDeltaY = 0;
307 waitingForPaint = false;
308 });
309 waitingForPaint = true;
310 }
311 if (e.axis == e.HORIZONTAL_AXIS) {
312 accumulatedDeltaX += e.detail;
313 } else {
314 accumulatedDeltaY += e.detail;
315 }
316 e.preventDefault();
317 }
318 this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
319 this._verticalScrollbox.cleanUp = function () {
320 self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
321 };
322 },
323 _scrollHeightChanged: function TreeView__scrollHeightChanged() {
324 if (!this._pendingScrollHeightChanged) {
325 var self = this;
326 this._pendingScrollHeightChanged = setTimeout(function() {
327 self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
328 self._pendingScrollHeightChanged = null;
329 }, 0);
330 }
331 },
332 _createTree: function TreeView__createTree(parentElement, parentNode, data) {
333 var div = document.createElement("div");
334 div.className = "treeViewNode collapsed";
335 var hasChildren = ("children" in data) && (data.children.length > 0);
336 if (!hasChildren)
337 div.classList.add("leaf");
338 var treeLine = document.createElement("div");
339 treeLine.className = "treeLine";
340 treeLine.innerHTML = this._HTMLForFunction(data);
341 div.depth = parentNode ? parentNode.depth + 1 : 0;
342 div.style.marginLeft = div.depth + "em";
343 // When this item is toggled we will expand its children
344 div.pendingExpand = [];
345 div.treeLine = treeLine;
346 div.data = data;
347 // Useful for debugging
348 //this.uniqueID = this.uniqueID || 0;
349 //div.id = "Node" + this.uniqueID++;
350 div.appendChild(treeLine);
351 div.treeChildren = [];
352 div.treeParent = parentNode;
353 if (hasChildren) {
354 for (var i = 0; i < data.children.length; ++i) {
355 div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
356 }
357 }
358 if (parentNode) {
359 parentNode.treeChildren.push(div);
360 }
361 if (parentNode != null) {
362 var nextTo;
363 if (parentNode.treeChildren.length > 1) {
364 nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
365 } else {
366 nextTo = parentNode.nextSibling;
367 }
368 parentElement.insertBefore(div, nextTo);
369 } else {
370 parentElement.appendChild(div);
371 }
372 return div;
373 },
374 _addResourceIconStyles: function TreeView__addResourceIconStyles() {
375 var styles = [];
376 for (var resourceName in this._resources) {
377 var resource = this._resources[resourceName];
378 if (resource.icon) {
379 styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
380 }
381 }
382 this._styleElement.textContent = styles.join("\n");
383 },
384 _populateContextMenu: function TreeView__populateContextMenu(event) {
385 this._verticalScrollbox.setAttribute("contextmenu", "");
386
387 var target = event.target;
388 if (target.classList.contains("expandCollapseButton") ||
389 target.classList.contains("focusCallstackButton"))
390 return;
391
392 var li = this._getParentTreeViewNode(target);
393 if (!li)
394 return;
395
396 this._select(li);
397
398 this._contextMenu.innerHTML = "";
399
400 var self = this;
401 this._contextMenuForFunction(li.data).forEach(function (menuItem) {
402 var menuItemNode = document.createElement("menuitem");
403 menuItemNode.onclick = (function (menuItem) {
404 return function() {
405 self._contextMenuClick(li.data, menuItem);
406 };
407 })(menuItem);
408 menuItemNode.label = menuItem;
409 self._contextMenu.appendChild(menuItemNode);
410 });
411
412 this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
413 },
414 _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
415 this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
416 },
417 _contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
418 // TODO move me outside tree.js
419 var menu = [];
420 if (node.library && (
421 node.library.toLowerCase() == "lib_xul" ||
422 node.library.toLowerCase() == "lib_xul.dll"
423 )) {
424 menu.push("View Source");
425 }
426 if (node.isJSFrame && node.scriptLocation) {
427 menu.push("View JS Source");
428 }
429 menu.push("Focus Frame");
430 menu.push("Focus Callstack");
431 menu.push("Google Search");
432 menu.push("Plugin View: Pie");
433 menu.push("Plugin View: Tree");
434 return menu;
435 },
436 _HTMLForFunction: function TreeView__HTMLForFunction(node) {
437 var nodeName = escapeHTML(node.name);
438 var resource = this._resources[node.library] || {};
439 var libName = escapeHTML(resource.name || "");
440 if (this._filterByName) {
441 if (!this._filterByNameReg) {
442 this._filterByName = RegExp.escape(this._filterByName);
443 this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
444 }
445 nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
446 libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
447 }
448 var samplePercentage;
449 if (isNaN(node.ratio)) {
450 samplePercentage = "";
451 } else {
452 samplePercentage = (100 * node.ratio).toFixed(1) + "%";
453 }
454 return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
455 '<span class="sampleCount">' + node.counter + '</span> ' +
456 '<span class="samplePercentage">' + samplePercentage + '</span> ' +
457 '<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
458 '<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
459 '<span class="functionName">' + nodeName + '</span>' +
460 '<span class="libraryName">' + libName + '</span>' +
461 ((nodeName === '(total)' || gHideSourceLinks) ? '' :
462 '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
463 },
464 _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
465 while (div.pendingExpand != null && div.pendingExpand.length > 0) {
466 var pendingExpand = div.pendingExpand.shift();
467 pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
468 this._pendingActions.push(pendingExpand);
469 this._schedulePendingActionProcessing();
470 }
471 },
472 _showChild: function TreeView__showChild(div, isVisible) {
473 for (var i = 0; i < div.treeChildren.length; i++) {
474 div.treeChildren[i].style.display = isVisible?"":"none";
475 if (!isVisible) {
476 div.treeChildren[i].classList.add("collapsed");
477 this._showChild(div.treeChildren[i], isVisible);
478 }
479 }
480 },
481 _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
482 var currentCollapsedValue = this._isCollapsed(div);
483 if (newCollapsedValue === undefined)
484 newCollapsedValue = !currentCollapsedValue;
485 if (newCollapsedValue) {
486 div.classList.add("collapsed");
487 this._showChild(div, false);
488 } else {
489 this._resolveChildren(div, true);
490 div.classList.remove("collapsed");
491 this._showChild(div, true);
492 }
493 if (!suppressScrollHeightNotification)
494 this._scrollHeightChanged();
495 },
496 _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
497
498 // Reset abort
499 this._abortToggleAll = false;
500
501 // Expands / collapses all child nodes, too.
502
503 if (newCollapsedValue === undefined)
504 newCollapsedValue = !this._isCollapsed(subtreeRoot);
505 if (!newCollapsedValue) {
506 // expanding
507 this._resolveChildren(subtreeRoot, newCollapsedValue);
508 }
509 this._toggle(subtreeRoot, newCollapsedValue, true);
510 for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
511 this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
512 }
513 if (!suppressScrollHeightNotification)
514 this._scrollHeightChanged();
515 },
516 _getParent: function TreeView__getParent(div) {
517 return div.treeParent;
518 },
519 _getFirstChild: function TreeView__getFirstChild(div) {
520 if (this._isCollapsed(div))
521 return null;
522 var child = div.treeChildren[0];
523 return child;
524 },
525 _getLastChild: function TreeView__getLastChild(div) {
526 if (this._isCollapsed(div))
527 return div;
528 var lastChild = div.treeChildren[div.treeChildren.length-1];
529 if (lastChild == null)
530 return div;
531 return this._getLastChild(lastChild);
532 },
533 _getPrevSib: function TreeView__getPevSib(div) {
534 if (div.treeParent == null)
535 return null;
536 var nodeIndex = div.treeParent.treeChildren.indexOf(div);
537 if (nodeIndex == 0)
538 return null;
539 return div.treeParent.treeChildren[nodeIndex-1];
540 },
541 _getNextSib: function TreeView__getNextSib(div) {
542 if (div.treeParent == null)
543 return null;
544 var nodeIndex = div.treeParent.treeChildren.indexOf(div);
545 if (nodeIndex == div.treeParent.treeChildren.length - 1)
546 return this._getNextSib(div.treeParent);
547 return div.treeParent.treeChildren[nodeIndex+1];
548 },
549 _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
550 // Schedule this on the animation frame otherwise we may run this more then once per frames
551 // causing more work then needed.
552 var self = this;
553 if (self._pendingAnimationFrame != null) {
554 return;
555 }
556 self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
557 cancelAnimationFrame(self._pendingAnimationFrame);
558 self._pendingAnimationFrame = null;
559 self._scrollIntoView(element, maxImportantWidth);
560 });
561 },
562 _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
563 // Make sure that element is inside the visible part of our scrollbox by
564 // adjusting the scroll positions. If element is wider or
565 // higher than the scroll port, the left and top edges are prioritized over
566 // the right and bottom edges.
567 // If maxImportantWidth is set, parts of the beyond this widths are
568 // considered as not important; they'll not be moved into view.
569
570 if (maxImportantWidth === undefined)
571 maxImportantWidth = Infinity;
572
573 var visibleRect = {
574 left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
575 top: this._verticalScrollbox.getBoundingClientRect().top,
576 right: this._horizontalScrollbox.getBoundingClientRect().right,
577 bottom: this._verticalScrollbox.getBoundingClientRect().bottom
578 }
579 var r = element.getBoundingClientRect();
580 var right = Math.min(r.right, r.left + maxImportantWidth);
581 var leftCutoff = visibleRect.left - r.left;
582 var rightCutoff = right - visibleRect.right;
583 var topCutoff = visibleRect.top - r.top;
584 var bottomCutoff = r.bottom - visibleRect.bottom;
585 if (leftCutoff > 0)
586 this._horizontalScrollbox.scrollLeft -= leftCutoff;
587 else if (rightCutoff > 0)
588 this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
589 if (topCutoff > 0)
590 this._verticalScrollbox.scrollTop -= topCutoff;
591 else if (bottomCutoff > 0)
592 this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
593 },
594 _select: function TreeView__select(li) {
595 if (this._selectedNode != null) {
596 this._selectedNode.treeLine.classList.remove("selected");
597 this._selectedNode = null;
598 }
599 if (li) {
600 li.treeLine.classList.add("selected");
601 this._selectedNode = li;
602 var functionName = li.treeLine.querySelector(".functionName");
603 this._scheduleScrollIntoView(functionName, 400);
604 this._fireEvent("select", li.data);
605 }
606 updateDocumentURL();
607 },
608 _isCollapsed: function TreeView__isCollapsed(div) {
609 return div.classList.contains("collapsed");
610 },
611 _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
612 while (node) {
613 if (node.nodeType != node.ELEMENT_NODE)
614 break;
615 if (node.classList.contains("treeViewNode"))
616 return node;
617 node = node.parentNode;
618 }
619 return null;
620 },
621 _onclick: function TreeView__onclick(event) {
622 var target = event.target;
623 var node = this._getParentTreeViewNode(target);
624 if (!node)
625 return;
626 if (target.classList.contains("expandCollapseButton")) {
627 if (event.altKey)
628 this._toggleAll(node);
629 else
630 this._toggle(node);
631 } else if (target.classList.contains("focusCallstackButton")) {
632 this._fireEvent("focusCallstackButtonClicked", node.data);
633 } else {
634 this._select(node);
635 if (event.detail == 2) // dblclick
636 this._toggle(node);
637 }
638 },
639 _onkeypress: function TreeView__onkeypress(event) {
640 if (event.ctrlKey || event.altKey || event.metaKey)
641 return;
642
643 this._abortToggleAll = true;
644
645 var selected = this._selectedNode;
646 if (event.keyCode < 37 || event.keyCode > 40) {
647 if (event.keyCode != 0 ||
648 String.fromCharCode(event.charCode) != '*') {
649 return;
650 }
651 }
652 event.stopPropagation();
653 event.preventDefault();
654 if (!selected)
655 return;
656 if (event.keyCode == 37) { // KEY_LEFT
657 var isCollapsed = this._isCollapsed(selected);
658 if (!isCollapsed) {
659 this._toggle(selected);
660 } else {
661 var parent = this._getParent(selected);
662 if (parent != null) {
663 this._select(parent);
664 }
665 }
666 } else if (event.keyCode == 38) { // KEY_UP
667 var prevSib = this._getPrevSib(selected);
668 var parent = this._getParent(selected);
669 if (prevSib != null) {
670 this._select(this._getLastChild(prevSib));
671 } else if (parent != null) {
672 this._select(parent);
673 }
674 } else if (event.keyCode == 39) { // KEY_RIGHT
675 var isCollapsed = this._isCollapsed(selected);
676 if (isCollapsed) {
677 this._toggle(selected);
678 this._syncProcessPendingActionProcessing();
679 } else {
680 // Do KEY_DOWN
681 var nextSib = this._getNextSib(selected);
682 var child = this._getFirstChild(selected);
683 if (child != null) {
684 this._select(child);
685 } else if (nextSib) {
686 this._select(nextSib);
687 }
688 }
689 } else if (event.keyCode == 40) { // KEY_DOWN
690 var nextSib = this._getNextSib(selected);
691 var child = this._getFirstChild(selected);
692 if (child != null) {
693 this._select(child);
694 } else if (nextSib) {
695 this._select(nextSib);
696 }
697 } else if (String.fromCharCode(event.charCode) == '*') {
698 this._toggleAll(selected);
699 }
700 },
701 };
702

mercurial