Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 });
1006 }
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 });
1012 }
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);
1066 }
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);
1078 }
1079 contentDiv.appendChild(uploadButton);
1081 document.body.appendChild(overlay);
1082 }
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.";
1095 }
1096 };
1097 oXHR.onerror = function (oEvent) {
1098 document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file.";
1099 }
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) + "%";
1107 }
1108 }
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)";
1116 }
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 });
1124 }
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);
1134 }
1136 }
1138 function delete_skip_symbol() {
1139 var skipSymbol = document.getElementById('skipsymbol').value
1140 }
1142 function add_skip_symbol() {
1144 }
1146 var gFilterChangeCallback = null;
1147 var gFilterChangeDelay = 1200;
1148 function filterOnChange() {
1149 if (gFilterChangeCallback != null) {
1150 clearTimeout(gFilterChangeCallback);
1151 gFilterChangeCallback = null;
1152 }
1154 gFilterChangeCallback = setTimeout(filterUpdate, gFilterChangeDelay);
1155 }
1156 function filterUpdate() {
1157 gFilterChangeCallback = null;
1159 filtersChanged();
1161 var filterNameInput = document.getElementById("filterName");
1162 if (filterNameInput != null) {
1163 changeFocus(filterNameInput);
1164 }
1165 }
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.",
1178 }
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];
1188 }
1189 }
1191 function InfoBar() {
1192 this._container = document.createElement("div");
1193 this._container.id = "infoBar";
1194 this._container.className = "sideBar";
1195 }
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;
1206 }
1207 function getPlatformInfo() {
1208 return gMeta.oscpu + " (" + gMeta.toolkit + ")";
1209 }
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>";
1219 }
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;
1265 }
1266 document.getElementById('compare').onclick = function() {
1267 openProfileCompare();
1268 }
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();
1280 }
1281 }
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"));
1312 }
1313 return data.join("\n");
1314 }
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...");
1336 }
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...");
1350 }
1352 function appendVideoCapture(videoCapture) {
1353 if (videoCapture.indexOf("://") == -1) {
1354 videoCapture = EIDETICKER_BASE_URL + videoCapture;
1355 }
1356 gAppendVideoCapture = videoCapture;
1357 }
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;
1369 }
1370 reporter.begin("Fetching " + url);
1371 PROFILERTRACE("Fetch url: " + url);
1373 function onerror(e) {
1374 PROFILERERROR("zip.js error");
1375 PROFILERERROR(JSON.stringify(e));
1376 }
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;
1393 }
1394 onerror("symbolicated_profile.txt not found in zip file.");
1395 }
1396 });
1397 }, onerror);
1398 }
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;
1417 }
1418 loadRawProfile(subreporters.parsing, xhr.responseText, url);
1419 }
1420 };
1421 xhr.onerror = function (e) {
1422 subreporters.fileLoading.begin("Error fetching profile :(. URL: '" + url + "'. Did you set the CORS headers?");
1423 }
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);
1429 }
1430 };
1431 xhr.send(null);
1432 subreporters.fileLoading.begin("Loading remote file...");
1433 }
1435 function loadProfile(rawProfile) {
1436 if (!rawProfile)
1437 return;
1438 var reporter = enterProgressUI();
1439 loadRawProfile(reporter, rawProfile);
1440 }
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;
1448 }
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 });
1469 }
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);
1489 }
1490 break;
1491 case "importFromAddonFinish":
1492 importFromAddonFinish(o.rawProfile);
1493 break;
1494 }
1495 });
1497 function importFromAddonFinish(rawProfile) {
1498 gImportFromAddonSubreporters.import.finish();
1499 loadRawProfile(gImportFromAddonSubreporters.parsing, rawProfile);
1500 }
1502 var gInvertCallstack = false;
1503 function toggleInvertCallStack() {
1504 gTreeManager.saveReverseSelectionSnapshot(gJavascriptOnly);
1505 gInvertCallstack = !gInvertCallstack;
1506 var startTime = Date.now();
1507 viewOptionsChanged();
1508 }
1510 var gMergeUnbranched = false;
1511 function toggleMergeUnbranched() {
1512 gMergeUnbranched = !gMergeUnbranched;
1513 viewOptionsChanged();
1514 }
1516 var gMergeFunctions = true;
1517 function toggleMergeFunctions() {
1518 gMergeFunctions = !gMergeFunctions;
1519 filtersChanged();
1520 }
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;
1530 }
1531 filtersChanged();
1532 }
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();
1541 }
1542 gJavascriptOnly = !gJavascriptOnly;
1543 gTreeManager.saveSelectionSnapshot(gJavascriptOnly);
1544 filtersChanged();
1545 }
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();
1555 }
1556 });
1557 }
1559 function focusOnCallstack(focusedCallstack, name, overwriteCallstack) {
1560 var invertCallstack = gInvertCallstack;
1562 if (overwriteCallstack != null) {
1563 invertCallstack = overwriteCallstack;
1564 }
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();
1577 }
1578 })
1579 }
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();
1593 }
1594 })
1595 }
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());
1603 }
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();
1612 }
1614 if (gSampleBar) {
1615 gSampleBar.setSample(heaviestSample);
1616 }
1617 }
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);
1648 }
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;
1675 }
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);
1699 }
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());
1715 }
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());
1725 }
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());
1740 }
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();
1772 }
1773 });
1774 if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
1775 gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
1776 viewOptionsChanged();
1777 }
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;
1783 }
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);
1797 }
1798 }
1799 }
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();
1812 }
1813 }
1815 function comparator_receiveSelection(snapshot, frameData) {
1816 gTreeManager.restoreSerializedSelectionSnapshot(snapshot);
1817 if (frameData)
1818 gTreeManager.highlightFrame(frameData);
1819 viewOptionsChanged();
1820 }
1822 function filtersChanged() {
1823 if (window.comparator_setSelection) {
1824 // window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), null);
1825 }
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();
1849 }
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 });
1866 }
1868 viewOptionsChanged();
1869 }
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 });
1882 }
1884 function loadQueryData(queryDataOriginal) {
1885 var isFiltersChanged = false;
1886 var queryData = {};
1887 for (var i in queryDataOriginal) {
1888 queryData[i] = unQueryEscape(queryDataOriginal[i]);
1889 }
1890 if (queryData.search) {
1891 gQueryParamFilterName = queryData.search;
1892 isFiltersChanged = true;
1893 }
1894 if (queryData.jankOnly) {
1895 gJankOnly = queryData.jankOnly;
1896 isFiltersChanged = true;
1897 }
1898 if (queryData.javascriptOnly) {
1899 gJavascriptOnly = queryData.javascriptOnly;
1900 isFiltersChanged = true;
1901 }
1902 if (queryData.mergeUnbranched) {
1903 gMergeUnbranched = queryData.mergeUnbranched;
1904 isFiltersChanged = true;
1905 }
1906 if (queryData.invertCallback) {
1907 gInvertCallstack = queryData.invertCallback;
1908 isFiltersChanged = true;
1909 }
1910 if (queryData.report) {
1911 gReportID = queryData.report;
1912 }
1913 if (queryData.filter) {
1914 var filterChain = JSON.parse(queryData.filter);
1915 gSampleFilters = filterChain;
1916 }
1917 if (queryData.selection) {
1918 var selection = queryData.selection;
1919 gRestoreSelection = selection;
1920 }
1922 if (isFiltersChanged) {
1923 //filtersChanged();
1924 }
1925 }
1927 function unQueryEscape(str) {
1928 return decodeURIComponent(str);
1929 }
1931 function queryEscape(str) {
1932 return encodeURIComponent(encodeURIComponent(str));
1933 }
1935 function updateDocumentURL() {
1936 location.hash = getDocumentHashString();
1937 return document.location;
1938 }
1940 function getDocumentHashString() {
1941 var query = "";
1942 if (gReportID) {
1943 if (query != "")
1944 query += "&";
1945 query += "report=" + queryEscape(gReportID);
1946 }
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);
1953 }
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);
1960 }
1961 if (gJavascriptOnly) {
1962 if (query != "")
1963 query += "&";
1964 query += "javascriptOnly=" + queryEscape(gJavascriptOnly);
1965 }
1966 if (gMergeUnbranched) {
1967 if (query != "")
1968 query += "&";
1969 query += "mergeUnbranched=" + queryEscape(gMergeUnbranched);
1970 }
1971 if (gInvertCallstack) {
1972 if (query != "")
1973 query += "&";
1974 query += "invertCallback=" + queryEscape(gInvertCallstack);
1975 }
1976 if (gSampleFilters && gSampleFilters.length != 0) {
1977 if (query != "")
1978 query += "&";
1979 query += "filter=" + queryEscape(JSON.stringify(gSampleFilters));
1980 }
1981 if (gTreeManager.hasNonTrivialSelection()) {
1982 if (query != "")
1983 query += "&";
1984 query += "selection=" + queryEscape(gTreeManager.serializeCurrentSelectionSnapshot());
1985 }
1986 if (!gReportID) {
1987 query = "uploadProfileFirst!";
1988 }
1990 return query;
1991 }