Wed, 31 Dec 2014 06:09:35 +0100
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 |