browser/devtools/profiler/cleopatra/js/ui.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 EIDETICKER_BASE_URL = "http://eideticker.wrla.ch/";
michael@0 8
michael@0 9 var gDebugLog = false;
michael@0 10 var gDebugTrace = false;
michael@0 11 var gLocation = window.location + "";
michael@0 12 if (gLocation.indexOf("file:") == 0) {
michael@0 13 gDebugLog = true;
michael@0 14 gDebugTrace = true;
michael@0 15 PROFILERLOG("Turning on logging+tracing since cleopatra is served from the file protocol");
michael@0 16 }
michael@0 17 // Use for verbose tracing, otherwise use log
michael@0 18 function PROFILERTRACE(msg) {
michael@0 19 if (gDebugTrace)
michael@0 20 PROFILERLOG(msg);
michael@0 21 }
michael@0 22 function PROFILERLOG(msg) {
michael@0 23 if (gDebugLog) {
michael@0 24 msg = "Cleo: " + msg;
michael@0 25 console.log(msg);
michael@0 26 if (window.dump)
michael@0 27 window.dump(msg + "\n");
michael@0 28 }
michael@0 29 }
michael@0 30 function PROFILERERROR(msg) {
michael@0 31 msg = "Cleo: " + msg;
michael@0 32 console.log(msg);
michael@0 33 if (window.dump)
michael@0 34 window.dump(msg + "\n");
michael@0 35 }
michael@0 36 function enableProfilerTracing() {
michael@0 37 gDebugLog = true;
michael@0 38 gDebugTrace = true;
michael@0 39 Parser.updateLogSetting();
michael@0 40 }
michael@0 41 function enableProfilerLogging() {
michael@0 42 gDebugLog = true;
michael@0 43 Parser.updateLogSetting();
michael@0 44 }
michael@0 45
michael@0 46 function removeAllChildren(element) {
michael@0 47 while (element.firstChild) {
michael@0 48 element.removeChild(element.firstChild);
michael@0 49 }
michael@0 50 }
michael@0 51
michael@0 52 function FileList() {
michael@0 53 this._container = document.createElement("ul");
michael@0 54 this._container.id = "fileList";
michael@0 55 this._selectedFileItem = null;
michael@0 56 this._fileItemList = [];
michael@0 57 }
michael@0 58
michael@0 59 FileList.prototype = {
michael@0 60 getContainer: function FileList_getContainer() {
michael@0 61 return this._container;
michael@0 62 },
michael@0 63
michael@0 64 clearFiles: function FileList_clearFiles() {
michael@0 65 this.fileItemList = [];
michael@0 66 this._selectedFileItem = null;
michael@0 67 this._container.innerHTML = "";
michael@0 68 },
michael@0 69
michael@0 70 loadProfileListFromLocalStorage: function FileList_loadProfileListFromLocalStorage() {
michael@0 71 var self = this;
michael@0 72 gLocalStorage.getProfileList(function(profileList) {
michael@0 73 for (var i = profileList.length - 1; i >= 0; i--) {
michael@0 74 (function closure() {
michael@0 75 // This only carries info about the profile and the access key to retrieve it.
michael@0 76 var profileInfo = profileList[i];
michael@0 77 //PROFILERTRACE("Profile list from local storage: " + JSON.stringify(profileInfo));
michael@0 78 var dateObj = new Date(profileInfo.date);
michael@0 79 var fileEntry = self.addFile(profileInfo, dateObj.toLocaleString(), function fileEntryClick() {
michael@0 80 PROFILERLOG("open: " + profileInfo.profileKey + "\n");
michael@0 81 loadLocalStorageProfile(profileInfo.profileKey);
michael@0 82 });
michael@0 83 })();
michael@0 84 }
michael@0 85 });
michael@0 86 gLocalStorage.onProfileListChange(function(profileList) {
michael@0 87 self.clearFiles();
michael@0 88 self.loadProfileListFromLocalStorage();
michael@0 89 });
michael@0 90 },
michael@0 91
michael@0 92 addFile: function FileList_addFile(profileInfo, description, onselect) {
michael@0 93 var li = document.createElement("li");
michael@0 94
michael@0 95 var fileName;
michael@0 96 if (profileInfo.profileKey && profileInfo.profileKey.indexOf("http://profile-store.commondatastorage.googleapis.com/") >= 0) {
michael@0 97 fileName = profileInfo.profileKey.substring(54);
michael@0 98 fileName = fileName.substring(0, 8) + "..." + fileName.substring(28);
michael@0 99 } else {
michael@0 100 fileName = profileInfo.name;
michael@0 101 }
michael@0 102 li.fileName = fileName || "(New Profile)";
michael@0 103 li.description = description || "(empty)";
michael@0 104
michael@0 105 li.className = "fileListItem";
michael@0 106 if (!this._selectedFileItem) {
michael@0 107 li.classList.add("selected");
michael@0 108 this._selectedFileItem = li;
michael@0 109 }
michael@0 110
michael@0 111 var self = this;
michael@0 112 li.onclick = function() {
michael@0 113 self.setSelection(li);
michael@0 114 if (onselect)
michael@0 115 onselect();
michael@0 116 }
michael@0 117
michael@0 118 var fileListItemTitleSpan = document.createElement("span");
michael@0 119 fileListItemTitleSpan.className = "fileListItemTitle";
michael@0 120 fileListItemTitleSpan.textContent = li.fileName;
michael@0 121 li.appendChild(fileListItemTitleSpan);
michael@0 122
michael@0 123 var fileListItemDescriptionSpan = document.createElement("span");
michael@0 124 fileListItemDescriptionSpan.className = "fileListItemDescription";
michael@0 125 fileListItemDescriptionSpan.textContent = li.description;
michael@0 126 li.appendChild(fileListItemDescriptionSpan);
michael@0 127
michael@0 128 this._container.appendChild(li);
michael@0 129
michael@0 130 this._fileItemList.push(li);
michael@0 131
michael@0 132 return li;
michael@0 133 },
michael@0 134
michael@0 135 setSelection: function FileList_setSelection(fileEntry) {
michael@0 136 if (this._selectedFileItem) {
michael@0 137 this._selectedFileItem.classList.remove("selected");
michael@0 138 }
michael@0 139 this._selectedFileItem = fileEntry;
michael@0 140 fileEntry.classList.add("selected");
michael@0 141 if (this._selectedFileItem.onselect)
michael@0 142 this._selectedFileItem.onselect();
michael@0 143 },
michael@0 144
michael@0 145 profileParsingFinished: function FileList_profileParsingFinished() {
michael@0 146 //this._container.querySelector(".fileListItemTitle").textContent = "Current Profile";
michael@0 147 //this._container.querySelector(".fileListItemDescription").textContent = gNumSamples + " Samples";
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 function treeObjSort(a, b) {
michael@0 152 return b.counter - a.counter;
michael@0 153 }
michael@0 154
michael@0 155 function ProfileTreeManager() {
michael@0 156 this.treeView = new TreeView();
michael@0 157 this.treeView.setColumns([
michael@0 158 { name: "sampleCount", title: gStrings["Running Time"] },
michael@0 159 { name: "selfSampleCount", title: gStrings["Self"] },
michael@0 160 { name: "resource", title: "" },
michael@0 161 { name: "symbolName", title: gStrings["Symbol Name"] }
michael@0 162 ]);
michael@0 163 var self = this;
michael@0 164 this.treeView.addEventListener("select", function (frameData) {
michael@0 165 self.highlightFrame(frameData);
michael@0 166 if (window.comparator_setSelection) {
michael@0 167 window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), frameData);
michael@0 168 }
michael@0 169 });
michael@0 170 this.treeView.addEventListener("contextMenuClick", function (e) {
michael@0 171 self._onContextMenuClick(e);
michael@0 172 });
michael@0 173 this.treeView.addEventListener("focusCallstackButtonClicked", function (frameData) {
michael@0 174 // NOTE: Not in the original Cleopatra source code.
michael@0 175 notifyParent("displaysource", {
michael@0 176 line: frameData.scriptLocation.lineInformation,
michael@0 177 uri: frameData.scriptLocation.scriptURI,
michael@0 178 isChrome: /^otherhost_*/.test(frameData.library)
michael@0 179 });
michael@0 180 });
michael@0 181 this._container = document.createElement("div");
michael@0 182 this._container.className = "tree";
michael@0 183 this._container.appendChild(this.treeView.getContainer());
michael@0 184
michael@0 185 // If this is set when the tree changes the snapshot is immediately restored.
michael@0 186 this._savedSnapshot = null;
michael@0 187 this._allowNonContiguous = false;
michael@0 188 }
michael@0 189 ProfileTreeManager.prototype = {
michael@0 190 getContainer: function ProfileTreeManager_getContainer() {
michael@0 191 return this._container;
michael@0 192 },
michael@0 193 highlightFrame: function Treedisplay_highlightFrame(frameData) {
michael@0 194 setHighlightedCallstack(this._getCallstackUpTo(frameData), this._getHeaviestCallstack(frameData));
michael@0 195 },
michael@0 196 dataIsOutdated: function ProfileTreeManager_dataIsOutdated() {
michael@0 197 this.treeView.dataIsOutdated();
michael@0 198 },
michael@0 199 saveSelectionSnapshot: function ProfileTreeManager_saveSelectionSnapshot(isJavascriptOnly) {
michael@0 200 this._savedSnapshot = this.treeView.getSelectionSnapshot(isJavascriptOnly);
michael@0 201 },
michael@0 202 saveReverseSelectionSnapshot: function ProfileTreeManager_saveReverseSelectionSnapshot(isJavascriptOnly) {
michael@0 203 this._savedSnapshot = this.treeView.getReverseSelectionSnapshot(isJavascriptOnly);
michael@0 204 },
michael@0 205 hasNonTrivialSelection: function ProfileTreeManager_hasNonTrivialSelection() {
michael@0 206 return this.treeView.getSelectionSnapshot().length > 1;
michael@0 207 },
michael@0 208 serializeCurrentSelectionSnapshot: function ProfileTreeManager_serializeCurrentSelectionSnapshot() {
michael@0 209 return JSON.stringify(this.treeView.getSelectionSnapshot());
michael@0 210 },
michael@0 211 restoreSerializedSelectionSnapshot: function ProfileTreeManager_restoreSerializedSelectionSnapshot(selection) {
michael@0 212 this._savedSnapshot = JSON.parse(selection);
michael@0 213 },
michael@0 214 _restoreSelectionSnapshot: function ProfileTreeManager__restoreSelectionSnapshot(snapshot, allowNonContiguous) {
michael@0 215 return this.treeView.restoreSelectionSnapshot(snapshot, allowNonContiguous);
michael@0 216 },
michael@0 217 setSelection: function ProfileTreeManager_setSelection(frames) {
michael@0 218 return this.treeView.setSelection(frames);
michael@0 219 },
michael@0 220 _getCallstackUpTo: function ProfileTreeManager__getCallstackUpTo(frame) {
michael@0 221 var callstack = [];
michael@0 222 var curr = frame;
michael@0 223 while (curr != null) {
michael@0 224 if (curr.name != null) {
michael@0 225 var subCallstack = curr.fullFrameNamesAsInSample.clone();
michael@0 226 subCallstack.reverse();
michael@0 227 callstack = callstack.concat(subCallstack);
michael@0 228 }
michael@0 229 curr = curr.parent;
michael@0 230 }
michael@0 231 callstack.reverse();
michael@0 232 if (gInvertCallstack)
michael@0 233 callstack.shift(); // remove (total)
michael@0 234 return callstack;
michael@0 235 },
michael@0 236 _getHeaviestCallstack: function ProfileTreeManager__getHeaviestCallstack(frame) {
michael@0 237 // FIXME: This gets the first leaf which is not the heaviest leaf.
michael@0 238 while(frame.children && frame.children.length > 0) {
michael@0 239 var nextFrame = frame.children[0].getData();
michael@0 240 if (!nextFrame)
michael@0 241 break;
michael@0 242 frame = nextFrame;
michael@0 243 }
michael@0 244 return this._getCallstackUpTo(frame);
michael@0 245 },
michael@0 246 _onContextMenuClick: function ProfileTreeManager__onContextMenuClick(e) {
michael@0 247 var node = e.node;
michael@0 248 var menuItem = e.menuItem;
michael@0 249
michael@0 250 if (menuItem == "View Source") {
michael@0 251 // Remove anything after ( since MXR doesn't handle search with the arguments.
michael@0 252 var symbol = node.name.split("(")[0];
michael@0 253 window.open("http://mxr.mozilla.org/mozilla-central/search?string=" + symbol, "View Source");
michael@0 254 } else if (menuItem == "View JS Source") {
michael@0 255 viewJSSource(node);
michael@0 256 } else if (menuItem == "Plugin View: Pie") {
michael@0 257 focusOnPluginView("protovis", {type:"pie"});
michael@0 258 } else if (menuItem == "Plugin View: Tree") {
michael@0 259 focusOnPluginView("protovis", {type:"tree"});
michael@0 260 } else if (menuItem == "Google Search") {
michael@0 261 var symbol = node.name;
michael@0 262 window.open("https://www.google.ca/search?q=" + symbol, "View Source");
michael@0 263 } else if (menuItem == "Focus Frame") {
michael@0 264 var symbol = node.fullFrameNamesAsInSample[0]; // TODO: we only function one symbol when callpath merging is on, fix that
michael@0 265 focusOnSymbol(symbol, node.name);
michael@0 266 } else if (menuItem == "Focus Callstack") {
michael@0 267 var focusedCallstack = this._getCallstackUpTo(node);
michael@0 268 focusOnCallstack(focusedCallstack, node.name);
michael@0 269 }
michael@0 270 },
michael@0 271 setAllowNonContiguous: function ProfileTreeManager_setAllowNonContiguous() {
michael@0 272 this._allowNonContiguous = true;
michael@0 273 },
michael@0 274 display: function ProfileTreeManager_display(tree, symbols, functions, resources, useFunctions, filterByName) {
michael@0 275 this.treeView.display(this.convertToJSTreeData(tree, symbols, functions, useFunctions), resources, filterByName);
michael@0 276 if (this._savedSnapshot) {
michael@0 277 var old = this._savedSnapshot.clone();
michael@0 278 this._restoreSelectionSnapshot(this._savedSnapshot, this._allowNonContiguous);
michael@0 279 this._savedSnapshot = old;
michael@0 280 this._allowNonContiguous = false;
michael@0 281 }
michael@0 282 },
michael@0 283 convertToJSTreeData: function ProfileTreeManager__convertToJSTreeData(rootNode, symbols, functions, useFunctions) {
michael@0 284 var totalSamples = rootNode.counter;
michael@0 285 function createTreeViewNode(node, parent) {
michael@0 286 var curObj = {};
michael@0 287 curObj.parent = parent;
michael@0 288 curObj.counter = node.counter;
michael@0 289 var selfCounter = node.counter;
michael@0 290 for (var i = 0; i < node.children.length; ++i) {
michael@0 291 selfCounter -= node.children[i].counter;
michael@0 292 }
michael@0 293 curObj.selfCounter = selfCounter;
michael@0 294 curObj.ratio = node.counter / totalSamples;
michael@0 295 curObj.fullFrameNamesAsInSample = node.mergedNames ? node.mergedNames : [node.name];
michael@0 296 if (!(node.name in (useFunctions ? functions : symbols))) {
michael@0 297 curObj.name = node.name;
michael@0 298 curObj.library = "";
michael@0 299 } else {
michael@0 300 var functionObj = useFunctions ? functions[node.name] : functions[symbols[node.name].functionIndex];
michael@0 301 var info = {
michael@0 302 functionName: functionObj.functionName,
michael@0 303 libraryName: functionObj.libraryName,
michael@0 304 lineInformation: useFunctions ? "" : symbols[node.name].lineInformation
michael@0 305 };
michael@0 306 curObj.name = (info.functionName + " " + info.lineInformation).trim();
michael@0 307 curObj.library = info.libraryName;
michael@0 308 curObj.isJSFrame = functionObj.isJSFrame;
michael@0 309 if (functionObj.scriptLocation) {
michael@0 310 curObj.scriptLocation = functionObj.scriptLocation;
michael@0 311 }
michael@0 312 }
michael@0 313 if (node.children.length) {
michael@0 314 curObj.children = getChildrenObjects(node.children, curObj);
michael@0 315 }
michael@0 316 return curObj;
michael@0 317 }
michael@0 318 function getChildrenObjects(children, parent) {
michael@0 319 var sortedChildren = children.slice(0).sort(treeObjSort);
michael@0 320 return sortedChildren.map(function (child) {
michael@0 321 var createdNode = null;
michael@0 322 return {
michael@0 323 getData: function () {
michael@0 324 if (!createdNode) {
michael@0 325 createdNode = createTreeViewNode(child, parent);
michael@0 326 }
michael@0 327 return createdNode;
michael@0 328 }
michael@0 329 };
michael@0 330 });
michael@0 331 }
michael@0 332 return getChildrenObjects([rootNode], null);
michael@0 333 },
michael@0 334 };
michael@0 335
michael@0 336 function SampleBar() {
michael@0 337 this._container = document.createElement("div");
michael@0 338 this._container.id = "sampleBar";
michael@0 339 this._container.className = "sideBar";
michael@0 340
michael@0 341 this._header = document.createElement("h2");
michael@0 342 this._header.innerHTML = "Selection - Most time spent in:";
michael@0 343 this._header.alt = "This shows the heaviest leaf of the selected sample. Use this to get a quick glimpse of where the selection is spending most of its time.";
michael@0 344 this._container.appendChild(this._header);
michael@0 345
michael@0 346 this._text = document.createElement("ul");
michael@0 347 this._text.style.whiteSpace = "pre";
michael@0 348 this._text.innerHTML = "Sample text";
michael@0 349 this._container.appendChild(this._text);
michael@0 350 }
michael@0 351
michael@0 352 SampleBar.prototype = {
michael@0 353 getContainer: function SampleBar_getContainer() {
michael@0 354 return this._container;
michael@0 355 },
michael@0 356 setSample: function SampleBar_setSample(sample) {
michael@0 357 var str = "";
michael@0 358 var list = [];
michael@0 359
michael@0 360 this._text.innerHTML = "";
michael@0 361
michael@0 362 for (var i = 0; i < sample.length; i++) {
michael@0 363 var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex];
michael@0 364 if (!functionObj)
michael@0 365 continue;
michael@0 366 var functionItem = document.createElement("li");
michael@0 367 var functionLink = document.createElement("a");
michael@0 368 functionLink.textContent = functionLink.title = functionObj.functionName;
michael@0 369 functionLink.href = "#";
michael@0 370 functionItem.appendChild(functionLink);
michael@0 371 this._text.appendChild(functionItem);
michael@0 372 list.push(functionObj.functionName);
michael@0 373 functionLink.selectIndex = i;
michael@0 374 functionLink.onclick = function() {
michael@0 375 var selectedFrames = [];
michael@0 376 if (gInvertCallstack) {
michael@0 377 for (var i = 0; i <= this.selectIndex; i++) {
michael@0 378 var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex];
michael@0 379 selectedFrames.push(functionObj.functionName);
michael@0 380 }
michael@0 381 } else {
michael@0 382 for (var i = sample.length - 1; i >= this.selectIndex; i--) {
michael@0 383 var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex];
michael@0 384 selectedFrames.push(functionObj.functionName);
michael@0 385 }
michael@0 386 }
michael@0 387 gTreeManager.setSelection(selectedFrames);
michael@0 388 return false;
michael@0 389 }
michael@0 390 }
michael@0 391 return list;
michael@0 392 },
michael@0 393 }
michael@0 394
michael@0 395
michael@0 396 function PluginView() {
michael@0 397 this._container = document.createElement("div");
michael@0 398 this._container.className = "pluginview";
michael@0 399 this._container.style.visibility = 'hidden';
michael@0 400 this._iframe = document.createElement("iframe");
michael@0 401 this._iframe.className = "pluginviewIFrame";
michael@0 402 this._container.appendChild(this._iframe);
michael@0 403 this._container.style.top = "";
michael@0 404 }
michael@0 405 PluginView.prototype = {
michael@0 406 getContainer: function PluginView_getContainer() {
michael@0 407 return this._container;
michael@0 408 },
michael@0 409 hide: function() {
michael@0 410 // get rid of the scrollbars
michael@0 411 this._container.style.top = "";
michael@0 412 this._container.style.visibility = 'hidden';
michael@0 413 },
michael@0 414 show: function() {
michael@0 415 // This creates extra scrollbar so only do it when needed
michael@0 416 this._container.style.top = "0px";
michael@0 417 this._container.style.visibility = '';
michael@0 418 },
michael@0 419 display: function(pluginName, param, data) {
michael@0 420 this._iframe.src = "js/plugins/" + pluginName + "/index.html";
michael@0 421 var self = this;
michael@0 422 this._iframe.onload = function() {
michael@0 423 self._iframe.contentWindow.initCleopatraPlugin(data, param, gSymbols);
michael@0 424 }
michael@0 425 this.show();
michael@0 426 },
michael@0 427 }
michael@0 428
michael@0 429 function HistogramView() {
michael@0 430 this._container = document.createElement("div");
michael@0 431 this._container.className = "histogram";
michael@0 432
michael@0 433 this._canvas = this._createCanvas();
michael@0 434 this._container.appendChild(this._canvas);
michael@0 435
michael@0 436 this._rangeSelector = new RangeSelector(this._canvas, this);
michael@0 437 this._rangeSelector.enableRangeSelectionOnHistogram();
michael@0 438 this._container.appendChild(this._rangeSelector.getContainer());
michael@0 439
michael@0 440 this._busyCover = document.createElement("div");
michael@0 441 this._busyCover.className = "busyCover";
michael@0 442 this._container.appendChild(this._busyCover);
michael@0 443
michael@0 444 this._histogramData = [];
michael@0 445 }
michael@0 446 HistogramView.prototype = {
michael@0 447 dataIsOutdated: function HistogramView_dataIsOutdated() {
michael@0 448 this._busyCover.classList.add("busy");
michael@0 449 },
michael@0 450 _createCanvas: function HistogramView__createCanvas() {
michael@0 451 var canvas = document.createElement("canvas");
michael@0 452 canvas.height = 60;
michael@0 453 canvas.style.width = "100%";
michael@0 454 canvas.style.height = "100%";
michael@0 455 return canvas;
michael@0 456 },
michael@0 457 getContainer: function HistogramView_getContainer() {
michael@0 458 return this._container;
michael@0 459 },
michael@0 460 selectRange: function HistogramView_selectRange(start, end) {
michael@0 461 this._rangeSelector._finishSelection(start, end);
michael@0 462 },
michael@0 463 showVideoFramePosition: function HistogramView_showVideoFramePosition(frame) {
michael@0 464 if (!this._frameStart || !this._frameStart[frame])
michael@0 465 return;
michael@0 466 var frameStart = this._frameStart[frame];
michael@0 467 // Now we look for the frame end. Because we can swap frame we don't present we have to look ahead
michael@0 468 // in the stream if frame+1 doesn't exist.
michael@0 469 var frameEnd = this._frameStart[frame+1];
michael@0 470 for (var i = 0; i < 10 && !frameEnd; i++) {
michael@0 471 frameEnd = this._frameStart[frame+1+i];
michael@0 472 }
michael@0 473 this._rangeSelector.showVideoRange(frameStart, frameEnd);
michael@0 474 },
michael@0 475 showVideoPosition: function HistogramView_showVideoPosition(position) {
michael@0 476 // position in 0..1
michael@0 477 this._rangeSelector.showVideoPosition(position);
michael@0 478 },
michael@0 479 _gatherMarkersList: function HistogramView__gatherMarkersList(histogramData) {
michael@0 480 var markers = [];
michael@0 481 for (var i = 0; i < histogramData.length; ++i) {
michael@0 482 var step = histogramData[i];
michael@0 483 if ("marker" in step) {
michael@0 484 markers.push({
michael@0 485 index: i,
michael@0 486 name: step.marker
michael@0 487 });
michael@0 488 }
michael@0 489 }
michael@0 490 return markers;
michael@0 491 },
michael@0 492 _calculateWidthMultiplier: function () {
michael@0 493 var minWidth = 2000;
michael@0 494 return Math.ceil(minWidth / this._widthSum);
michael@0 495 },
michael@0 496 histogramClick: function HistogramView_histogramClick(index) {
michael@0 497 var sample = this._histogramData[index];
michael@0 498 var frames = sample.frames;
michael@0 499 var list = gSampleBar.setSample(frames[0]);
michael@0 500 gTreeManager.setSelection(list);
michael@0 501 setHighlightedCallstack(frames[0], frames[0]);
michael@0 502 },
michael@0 503 display: function HistogramView_display(histogramData, frameStart, widthSum, highlightedCallstack) {
michael@0 504 this._histogramData = histogramData;
michael@0 505 this._frameStart = frameStart;
michael@0 506 this._widthSum = widthSum;
michael@0 507 this._widthMultiplier = this._calculateWidthMultiplier();
michael@0 508 this._canvas.width = this._widthMultiplier * this._widthSum;
michael@0 509 this._render(highlightedCallstack);
michael@0 510 this._busyCover.classList.remove("busy");
michael@0 511 },
michael@0 512 _scheduleRender: function HistogramView__scheduleRender(highlightedCallstack) {
michael@0 513 var self = this;
michael@0 514 if (self._pendingAnimationFrame != null) {
michael@0 515 return;
michael@0 516 }
michael@0 517 self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
michael@0 518 cancelAnimationFrame(self._pendingAnimationFrame);
michael@0 519 self._pendingAnimationFrame = null;
michael@0 520 self._render(highlightedCallstack);
michael@0 521 self._busyCover.classList.remove("busy");
michael@0 522 });
michael@0 523 },
michael@0 524 _render: function HistogramView__render(highlightedCallstack) {
michael@0 525 var ctx = this._canvas.getContext("2d");
michael@0 526 var height = this._canvas.height;
michael@0 527 ctx.setTransform(this._widthMultiplier, 0, 0, 1, 0, 0);
michael@0 528 ctx.font = "20px Georgia";
michael@0 529 ctx.clearRect(0, 0, this._widthSum, height);
michael@0 530
michael@0 531 var self = this;
michael@0 532 var markerCount = 0;
michael@0 533 for (var i = 0; i < this._histogramData.length; i++) {
michael@0 534 var step = this._histogramData[i];
michael@0 535 var isSelected = self._isStepSelected(step, highlightedCallstack);
michael@0 536 var isInRangeSelector = self._isInRangeSelector(i);
michael@0 537 if (isSelected) {
michael@0 538 ctx.fillStyle = "green";
michael@0 539 } else if (isInRangeSelector) {
michael@0 540 ctx.fillStyle = "blue";
michael@0 541 } else {
michael@0 542 ctx.fillStyle = step.color;
michael@0 543 }
michael@0 544 var roundedHeight = Math.round(step.value * height);
michael@0 545 ctx.fillRect(step.x, height - roundedHeight, step.width, roundedHeight);
michael@0 546 }
michael@0 547
michael@0 548 this._finishedRendering = true;
michael@0 549 },
michael@0 550 highlightedCallstackChanged: function HistogramView_highlightedCallstackChanged(highlightedCallstack) {
michael@0 551 this._scheduleRender(highlightedCallstack);
michael@0 552 },
michael@0 553 _isInRangeSelector: function HistogramView_isInRangeSelector(index) {
michael@0 554 return false;
michael@0 555 },
michael@0 556 _isStepSelected: function HistogramView__isStepSelected(step, highlightedCallstack) {
michael@0 557 if ("marker" in step)
michael@0 558 return false;
michael@0 559
michael@0 560 search_frames: for (var i = 0; i < step.frames.length; i++) {
michael@0 561 var frames = step.frames[i];
michael@0 562
michael@0 563 if (frames.length < highlightedCallstack.length ||
michael@0 564 highlightedCallstack.length <= (gInvertCallstack ? 0 : 1))
michael@0 565 continue;
michael@0 566
michael@0 567 var compareFrames = frames;
michael@0 568 if (gInvertCallstack) {
michael@0 569 for (var j = 0; j < highlightedCallstack.length; j++) {
michael@0 570 var compareFrameIndex = compareFrames.length - 1 - j;
michael@0 571 if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) {
michael@0 572 continue search_frames;
michael@0 573 }
michael@0 574 }
michael@0 575 } else {
michael@0 576 for (var j = 0; j < highlightedCallstack.length; j++) {
michael@0 577 var compareFrameIndex = j;
michael@0 578 if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) {
michael@0 579 continue search_frames;
michael@0 580 }
michael@0 581 }
michael@0 582 }
michael@0 583 return true;
michael@0 584 };
michael@0 585 return false;
michael@0 586 },
michael@0 587 getHistogramData: function HistogramView__getHistogramData() {
michael@0 588 return this._histogramData;
michael@0 589 },
michael@0 590 _getStepColor: function HistogramView__getStepColor(step) {
michael@0 591 if ("responsiveness" in step.extraInfo) {
michael@0 592 var res = step.extraInfo.responsiveness;
michael@0 593 var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness));
michael@0 594 return "rgb(" + redComponent + ",0,0)";
michael@0 595 }
michael@0 596
michael@0 597 return "rgb(0,0,0)";
michael@0 598 },
michael@0 599 };
michael@0 600
michael@0 601 function RangeSelector(graph, histogram) {
michael@0 602 this._histogram = histogram;
michael@0 603 this.container = document.createElement("div");
michael@0 604 this.container.className = "rangeSelectorContainer";
michael@0 605 this._graph = graph;
michael@0 606 this._selectedRange = { startX: 0, endX: 0 };
michael@0 607 this._selectedSampleRange = { start: 0, end: 0 };
michael@0 608
michael@0 609 this._highlighter = document.createElement("div");
michael@0 610 this._highlighter.className = "histogramHilite collapsed";
michael@0 611 this.container.appendChild(this._highlighter);
michael@0 612
michael@0 613 this._mouseMarker = document.createElement("div");
michael@0 614 this._mouseMarker.className = "histogramMouseMarker";
michael@0 615 this.container.appendChild(this._mouseMarker);
michael@0 616 }
michael@0 617 RangeSelector.prototype = {
michael@0 618 getContainer: function RangeSelector_getContainer() {
michael@0 619 return this.container;
michael@0 620 },
michael@0 621 // echo the location off the mouse on the histogram
michael@0 622 drawMouseMarker: function RangeSelector_drawMouseMarker(x) {
michael@0 623 var mouseMarker = this._mouseMarker;
michael@0 624 mouseMarker.style.left = x + "px";
michael@0 625 },
michael@0 626 showVideoPosition: function RangeSelector_showVideoPosition(position) {
michael@0 627 this.drawMouseMarker(position * (this._graph.parentNode.clientWidth-1));
michael@0 628 PROFILERLOG("Show video position: " + position);
michael@0 629 },
michael@0 630 drawHiliteRectangle: function RangeSelector_drawHiliteRectangle(x, y, width, height) {
michael@0 631 var hilite = this._highlighter;
michael@0 632 hilite.style.left = x + "px";
michael@0 633 hilite.style.top = "0";
michael@0 634 hilite.style.width = width + "px";
michael@0 635 hilite.style.height = height + "px";
michael@0 636 },
michael@0 637 clearCurrentRangeSelection: function RangeSelector_clearCurrentRangeSelection() {
michael@0 638 try {
michael@0 639 this.changeEventSuppressed = true;
michael@0 640 var children = this.selector.childNodes;
michael@0 641 for (var i = 0; i < children.length; ++i) {
michael@0 642 children[i].selected = false;
michael@0 643 }
michael@0 644 } finally {
michael@0 645 this.changeEventSuppressed = false;
michael@0 646 }
michael@0 647 },
michael@0 648 showVideoRange: function RangeSelector_showVideoRange(startIndex, endIndex) {
michael@0 649 if (!endIndex || endIndex < 0)
michael@0 650 endIndex = gCurrentlyShownSampleData.length;
michael@0 651
michael@0 652 var len = this._graph.parentNode.getBoundingClientRect().right - this._graph.parentNode.getBoundingClientRect().left;
michael@0 653 this._selectedRange.startX = startIndex * len / this._histogram._histogramData.length;
michael@0 654 this._selectedRange.endX = endIndex * len / this._histogram._histogramData.length;
michael@0 655 var width = this._selectedRange.endX - this._selectedRange.startX;
michael@0 656 var height = this._graph.parentNode.clientHeight;
michael@0 657 this._highlighter.classList.remove("collapsed");
michael@0 658 this.drawHiliteRectangle(this._selectedRange.startX, 0, width, height);
michael@0 659 //this._finishSelection(startIndex, endIndex);
michael@0 660 },
michael@0 661 enableRangeSelectionOnHistogram: function RangeSelector_enableRangeSelectionOnHistogram() {
michael@0 662 var graph = this._graph;
michael@0 663 var isDrawingRectangle = false;
michael@0 664 var origX, origY;
michael@0 665 var self = this;
michael@0 666 // Compute this on the mouse down rather then forcing a sync reflow
michael@0 667 // every frame.
michael@0 668 var boundingRect = null;
michael@0 669 function histogramClick(clickX, clickY) {
michael@0 670 clickX = Math.min(clickX, graph.parentNode.getBoundingClientRect().right);
michael@0 671 clickX = clickX - graph.parentNode.getBoundingClientRect().left;
michael@0 672 var index = self._histogramIndexFromPoint(clickX);
michael@0 673 self._histogram.histogramClick(index);
michael@0 674 }
michael@0 675 function updateHiliteRectangle(newX, newY) {
michael@0 676 newX = Math.min(newX, boundingRect.right);
michael@0 677 var startX = Math.min(newX, origX) - boundingRect.left;
michael@0 678 var startY = 0;
michael@0 679 var width = Math.abs(newX - origX);
michael@0 680 var height = graph.parentNode.clientHeight;
michael@0 681 if (startX < 0) {
michael@0 682 width += startX;
michael@0 683 startX = 0;
michael@0 684 }
michael@0 685 self._selectedRange.startX = startX;
michael@0 686 self._selectedRange.endX = startX + width;
michael@0 687 self.drawHiliteRectangle(startX, startY, width, height);
michael@0 688 }
michael@0 689 function updateMouseMarker(newX) {
michael@0 690 self.drawMouseMarker(newX - graph.parentNode.getBoundingClientRect().left);
michael@0 691 }
michael@0 692 graph.addEventListener("mousedown", function(e) {
michael@0 693 if (e.button != 0)
michael@0 694 return;
michael@0 695 graph.style.cursor = "col-resize";
michael@0 696 isDrawingRectangle = true;
michael@0 697 self.beginHistogramSelection();
michael@0 698 origX = e.pageX;
michael@0 699 origY = e.pageY;
michael@0 700 boundingRect = graph.parentNode.getBoundingClientRect();
michael@0 701 if (this.setCapture)
michael@0 702 this.setCapture();
michael@0 703 // Reset the highlight rectangle
michael@0 704 updateHiliteRectangle(e.pageX, e.pageY);
michael@0 705 e.preventDefault();
michael@0 706 this._movedDuringClick = false;
michael@0 707 }, false);
michael@0 708 graph.addEventListener("mouseup", function(e) {
michael@0 709 graph.style.cursor = "default";
michael@0 710 if (!this._movedDuringClick) {
michael@0 711 isDrawingRectangle = false;
michael@0 712 // Handle as a click on the histogram. Select the sample:
michael@0 713 histogramClick(e.pageX, e.pageY);
michael@0 714 } else if (isDrawingRectangle) {
michael@0 715 isDrawingRectangle = false;
michael@0 716 updateHiliteRectangle(e.pageX, e.pageY);
michael@0 717 self.finishHistogramSelection(e.pageX != origX);
michael@0 718 if (e.pageX == origX) {
michael@0 719 // Simple click in the histogram
michael@0 720 var index = self._sampleIndexFromPoint(e.pageX - graph.parentNode.getBoundingClientRect().left);
michael@0 721 // TODO Select this sample in the tree view
michael@0 722 var sample = gCurrentlyShownSampleData[index];
michael@0 723 }
michael@0 724 }
michael@0 725 }, false);
michael@0 726 graph.addEventListener("mousemove", function(e) {
michael@0 727 this._movedDuringClick = true;
michael@0 728 if (isDrawingRectangle) {
michael@0 729 updateMouseMarker(-1); // Clear
michael@0 730 updateHiliteRectangle(e.pageX, e.pageY);
michael@0 731 } else {
michael@0 732 updateMouseMarker(e.pageX);
michael@0 733 }
michael@0 734 }, false);
michael@0 735 graph.addEventListener("mouseout", function(e) {
michael@0 736 updateMouseMarker(-1); // Clear
michael@0 737 }, false);
michael@0 738 },
michael@0 739 beginHistogramSelection: function RangeSelector_beginHistgramSelection() {
michael@0 740 var hilite = this._highlighter;
michael@0 741 hilite.classList.remove("finished");
michael@0 742 hilite.classList.add("selecting");
michael@0 743 hilite.classList.remove("collapsed");
michael@0 744 if (this._transientRestrictionEnteringAffordance) {
michael@0 745 this._transientRestrictionEnteringAffordance.discard();
michael@0 746 }
michael@0 747 },
michael@0 748 _finishSelection: function RangeSelector__finishSelection(start, end) {
michael@0 749 var newFilterChain = gSampleFilters.concat({ type: "RangeSampleFilter", start: start, end: end });
michael@0 750 var self = this;
michael@0 751 self._transientRestrictionEnteringAffordance = gBreadcrumbTrail.add({
michael@0 752 title: gStrings["Sample Range"] + " [" + start + ", " + (end + 1) + "]",
michael@0 753 enterCallback: function () {
michael@0 754 gSampleFilters = newFilterChain;
michael@0 755 self.collapseHistogramSelection();
michael@0 756 filtersChanged();
michael@0 757 }
michael@0 758 });
michael@0 759 },
michael@0 760 finishHistogramSelection: function RangeSelector_finishHistgramSelection(isSomethingSelected) {
michael@0 761 var self = this;
michael@0 762 var hilite = this._highlighter;
michael@0 763 hilite.classList.remove("selecting");
michael@0 764 if (isSomethingSelected) {
michael@0 765 hilite.classList.add("finished");
michael@0 766 var start = this._sampleIndexFromPoint(this._selectedRange.startX);
michael@0 767 var end = this._sampleIndexFromPoint(this._selectedRange.endX);
michael@0 768 self._finishSelection(start, end);
michael@0 769 } else {
michael@0 770 hilite.classList.add("collapsed");
michael@0 771 }
michael@0 772 },
michael@0 773 collapseHistogramSelection: function RangeSelector_collapseHistogramSelection() {
michael@0 774 var hilite = this._highlighter;
michael@0 775 hilite.classList.add("collapsed");
michael@0 776 },
michael@0 777 _sampleIndexFromPoint: function RangeSelector__sampleIndexFromPoint(x) {
michael@0 778 // XXX this is completely wrong, fix please
michael@0 779 var totalSamples = parseFloat(gCurrentlyShownSampleData.length);
michael@0 780 var width = parseFloat(this._graph.parentNode.clientWidth);
michael@0 781 var factor = totalSamples / width;
michael@0 782 return parseInt(parseFloat(x) * factor);
michael@0 783 },
michael@0 784 _histogramIndexFromPoint: function RangeSelector__histogramIndexFromPoint(x) {
michael@0 785 // XXX this is completely wrong, fix please
michael@0 786 var totalSamples = parseFloat(this._histogram._histogramData.length);
michael@0 787 var width = parseFloat(this._graph.parentNode.clientWidth);
michael@0 788 var factor = totalSamples / width;
michael@0 789 return parseInt(parseFloat(x) * factor);
michael@0 790 },
michael@0 791 };
michael@0 792
michael@0 793 function videoPaneTimeChange(video) {
michael@0 794 if (!gMeta || !gMeta.frameStart)
michael@0 795 return;
michael@0 796
michael@0 797 var frame = gVideoPane.getCurrentFrameNumber();
michael@0 798 //var frameStart = gMeta.frameStart[frame];
michael@0 799 //var frameEnd = gMeta.frameStart[frame+1]; // If we don't have a frameEnd assume the end of the profile
michael@0 800
michael@0 801 gHistogramView.showVideoFramePosition(frame);
michael@0 802 }
michael@0 803
michael@0 804
michael@0 805 window.onpopstate = function(ev) {
michael@0 806 return; // Conflicts with document url
michael@0 807 if (!gBreadcrumbTrail)
michael@0 808 return;
michael@0 809
michael@0 810 gBreadcrumbTrail.pop();
michael@0 811 if (ev.state) {
michael@0 812 if (ev.state.action === "popbreadcrumb") {
michael@0 813 //gBreadcrumbTrail.pop();
michael@0 814 }
michael@0 815 }
michael@0 816 }
michael@0 817
michael@0 818 function BreadcrumbTrail() {
michael@0 819 this._breadcrumbs = [];
michael@0 820 this._selectedBreadcrumbIndex = -1;
michael@0 821
michael@0 822 this._containerElement = document.createElement("div");
michael@0 823 this._containerElement.className = "breadcrumbTrail";
michael@0 824 var self = this;
michael@0 825 this._containerElement.addEventListener("click", function (e) {
michael@0 826 if (!e.target.classList.contains("breadcrumbTrailItem"))
michael@0 827 return;
michael@0 828 self._enter(e.target.breadcrumbIndex);
michael@0 829 });
michael@0 830 }
michael@0 831 BreadcrumbTrail.prototype = {
michael@0 832 getContainer: function BreadcrumbTrail_getContainer() {
michael@0 833 return this._containerElement;
michael@0 834 },
michael@0 835 /**
michael@0 836 * Add a breadcrumb. The breadcrumb parameter is an object with the following
michael@0 837 * properties:
michael@0 838 * - title: The text that will be shown in the breadcrumb's button.
michael@0 839 * - enterCallback: A function that will be called when entering this
michael@0 840 * breadcrumb.
michael@0 841 */
michael@0 842 add: function BreadcrumbTrail_add(breadcrumb) {
michael@0 843 for (var i = this._breadcrumbs.length - 1; i > this._selectedBreadcrumbIndex; i--) {
michael@0 844 var rearLi = this._breadcrumbs[i];
michael@0 845 if (!rearLi.breadcrumbIsTransient)
michael@0 846 throw "Can only add new breadcrumbs if after the current one there are only transient ones.";
michael@0 847 rearLi.breadcrumbDiscarder.discard();
michael@0 848 }
michael@0 849 var div = document.createElement("div");
michael@0 850 div.className = "breadcrumbTrailItem";
michael@0 851 div.textContent = breadcrumb.title;
michael@0 852 var index = this._breadcrumbs.length;
michael@0 853 div.breadcrumbIndex = index;
michael@0 854 div.breadcrumbEnterCallback = breadcrumb.enterCallback;
michael@0 855 div.breadcrumbIsTransient = true;
michael@0 856 div.style.zIndex = 1000 - index;
michael@0 857 this._containerElement.appendChild(div);
michael@0 858 this._breadcrumbs.push(div);
michael@0 859 if (index == 0)
michael@0 860 this._enter(index);
michael@0 861 var self = this;
michael@0 862 div.breadcrumbDiscarder = {
michael@0 863 discard: function () {
michael@0 864 if (div.breadcrumbIsTransient) {
michael@0 865 self._deleteBeyond(index - 1);
michael@0 866 delete div.breadcrumbIsTransient;
michael@0 867 delete div.breadcrumbDiscarder;
michael@0 868 }
michael@0 869 }
michael@0 870 };
michael@0 871 return div.breadcrumbDiscarder;
michael@0 872 },
michael@0 873 addAndEnter: function BreadcrumbTrail_addAndEnter(breadcrumb) {
michael@0 874 var removalHandle = this.add(breadcrumb);
michael@0 875 this._enter(this._breadcrumbs.length - 1);
michael@0 876 },
michael@0 877 pop : function BreadcrumbTrail_pop() {
michael@0 878 if (this._breadcrumbs.length-2 >= 0)
michael@0 879 this._enter(this._breadcrumbs.length-2);
michael@0 880 },
michael@0 881 enterLastItem: function BreadcrumbTrail_enterLastItem(forceSelection) {
michael@0 882 this._enter(this._breadcrumbs.length-1, forceSelection);
michael@0 883 },
michael@0 884 _enter: function BreadcrumbTrail__select(index, forceSelection) {
michael@0 885 if (index == this._selectedBreadcrumbIndex)
michael@0 886 return;
michael@0 887 if (forceSelection) {
michael@0 888 gTreeManager.restoreSerializedSelectionSnapshot(forceSelection);
michael@0 889 } else {
michael@0 890 gTreeManager.saveSelectionSnapshot();
michael@0 891 }
michael@0 892 var prevSelected = this._breadcrumbs[this._selectedBreadcrumbIndex];
michael@0 893 if (prevSelected)
michael@0 894 prevSelected.classList.remove("selected");
michael@0 895 var li = this._breadcrumbs[index];
michael@0 896 if (this === gBreadcrumbTrail && index != 0) {
michael@0 897 // Support for back button, disabled until the forward button is implemented.
michael@0 898 //var state = {action: "popbreadcrumb",};
michael@0 899 //window.history.pushState(state, "Cleopatra");
michael@0 900 }
michael@0 901
michael@0 902 delete li.breadcrumbIsTransient;
michael@0 903 li.classList.add("selected");
michael@0 904 this._deleteBeyond(index);
michael@0 905 this._selectedBreadcrumbIndex = index;
michael@0 906 li.breadcrumbEnterCallback();
michael@0 907 // Add history state
michael@0 908 },
michael@0 909 _deleteBeyond: function BreadcrumbTrail__deleteBeyond(index) {
michael@0 910 while (this._breadcrumbs.length > index + 1) {
michael@0 911 this._hide(this._breadcrumbs[index + 1]);
michael@0 912 this._breadcrumbs.splice(index + 1, 1);
michael@0 913 }
michael@0 914 },
michael@0 915 _hide: function BreadcrumbTrail__hide(breadcrumb) {
michael@0 916 delete breadcrumb.breadcrumbIsTransient;
michael@0 917 breadcrumb.classList.add("deleted");
michael@0 918 setTimeout(function () {
michael@0 919 breadcrumb.parentNode.removeChild(breadcrumb);
michael@0 920 }, 1000);
michael@0 921 },
michael@0 922 };
michael@0 923
michael@0 924 function maxResponsiveness() {
michael@0 925 var data = gCurrentlyShownSampleData;
michael@0 926 var maxRes = 0.0;
michael@0 927 for (var i = 0; i < data.length; ++i) {
michael@0 928 if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"])
michael@0 929 continue;
michael@0 930 if (maxRes < data[i].extraInfo["responsiveness"])
michael@0 931 maxRes = data[i].extraInfo["responsiveness"];
michael@0 932 }
michael@0 933 return maxRes;
michael@0 934 }
michael@0 935
michael@0 936 function effectiveInterval() {
michael@0 937 var data = gCurrentlyShownSampleData;
michael@0 938 var interval = 0.0;
michael@0 939 var sampleCount = 0;
michael@0 940 var timeCount = 0;
michael@0 941 var lastTime = null;
michael@0 942 for (var i = 0; i < data.length; ++i) {
michael@0 943 if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) {
michael@0 944 lastTime = null;
michael@0 945 continue;
michael@0 946 }
michael@0 947 if (lastTime) {
michael@0 948 sampleCount++;
michael@0 949 timeCount += data[i].extraInfo["time"] - lastTime;
michael@0 950 }
michael@0 951 lastTime = data[i].extraInfo["time"];
michael@0 952 }
michael@0 953 var effectiveInterval = timeCount/sampleCount;
michael@0 954 // Biggest diff
michael@0 955 var biggestDiff = 0;
michael@0 956 lastTime = null;
michael@0 957 for (var i = 0; i < data.length; ++i) {
michael@0 958 if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) {
michael@0 959 lastTime = null;
michael@0 960 continue;
michael@0 961 }
michael@0 962 if (lastTime) {
michael@0 963 if (biggestDiff < Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime)))
michael@0 964 biggestDiff = Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime));
michael@0 965 }
michael@0 966 lastTime = data[i].extraInfo["time"];
michael@0 967 }
michael@0 968
michael@0 969 if (effectiveInterval != effectiveInterval)
michael@0 970 return "Time info not collected";
michael@0 971
michael@0 972 return (effectiveInterval).toFixed(2) + " ms ±" + biggestDiff.toFixed(2);
michael@0 973 }
michael@0 974
michael@0 975 function numberOfCurrentlyShownSamples() {
michael@0 976 var data = gCurrentlyShownSampleData;
michael@0 977 var num = 0;
michael@0 978 for (var i = 0; i < data.length; ++i) {
michael@0 979 if (data[i])
michael@0 980 num++;
michael@0 981 }
michael@0 982 return num;
michael@0 983 }
michael@0 984
michael@0 985 function avgResponsiveness() {
michael@0 986 var data = gCurrentlyShownSampleData;
michael@0 987 var totalRes = 0.0;
michael@0 988 for (var i = 0; i < data.length; ++i) {
michael@0 989 if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"])
michael@0 990 continue;
michael@0 991 totalRes += data[i].extraInfo["responsiveness"];
michael@0 992 }
michael@0 993 return totalRes / numberOfCurrentlyShownSamples();
michael@0 994 }
michael@0 995
michael@0 996 function copyProfile() {
michael@0 997 window.prompt ("Copy to clipboard: Ctrl+C, Enter", document.getElementById("data").value);
michael@0 998 }
michael@0 999
michael@0 1000 function saveProfileToLocalStorage() {
michael@0 1001 Parser.getSerializedProfile(true, function (serializedProfile) {
michael@0 1002 gLocalStorage.storeLocalProfile(serializedProfile, gMeta.profileId, function profileSaved() {
michael@0 1003
michael@0 1004 });
michael@0 1005 });
michael@0 1006 }
michael@0 1007 function downloadProfile() {
michael@0 1008 Parser.getSerializedProfile(true, function (serializedProfile) {
michael@0 1009 var blob = new Blob([serializedProfile], { "type": "application/octet-stream" });
michael@0 1010 location.href = window.URL.createObjectURL(blob);
michael@0 1011 });
michael@0 1012 }
michael@0 1013
michael@0 1014 function promptUploadProfile(selected) {
michael@0 1015 var overlay = document.createElement("div");
michael@0 1016 overlay.style.position = "absolute";
michael@0 1017 overlay.style.top = 0;
michael@0 1018 overlay.style.left = 0;
michael@0 1019 overlay.style.width = "100%";
michael@0 1020 overlay.style.height = "100%";
michael@0 1021 overlay.style.backgroundColor = "transparent";
michael@0 1022
michael@0 1023 var bg = document.createElement("div");
michael@0 1024 bg.style.position = "absolute";
michael@0 1025 bg.style.top = 0;
michael@0 1026 bg.style.left = 0;
michael@0 1027 bg.style.width = "100%";
michael@0 1028 bg.style.height = "100%";
michael@0 1029 bg.style.opacity = "0.6";
michael@0 1030 bg.style.backgroundColor = "#aaaaaa";
michael@0 1031 overlay.appendChild(bg);
michael@0 1032
michael@0 1033 var contentDiv = document.createElement("div");
michael@0 1034 contentDiv.className = "sideBar";
michael@0 1035 contentDiv.style.position = "absolute";
michael@0 1036 contentDiv.style.top = "50%";
michael@0 1037 contentDiv.style.left = "50%";
michael@0 1038 contentDiv.style.width = "40em";
michael@0 1039 contentDiv.style.height = "20em";
michael@0 1040 contentDiv.style.marginLeft = "-20em";
michael@0 1041 contentDiv.style.marginTop = "-10em";
michael@0 1042 contentDiv.style.padding = "10px";
michael@0 1043 contentDiv.style.border = "2px solid black";
michael@0 1044 contentDiv.style.backgroundColor = "rgb(219, 223, 231)";
michael@0 1045 overlay.appendChild(contentDiv);
michael@0 1046
michael@0 1047 var noticeHTML = "";
michael@0 1048 noticeHTML += "<center><h2 style='font-size: 2em'>Upload Profile - Privacy Notice</h2></center>";
michael@0 1049 noticeHTML += "You're about to upload your profile publicly where anyone will be able to access it. ";
michael@0 1050 noticeHTML += "To better diagnose performance problems profiles include the following information:";
michael@0 1051 noticeHTML += "<ul>";
michael@0 1052 noticeHTML += " <li>The <b>URLs</b> and scripts of the tabs that were executing.</li>";
michael@0 1053 noticeHTML += " <li>The <b>metadata of all your Add-ons</b> to identify slow Add-ons.</li>";
michael@0 1054 noticeHTML += " <li>Firefox build and runtime configuration.</li>";
michael@0 1055 noticeHTML += "</ul><br>";
michael@0 1056 noticeHTML += "To view all the information you can download the full profile to a file and open the json structure with a text editor.<br><br>";
michael@0 1057 contentDiv.innerHTML = noticeHTML;
michael@0 1058
michael@0 1059 var cancelButton = document.createElement("input");
michael@0 1060 cancelButton.style.position = "absolute";
michael@0 1061 cancelButton.style.bottom = "10px";
michael@0 1062 cancelButton.type = "button";
michael@0 1063 cancelButton.value = "Cancel";
michael@0 1064 cancelButton.onclick = function() {
michael@0 1065 document.body.removeChild(overlay);
michael@0 1066 }
michael@0 1067 contentDiv.appendChild(cancelButton);
michael@0 1068
michael@0 1069 var uploadButton = document.createElement("input");
michael@0 1070 uploadButton.style.position = "absolute";
michael@0 1071 uploadButton.style.right = "10px";
michael@0 1072 uploadButton.style.bottom = "10px";
michael@0 1073 uploadButton.type = "button";
michael@0 1074 uploadButton.value = "Upload";
michael@0 1075 uploadButton.onclick = function() {
michael@0 1076 document.body.removeChild(overlay);
michael@0 1077 uploadProfile(selected);
michael@0 1078 }
michael@0 1079 contentDiv.appendChild(uploadButton);
michael@0 1080
michael@0 1081 document.body.appendChild(overlay);
michael@0 1082 }
michael@0 1083
michael@0 1084 function uploadProfile(selected) {
michael@0 1085 Parser.getSerializedProfile(!selected, function (dataToUpload) {
michael@0 1086 var oXHR = new XMLHttpRequest();
michael@0 1087 oXHR.onload = function (oEvent) {
michael@0 1088 if (oXHR.status == 200) {
michael@0 1089 gReportID = oXHR.responseText;
michael@0 1090 updateDocumentURL();
michael@0 1091 document.getElementById("upload_status").innerHTML = "Success! Use this <a id='linkElem'>link</a>";
michael@0 1092 document.getElementById("linkElem").href = document.URL;
michael@0 1093 } else {
michael@0 1094 document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file.";
michael@0 1095 }
michael@0 1096 };
michael@0 1097 oXHR.onerror = function (oEvent) {
michael@0 1098 document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file.";
michael@0 1099 }
michael@0 1100 oXHR.upload.onprogress = function(oEvent) {
michael@0 1101 if (oEvent.lengthComputable) {
michael@0 1102 var progress = Math.round((oEvent.loaded / oEvent.total)*100);
michael@0 1103 if (progress == 100) {
michael@0 1104 document.getElementById("upload_status").innerHTML = "Uploading: Waiting for server side compression";
michael@0 1105 } else {
michael@0 1106 document.getElementById("upload_status").innerHTML = "Uploading: " + Math.round((oEvent.loaded / oEvent.total)*100) + "%";
michael@0 1107 }
michael@0 1108 }
michael@0 1109 };
michael@0 1110
michael@0 1111 var dataSize;
michael@0 1112 if (dataToUpload.length > 1024*1024) {
michael@0 1113 dataSize = (dataToUpload.length/1024/1024).toFixed(1) + " MB(s)";
michael@0 1114 } else {
michael@0 1115 dataSize = (dataToUpload.length/1024).toFixed(1) + " KB(s)";
michael@0 1116 }
michael@0 1117
michael@0 1118 var formData = new FormData();
michael@0 1119 formData.append("file", dataToUpload);
michael@0 1120 document.getElementById("upload_status").innerHTML = "Uploading Profile (" + dataSize + ")";
michael@0 1121 oXHR.open("POST", "http://profile-store.appspot.com/store", true);
michael@0 1122 oXHR.send(formData);
michael@0 1123 });
michael@0 1124 }
michael@0 1125
michael@0 1126 function populate_skip_symbol() {
michael@0 1127 var skipSymbolCtrl = document.getElementById('skipsymbol')
michael@0 1128 //skipSymbolCtrl.options = gSkipSymbols;
michael@0 1129 for (var i = 0; i < gSkipSymbols.length; i++) {
michael@0 1130 var elOptNew = document.createElement('option');
michael@0 1131 elOptNew.text = gSkipSymbols[i];
michael@0 1132 elOptNew.value = gSkipSymbols[i];
michael@0 1133 elSel.add(elOptNew);
michael@0 1134 }
michael@0 1135
michael@0 1136 }
michael@0 1137
michael@0 1138 function delete_skip_symbol() {
michael@0 1139 var skipSymbol = document.getElementById('skipsymbol').value
michael@0 1140 }
michael@0 1141
michael@0 1142 function add_skip_symbol() {
michael@0 1143
michael@0 1144 }
michael@0 1145
michael@0 1146 var gFilterChangeCallback = null;
michael@0 1147 var gFilterChangeDelay = 1200;
michael@0 1148 function filterOnChange() {
michael@0 1149 if (gFilterChangeCallback != null) {
michael@0 1150 clearTimeout(gFilterChangeCallback);
michael@0 1151 gFilterChangeCallback = null;
michael@0 1152 }
michael@0 1153
michael@0 1154 gFilterChangeCallback = setTimeout(filterUpdate, gFilterChangeDelay);
michael@0 1155 }
michael@0 1156 function filterUpdate() {
michael@0 1157 gFilterChangeCallback = null;
michael@0 1158
michael@0 1159 filtersChanged();
michael@0 1160
michael@0 1161 var filterNameInput = document.getElementById("filterName");
michael@0 1162 if (filterNameInput != null) {
michael@0 1163 changeFocus(filterNameInput);
michael@0 1164 }
michael@0 1165 }
michael@0 1166
michael@0 1167 // Maps document id to a tooltip description
michael@0 1168 var tooltip = {
michael@0 1169 "mergeFunctions" : "Ignore line information and merge samples based on function names.",
michael@0 1170 "showJank" : "Show only samples with >50ms responsiveness.",
michael@0 1171 "showJS" : "Show only samples which involve running chrome or content Javascript code.",
michael@0 1172 "mergeUnbranched" : "Collapse unbranched call paths in the call tree into a single node.",
michael@0 1173 "filterName" : "Show only samples with a frame containing the filter as a substring.",
michael@0 1174 "invertCallstack" : "Invert the callstack (Heavy view) to find the most expensive leaf functions.",
michael@0 1175 "upload" : "Upload the full profile to public cloud storage to share with others.",
michael@0 1176 "upload_select" : "Upload only the selected view.",
michael@0 1177 "download" : "Initiate a download of the full profile.",
michael@0 1178 }
michael@0 1179
michael@0 1180 function addTooltips() {
michael@0 1181 for (var elemId in tooltip) {
michael@0 1182 var elem = document.getElementById(elemId);
michael@0 1183 if (!elem)
michael@0 1184 continue;
michael@0 1185 if (elem.parentNode.nodeName.toLowerCase() == "label")
michael@0 1186 elem = elem.parentNode;
michael@0 1187 elem.title = tooltip[elemId];
michael@0 1188 }
michael@0 1189 }
michael@0 1190
michael@0 1191 function InfoBar() {
michael@0 1192 this._container = document.createElement("div");
michael@0 1193 this._container.id = "infoBar";
michael@0 1194 this._container.className = "sideBar";
michael@0 1195 }
michael@0 1196
michael@0 1197 InfoBar.prototype = {
michael@0 1198 getContainer: function InfoBar_getContainer() {
michael@0 1199 return this._container;
michael@0 1200 },
michael@0 1201 display: function InfoBar_display() {
michael@0 1202 function getMetaFeatureString() {
michael@0 1203 features = "<dt>Stackwalk:</dt><dd>" + (gMeta.stackwalk ? "True" : "False") + "</dd>";
michael@0 1204 features += "<dt>Jank:</dt><dd>" + (gMeta.stackwalk ? "True" : "False") + "</dd>";
michael@0 1205 return features;
michael@0 1206 }
michael@0 1207 function getPlatformInfo() {
michael@0 1208 return gMeta.oscpu + " (" + gMeta.toolkit + ")";
michael@0 1209 }
michael@0 1210 var infobar = this._container;
michael@0 1211 var infoText = "";
michael@0 1212
michael@0 1213 if (gMeta) {
michael@0 1214 infoText += "<h2>Profile Info</h2>\n<dl>\n";
michael@0 1215 infoText += "<dt>Product:</dt><dd>" + gMeta.product + "</dd>";
michael@0 1216 infoText += "<dt>Platform:</dt><dd>" + getPlatformInfo() + "</dd>";
michael@0 1217 infoText += getMetaFeatureString();
michael@0 1218 infoText += "<dt>Interval:</dt><dd>" + gMeta.interval + " ms</dd></dl>";
michael@0 1219 }
michael@0 1220 infoText += "<h2>Selection Info</h2>\n<dl>\n";
michael@0 1221 infoText += " <dt>Avg. Responsiveness:</dt><dd>" + avgResponsiveness().toFixed(2) + " ms</dd>\n";
michael@0 1222 infoText += " <dt>Max Responsiveness:</dt><dd>" + maxResponsiveness().toFixed(2) + " ms</dd>\n";
michael@0 1223 infoText += " <dt>Real Interval:</dt><dd>" + effectiveInterval() + "</dd>";
michael@0 1224 infoText += "</dl>\n";
michael@0 1225 infoText += "<h2>Pre Filtering</h2>\n";
michael@0 1226 // Disable for now since it's buggy and not useful
michael@0 1227 //infoText += "<label><input type='checkbox' id='mergeFunctions' " + (gMergeFunctions ?" checked='true' ":" ") + " onchange='toggleMergeFunctions()'/>Functions, not lines</label><br>\n";
michael@0 1228
michael@0 1229 var filterNameInputOld = document.getElementById("filterName");
michael@0 1230 infoText += "<a>Filter:\n";
michael@0 1231 infoText += "<input type='search' id='filterName' oninput='filterOnChange()'/></a>\n";
michael@0 1232
michael@0 1233 infoText += "<h2>Post Filtering</h2>\n";
michael@0 1234 infoText += "<label><input type='checkbox' id='showJank' " + (gJankOnly ?" checked='true' ":" ") + " onchange='toggleJank()'/>Show Jank only</label>\n";
michael@0 1235 infoText += "<h2>View Options</h2>\n";
michael@0 1236 infoText += "<label><input type='checkbox' id='showJS' " + (gJavascriptOnly ?" checked='true' ":" ") + " onchange='toggleJavascriptOnly()'/>Javascript only</label><br>\n";
michael@0 1237 infoText += "<label><input type='checkbox' id='mergeUnbranched' " + (gMergeUnbranched ?" checked='true' ":" ") + " onchange='toggleMergeUnbranched()'/>Merge unbranched call paths</label><br>\n";
michael@0 1238 infoText += "<label><input type='checkbox' id='invertCallstack' " + (gInvertCallstack ?" checked='true' ":" ") + " onchange='toggleInvertCallStack()'/>Invert callstack</label><br>\n";
michael@0 1239
michael@0 1240 infoText += "<h2>Share</h2>\n";
michael@0 1241 infoText += "<div id='upload_status' aria-live='polite'>No upload in progress</div><br>\n";
michael@0 1242 infoText += "<input type='button' id='upload' value='Upload full profile'>\n";
michael@0 1243 infoText += "<input type='button' id='upload_select' value='Upload view'><br>\n";
michael@0 1244 infoText += "<input type='button' id='download' value='Download full profile'>\n";
michael@0 1245
michael@0 1246 infoText += "<h2>Compare</h2>\n";
michael@0 1247 infoText += "<input type='button' id='compare' value='Compare'>\n";
michael@0 1248
michael@0 1249 //infoText += "<br>\n";
michael@0 1250 //infoText += "Skip functions:<br>\n";
michael@0 1251 //infoText += "<select size=8 id='skipsymbol'></select><br />"
michael@0 1252 //infoText += "<input type='button' id='delete_skipsymbol' value='Delete'/><br />\n";
michael@0 1253 //infoText += "<input type='button' id='add_skipsymbol' value='Add'/><br />\n";
michael@0 1254
michael@0 1255 infobar.innerHTML = infoText;
michael@0 1256 addTooltips();
michael@0 1257
michael@0 1258 var filterNameInputNew = document.getElementById("filterName");
michael@0 1259 if (filterNameInputOld != null && filterNameInputNew != null) {
michael@0 1260 filterNameInputNew.parentNode.replaceChild(filterNameInputOld, filterNameInputNew);
michael@0 1261 //filterNameInputNew.value = filterNameInputOld.value;
michael@0 1262 } else if (gQueryParamFilterName != null) {
michael@0 1263 filterNameInputNew.value = gQueryParamFilterName;
michael@0 1264 gQueryParamFilterName = null;
michael@0 1265 }
michael@0 1266 document.getElementById('compare').onclick = function() {
michael@0 1267 openProfileCompare();
michael@0 1268 }
michael@0 1269 document.getElementById('upload').onclick = function() {
michael@0 1270 promptUploadProfile(false);
michael@0 1271 };
michael@0 1272 document.getElementById('download').onclick = downloadProfile;
michael@0 1273 document.getElementById('upload_select').onclick = function() {
michael@0 1274 promptUploadProfile(true);
michael@0 1275 };
michael@0 1276 //document.getElementById('delete_skipsymbol').onclick = delete_skip_symbol;
michael@0 1277 //document.getElementById('add_skipsymbol').onclick = add_skip_symbol;
michael@0 1278
michael@0 1279 //populate_skip_symbol();
michael@0 1280 }
michael@0 1281 }
michael@0 1282
michael@0 1283 var gNumSamples = 0;
michael@0 1284 var gMeta = null;
michael@0 1285 var gSymbols = {};
michael@0 1286 var gFunctions = {};
michael@0 1287 var gResources = {};
michael@0 1288 var gHighlightedCallstack = [];
michael@0 1289 var gFrameView = null;
michael@0 1290 var gTreeManager = null;
michael@0 1291 var gSampleBar = null;
michael@0 1292 var gBreadcrumbTrail = null;
michael@0 1293 var gHistogramView = null;
michael@0 1294 var gDiagnosticBar = null;
michael@0 1295 var gVideoPane = null;
michael@0 1296 var gPluginView = null;
michael@0 1297 var gFileList = null;
michael@0 1298 var gInfoBar = null;
michael@0 1299 var gMainArea = null;
michael@0 1300 var gCurrentlyShownSampleData = null;
michael@0 1301 var gSkipSymbols = ["test2", "test1"];
michael@0 1302 var gAppendVideoCapture = null;
michael@0 1303 var gQueryParamFilterName = null;
michael@0 1304 var gRestoreSelection = null;
michael@0 1305 var gReportID = null;
michael@0 1306
michael@0 1307 function getTextData() {
michael@0 1308 var data = [];
michael@0 1309 var samples = gCurrentlyShownSampleData;
michael@0 1310 for (var i = 0; i < samples.length; i++) {
michael@0 1311 data.push(samples[i].lines.join("\n"));
michael@0 1312 }
michael@0 1313 return data.join("\n");
michael@0 1314 }
michael@0 1315
michael@0 1316 function loadProfileFile(fileList) {
michael@0 1317 if (fileList.length == 0)
michael@0 1318 return;
michael@0 1319 var file = fileList[0];
michael@0 1320 var reporter = enterProgressUI();
michael@0 1321 var subreporters = reporter.addSubreporters({
michael@0 1322 fileLoading: 1000,
michael@0 1323 parsing: 1000
michael@0 1324 });
michael@0 1325
michael@0 1326 var reader = new FileReader();
michael@0 1327 reader.onloadend = function () {
michael@0 1328 subreporters.fileLoading.finish();
michael@0 1329 loadRawProfile(subreporters.parsing, reader.result);
michael@0 1330 };
michael@0 1331 reader.onprogress = function (e) {
michael@0 1332 subreporters.fileLoading.setProgress(e.loaded / e.total);
michael@0 1333 };
michael@0 1334 reader.readAsText(file, "utf-8");
michael@0 1335 subreporters.fileLoading.begin("Reading local file...");
michael@0 1336 }
michael@0 1337
michael@0 1338 function loadLocalStorageProfile(profileKey) {
michael@0 1339 var reporter = enterProgressUI();
michael@0 1340 var subreporters = reporter.addSubreporters({
michael@0 1341 fileLoading: 1000,
michael@0 1342 parsing: 1000
michael@0 1343 });
michael@0 1344
michael@0 1345 gLocalStorage.getProfile(profileKey, function(profile) {
michael@0 1346 subreporters.fileLoading.finish();
michael@0 1347 loadRawProfile(subreporters.parsing, profile, profileKey);
michael@0 1348 });
michael@0 1349 subreporters.fileLoading.begin("Reading local storage...");
michael@0 1350 }
michael@0 1351
michael@0 1352 function appendVideoCapture(videoCapture) {
michael@0 1353 if (videoCapture.indexOf("://") == -1) {
michael@0 1354 videoCapture = EIDETICKER_BASE_URL + videoCapture;
michael@0 1355 }
michael@0 1356 gAppendVideoCapture = videoCapture;
michael@0 1357 }
michael@0 1358
michael@0 1359 function loadZippedProfileURL(url) {
michael@0 1360 var reporter = enterProgressUI();
michael@0 1361 var subreporters = reporter.addSubreporters({
michael@0 1362 fileLoading: 1000,
michael@0 1363 parsing: 1000
michael@0 1364 });
michael@0 1365
michael@0 1366 // Crude way to detect if we're using a relative URL or not :(
michael@0 1367 if (url.indexOf("://") == -1) {
michael@0 1368 url = EIDETICKER_BASE_URL + url;
michael@0 1369 }
michael@0 1370 reporter.begin("Fetching " + url);
michael@0 1371 PROFILERTRACE("Fetch url: " + url);
michael@0 1372
michael@0 1373 function onerror(e) {
michael@0 1374 PROFILERERROR("zip.js error");
michael@0 1375 PROFILERERROR(JSON.stringify(e));
michael@0 1376 }
michael@0 1377
michael@0 1378 zip.workerScriptsPath = "js/zip.js/";
michael@0 1379 zip.createReader(new zip.HttpReader(url), function(zipReader) {
michael@0 1380 subreporters.fileLoading.setProgress(0.4);
michael@0 1381 zipReader.getEntries(function(entries) {
michael@0 1382 for (var i = 0; i < entries.length; i++) {
michael@0 1383 var entry = entries[i];
michael@0 1384 PROFILERTRACE("Zip file: " + entry.filename);
michael@0 1385 if (entry.filename === "symbolicated_profile.txt") {
michael@0 1386 reporter.begin("Decompressing " + url);
michael@0 1387 subreporters.fileLoading.setProgress(0.8);
michael@0 1388 entry.getData(new zip.TextWriter(), function(profileText) {
michael@0 1389 subreporters.fileLoading.finish();
michael@0 1390 loadRawProfile(subreporters.parsing, profileText);
michael@0 1391 });
michael@0 1392 return;
michael@0 1393 }
michael@0 1394 onerror("symbolicated_profile.txt not found in zip file.");
michael@0 1395 }
michael@0 1396 });
michael@0 1397 }, onerror);
michael@0 1398 }
michael@0 1399
michael@0 1400 function loadProfileURL(url) {
michael@0 1401 var reporter = enterProgressUI();
michael@0 1402 var subreporters = reporter.addSubreporters({
michael@0 1403 fileLoading: 1000,
michael@0 1404 parsing: 1000
michael@0 1405 });
michael@0 1406
michael@0 1407 var xhr = new XMLHttpRequest();
michael@0 1408 xhr.open("GET", url, true);
michael@0 1409 xhr.responseType = "text";
michael@0 1410 xhr.onreadystatechange = function (e) {
michael@0 1411 if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) {
michael@0 1412 subreporters.fileLoading.finish();
michael@0 1413 PROFILERLOG("Got profile from '" + url + "'.");
michael@0 1414 if (xhr.responseText == null || xhr.responseText === "") {
michael@0 1415 subreporters.fileLoading.begin("Profile '" + url + "' is empty. Did you set the CORS headers?");
michael@0 1416 return;
michael@0 1417 }
michael@0 1418 loadRawProfile(subreporters.parsing, xhr.responseText, url);
michael@0 1419 }
michael@0 1420 };
michael@0 1421 xhr.onerror = function (e) {
michael@0 1422 subreporters.fileLoading.begin("Error fetching profile :(. URL: '" + url + "'. Did you set the CORS headers?");
michael@0 1423 }
michael@0 1424 xhr.onprogress = function (e) {
michael@0 1425 if (e.lengthComputable && (e.loaded <= e.total)) {
michael@0 1426 subreporters.fileLoading.setProgress(e.loaded / e.total);
michael@0 1427 } else {
michael@0 1428 subreporters.fileLoading.setProgress(NaN);
michael@0 1429 }
michael@0 1430 };
michael@0 1431 xhr.send(null);
michael@0 1432 subreporters.fileLoading.begin("Loading remote file...");
michael@0 1433 }
michael@0 1434
michael@0 1435 function loadProfile(rawProfile) {
michael@0 1436 if (!rawProfile)
michael@0 1437 return;
michael@0 1438 var reporter = enterProgressUI();
michael@0 1439 loadRawProfile(reporter, rawProfile);
michael@0 1440 }
michael@0 1441
michael@0 1442 function loadRawProfile(reporter, rawProfile, profileId) {
michael@0 1443 PROFILERLOG("Parse raw profile: ~" + rawProfile.length + " bytes");
michael@0 1444 reporter.begin("Parsing...");
michael@0 1445 if (rawProfile == null || rawProfile.length === 0) {
michael@0 1446 reporter.begin("Profile is null or empty");
michael@0 1447 return;
michael@0 1448 }
michael@0 1449 var startTime = Date.now();
michael@0 1450 var parseRequest = Parser.parse(rawProfile, {
michael@0 1451 appendVideoCapture : gAppendVideoCapture,
michael@0 1452 profileId: profileId,
michael@0 1453 });
michael@0 1454 parseRequest.addEventListener("progress", function (progress, action) {
michael@0 1455 if (action)
michael@0 1456 reporter.setAction(action);
michael@0 1457 reporter.setProgress(progress);
michael@0 1458 });
michael@0 1459 parseRequest.addEventListener("finished", function (result) {
michael@0 1460 reporter.finish();
michael@0 1461 gMeta = result.meta;
michael@0 1462 gNumSamples = result.numSamples;
michael@0 1463 gSymbols = result.symbols;
michael@0 1464 gFunctions = result.functions;
michael@0 1465 gResources = result.resources;
michael@0 1466 enterFinishedProfileUI();
michael@0 1467 gFileList.profileParsingFinished();
michael@0 1468 });
michael@0 1469 }
michael@0 1470
michael@0 1471 var gImportFromAddonSubreporters = null;
michael@0 1472
michael@0 1473 window.addEventListener("message", function messageFromAddon(msg) {
michael@0 1474 // This is triggered by the profiler add-on.
michael@0 1475 var o = JSON.parse(msg.data);
michael@0 1476 switch (o.task) {
michael@0 1477 case "importFromAddonStart":
michael@0 1478 var totalReporter = enterProgressUI();
michael@0 1479 gImportFromAddonSubreporters = totalReporter.addSubreporters({
michael@0 1480 import: 10000,
michael@0 1481 parsing: 1000
michael@0 1482 });
michael@0 1483 gImportFromAddonSubreporters.import.begin("Symbolicating...");
michael@0 1484 break;
michael@0 1485 case "importFromAddonProgress":
michael@0 1486 gImportFromAddonSubreporters.import.setProgress(o.progress);
michael@0 1487 if (o.action != null) {
michael@0 1488 gImportFromAddonSubreporters.import.setAction(o.action);
michael@0 1489 }
michael@0 1490 break;
michael@0 1491 case "importFromAddonFinish":
michael@0 1492 importFromAddonFinish(o.rawProfile);
michael@0 1493 break;
michael@0 1494 }
michael@0 1495 });
michael@0 1496
michael@0 1497 function importFromAddonFinish(rawProfile) {
michael@0 1498 gImportFromAddonSubreporters.import.finish();
michael@0 1499 loadRawProfile(gImportFromAddonSubreporters.parsing, rawProfile);
michael@0 1500 }
michael@0 1501
michael@0 1502 var gInvertCallstack = false;
michael@0 1503 function toggleInvertCallStack() {
michael@0 1504 gTreeManager.saveReverseSelectionSnapshot(gJavascriptOnly);
michael@0 1505 gInvertCallstack = !gInvertCallstack;
michael@0 1506 var startTime = Date.now();
michael@0 1507 viewOptionsChanged();
michael@0 1508 }
michael@0 1509
michael@0 1510 var gMergeUnbranched = false;
michael@0 1511 function toggleMergeUnbranched() {
michael@0 1512 gMergeUnbranched = !gMergeUnbranched;
michael@0 1513 viewOptionsChanged();
michael@0 1514 }
michael@0 1515
michael@0 1516 var gMergeFunctions = true;
michael@0 1517 function toggleMergeFunctions() {
michael@0 1518 gMergeFunctions = !gMergeFunctions;
michael@0 1519 filtersChanged();
michael@0 1520 }
michael@0 1521
michael@0 1522 var gJankOnly = false;
michael@0 1523 var gJankThreshold = 50 /* ms */;
michael@0 1524 function toggleJank(/* optional */ threshold) {
michael@0 1525 // Currently we have no way to change the threshold in the UI
michael@0 1526 // once we add this we will need to change the tooltip.
michael@0 1527 gJankOnly = !gJankOnly;
michael@0 1528 if (threshold != null ) {
michael@0 1529 gJankThreshold = threshold;
michael@0 1530 }
michael@0 1531 filtersChanged();
michael@0 1532 }
michael@0 1533
michael@0 1534 var gJavascriptOnly = false;
michael@0 1535 function toggleJavascriptOnly() {
michael@0 1536 if (gJavascriptOnly) {
michael@0 1537 // When going from JS only to non js there's going to be new C++
michael@0 1538 // frames in the selection so we need to restore the selection
michael@0 1539 // while allowing non contigous symbols to be in the stack (the c++ ones)
michael@0 1540 gTreeManager.setAllowNonContiguous();
michael@0 1541 }
michael@0 1542 gJavascriptOnly = !gJavascriptOnly;
michael@0 1543 gTreeManager.saveSelectionSnapshot(gJavascriptOnly);
michael@0 1544 filtersChanged();
michael@0 1545 }
michael@0 1546
michael@0 1547 var gSampleFilters = [];
michael@0 1548 function focusOnSymbol(focusSymbol, name) {
michael@0 1549 var newFilterChain = gSampleFilters.concat([{type: "FocusedFrameSampleFilter", name: name, focusedSymbol: focusSymbol}]);
michael@0 1550 gBreadcrumbTrail.addAndEnter({
michael@0 1551 title: name,
michael@0 1552 enterCallback: function () {
michael@0 1553 gSampleFilters = newFilterChain;
michael@0 1554 filtersChanged();
michael@0 1555 }
michael@0 1556 });
michael@0 1557 }
michael@0 1558
michael@0 1559 function focusOnCallstack(focusedCallstack, name, overwriteCallstack) {
michael@0 1560 var invertCallstack = gInvertCallstack;
michael@0 1561
michael@0 1562 if (overwriteCallstack != null) {
michael@0 1563 invertCallstack = overwriteCallstack;
michael@0 1564 }
michael@0 1565 var filter = {
michael@0 1566 type: !invertCallstack ? "FocusedCallstackPostfixSampleFilter" : "FocusedCallstackPrefixSampleFilter",
michael@0 1567 name: name,
michael@0 1568 focusedCallstack: focusedCallstack,
michael@0 1569 appliesToJS: gJavascriptOnly
michael@0 1570 };
michael@0 1571 var newFilterChain = gSampleFilters.concat([filter]);
michael@0 1572 gBreadcrumbTrail.addAndEnter({
michael@0 1573 title: name,
michael@0 1574 enterCallback: function () {
michael@0 1575 gSampleFilters = newFilterChain;
michael@0 1576 filtersChanged();
michael@0 1577 }
michael@0 1578 })
michael@0 1579 }
michael@0 1580
michael@0 1581 function focusOnPluginView(pluginName, param) {
michael@0 1582 var filter = {
michael@0 1583 type: "PluginView",
michael@0 1584 pluginName: pluginName,
michael@0 1585 param: param,
michael@0 1586 };
michael@0 1587 var newFilterChain = gSampleFilters.concat([filter]);
michael@0 1588 gBreadcrumbTrail.addAndEnter({
michael@0 1589 title: "Plugin View: " + pluginName,
michael@0 1590 enterCallback: function () {
michael@0 1591 gSampleFilters = newFilterChain;
michael@0 1592 filtersChanged();
michael@0 1593 }
michael@0 1594 })
michael@0 1595 }
michael@0 1596
michael@0 1597 function viewJSSource(sample) {
michael@0 1598 var sourceView = new SourceView();
michael@0 1599 sourceView.setScriptLocation(sample.scriptLocation);
michael@0 1600 sourceView.setSource(gMeta.js.source[sample.scriptLocation.scriptURI]);
michael@0 1601 gMainArea.appendChild(sourceView.getContainer());
michael@0 1602
michael@0 1603 }
michael@0 1604
michael@0 1605 function setHighlightedCallstack(samples, heaviestSample) {
michael@0 1606 PROFILERTRACE("highlight: " + samples);
michael@0 1607 gHighlightedCallstack = samples;
michael@0 1608 gHistogramView.highlightedCallstackChanged(gHighlightedCallstack);
michael@0 1609 if (!gInvertCallstack) {
michael@0 1610 // Always show heavy
michael@0 1611 heaviestSample = heaviestSample.clone().reverse();
michael@0 1612 }
michael@0 1613
michael@0 1614 if (gSampleBar) {
michael@0 1615 gSampleBar.setSample(heaviestSample);
michael@0 1616 }
michael@0 1617 }
michael@0 1618
michael@0 1619 function enterMainUI() {
michael@0 1620 var uiContainer = document.createElement("div");
michael@0 1621 uiContainer.id = "ui";
michael@0 1622
michael@0 1623 gFileList = new FileList();
michael@0 1624 uiContainer.appendChild(gFileList.getContainer());
michael@0 1625
michael@0 1626 //gFileList.addFile();
michael@0 1627 gFileList.loadProfileListFromLocalStorage();
michael@0 1628
michael@0 1629 gInfoBar = new InfoBar();
michael@0 1630 uiContainer.appendChild(gInfoBar.getContainer());
michael@0 1631
michael@0 1632 gMainArea = document.createElement("div");
michael@0 1633 gMainArea.id = "mainarea";
michael@0 1634 uiContainer.appendChild(gMainArea);
michael@0 1635 document.body.appendChild(uiContainer);
michael@0 1636
michael@0 1637 var profileEntryPane = document.createElement("div");
michael@0 1638 profileEntryPane.className = "profileEntryPane";
michael@0 1639 profileEntryPane.innerHTML = '' +
michael@0 1640 '<h1>Upload your profile here:</h1>' +
michael@0 1641 '<input type="file" id="datafile" onchange="loadProfileFile(this.files);">' +
michael@0 1642 '<h1>Or, alternatively, enter your profile data here:</h1>' +
michael@0 1643 '<textarea rows=20 cols=80 id=data autofocus spellcheck=false></textarea>' +
michael@0 1644 '<p><button onclick="loadProfile(document.getElementById(\'data\').value);">Parse</button></p>' +
michael@0 1645 '';
michael@0 1646
michael@0 1647 gMainArea.appendChild(profileEntryPane);
michael@0 1648 }
michael@0 1649
michael@0 1650 function enterProgressUI() {
michael@0 1651 var profileProgressPane = document.createElement("div");
michael@0 1652 profileProgressPane.className = "profileProgressPane";
michael@0 1653
michael@0 1654 var progressLabel = document.createElement("a");
michael@0 1655 profileProgressPane.appendChild(progressLabel);
michael@0 1656
michael@0 1657 var progressBar = document.createElement("progress");
michael@0 1658 profileProgressPane.appendChild(progressBar);
michael@0 1659
michael@0 1660 var totalProgressReporter = new ProgressReporter();
michael@0 1661 totalProgressReporter.addListener(function (r) {
michael@0 1662 var progress = r.getProgress();
michael@0 1663 progressLabel.innerHTML = r.getAction();
michael@0 1664 if (isNaN(progress))
michael@0 1665 progressBar.removeAttribute("value");
michael@0 1666 else
michael@0 1667 progressBar.value = progress;
michael@0 1668 });
michael@0 1669
michael@0 1670 gMainArea.appendChild(profileProgressPane);
michael@0 1671
michael@0 1672 Parser.updateLogSetting();
michael@0 1673
michael@0 1674 return totalProgressReporter;
michael@0 1675 }
michael@0 1676
michael@0 1677 function enterFinishedProfileUI() {
michael@0 1678 saveProfileToLocalStorage();
michael@0 1679
michael@0 1680 var finishedProfilePaneBackgroundCover = document.createElement("div");
michael@0 1681 finishedProfilePaneBackgroundCover.className = "finishedProfilePaneBackgroundCover";
michael@0 1682
michael@0 1683 var finishedProfilePane = document.createElement("table");
michael@0 1684 var rowIndex = 0;
michael@0 1685 var currRow;
michael@0 1686 finishedProfilePane.style.width = "100%";
michael@0 1687 finishedProfilePane.style.height = "100%";
michael@0 1688 finishedProfilePane.border = "0";
michael@0 1689 finishedProfilePane.cellPadding = "0";
michael@0 1690 finishedProfilePane.cellSpacing = "0";
michael@0 1691 finishedProfilePane.borderCollapse = "collapse";
michael@0 1692 finishedProfilePane.className = "finishedProfilePane";
michael@0 1693 setTimeout(function() {
michael@0 1694 // Work around a webkit bug. For some reason the table doesn't show up
michael@0 1695 // until some actions happen such as focusing this box
michael@0 1696 var filterNameInput = document.getElementById("filterName");
michael@0 1697 if (filterNameInput != null) {
michael@0 1698 changeFocus(filterNameInput);
michael@0 1699 }
michael@0 1700 }, 100);
michael@0 1701
michael@0 1702 gBreadcrumbTrail = new BreadcrumbTrail();
michael@0 1703 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1704 currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer());
michael@0 1705
michael@0 1706 gHistogramView = new HistogramView();
michael@0 1707 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1708 currRow.insertCell(0).appendChild(gHistogramView.getContainer());
michael@0 1709
michael@0 1710 if (false && gLocation.indexOf("file:") == 0) {
michael@0 1711 // Local testing for frameView
michael@0 1712 gFrameView = new FrameView();
michael@0 1713 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1714 currRow.insertCell(0).appendChild(gFrameView.getContainer());
michael@0 1715 }
michael@0 1716
michael@0 1717 gDiagnosticBar = new DiagnosticBar();
michael@0 1718 gDiagnosticBar.setDetailsListener(function(details) {
michael@0 1719 if (details.indexOf("bug ") == 0) {
michael@0 1720 window.open('https://bugzilla.mozilla.org/show_bug.cgi?id=' + details.substring(4));
michael@0 1721 } else {
michael@0 1722 var sourceView = new SourceView();
michael@0 1723 sourceView.setText("Diagnostic", js_beautify(details));
michael@0 1724 gMainArea.appendChild(sourceView.getContainer());
michael@0 1725 }
michael@0 1726 });
michael@0 1727 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1728 currRow.insertCell(0).appendChild(gDiagnosticBar.getContainer());
michael@0 1729
michael@0 1730 // For testing:
michael@0 1731 //gMeta.videoCapture = {
michael@0 1732 // src: "http://videos-cdn.mozilla.net/brand/Mozilla_Firefox_Manifesto_v0.2_640.webm",
michael@0 1733 //};
michael@0 1734
michael@0 1735 if (gMeta && gMeta.videoCapture) {
michael@0 1736 gVideoPane = new VideoPane(gMeta.videoCapture);
michael@0 1737 gVideoPane.onTimeChange(videoPaneTimeChange);
michael@0 1738 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1739 currRow.insertCell(0).appendChild(gVideoPane.getContainer());
michael@0 1740 }
michael@0 1741
michael@0 1742 var treeContainerDiv = document.createElement("div");
michael@0 1743 treeContainerDiv.className = "treeContainer";
michael@0 1744 treeContainerDiv.style.width = "100%";
michael@0 1745 treeContainerDiv.style.height = "100%";
michael@0 1746
michael@0 1747 gTreeManager = new ProfileTreeManager();
michael@0 1748 currRow = finishedProfilePane.insertRow(rowIndex++);
michael@0 1749 currRow.style.height = "100%";
michael@0 1750 var cell = currRow.insertCell(0);
michael@0 1751 cell.appendChild(treeContainerDiv);
michael@0 1752 treeContainerDiv.appendChild(gTreeManager.getContainer());
michael@0 1753
michael@0 1754 gSampleBar = new SampleBar();
michael@0 1755 treeContainerDiv.appendChild(gSampleBar.getContainer());
michael@0 1756
michael@0 1757 // sampleBar
michael@0 1758
michael@0 1759 gPluginView = new PluginView();
michael@0 1760 //currRow = finishedProfilePane.insertRow(4);
michael@0 1761 treeContainerDiv.appendChild(gPluginView.getContainer());
michael@0 1762
michael@0 1763 gMainArea.appendChild(finishedProfilePaneBackgroundCover);
michael@0 1764 gMainArea.appendChild(finishedProfilePane);
michael@0 1765
michael@0 1766 var currentBreadcrumb = gSampleFilters;
michael@0 1767 gBreadcrumbTrail.add({
michael@0 1768 title: gStrings["Complete Profile"],
michael@0 1769 enterCallback: function () {
michael@0 1770 gSampleFilters = [];
michael@0 1771 filtersChanged();
michael@0 1772 }
michael@0 1773 });
michael@0 1774 if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
michael@0 1775 gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
michael@0 1776 viewOptionsChanged();
michael@0 1777 }
michael@0 1778 for (var i = 0; i < currentBreadcrumb.length; i++) {
michael@0 1779 var filter = currentBreadcrumb[i];
michael@0 1780 var forceSelection = null;
michael@0 1781 if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) {
michael@0 1782 forceSelection = gRestoreSelection;
michael@0 1783 }
michael@0 1784 switch (filter.type) {
michael@0 1785 case "FocusedFrameSampleFilter":
michael@0 1786 focusOnSymbol(filter.name, filter.symbolName);
michael@0 1787 gBreadcrumbTrail.enterLastItem(forceSelection);
michael@0 1788 case "FocusedCallstackPrefixSampleFilter":
michael@0 1789 focusOnCallstack(filter.focusedCallstack, filter.name, false);
michael@0 1790 gBreadcrumbTrail.enterLastItem(forceSelection);
michael@0 1791 case "FocusedCallstackPostfixSampleFilter":
michael@0 1792 focusOnCallstack(filter.focusedCallstack, filter.name, true);
michael@0 1793 gBreadcrumbTrail.enterLastItem(forceSelection);
michael@0 1794 case "RangeSampleFilter":
michael@0 1795 gHistogramView.selectRange(filter.start, filter.end);
michael@0 1796 gBreadcrumbTrail.enterLastItem(forceSelection);
michael@0 1797 }
michael@0 1798 }
michael@0 1799 }
michael@0 1800
michael@0 1801 // Make all focus change events go through this function.
michael@0 1802 // This function will mediate the focus changes in case
michael@0 1803 // that we're in a compare view. In a compare view an inactive
michael@0 1804 // instance of cleopatra should not steal focus from the active
michael@0 1805 // cleopatra instance.
michael@0 1806 function changeFocus(elem) {
michael@0 1807 if (window.comparator_changeFocus) {
michael@0 1808 window.comparator_changeFocus(elem);
michael@0 1809 } else {
michael@0 1810 PROFILERLOG("FOCUS\n\n\n\n\n\n\n\n\n");
michael@0 1811 elem.focus();
michael@0 1812 }
michael@0 1813 }
michael@0 1814
michael@0 1815 function comparator_receiveSelection(snapshot, frameData) {
michael@0 1816 gTreeManager.restoreSerializedSelectionSnapshot(snapshot);
michael@0 1817 if (frameData)
michael@0 1818 gTreeManager.highlightFrame(frameData);
michael@0 1819 viewOptionsChanged();
michael@0 1820 }
michael@0 1821
michael@0 1822 function filtersChanged() {
michael@0 1823 if (window.comparator_setSelection) {
michael@0 1824 // window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), null);
michael@0 1825 }
michael@0 1826 updateDocumentURL();
michael@0 1827 var data = { symbols: {}, functions: {}, samples: [] };
michael@0 1828
michael@0 1829 gHistogramView.dataIsOutdated();
michael@0 1830 var filterNameInput = document.getElementById("filterName");
michael@0 1831 var updateRequest = Parser.updateFilters({
michael@0 1832 mergeFunctions: gMergeFunctions,
michael@0 1833 nameFilter: (filterNameInput && filterNameInput.value) || gQueryParamFilterName || "",
michael@0 1834 sampleFilters: gSampleFilters,
michael@0 1835 jankOnly: gJankOnly,
michael@0 1836 javascriptOnly: gJavascriptOnly
michael@0 1837 });
michael@0 1838 var start = Date.now();
michael@0 1839 updateRequest.addEventListener("finished", function (filteredSamples) {
michael@0 1840 gCurrentlyShownSampleData = filteredSamples;
michael@0 1841 gInfoBar.display();
michael@0 1842
michael@0 1843 if (gSampleFilters.length > 0 && gSampleFilters[gSampleFilters.length-1].type === "PluginView") {
michael@0 1844 start = Date.now();
michael@0 1845 gPluginView.display(gSampleFilters[gSampleFilters.length-1].pluginName, gSampleFilters[gSampleFilters.length-1].param,
michael@0 1846 gCurrentlyShownSampleData, gHighlightedCallstack);
michael@0 1847 } else {
michael@0 1848 gPluginView.hide();
michael@0 1849 }
michael@0 1850 });
michael@0 1851
michael@0 1852 var histogramRequest = Parser.calculateHistogramData();
michael@0 1853 histogramRequest.addEventListener("finished", function (data) {
michael@0 1854 start = Date.now();
michael@0 1855 gHistogramView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack);
michael@0 1856 if (gFrameView)
michael@0 1857 gFrameView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack);
michael@0 1858 });
michael@0 1859
michael@0 1860 if (gDiagnosticBar) {
michael@0 1861 var diagnosticsRequest = Parser.calculateDiagnosticItems(gMeta);
michael@0 1862 diagnosticsRequest.addEventListener("finished", function (diagnosticItems) {
michael@0 1863 start = Date.now();
michael@0 1864 gDiagnosticBar.display(diagnosticItems);
michael@0 1865 });
michael@0 1866 }
michael@0 1867
michael@0 1868 viewOptionsChanged();
michael@0 1869 }
michael@0 1870
michael@0 1871 function viewOptionsChanged() {
michael@0 1872 gTreeManager.dataIsOutdated();
michael@0 1873 var filterNameInput = document.getElementById("filterName");
michael@0 1874 var updateViewOptionsRequest = Parser.updateViewOptions({
michael@0 1875 invertCallstack: gInvertCallstack,
michael@0 1876 mergeUnbranched: gMergeUnbranched
michael@0 1877 });
michael@0 1878 updateViewOptionsRequest.addEventListener("finished", function (calltree) {
michael@0 1879 var start = Date.now();
michael@0 1880 gTreeManager.display(calltree, gSymbols, gFunctions, gResources, gMergeFunctions, filterNameInput && filterNameInput.value);
michael@0 1881 });
michael@0 1882 }
michael@0 1883
michael@0 1884 function loadQueryData(queryDataOriginal) {
michael@0 1885 var isFiltersChanged = false;
michael@0 1886 var queryData = {};
michael@0 1887 for (var i in queryDataOriginal) {
michael@0 1888 queryData[i] = unQueryEscape(queryDataOriginal[i]);
michael@0 1889 }
michael@0 1890 if (queryData.search) {
michael@0 1891 gQueryParamFilterName = queryData.search;
michael@0 1892 isFiltersChanged = true;
michael@0 1893 }
michael@0 1894 if (queryData.jankOnly) {
michael@0 1895 gJankOnly = queryData.jankOnly;
michael@0 1896 isFiltersChanged = true;
michael@0 1897 }
michael@0 1898 if (queryData.javascriptOnly) {
michael@0 1899 gJavascriptOnly = queryData.javascriptOnly;
michael@0 1900 isFiltersChanged = true;
michael@0 1901 }
michael@0 1902 if (queryData.mergeUnbranched) {
michael@0 1903 gMergeUnbranched = queryData.mergeUnbranched;
michael@0 1904 isFiltersChanged = true;
michael@0 1905 }
michael@0 1906 if (queryData.invertCallback) {
michael@0 1907 gInvertCallstack = queryData.invertCallback;
michael@0 1908 isFiltersChanged = true;
michael@0 1909 }
michael@0 1910 if (queryData.report) {
michael@0 1911 gReportID = queryData.report;
michael@0 1912 }
michael@0 1913 if (queryData.filter) {
michael@0 1914 var filterChain = JSON.parse(queryData.filter);
michael@0 1915 gSampleFilters = filterChain;
michael@0 1916 }
michael@0 1917 if (queryData.selection) {
michael@0 1918 var selection = queryData.selection;
michael@0 1919 gRestoreSelection = selection;
michael@0 1920 }
michael@0 1921
michael@0 1922 if (isFiltersChanged) {
michael@0 1923 //filtersChanged();
michael@0 1924 }
michael@0 1925 }
michael@0 1926
michael@0 1927 function unQueryEscape(str) {
michael@0 1928 return decodeURIComponent(str);
michael@0 1929 }
michael@0 1930
michael@0 1931 function queryEscape(str) {
michael@0 1932 return encodeURIComponent(encodeURIComponent(str));
michael@0 1933 }
michael@0 1934
michael@0 1935 function updateDocumentURL() {
michael@0 1936 location.hash = getDocumentHashString();
michael@0 1937 return document.location;
michael@0 1938 }
michael@0 1939
michael@0 1940 function getDocumentHashString() {
michael@0 1941 var query = "";
michael@0 1942 if (gReportID) {
michael@0 1943 if (query != "")
michael@0 1944 query += "&";
michael@0 1945 query += "report=" + queryEscape(gReportID);
michael@0 1946 }
michael@0 1947 if (document.getElementById("filterName") != null &&
michael@0 1948 document.getElementById("filterName").value != null &&
michael@0 1949 document.getElementById("filterName").value != "") {
michael@0 1950 if (query != "")
michael@0 1951 query += "&";
michael@0 1952 query += "search=" + queryEscape(document.getElementById("filterName").value);
michael@0 1953 }
michael@0 1954 // For now don't restore the view rest
michael@0 1955 return query;
michael@0 1956 if (gJankOnly) {
michael@0 1957 if (query != "")
michael@0 1958 query += "&";
michael@0 1959 query += "jankOnly=" + queryEscape(gJankOnly);
michael@0 1960 }
michael@0 1961 if (gJavascriptOnly) {
michael@0 1962 if (query != "")
michael@0 1963 query += "&";
michael@0 1964 query += "javascriptOnly=" + queryEscape(gJavascriptOnly);
michael@0 1965 }
michael@0 1966 if (gMergeUnbranched) {
michael@0 1967 if (query != "")
michael@0 1968 query += "&";
michael@0 1969 query += "mergeUnbranched=" + queryEscape(gMergeUnbranched);
michael@0 1970 }
michael@0 1971 if (gInvertCallstack) {
michael@0 1972 if (query != "")
michael@0 1973 query += "&";
michael@0 1974 query += "invertCallback=" + queryEscape(gInvertCallstack);
michael@0 1975 }
michael@0 1976 if (gSampleFilters && gSampleFilters.length != 0) {
michael@0 1977 if (query != "")
michael@0 1978 query += "&";
michael@0 1979 query += "filter=" + queryEscape(JSON.stringify(gSampleFilters));
michael@0 1980 }
michael@0 1981 if (gTreeManager.hasNonTrivialSelection()) {
michael@0 1982 if (query != "")
michael@0 1983 query += "&";
michael@0 1984 query += "selection=" + queryEscape(gTreeManager.serializeCurrentSelectionSnapshot());
michael@0 1985 }
michael@0 1986 if (!gReportID) {
michael@0 1987 query = "uploadProfileFirst!";
michael@0 1988 }
michael@0 1989
michael@0 1990 return query;
michael@0 1991 }
michael@0 1992

mercurial