1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/profiler/cleopatra/js/tree.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,702 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +var kMaxChunkDuration = 30; // ms 1.11 + 1.12 +function escapeHTML(html) { 1.13 + var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre"); 1.14 + var text = document.createTextNode(html); 1.15 + pre.appendChild(text); 1.16 + return pre.innerHTML; 1.17 +} 1.18 + 1.19 +RegExp.escape = function(text) { 1.20 + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 1.21 +} 1.22 + 1.23 +var requestAnimationFrame = window.webkitRequestAnimationFrame || 1.24 + window.mozRequestAnimationFrame || 1.25 + window.oRequestAnimationFrame || 1.26 + window.msRequestAnimationFrame || 1.27 + function(callback, element) { 1.28 + return window.setTimeout(callback, 1000 / 60); 1.29 + }; 1.30 + 1.31 +var cancelAnimationFrame = window.webkitCancelAnimationFrame || 1.32 + window.mozCancelAnimationFrame || 1.33 + window.oCancelAnimationFrame || 1.34 + window.msCancelAnimationFrame || 1.35 + function(req) { 1.36 + window.clearTimeout(req); 1.37 + }; 1.38 + 1.39 +function TreeView() { 1.40 + this._eventListeners = {}; 1.41 + this._pendingActions = []; 1.42 + this._pendingActionsProcessingCallback = null; 1.43 + 1.44 + this._container = document.createElement("div"); 1.45 + this._container.className = "treeViewContainer"; 1.46 + this._container.setAttribute("tabindex", "0"); // make it focusable 1.47 + 1.48 + this._header = document.createElement("ul"); 1.49 + this._header.className = "treeHeader"; 1.50 + this._container.appendChild(this._header); 1.51 + 1.52 + this._verticalScrollbox = document.createElement("div"); 1.53 + this._verticalScrollbox.className = "treeViewVerticalScrollbox"; 1.54 + this._container.appendChild(this._verticalScrollbox); 1.55 + 1.56 + this._leftColumnBackground = document.createElement("div"); 1.57 + this._leftColumnBackground.className = "leftColumnBackground"; 1.58 + this._verticalScrollbox.appendChild(this._leftColumnBackground); 1.59 + 1.60 + this._horizontalScrollbox = document.createElement("div"); 1.61 + this._horizontalScrollbox.className = "treeViewHorizontalScrollbox"; 1.62 + this._verticalScrollbox.appendChild(this._horizontalScrollbox); 1.63 + 1.64 + this._styleElement = document.createElement("style"); 1.65 + this._styleElement.setAttribute("type", "text/css"); 1.66 + this._container.appendChild(this._styleElement); 1.67 + 1.68 + this._contextMenu = document.createElement("menu"); 1.69 + this._contextMenu.setAttribute("type", "context"); 1.70 + this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++; 1.71 + this._container.appendChild(this._contextMenu); 1.72 + 1.73 + this._busyCover = document.createElement("div"); 1.74 + this._busyCover.className = "busyCover"; 1.75 + this._container.appendChild(this._busyCover); 1.76 + this._abortToggleAll = false; 1.77 + this.initSelection = true; 1.78 + 1.79 + var self = this; 1.80 + this._container.onkeydown = function (e) { 1.81 + self._onkeypress(e); 1.82 + }; 1.83 + this._container.onkeypress = function (e) { 1.84 + // on key down gives us '8' and mapping shift+8='*' may not be portable. 1.85 + if (String.fromCharCode(e.charCode) == '*') 1.86 + self._onkeypress(e); 1.87 + }; 1.88 + this._container.onclick = function (e) { 1.89 + self._onclick(e); 1.90 + }; 1.91 + this._verticalScrollbox.addEventListener("contextmenu", function(event) { 1.92 + self._populateContextMenu(event); 1.93 + }, true); 1.94 + this._setUpScrolling(); 1.95 +}; 1.96 +TreeView.instanceCounter = 0; 1.97 + 1.98 +TreeView.prototype = { 1.99 + getContainer: function TreeView_getContainer() { 1.100 + return this._container; 1.101 + }, 1.102 + setColumns: function TreeView_setColumns(columns) { 1.103 + this._header.innerHTML = ""; 1.104 + for (var i = 0; i < columns.length; i++) { 1.105 + var li = document.createElement("li"); 1.106 + li.className = "treeColumnHeader treeColumnHeader" + i; 1.107 + li.id = columns[i].name + "Header"; 1.108 + li.textContent = columns[i].title; 1.109 + this._header.appendChild(li); 1.110 + } 1.111 + }, 1.112 + dataIsOutdated: function TreeView_dataIsOutdated() { 1.113 + this._busyCover.classList.add("busy"); 1.114 + }, 1.115 + display: function TreeView_display(data, resources, filterByName) { 1.116 + this._busyCover.classList.remove("busy"); 1.117 + this._filterByName = filterByName; 1.118 + this._resources = resources; 1.119 + this._addResourceIconStyles(); 1.120 + this._filterByNameReg = null; // lazy init 1.121 + if (this._filterByName === "") 1.122 + this._filterByName = null; 1.123 + this._horizontalScrollbox.innerHTML = ""; 1.124 + this._horizontalScrollbox.data = data[0].getData(); 1.125 + if (this._pendingActionsProcessingCallback) { 1.126 + cancelAnimationFrame(this._pendingActionsProcessingCallback); 1.127 + this._pendingActionsProcessingCallback = 0; 1.128 + } 1.129 + this._pendingActions = []; 1.130 + 1.131 + this._pendingActions.push({ 1.132 + parentElement: this._horizontalScrollbox, 1.133 + parentNode: null, 1.134 + data: data[0].getData() 1.135 + }); 1.136 + this._processPendingActionsChunk(); 1.137 + changeFocus(this._container); 1.138 + }, 1.139 + // Provide a snapshot of the reverse selection to restore with 'invert callback' 1.140 + getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) { 1.141 + var snapshot = []; 1.142 + 1.143 + if (!this._selectedNode) { 1.144 + return snapshot; 1.145 + } 1.146 + 1.147 + var curr = this._selectedNode.data; 1.148 + 1.149 + while(curr) { 1.150 + if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) { 1.151 + snapshot.push(curr.name); 1.152 + //dump(JSON.stringify(curr.name) + "\n"); 1.153 + } 1.154 + if (curr.treeChildren && curr.treeChildren.length >= 1) { 1.155 + curr = curr.treeChildren[0].getData(); 1.156 + } else { 1.157 + break; 1.158 + } 1.159 + } 1.160 + 1.161 + return snapshot.reverse(); 1.162 + }, 1.163 + // Provide a snapshot of the current selection to restore 1.164 + getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) { 1.165 + var snapshot = []; 1.166 + var curr = this._selectedNode; 1.167 + 1.168 + while(curr) { 1.169 + if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) { 1.170 + snapshot.push(curr.data.name); 1.171 + //dump(JSON.stringify(curr.data.name) + "\n"); 1.172 + } 1.173 + curr = curr.treeParent; 1.174 + } 1.175 + 1.176 + return snapshot.reverse(); 1.177 + }, 1.178 + setSelection: function TreeView_setSelection(frames) { 1.179 + this.restoreSelectionSnapshot(frames, false); 1.180 + }, 1.181 + // Take a selection snapshot and restore the selection 1.182 + restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) { 1.183 + var currNode = this._horizontalScrollbox.firstChild; 1.184 + if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") { 1.185 + snapshot.shift(); 1.186 + } 1.187 + //dump("len: " + snapshot.length + "\n"); 1.188 + next_level: while (currNode && snapshot.length > 0) { 1.189 + this._toggle(currNode, false, true); 1.190 + this._syncProcessPendingActionProcessing(); 1.191 + for (var i = 0; i < currNode.treeChildren.length; i++) { 1.192 + if (currNode.treeChildren[i].data.name == snapshot[0]) { 1.193 + snapshot.shift(); 1.194 + this._toggle(currNode, false, true); 1.195 + currNode = currNode.treeChildren[i]; 1.196 + continue next_level; 1.197 + } 1.198 + } 1.199 + if (allowNonContiguous) { 1.200 + // We need to do a Breadth-first search to find a match 1.201 + var pendingSearch = [currNode.data]; 1.202 + while (pendingSearch.length > 0) { 1.203 + var node = pendingSearch.shift(); 1.204 + if (!node.treeChildren) 1.205 + continue; 1.206 + for (var i = 0; i < node.treeChildren.length; i++) { 1.207 + var childNode = node.treeChildren[i].getData(); 1.208 + if (childNode.name == snapshot[0]) { 1.209 + //dump("found: " + childNode.name + "\n"); 1.210 + snapshot.shift(); 1.211 + var nodesToToggle = [childNode]; 1.212 + while (nodesToToggle[0].name != currNode.data.name) { 1.213 + nodesToToggle.splice(0, 0, nodesToToggle[0].parent); 1.214 + } 1.215 + var lastToggle = currNode; 1.216 + for (var j = 0; j < nodesToToggle.length; j++) { 1.217 + for (var k = 0; k < lastToggle.treeChildren.length; k++) { 1.218 + if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) { 1.219 + //dump("Expend: " + nodesToToggle[j].name + "\n"); 1.220 + this._toggle(lastToggle.treeChildren[k], false, true); 1.221 + lastToggle = lastToggle.treeChildren[k]; 1.222 + this._syncProcessPendingActionProcessing(); 1.223 + } 1.224 + } 1.225 + } 1.226 + currNode = lastToggle; 1.227 + continue next_level; 1.228 + } 1.229 + //dump("pending: " + childNode.name + "\n"); 1.230 + pendingSearch.push(childNode); 1.231 + } 1.232 + } 1.233 + } 1.234 + break; // Didn't find child node matching 1.235 + } 1.236 + 1.237 + if (currNode == this._horizontalScrollbox) { 1.238 + PROFILERERROR("Failed to restore selection, could not find root.\n"); 1.239 + return; 1.240 + } 1.241 + 1.242 + this._toggle(currNode, true, true); 1.243 + this._select(currNode); 1.244 + }, 1.245 + _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) { 1.246 + this._pendingActionsProcessingCallback = 0; 1.247 + 1.248 + var startTime = Date.now(); 1.249 + var endTime = startTime + kMaxChunkDuration; 1.250 + while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) { 1.251 + this._processOneAction(this._pendingActions.shift()); 1.252 + } 1.253 + this._scrollHeightChanged(); 1.254 + 1.255 + this._schedulePendingActionProcessing(); 1.256 + }, 1.257 + _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() { 1.258 + if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) { 1.259 + var self = this; 1.260 + this._pendingActionsProcessingCallback = requestAnimationFrame(function () { 1.261 + self._processPendingActionsChunk(); 1.262 + }); 1.263 + } 1.264 + }, 1.265 + _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() { 1.266 + this._processPendingActionsChunk(true); 1.267 + }, 1.268 + _processOneAction: function TreeView__processOneAction(action) { 1.269 + var li = this._createTree(action.parentElement, action.parentNode, action.data); 1.270 + if ("allChildrenCollapsedValue" in action) { 1.271 + if (this._abortToggleAll) 1.272 + return; 1.273 + this._toggleAll(li, action.allChildrenCollapsedValue, true); 1.274 + } 1.275 + }, 1.276 + addEventListener: function TreeView_addEventListener(eventName, callbackFunction) { 1.277 + if (!(eventName in this._eventListeners)) 1.278 + this._eventListeners[eventName] = []; 1.279 + if (this._eventListeners[eventName].indexOf(callbackFunction) != -1) 1.280 + return; 1.281 + this._eventListeners[eventName].push(callbackFunction); 1.282 + }, 1.283 + removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) { 1.284 + if (!(eventName in this._eventListeners)) 1.285 + return; 1.286 + var index = this._eventListeners[eventName].indexOf(callbackFunction); 1.287 + if (index == -1) 1.288 + return; 1.289 + this._eventListeners[eventName].splice(index, 1); 1.290 + }, 1.291 + _fireEvent: function TreeView__fireEvent(eventName, eventObject) { 1.292 + if (!(eventName in this._eventListeners)) 1.293 + return; 1.294 + this._eventListeners[eventName].forEach(function (callbackFunction) { 1.295 + callbackFunction(eventObject); 1.296 + }); 1.297 + }, 1.298 + _setUpScrolling: function TreeView__setUpScrolling() { 1.299 + var waitingForPaint = false; 1.300 + var accumulatedDeltaX = 0; 1.301 + var accumulatedDeltaY = 0; 1.302 + var self = this; 1.303 + function scrollListener(e) { 1.304 + if (!waitingForPaint) { 1.305 + requestAnimationFrame(function () { 1.306 + self._horizontalScrollbox.scrollLeft += accumulatedDeltaX; 1.307 + self._verticalScrollbox.scrollTop += accumulatedDeltaY; 1.308 + accumulatedDeltaX = 0; 1.309 + accumulatedDeltaY = 0; 1.310 + waitingForPaint = false; 1.311 + }); 1.312 + waitingForPaint = true; 1.313 + } 1.314 + if (e.axis == e.HORIZONTAL_AXIS) { 1.315 + accumulatedDeltaX += e.detail; 1.316 + } else { 1.317 + accumulatedDeltaY += e.detail; 1.318 + } 1.319 + e.preventDefault(); 1.320 + } 1.321 + this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false); 1.322 + this._verticalScrollbox.cleanUp = function () { 1.323 + self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false); 1.324 + }; 1.325 + }, 1.326 + _scrollHeightChanged: function TreeView__scrollHeightChanged() { 1.327 + if (!this._pendingScrollHeightChanged) { 1.328 + var self = this; 1.329 + this._pendingScrollHeightChanged = setTimeout(function() { 1.330 + self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px'; 1.331 + self._pendingScrollHeightChanged = null; 1.332 + }, 0); 1.333 + } 1.334 + }, 1.335 + _createTree: function TreeView__createTree(parentElement, parentNode, data) { 1.336 + var div = document.createElement("div"); 1.337 + div.className = "treeViewNode collapsed"; 1.338 + var hasChildren = ("children" in data) && (data.children.length > 0); 1.339 + if (!hasChildren) 1.340 + div.classList.add("leaf"); 1.341 + var treeLine = document.createElement("div"); 1.342 + treeLine.className = "treeLine"; 1.343 + treeLine.innerHTML = this._HTMLForFunction(data); 1.344 + div.depth = parentNode ? parentNode.depth + 1 : 0; 1.345 + div.style.marginLeft = div.depth + "em"; 1.346 + // When this item is toggled we will expand its children 1.347 + div.pendingExpand = []; 1.348 + div.treeLine = treeLine; 1.349 + div.data = data; 1.350 + // Useful for debugging 1.351 + //this.uniqueID = this.uniqueID || 0; 1.352 + //div.id = "Node" + this.uniqueID++; 1.353 + div.appendChild(treeLine); 1.354 + div.treeChildren = []; 1.355 + div.treeParent = parentNode; 1.356 + if (hasChildren) { 1.357 + for (var i = 0; i < data.children.length; ++i) { 1.358 + div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() }); 1.359 + } 1.360 + } 1.361 + if (parentNode) { 1.362 + parentNode.treeChildren.push(div); 1.363 + } 1.364 + if (parentNode != null) { 1.365 + var nextTo; 1.366 + if (parentNode.treeChildren.length > 1) { 1.367 + nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling; 1.368 + } else { 1.369 + nextTo = parentNode.nextSibling; 1.370 + } 1.371 + parentElement.insertBefore(div, nextTo); 1.372 + } else { 1.373 + parentElement.appendChild(div); 1.374 + } 1.375 + return div; 1.376 + }, 1.377 + _addResourceIconStyles: function TreeView__addResourceIconStyles() { 1.378 + var styles = []; 1.379 + for (var resourceName in this._resources) { 1.380 + var resource = this._resources[resourceName]; 1.381 + if (resource.icon) { 1.382 + styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }'); 1.383 + } 1.384 + } 1.385 + this._styleElement.textContent = styles.join("\n"); 1.386 + }, 1.387 + _populateContextMenu: function TreeView__populateContextMenu(event) { 1.388 + this._verticalScrollbox.setAttribute("contextmenu", ""); 1.389 + 1.390 + var target = event.target; 1.391 + if (target.classList.contains("expandCollapseButton") || 1.392 + target.classList.contains("focusCallstackButton")) 1.393 + return; 1.394 + 1.395 + var li = this._getParentTreeViewNode(target); 1.396 + if (!li) 1.397 + return; 1.398 + 1.399 + this._select(li); 1.400 + 1.401 + this._contextMenu.innerHTML = ""; 1.402 + 1.403 + var self = this; 1.404 + this._contextMenuForFunction(li.data).forEach(function (menuItem) { 1.405 + var menuItemNode = document.createElement("menuitem"); 1.406 + menuItemNode.onclick = (function (menuItem) { 1.407 + return function() { 1.408 + self._contextMenuClick(li.data, menuItem); 1.409 + }; 1.410 + })(menuItem); 1.411 + menuItemNode.label = menuItem; 1.412 + self._contextMenu.appendChild(menuItemNode); 1.413 + }); 1.414 + 1.415 + this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id); 1.416 + }, 1.417 + _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) { 1.418 + this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem }); 1.419 + }, 1.420 + _contextMenuForFunction: function TreeView__contextMenuForFunction(node) { 1.421 + // TODO move me outside tree.js 1.422 + var menu = []; 1.423 + if (node.library && ( 1.424 + node.library.toLowerCase() == "lib_xul" || 1.425 + node.library.toLowerCase() == "lib_xul.dll" 1.426 + )) { 1.427 + menu.push("View Source"); 1.428 + } 1.429 + if (node.isJSFrame && node.scriptLocation) { 1.430 + menu.push("View JS Source"); 1.431 + } 1.432 + menu.push("Focus Frame"); 1.433 + menu.push("Focus Callstack"); 1.434 + menu.push("Google Search"); 1.435 + menu.push("Plugin View: Pie"); 1.436 + menu.push("Plugin View: Tree"); 1.437 + return menu; 1.438 + }, 1.439 + _HTMLForFunction: function TreeView__HTMLForFunction(node) { 1.440 + var nodeName = escapeHTML(node.name); 1.441 + var resource = this._resources[node.library] || {}; 1.442 + var libName = escapeHTML(resource.name || ""); 1.443 + if (this._filterByName) { 1.444 + if (!this._filterByNameReg) { 1.445 + this._filterByName = RegExp.escape(this._filterByName); 1.446 + this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi"); 1.447 + } 1.448 + nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>"); 1.449 + libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>"); 1.450 + } 1.451 + var samplePercentage; 1.452 + if (isNaN(node.ratio)) { 1.453 + samplePercentage = ""; 1.454 + } else { 1.455 + samplePercentage = (100 * node.ratio).toFixed(1) + "%"; 1.456 + } 1.457 + return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' + 1.458 + '<span class="sampleCount">' + node.counter + '</span> ' + 1.459 + '<span class="samplePercentage">' + samplePercentage + '</span> ' + 1.460 + '<span class="selfSampleCount">' + node.selfCounter + '</span> ' + 1.461 + '<span class="resourceIcon" data-resource="' + node.library + '"></span> ' + 1.462 + '<span class="functionName">' + nodeName + '</span>' + 1.463 + '<span class="libraryName">' + libName + '</span>' + 1.464 + ((nodeName === '(total)' || gHideSourceLinks) ? '' : 1.465 + '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">'); 1.466 + }, 1.467 + _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) { 1.468 + while (div.pendingExpand != null && div.pendingExpand.length > 0) { 1.469 + var pendingExpand = div.pendingExpand.shift(); 1.470 + pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue; 1.471 + this._pendingActions.push(pendingExpand); 1.472 + this._schedulePendingActionProcessing(); 1.473 + } 1.474 + }, 1.475 + _showChild: function TreeView__showChild(div, isVisible) { 1.476 + for (var i = 0; i < div.treeChildren.length; i++) { 1.477 + div.treeChildren[i].style.display = isVisible?"":"none"; 1.478 + if (!isVisible) { 1.479 + div.treeChildren[i].classList.add("collapsed"); 1.480 + this._showChild(div.treeChildren[i], isVisible); 1.481 + } 1.482 + } 1.483 + }, 1.484 + _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { 1.485 + var currentCollapsedValue = this._isCollapsed(div); 1.486 + if (newCollapsedValue === undefined) 1.487 + newCollapsedValue = !currentCollapsedValue; 1.488 + if (newCollapsedValue) { 1.489 + div.classList.add("collapsed"); 1.490 + this._showChild(div, false); 1.491 + } else { 1.492 + this._resolveChildren(div, true); 1.493 + div.classList.remove("collapsed"); 1.494 + this._showChild(div, true); 1.495 + } 1.496 + if (!suppressScrollHeightNotification) 1.497 + this._scrollHeightChanged(); 1.498 + }, 1.499 + _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { 1.500 + 1.501 + // Reset abort 1.502 + this._abortToggleAll = false; 1.503 + 1.504 + // Expands / collapses all child nodes, too. 1.505 + 1.506 + if (newCollapsedValue === undefined) 1.507 + newCollapsedValue = !this._isCollapsed(subtreeRoot); 1.508 + if (!newCollapsedValue) { 1.509 + // expanding 1.510 + this._resolveChildren(subtreeRoot, newCollapsedValue); 1.511 + } 1.512 + this._toggle(subtreeRoot, newCollapsedValue, true); 1.513 + for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) { 1.514 + this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true); 1.515 + } 1.516 + if (!suppressScrollHeightNotification) 1.517 + this._scrollHeightChanged(); 1.518 + }, 1.519 + _getParent: function TreeView__getParent(div) { 1.520 + return div.treeParent; 1.521 + }, 1.522 + _getFirstChild: function TreeView__getFirstChild(div) { 1.523 + if (this._isCollapsed(div)) 1.524 + return null; 1.525 + var child = div.treeChildren[0]; 1.526 + return child; 1.527 + }, 1.528 + _getLastChild: function TreeView__getLastChild(div) { 1.529 + if (this._isCollapsed(div)) 1.530 + return div; 1.531 + var lastChild = div.treeChildren[div.treeChildren.length-1]; 1.532 + if (lastChild == null) 1.533 + return div; 1.534 + return this._getLastChild(lastChild); 1.535 + }, 1.536 + _getPrevSib: function TreeView__getPevSib(div) { 1.537 + if (div.treeParent == null) 1.538 + return null; 1.539 + var nodeIndex = div.treeParent.treeChildren.indexOf(div); 1.540 + if (nodeIndex == 0) 1.541 + return null; 1.542 + return div.treeParent.treeChildren[nodeIndex-1]; 1.543 + }, 1.544 + _getNextSib: function TreeView__getNextSib(div) { 1.545 + if (div.treeParent == null) 1.546 + return null; 1.547 + var nodeIndex = div.treeParent.treeChildren.indexOf(div); 1.548 + if (nodeIndex == div.treeParent.treeChildren.length - 1) 1.549 + return this._getNextSib(div.treeParent); 1.550 + return div.treeParent.treeChildren[nodeIndex+1]; 1.551 + }, 1.552 + _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) { 1.553 + // Schedule this on the animation frame otherwise we may run this more then once per frames 1.554 + // causing more work then needed. 1.555 + var self = this; 1.556 + if (self._pendingAnimationFrame != null) { 1.557 + return; 1.558 + } 1.559 + self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() { 1.560 + cancelAnimationFrame(self._pendingAnimationFrame); 1.561 + self._pendingAnimationFrame = null; 1.562 + self._scrollIntoView(element, maxImportantWidth); 1.563 + }); 1.564 + }, 1.565 + _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) { 1.566 + // Make sure that element is inside the visible part of our scrollbox by 1.567 + // adjusting the scroll positions. If element is wider or 1.568 + // higher than the scroll port, the left and top edges are prioritized over 1.569 + // the right and bottom edges. 1.570 + // If maxImportantWidth is set, parts of the beyond this widths are 1.571 + // considered as not important; they'll not be moved into view. 1.572 + 1.573 + if (maxImportantWidth === undefined) 1.574 + maxImportantWidth = Infinity; 1.575 + 1.576 + var visibleRect = { 1.577 + left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150 1.578 + top: this._verticalScrollbox.getBoundingClientRect().top, 1.579 + right: this._horizontalScrollbox.getBoundingClientRect().right, 1.580 + bottom: this._verticalScrollbox.getBoundingClientRect().bottom 1.581 + } 1.582 + var r = element.getBoundingClientRect(); 1.583 + var right = Math.min(r.right, r.left + maxImportantWidth); 1.584 + var leftCutoff = visibleRect.left - r.left; 1.585 + var rightCutoff = right - visibleRect.right; 1.586 + var topCutoff = visibleRect.top - r.top; 1.587 + var bottomCutoff = r.bottom - visibleRect.bottom; 1.588 + if (leftCutoff > 0) 1.589 + this._horizontalScrollbox.scrollLeft -= leftCutoff; 1.590 + else if (rightCutoff > 0) 1.591 + this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff); 1.592 + if (topCutoff > 0) 1.593 + this._verticalScrollbox.scrollTop -= topCutoff; 1.594 + else if (bottomCutoff > 0) 1.595 + this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff); 1.596 + }, 1.597 + _select: function TreeView__select(li) { 1.598 + if (this._selectedNode != null) { 1.599 + this._selectedNode.treeLine.classList.remove("selected"); 1.600 + this._selectedNode = null; 1.601 + } 1.602 + if (li) { 1.603 + li.treeLine.classList.add("selected"); 1.604 + this._selectedNode = li; 1.605 + var functionName = li.treeLine.querySelector(".functionName"); 1.606 + this._scheduleScrollIntoView(functionName, 400); 1.607 + this._fireEvent("select", li.data); 1.608 + } 1.609 + updateDocumentURL(); 1.610 + }, 1.611 + _isCollapsed: function TreeView__isCollapsed(div) { 1.612 + return div.classList.contains("collapsed"); 1.613 + }, 1.614 + _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) { 1.615 + while (node) { 1.616 + if (node.nodeType != node.ELEMENT_NODE) 1.617 + break; 1.618 + if (node.classList.contains("treeViewNode")) 1.619 + return node; 1.620 + node = node.parentNode; 1.621 + } 1.622 + return null; 1.623 + }, 1.624 + _onclick: function TreeView__onclick(event) { 1.625 + var target = event.target; 1.626 + var node = this._getParentTreeViewNode(target); 1.627 + if (!node) 1.628 + return; 1.629 + if (target.classList.contains("expandCollapseButton")) { 1.630 + if (event.altKey) 1.631 + this._toggleAll(node); 1.632 + else 1.633 + this._toggle(node); 1.634 + } else if (target.classList.contains("focusCallstackButton")) { 1.635 + this._fireEvent("focusCallstackButtonClicked", node.data); 1.636 + } else { 1.637 + this._select(node); 1.638 + if (event.detail == 2) // dblclick 1.639 + this._toggle(node); 1.640 + } 1.641 + }, 1.642 + _onkeypress: function TreeView__onkeypress(event) { 1.643 + if (event.ctrlKey || event.altKey || event.metaKey) 1.644 + return; 1.645 + 1.646 + this._abortToggleAll = true; 1.647 + 1.648 + var selected = this._selectedNode; 1.649 + if (event.keyCode < 37 || event.keyCode > 40) { 1.650 + if (event.keyCode != 0 || 1.651 + String.fromCharCode(event.charCode) != '*') { 1.652 + return; 1.653 + } 1.654 + } 1.655 + event.stopPropagation(); 1.656 + event.preventDefault(); 1.657 + if (!selected) 1.658 + return; 1.659 + if (event.keyCode == 37) { // KEY_LEFT 1.660 + var isCollapsed = this._isCollapsed(selected); 1.661 + if (!isCollapsed) { 1.662 + this._toggle(selected); 1.663 + } else { 1.664 + var parent = this._getParent(selected); 1.665 + if (parent != null) { 1.666 + this._select(parent); 1.667 + } 1.668 + } 1.669 + } else if (event.keyCode == 38) { // KEY_UP 1.670 + var prevSib = this._getPrevSib(selected); 1.671 + var parent = this._getParent(selected); 1.672 + if (prevSib != null) { 1.673 + this._select(this._getLastChild(prevSib)); 1.674 + } else if (parent != null) { 1.675 + this._select(parent); 1.676 + } 1.677 + } else if (event.keyCode == 39) { // KEY_RIGHT 1.678 + var isCollapsed = this._isCollapsed(selected); 1.679 + if (isCollapsed) { 1.680 + this._toggle(selected); 1.681 + this._syncProcessPendingActionProcessing(); 1.682 + } else { 1.683 + // Do KEY_DOWN 1.684 + var nextSib = this._getNextSib(selected); 1.685 + var child = this._getFirstChild(selected); 1.686 + if (child != null) { 1.687 + this._select(child); 1.688 + } else if (nextSib) { 1.689 + this._select(nextSib); 1.690 + } 1.691 + } 1.692 + } else if (event.keyCode == 40) { // KEY_DOWN 1.693 + var nextSib = this._getNextSib(selected); 1.694 + var child = this._getFirstChild(selected); 1.695 + if (child != null) { 1.696 + this._select(child); 1.697 + } else if (nextSib) { 1.698 + this._select(nextSib); 1.699 + } 1.700 + } else if (String.fromCharCode(event.charCode) == '*') { 1.701 + this._toggleAll(selected); 1.702 + } 1.703 + }, 1.704 +}; 1.705 +