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.

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

mercurial