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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial