michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: var EIDETICKER_BASE_URL = "http://eideticker.wrla.ch/"; michael@0: michael@0: var gDebugLog = false; michael@0: var gDebugTrace = false; michael@0: var gLocation = window.location + ""; michael@0: if (gLocation.indexOf("file:") == 0) { michael@0: gDebugLog = true; michael@0: gDebugTrace = true; michael@0: PROFILERLOG("Turning on logging+tracing since cleopatra is served from the file protocol"); michael@0: } michael@0: // Use for verbose tracing, otherwise use log michael@0: function PROFILERTRACE(msg) { michael@0: if (gDebugTrace) michael@0: PROFILERLOG(msg); michael@0: } michael@0: function PROFILERLOG(msg) { michael@0: if (gDebugLog) { michael@0: msg = "Cleo: " + msg; michael@0: console.log(msg); michael@0: if (window.dump) michael@0: window.dump(msg + "\n"); michael@0: } michael@0: } michael@0: function PROFILERERROR(msg) { michael@0: msg = "Cleo: " + msg; michael@0: console.log(msg); michael@0: if (window.dump) michael@0: window.dump(msg + "\n"); michael@0: } michael@0: function enableProfilerTracing() { michael@0: gDebugLog = true; michael@0: gDebugTrace = true; michael@0: Parser.updateLogSetting(); michael@0: } michael@0: function enableProfilerLogging() { michael@0: gDebugLog = true; michael@0: Parser.updateLogSetting(); michael@0: } michael@0: michael@0: function removeAllChildren(element) { michael@0: while (element.firstChild) { michael@0: element.removeChild(element.firstChild); michael@0: } michael@0: } michael@0: michael@0: function FileList() { michael@0: this._container = document.createElement("ul"); michael@0: this._container.id = "fileList"; michael@0: this._selectedFileItem = null; michael@0: this._fileItemList = []; michael@0: } michael@0: michael@0: FileList.prototype = { michael@0: getContainer: function FileList_getContainer() { michael@0: return this._container; michael@0: }, michael@0: michael@0: clearFiles: function FileList_clearFiles() { michael@0: this.fileItemList = []; michael@0: this._selectedFileItem = null; michael@0: this._container.innerHTML = ""; michael@0: }, michael@0: michael@0: loadProfileListFromLocalStorage: function FileList_loadProfileListFromLocalStorage() { michael@0: var self = this; michael@0: gLocalStorage.getProfileList(function(profileList) { michael@0: for (var i = profileList.length - 1; i >= 0; i--) { michael@0: (function closure() { michael@0: // This only carries info about the profile and the access key to retrieve it. michael@0: var profileInfo = profileList[i]; michael@0: //PROFILERTRACE("Profile list from local storage: " + JSON.stringify(profileInfo)); michael@0: var dateObj = new Date(profileInfo.date); michael@0: var fileEntry = self.addFile(profileInfo, dateObj.toLocaleString(), function fileEntryClick() { michael@0: PROFILERLOG("open: " + profileInfo.profileKey + "\n"); michael@0: loadLocalStorageProfile(profileInfo.profileKey); michael@0: }); michael@0: })(); michael@0: } michael@0: }); michael@0: gLocalStorage.onProfileListChange(function(profileList) { michael@0: self.clearFiles(); michael@0: self.loadProfileListFromLocalStorage(); michael@0: }); michael@0: }, michael@0: michael@0: addFile: function FileList_addFile(profileInfo, description, onselect) { michael@0: var li = document.createElement("li"); michael@0: michael@0: var fileName; michael@0: if (profileInfo.profileKey && profileInfo.profileKey.indexOf("http://profile-store.commondatastorage.googleapis.com/") >= 0) { michael@0: fileName = profileInfo.profileKey.substring(54); michael@0: fileName = fileName.substring(0, 8) + "..." + fileName.substring(28); michael@0: } else { michael@0: fileName = profileInfo.name; michael@0: } michael@0: li.fileName = fileName || "(New Profile)"; michael@0: li.description = description || "(empty)"; michael@0: michael@0: li.className = "fileListItem"; michael@0: if (!this._selectedFileItem) { michael@0: li.classList.add("selected"); michael@0: this._selectedFileItem = li; michael@0: } michael@0: michael@0: var self = this; michael@0: li.onclick = function() { michael@0: self.setSelection(li); michael@0: if (onselect) michael@0: onselect(); michael@0: } michael@0: michael@0: var fileListItemTitleSpan = document.createElement("span"); michael@0: fileListItemTitleSpan.className = "fileListItemTitle"; michael@0: fileListItemTitleSpan.textContent = li.fileName; michael@0: li.appendChild(fileListItemTitleSpan); michael@0: michael@0: var fileListItemDescriptionSpan = document.createElement("span"); michael@0: fileListItemDescriptionSpan.className = "fileListItemDescription"; michael@0: fileListItemDescriptionSpan.textContent = li.description; michael@0: li.appendChild(fileListItemDescriptionSpan); michael@0: michael@0: this._container.appendChild(li); michael@0: michael@0: this._fileItemList.push(li); michael@0: michael@0: return li; michael@0: }, michael@0: michael@0: setSelection: function FileList_setSelection(fileEntry) { michael@0: if (this._selectedFileItem) { michael@0: this._selectedFileItem.classList.remove("selected"); michael@0: } michael@0: this._selectedFileItem = fileEntry; michael@0: fileEntry.classList.add("selected"); michael@0: if (this._selectedFileItem.onselect) michael@0: this._selectedFileItem.onselect(); michael@0: }, michael@0: michael@0: profileParsingFinished: function FileList_profileParsingFinished() { michael@0: //this._container.querySelector(".fileListItemTitle").textContent = "Current Profile"; michael@0: //this._container.querySelector(".fileListItemDescription").textContent = gNumSamples + " Samples"; michael@0: } michael@0: } michael@0: michael@0: function treeObjSort(a, b) { michael@0: return b.counter - a.counter; michael@0: } michael@0: michael@0: function ProfileTreeManager() { michael@0: this.treeView = new TreeView(); michael@0: this.treeView.setColumns([ michael@0: { name: "sampleCount", title: gStrings["Running Time"] }, michael@0: { name: "selfSampleCount", title: gStrings["Self"] }, michael@0: { name: "resource", title: "" }, michael@0: { name: "symbolName", title: gStrings["Symbol Name"] } michael@0: ]); michael@0: var self = this; michael@0: this.treeView.addEventListener("select", function (frameData) { michael@0: self.highlightFrame(frameData); michael@0: if (window.comparator_setSelection) { michael@0: window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), frameData); michael@0: } michael@0: }); michael@0: this.treeView.addEventListener("contextMenuClick", function (e) { michael@0: self._onContextMenuClick(e); michael@0: }); michael@0: this.treeView.addEventListener("focusCallstackButtonClicked", function (frameData) { michael@0: // NOTE: Not in the original Cleopatra source code. michael@0: notifyParent("displaysource", { michael@0: line: frameData.scriptLocation.lineInformation, michael@0: uri: frameData.scriptLocation.scriptURI, michael@0: isChrome: /^otherhost_*/.test(frameData.library) michael@0: }); michael@0: }); michael@0: this._container = document.createElement("div"); michael@0: this._container.className = "tree"; michael@0: this._container.appendChild(this.treeView.getContainer()); michael@0: michael@0: // If this is set when the tree changes the snapshot is immediately restored. michael@0: this._savedSnapshot = null; michael@0: this._allowNonContiguous = false; michael@0: } michael@0: ProfileTreeManager.prototype = { michael@0: getContainer: function ProfileTreeManager_getContainer() { michael@0: return this._container; michael@0: }, michael@0: highlightFrame: function Treedisplay_highlightFrame(frameData) { michael@0: setHighlightedCallstack(this._getCallstackUpTo(frameData), this._getHeaviestCallstack(frameData)); michael@0: }, michael@0: dataIsOutdated: function ProfileTreeManager_dataIsOutdated() { michael@0: this.treeView.dataIsOutdated(); michael@0: }, michael@0: saveSelectionSnapshot: function ProfileTreeManager_saveSelectionSnapshot(isJavascriptOnly) { michael@0: this._savedSnapshot = this.treeView.getSelectionSnapshot(isJavascriptOnly); michael@0: }, michael@0: saveReverseSelectionSnapshot: function ProfileTreeManager_saveReverseSelectionSnapshot(isJavascriptOnly) { michael@0: this._savedSnapshot = this.treeView.getReverseSelectionSnapshot(isJavascriptOnly); michael@0: }, michael@0: hasNonTrivialSelection: function ProfileTreeManager_hasNonTrivialSelection() { michael@0: return this.treeView.getSelectionSnapshot().length > 1; michael@0: }, michael@0: serializeCurrentSelectionSnapshot: function ProfileTreeManager_serializeCurrentSelectionSnapshot() { michael@0: return JSON.stringify(this.treeView.getSelectionSnapshot()); michael@0: }, michael@0: restoreSerializedSelectionSnapshot: function ProfileTreeManager_restoreSerializedSelectionSnapshot(selection) { michael@0: this._savedSnapshot = JSON.parse(selection); michael@0: }, michael@0: _restoreSelectionSnapshot: function ProfileTreeManager__restoreSelectionSnapshot(snapshot, allowNonContiguous) { michael@0: return this.treeView.restoreSelectionSnapshot(snapshot, allowNonContiguous); michael@0: }, michael@0: setSelection: function ProfileTreeManager_setSelection(frames) { michael@0: return this.treeView.setSelection(frames); michael@0: }, michael@0: _getCallstackUpTo: function ProfileTreeManager__getCallstackUpTo(frame) { michael@0: var callstack = []; michael@0: var curr = frame; michael@0: while (curr != null) { michael@0: if (curr.name != null) { michael@0: var subCallstack = curr.fullFrameNamesAsInSample.clone(); michael@0: subCallstack.reverse(); michael@0: callstack = callstack.concat(subCallstack); michael@0: } michael@0: curr = curr.parent; michael@0: } michael@0: callstack.reverse(); michael@0: if (gInvertCallstack) michael@0: callstack.shift(); // remove (total) michael@0: return callstack; michael@0: }, michael@0: _getHeaviestCallstack: function ProfileTreeManager__getHeaviestCallstack(frame) { michael@0: // FIXME: This gets the first leaf which is not the heaviest leaf. michael@0: while(frame.children && frame.children.length > 0) { michael@0: var nextFrame = frame.children[0].getData(); michael@0: if (!nextFrame) michael@0: break; michael@0: frame = nextFrame; michael@0: } michael@0: return this._getCallstackUpTo(frame); michael@0: }, michael@0: _onContextMenuClick: function ProfileTreeManager__onContextMenuClick(e) { michael@0: var node = e.node; michael@0: var menuItem = e.menuItem; michael@0: michael@0: if (menuItem == "View Source") { michael@0: // Remove anything after ( since MXR doesn't handle search with the arguments. michael@0: var symbol = node.name.split("(")[0]; michael@0: window.open("http://mxr.mozilla.org/mozilla-central/search?string=" + symbol, "View Source"); michael@0: } else if (menuItem == "View JS Source") { michael@0: viewJSSource(node); michael@0: } else if (menuItem == "Plugin View: Pie") { michael@0: focusOnPluginView("protovis", {type:"pie"}); michael@0: } else if (menuItem == "Plugin View: Tree") { michael@0: focusOnPluginView("protovis", {type:"tree"}); michael@0: } else if (menuItem == "Google Search") { michael@0: var symbol = node.name; michael@0: window.open("https://www.google.ca/search?q=" + symbol, "View Source"); michael@0: } else if (menuItem == "Focus Frame") { michael@0: var symbol = node.fullFrameNamesAsInSample[0]; // TODO: we only function one symbol when callpath merging is on, fix that michael@0: focusOnSymbol(symbol, node.name); michael@0: } else if (menuItem == "Focus Callstack") { michael@0: var focusedCallstack = this._getCallstackUpTo(node); michael@0: focusOnCallstack(focusedCallstack, node.name); michael@0: } michael@0: }, michael@0: setAllowNonContiguous: function ProfileTreeManager_setAllowNonContiguous() { michael@0: this._allowNonContiguous = true; michael@0: }, michael@0: display: function ProfileTreeManager_display(tree, symbols, functions, resources, useFunctions, filterByName) { michael@0: this.treeView.display(this.convertToJSTreeData(tree, symbols, functions, useFunctions), resources, filterByName); michael@0: if (this._savedSnapshot) { michael@0: var old = this._savedSnapshot.clone(); michael@0: this._restoreSelectionSnapshot(this._savedSnapshot, this._allowNonContiguous); michael@0: this._savedSnapshot = old; michael@0: this._allowNonContiguous = false; michael@0: } michael@0: }, michael@0: convertToJSTreeData: function ProfileTreeManager__convertToJSTreeData(rootNode, symbols, functions, useFunctions) { michael@0: var totalSamples = rootNode.counter; michael@0: function createTreeViewNode(node, parent) { michael@0: var curObj = {}; michael@0: curObj.parent = parent; michael@0: curObj.counter = node.counter; michael@0: var selfCounter = node.counter; michael@0: for (var i = 0; i < node.children.length; ++i) { michael@0: selfCounter -= node.children[i].counter; michael@0: } michael@0: curObj.selfCounter = selfCounter; michael@0: curObj.ratio = node.counter / totalSamples; michael@0: curObj.fullFrameNamesAsInSample = node.mergedNames ? node.mergedNames : [node.name]; michael@0: if (!(node.name in (useFunctions ? functions : symbols))) { michael@0: curObj.name = node.name; michael@0: curObj.library = ""; michael@0: } else { michael@0: var functionObj = useFunctions ? functions[node.name] : functions[symbols[node.name].functionIndex]; michael@0: var info = { michael@0: functionName: functionObj.functionName, michael@0: libraryName: functionObj.libraryName, michael@0: lineInformation: useFunctions ? "" : symbols[node.name].lineInformation michael@0: }; michael@0: curObj.name = (info.functionName + " " + info.lineInformation).trim(); michael@0: curObj.library = info.libraryName; michael@0: curObj.isJSFrame = functionObj.isJSFrame; michael@0: if (functionObj.scriptLocation) { michael@0: curObj.scriptLocation = functionObj.scriptLocation; michael@0: } michael@0: } michael@0: if (node.children.length) { michael@0: curObj.children = getChildrenObjects(node.children, curObj); michael@0: } michael@0: return curObj; michael@0: } michael@0: function getChildrenObjects(children, parent) { michael@0: var sortedChildren = children.slice(0).sort(treeObjSort); michael@0: return sortedChildren.map(function (child) { michael@0: var createdNode = null; michael@0: return { michael@0: getData: function () { michael@0: if (!createdNode) { michael@0: createdNode = createTreeViewNode(child, parent); michael@0: } michael@0: return createdNode; michael@0: } michael@0: }; michael@0: }); michael@0: } michael@0: return getChildrenObjects([rootNode], null); michael@0: }, michael@0: }; michael@0: michael@0: function SampleBar() { michael@0: this._container = document.createElement("div"); michael@0: this._container.id = "sampleBar"; michael@0: this._container.className = "sideBar"; michael@0: michael@0: this._header = document.createElement("h2"); michael@0: this._header.innerHTML = "Selection - Most time spent in:"; michael@0: this._header.alt = "This shows the heaviest leaf of the selected sample. Use this to get a quick glimpse of where the selection is spending most of its time."; michael@0: this._container.appendChild(this._header); michael@0: michael@0: this._text = document.createElement("ul"); michael@0: this._text.style.whiteSpace = "pre"; michael@0: this._text.innerHTML = "Sample text"; michael@0: this._container.appendChild(this._text); michael@0: } michael@0: michael@0: SampleBar.prototype = { michael@0: getContainer: function SampleBar_getContainer() { michael@0: return this._container; michael@0: }, michael@0: setSample: function SampleBar_setSample(sample) { michael@0: var str = ""; michael@0: var list = []; michael@0: michael@0: this._text.innerHTML = ""; michael@0: michael@0: for (var i = 0; i < sample.length; i++) { michael@0: var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; michael@0: if (!functionObj) michael@0: continue; michael@0: var functionItem = document.createElement("li"); michael@0: var functionLink = document.createElement("a"); michael@0: functionLink.textContent = functionLink.title = functionObj.functionName; michael@0: functionLink.href = "#"; michael@0: functionItem.appendChild(functionLink); michael@0: this._text.appendChild(functionItem); michael@0: list.push(functionObj.functionName); michael@0: functionLink.selectIndex = i; michael@0: functionLink.onclick = function() { michael@0: var selectedFrames = []; michael@0: if (gInvertCallstack) { michael@0: for (var i = 0; i <= this.selectIndex; i++) { michael@0: var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; michael@0: selectedFrames.push(functionObj.functionName); michael@0: } michael@0: } else { michael@0: for (var i = sample.length - 1; i >= this.selectIndex; i--) { michael@0: var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; michael@0: selectedFrames.push(functionObj.functionName); michael@0: } michael@0: } michael@0: gTreeManager.setSelection(selectedFrames); michael@0: return false; michael@0: } michael@0: } michael@0: return list; michael@0: }, michael@0: } michael@0: michael@0: michael@0: function PluginView() { michael@0: this._container = document.createElement("div"); michael@0: this._container.className = "pluginview"; michael@0: this._container.style.visibility = 'hidden'; michael@0: this._iframe = document.createElement("iframe"); michael@0: this._iframe.className = "pluginviewIFrame"; michael@0: this._container.appendChild(this._iframe); michael@0: this._container.style.top = ""; michael@0: } michael@0: PluginView.prototype = { michael@0: getContainer: function PluginView_getContainer() { michael@0: return this._container; michael@0: }, michael@0: hide: function() { michael@0: // get rid of the scrollbars michael@0: this._container.style.top = ""; michael@0: this._container.style.visibility = 'hidden'; michael@0: }, michael@0: show: function() { michael@0: // This creates extra scrollbar so only do it when needed michael@0: this._container.style.top = "0px"; michael@0: this._container.style.visibility = ''; michael@0: }, michael@0: display: function(pluginName, param, data) { michael@0: this._iframe.src = "js/plugins/" + pluginName + "/index.html"; michael@0: var self = this; michael@0: this._iframe.onload = function() { michael@0: self._iframe.contentWindow.initCleopatraPlugin(data, param, gSymbols); michael@0: } michael@0: this.show(); michael@0: }, michael@0: } michael@0: michael@0: function HistogramView() { michael@0: this._container = document.createElement("div"); michael@0: this._container.className = "histogram"; michael@0: michael@0: this._canvas = this._createCanvas(); michael@0: this._container.appendChild(this._canvas); michael@0: michael@0: this._rangeSelector = new RangeSelector(this._canvas, this); michael@0: this._rangeSelector.enableRangeSelectionOnHistogram(); michael@0: this._container.appendChild(this._rangeSelector.getContainer()); michael@0: michael@0: this._busyCover = document.createElement("div"); michael@0: this._busyCover.className = "busyCover"; michael@0: this._container.appendChild(this._busyCover); michael@0: michael@0: this._histogramData = []; michael@0: } michael@0: HistogramView.prototype = { michael@0: dataIsOutdated: function HistogramView_dataIsOutdated() { michael@0: this._busyCover.classList.add("busy"); michael@0: }, michael@0: _createCanvas: function HistogramView__createCanvas() { michael@0: var canvas = document.createElement("canvas"); michael@0: canvas.height = 60; michael@0: canvas.style.width = "100%"; michael@0: canvas.style.height = "100%"; michael@0: return canvas; michael@0: }, michael@0: getContainer: function HistogramView_getContainer() { michael@0: return this._container; michael@0: }, michael@0: selectRange: function HistogramView_selectRange(start, end) { michael@0: this._rangeSelector._finishSelection(start, end); michael@0: }, michael@0: showVideoFramePosition: function HistogramView_showVideoFramePosition(frame) { michael@0: if (!this._frameStart || !this._frameStart[frame]) michael@0: return; michael@0: var frameStart = this._frameStart[frame]; michael@0: // Now we look for the frame end. Because we can swap frame we don't present we have to look ahead michael@0: // in the stream if frame+1 doesn't exist. michael@0: var frameEnd = this._frameStart[frame+1]; michael@0: for (var i = 0; i < 10 && !frameEnd; i++) { michael@0: frameEnd = this._frameStart[frame+1+i]; michael@0: } michael@0: this._rangeSelector.showVideoRange(frameStart, frameEnd); michael@0: }, michael@0: showVideoPosition: function HistogramView_showVideoPosition(position) { michael@0: // position in 0..1 michael@0: this._rangeSelector.showVideoPosition(position); michael@0: }, michael@0: _gatherMarkersList: function HistogramView__gatherMarkersList(histogramData) { michael@0: var markers = []; michael@0: for (var i = 0; i < histogramData.length; ++i) { michael@0: var step = histogramData[i]; michael@0: if ("marker" in step) { michael@0: markers.push({ michael@0: index: i, michael@0: name: step.marker michael@0: }); michael@0: } michael@0: } michael@0: return markers; michael@0: }, michael@0: _calculateWidthMultiplier: function () { michael@0: var minWidth = 2000; michael@0: return Math.ceil(minWidth / this._widthSum); michael@0: }, michael@0: histogramClick: function HistogramView_histogramClick(index) { michael@0: var sample = this._histogramData[index]; michael@0: var frames = sample.frames; michael@0: var list = gSampleBar.setSample(frames[0]); michael@0: gTreeManager.setSelection(list); michael@0: setHighlightedCallstack(frames[0], frames[0]); michael@0: }, michael@0: display: function HistogramView_display(histogramData, frameStart, widthSum, highlightedCallstack) { michael@0: this._histogramData = histogramData; michael@0: this._frameStart = frameStart; michael@0: this._widthSum = widthSum; michael@0: this._widthMultiplier = this._calculateWidthMultiplier(); michael@0: this._canvas.width = this._widthMultiplier * this._widthSum; michael@0: this._render(highlightedCallstack); michael@0: this._busyCover.classList.remove("busy"); michael@0: }, michael@0: _scheduleRender: function HistogramView__scheduleRender(highlightedCallstack) { michael@0: var self = this; michael@0: if (self._pendingAnimationFrame != null) { michael@0: return; michael@0: } michael@0: self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() { michael@0: cancelAnimationFrame(self._pendingAnimationFrame); michael@0: self._pendingAnimationFrame = null; michael@0: self._render(highlightedCallstack); michael@0: self._busyCover.classList.remove("busy"); michael@0: }); michael@0: }, michael@0: _render: function HistogramView__render(highlightedCallstack) { michael@0: var ctx = this._canvas.getContext("2d"); michael@0: var height = this._canvas.height; michael@0: ctx.setTransform(this._widthMultiplier, 0, 0, 1, 0, 0); michael@0: ctx.font = "20px Georgia"; michael@0: ctx.clearRect(0, 0, this._widthSum, height); michael@0: michael@0: var self = this; michael@0: var markerCount = 0; michael@0: for (var i = 0; i < this._histogramData.length; i++) { michael@0: var step = this._histogramData[i]; michael@0: var isSelected = self._isStepSelected(step, highlightedCallstack); michael@0: var isInRangeSelector = self._isInRangeSelector(i); michael@0: if (isSelected) { michael@0: ctx.fillStyle = "green"; michael@0: } else if (isInRangeSelector) { michael@0: ctx.fillStyle = "blue"; michael@0: } else { michael@0: ctx.fillStyle = step.color; michael@0: } michael@0: var roundedHeight = Math.round(step.value * height); michael@0: ctx.fillRect(step.x, height - roundedHeight, step.width, roundedHeight); michael@0: } michael@0: michael@0: this._finishedRendering = true; michael@0: }, michael@0: highlightedCallstackChanged: function HistogramView_highlightedCallstackChanged(highlightedCallstack) { michael@0: this._scheduleRender(highlightedCallstack); michael@0: }, michael@0: _isInRangeSelector: function HistogramView_isInRangeSelector(index) { michael@0: return false; michael@0: }, michael@0: _isStepSelected: function HistogramView__isStepSelected(step, highlightedCallstack) { michael@0: if ("marker" in step) michael@0: return false; michael@0: michael@0: search_frames: for (var i = 0; i < step.frames.length; i++) { michael@0: var frames = step.frames[i]; michael@0: michael@0: if (frames.length < highlightedCallstack.length || michael@0: highlightedCallstack.length <= (gInvertCallstack ? 0 : 1)) michael@0: continue; michael@0: michael@0: var compareFrames = frames; michael@0: if (gInvertCallstack) { michael@0: for (var j = 0; j < highlightedCallstack.length; j++) { michael@0: var compareFrameIndex = compareFrames.length - 1 - j; michael@0: if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) { michael@0: continue search_frames; michael@0: } michael@0: } michael@0: } else { michael@0: for (var j = 0; j < highlightedCallstack.length; j++) { michael@0: var compareFrameIndex = j; michael@0: if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) { michael@0: continue search_frames; michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: }; michael@0: return false; michael@0: }, michael@0: getHistogramData: function HistogramView__getHistogramData() { michael@0: return this._histogramData; michael@0: }, michael@0: _getStepColor: function HistogramView__getStepColor(step) { michael@0: if ("responsiveness" in step.extraInfo) { michael@0: var res = step.extraInfo.responsiveness; michael@0: var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness)); michael@0: return "rgb(" + redComponent + ",0,0)"; michael@0: } michael@0: michael@0: return "rgb(0,0,0)"; michael@0: }, michael@0: }; michael@0: michael@0: function RangeSelector(graph, histogram) { michael@0: this._histogram = histogram; michael@0: this.container = document.createElement("div"); michael@0: this.container.className = "rangeSelectorContainer"; michael@0: this._graph = graph; michael@0: this._selectedRange = { startX: 0, endX: 0 }; michael@0: this._selectedSampleRange = { start: 0, end: 0 }; michael@0: michael@0: this._highlighter = document.createElement("div"); michael@0: this._highlighter.className = "histogramHilite collapsed"; michael@0: this.container.appendChild(this._highlighter); michael@0: michael@0: this._mouseMarker = document.createElement("div"); michael@0: this._mouseMarker.className = "histogramMouseMarker"; michael@0: this.container.appendChild(this._mouseMarker); michael@0: } michael@0: RangeSelector.prototype = { michael@0: getContainer: function RangeSelector_getContainer() { michael@0: return this.container; michael@0: }, michael@0: // echo the location off the mouse on the histogram michael@0: drawMouseMarker: function RangeSelector_drawMouseMarker(x) { michael@0: var mouseMarker = this._mouseMarker; michael@0: mouseMarker.style.left = x + "px"; michael@0: }, michael@0: showVideoPosition: function RangeSelector_showVideoPosition(position) { michael@0: this.drawMouseMarker(position * (this._graph.parentNode.clientWidth-1)); michael@0: PROFILERLOG("Show video position: " + position); michael@0: }, michael@0: drawHiliteRectangle: function RangeSelector_drawHiliteRectangle(x, y, width, height) { michael@0: var hilite = this._highlighter; michael@0: hilite.style.left = x + "px"; michael@0: hilite.style.top = "0"; michael@0: hilite.style.width = width + "px"; michael@0: hilite.style.height = height + "px"; michael@0: }, michael@0: clearCurrentRangeSelection: function RangeSelector_clearCurrentRangeSelection() { michael@0: try { michael@0: this.changeEventSuppressed = true; michael@0: var children = this.selector.childNodes; michael@0: for (var i = 0; i < children.length; ++i) { michael@0: children[i].selected = false; michael@0: } michael@0: } finally { michael@0: this.changeEventSuppressed = false; michael@0: } michael@0: }, michael@0: showVideoRange: function RangeSelector_showVideoRange(startIndex, endIndex) { michael@0: if (!endIndex || endIndex < 0) michael@0: endIndex = gCurrentlyShownSampleData.length; michael@0: michael@0: var len = this._graph.parentNode.getBoundingClientRect().right - this._graph.parentNode.getBoundingClientRect().left; michael@0: this._selectedRange.startX = startIndex * len / this._histogram._histogramData.length; michael@0: this._selectedRange.endX = endIndex * len / this._histogram._histogramData.length; michael@0: var width = this._selectedRange.endX - this._selectedRange.startX; michael@0: var height = this._graph.parentNode.clientHeight; michael@0: this._highlighter.classList.remove("collapsed"); michael@0: this.drawHiliteRectangle(this._selectedRange.startX, 0, width, height); michael@0: //this._finishSelection(startIndex, endIndex); michael@0: }, michael@0: enableRangeSelectionOnHistogram: function RangeSelector_enableRangeSelectionOnHistogram() { michael@0: var graph = this._graph; michael@0: var isDrawingRectangle = false; michael@0: var origX, origY; michael@0: var self = this; michael@0: // Compute this on the mouse down rather then forcing a sync reflow michael@0: // every frame. michael@0: var boundingRect = null; michael@0: function histogramClick(clickX, clickY) { michael@0: clickX = Math.min(clickX, graph.parentNode.getBoundingClientRect().right); michael@0: clickX = clickX - graph.parentNode.getBoundingClientRect().left; michael@0: var index = self._histogramIndexFromPoint(clickX); michael@0: self._histogram.histogramClick(index); michael@0: } michael@0: function updateHiliteRectangle(newX, newY) { michael@0: newX = Math.min(newX, boundingRect.right); michael@0: var startX = Math.min(newX, origX) - boundingRect.left; michael@0: var startY = 0; michael@0: var width = Math.abs(newX - origX); michael@0: var height = graph.parentNode.clientHeight; michael@0: if (startX < 0) { michael@0: width += startX; michael@0: startX = 0; michael@0: } michael@0: self._selectedRange.startX = startX; michael@0: self._selectedRange.endX = startX + width; michael@0: self.drawHiliteRectangle(startX, startY, width, height); michael@0: } michael@0: function updateMouseMarker(newX) { michael@0: self.drawMouseMarker(newX - graph.parentNode.getBoundingClientRect().left); michael@0: } michael@0: graph.addEventListener("mousedown", function(e) { michael@0: if (e.button != 0) michael@0: return; michael@0: graph.style.cursor = "col-resize"; michael@0: isDrawingRectangle = true; michael@0: self.beginHistogramSelection(); michael@0: origX = e.pageX; michael@0: origY = e.pageY; michael@0: boundingRect = graph.parentNode.getBoundingClientRect(); michael@0: if (this.setCapture) michael@0: this.setCapture(); michael@0: // Reset the highlight rectangle michael@0: updateHiliteRectangle(e.pageX, e.pageY); michael@0: e.preventDefault(); michael@0: this._movedDuringClick = false; michael@0: }, false); michael@0: graph.addEventListener("mouseup", function(e) { michael@0: graph.style.cursor = "default"; michael@0: if (!this._movedDuringClick) { michael@0: isDrawingRectangle = false; michael@0: // Handle as a click on the histogram. Select the sample: michael@0: histogramClick(e.pageX, e.pageY); michael@0: } else if (isDrawingRectangle) { michael@0: isDrawingRectangle = false; michael@0: updateHiliteRectangle(e.pageX, e.pageY); michael@0: self.finishHistogramSelection(e.pageX != origX); michael@0: if (e.pageX == origX) { michael@0: // Simple click in the histogram michael@0: var index = self._sampleIndexFromPoint(e.pageX - graph.parentNode.getBoundingClientRect().left); michael@0: // TODO Select this sample in the tree view michael@0: var sample = gCurrentlyShownSampleData[index]; michael@0: } michael@0: } michael@0: }, false); michael@0: graph.addEventListener("mousemove", function(e) { michael@0: this._movedDuringClick = true; michael@0: if (isDrawingRectangle) { michael@0: updateMouseMarker(-1); // Clear michael@0: updateHiliteRectangle(e.pageX, e.pageY); michael@0: } else { michael@0: updateMouseMarker(e.pageX); michael@0: } michael@0: }, false); michael@0: graph.addEventListener("mouseout", function(e) { michael@0: updateMouseMarker(-1); // Clear michael@0: }, false); michael@0: }, michael@0: beginHistogramSelection: function RangeSelector_beginHistgramSelection() { michael@0: var hilite = this._highlighter; michael@0: hilite.classList.remove("finished"); michael@0: hilite.classList.add("selecting"); michael@0: hilite.classList.remove("collapsed"); michael@0: if (this._transientRestrictionEnteringAffordance) { michael@0: this._transientRestrictionEnteringAffordance.discard(); michael@0: } michael@0: }, michael@0: _finishSelection: function RangeSelector__finishSelection(start, end) { michael@0: var newFilterChain = gSampleFilters.concat({ type: "RangeSampleFilter", start: start, end: end }); michael@0: var self = this; michael@0: self._transientRestrictionEnteringAffordance = gBreadcrumbTrail.add({ michael@0: title: gStrings["Sample Range"] + " [" + start + ", " + (end + 1) + "]", michael@0: enterCallback: function () { michael@0: gSampleFilters = newFilterChain; michael@0: self.collapseHistogramSelection(); michael@0: filtersChanged(); michael@0: } michael@0: }); michael@0: }, michael@0: finishHistogramSelection: function RangeSelector_finishHistgramSelection(isSomethingSelected) { michael@0: var self = this; michael@0: var hilite = this._highlighter; michael@0: hilite.classList.remove("selecting"); michael@0: if (isSomethingSelected) { michael@0: hilite.classList.add("finished"); michael@0: var start = this._sampleIndexFromPoint(this._selectedRange.startX); michael@0: var end = this._sampleIndexFromPoint(this._selectedRange.endX); michael@0: self._finishSelection(start, end); michael@0: } else { michael@0: hilite.classList.add("collapsed"); michael@0: } michael@0: }, michael@0: collapseHistogramSelection: function RangeSelector_collapseHistogramSelection() { michael@0: var hilite = this._highlighter; michael@0: hilite.classList.add("collapsed"); michael@0: }, michael@0: _sampleIndexFromPoint: function RangeSelector__sampleIndexFromPoint(x) { michael@0: // XXX this is completely wrong, fix please michael@0: var totalSamples = parseFloat(gCurrentlyShownSampleData.length); michael@0: var width = parseFloat(this._graph.parentNode.clientWidth); michael@0: var factor = totalSamples / width; michael@0: return parseInt(parseFloat(x) * factor); michael@0: }, michael@0: _histogramIndexFromPoint: function RangeSelector__histogramIndexFromPoint(x) { michael@0: // XXX this is completely wrong, fix please michael@0: var totalSamples = parseFloat(this._histogram._histogramData.length); michael@0: var width = parseFloat(this._graph.parentNode.clientWidth); michael@0: var factor = totalSamples / width; michael@0: return parseInt(parseFloat(x) * factor); michael@0: }, michael@0: }; michael@0: michael@0: function videoPaneTimeChange(video) { michael@0: if (!gMeta || !gMeta.frameStart) michael@0: return; michael@0: michael@0: var frame = gVideoPane.getCurrentFrameNumber(); michael@0: //var frameStart = gMeta.frameStart[frame]; michael@0: //var frameEnd = gMeta.frameStart[frame+1]; // If we don't have a frameEnd assume the end of the profile michael@0: michael@0: gHistogramView.showVideoFramePosition(frame); michael@0: } michael@0: michael@0: michael@0: window.onpopstate = function(ev) { michael@0: return; // Conflicts with document url michael@0: if (!gBreadcrumbTrail) michael@0: return; michael@0: michael@0: gBreadcrumbTrail.pop(); michael@0: if (ev.state) { michael@0: if (ev.state.action === "popbreadcrumb") { michael@0: //gBreadcrumbTrail.pop(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function BreadcrumbTrail() { michael@0: this._breadcrumbs = []; michael@0: this._selectedBreadcrumbIndex = -1; michael@0: michael@0: this._containerElement = document.createElement("div"); michael@0: this._containerElement.className = "breadcrumbTrail"; michael@0: var self = this; michael@0: this._containerElement.addEventListener("click", function (e) { michael@0: if (!e.target.classList.contains("breadcrumbTrailItem")) michael@0: return; michael@0: self._enter(e.target.breadcrumbIndex); michael@0: }); michael@0: } michael@0: BreadcrumbTrail.prototype = { michael@0: getContainer: function BreadcrumbTrail_getContainer() { michael@0: return this._containerElement; michael@0: }, michael@0: /** michael@0: * Add a breadcrumb. The breadcrumb parameter is an object with the following michael@0: * properties: michael@0: * - title: The text that will be shown in the breadcrumb's button. michael@0: * - enterCallback: A function that will be called when entering this michael@0: * breadcrumb. michael@0: */ michael@0: add: function BreadcrumbTrail_add(breadcrumb) { michael@0: for (var i = this._breadcrumbs.length - 1; i > this._selectedBreadcrumbIndex; i--) { michael@0: var rearLi = this._breadcrumbs[i]; michael@0: if (!rearLi.breadcrumbIsTransient) michael@0: throw "Can only add new breadcrumbs if after the current one there are only transient ones."; michael@0: rearLi.breadcrumbDiscarder.discard(); michael@0: } michael@0: var div = document.createElement("div"); michael@0: div.className = "breadcrumbTrailItem"; michael@0: div.textContent = breadcrumb.title; michael@0: var index = this._breadcrumbs.length; michael@0: div.breadcrumbIndex = index; michael@0: div.breadcrumbEnterCallback = breadcrumb.enterCallback; michael@0: div.breadcrumbIsTransient = true; michael@0: div.style.zIndex = 1000 - index; michael@0: this._containerElement.appendChild(div); michael@0: this._breadcrumbs.push(div); michael@0: if (index == 0) michael@0: this._enter(index); michael@0: var self = this; michael@0: div.breadcrumbDiscarder = { michael@0: discard: function () { michael@0: if (div.breadcrumbIsTransient) { michael@0: self._deleteBeyond(index - 1); michael@0: delete div.breadcrumbIsTransient; michael@0: delete div.breadcrumbDiscarder; michael@0: } michael@0: } michael@0: }; michael@0: return div.breadcrumbDiscarder; michael@0: }, michael@0: addAndEnter: function BreadcrumbTrail_addAndEnter(breadcrumb) { michael@0: var removalHandle = this.add(breadcrumb); michael@0: this._enter(this._breadcrumbs.length - 1); michael@0: }, michael@0: pop : function BreadcrumbTrail_pop() { michael@0: if (this._breadcrumbs.length-2 >= 0) michael@0: this._enter(this._breadcrumbs.length-2); michael@0: }, michael@0: enterLastItem: function BreadcrumbTrail_enterLastItem(forceSelection) { michael@0: this._enter(this._breadcrumbs.length-1, forceSelection); michael@0: }, michael@0: _enter: function BreadcrumbTrail__select(index, forceSelection) { michael@0: if (index == this._selectedBreadcrumbIndex) michael@0: return; michael@0: if (forceSelection) { michael@0: gTreeManager.restoreSerializedSelectionSnapshot(forceSelection); michael@0: } else { michael@0: gTreeManager.saveSelectionSnapshot(); michael@0: } michael@0: var prevSelected = this._breadcrumbs[this._selectedBreadcrumbIndex]; michael@0: if (prevSelected) michael@0: prevSelected.classList.remove("selected"); michael@0: var li = this._breadcrumbs[index]; michael@0: if (this === gBreadcrumbTrail && index != 0) { michael@0: // Support for back button, disabled until the forward button is implemented. michael@0: //var state = {action: "popbreadcrumb",}; michael@0: //window.history.pushState(state, "Cleopatra"); michael@0: } michael@0: michael@0: delete li.breadcrumbIsTransient; michael@0: li.classList.add("selected"); michael@0: this._deleteBeyond(index); michael@0: this._selectedBreadcrumbIndex = index; michael@0: li.breadcrumbEnterCallback(); michael@0: // Add history state michael@0: }, michael@0: _deleteBeyond: function BreadcrumbTrail__deleteBeyond(index) { michael@0: while (this._breadcrumbs.length > index + 1) { michael@0: this._hide(this._breadcrumbs[index + 1]); michael@0: this._breadcrumbs.splice(index + 1, 1); michael@0: } michael@0: }, michael@0: _hide: function BreadcrumbTrail__hide(breadcrumb) { michael@0: delete breadcrumb.breadcrumbIsTransient; michael@0: breadcrumb.classList.add("deleted"); michael@0: setTimeout(function () { michael@0: breadcrumb.parentNode.removeChild(breadcrumb); michael@0: }, 1000); michael@0: }, michael@0: }; michael@0: michael@0: function maxResponsiveness() { michael@0: var data = gCurrentlyShownSampleData; michael@0: var maxRes = 0.0; michael@0: for (var i = 0; i < data.length; ++i) { michael@0: if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"]) michael@0: continue; michael@0: if (maxRes < data[i].extraInfo["responsiveness"]) michael@0: maxRes = data[i].extraInfo["responsiveness"]; michael@0: } michael@0: return maxRes; michael@0: } michael@0: michael@0: function effectiveInterval() { michael@0: var data = gCurrentlyShownSampleData; michael@0: var interval = 0.0; michael@0: var sampleCount = 0; michael@0: var timeCount = 0; michael@0: var lastTime = null; michael@0: for (var i = 0; i < data.length; ++i) { michael@0: if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) { michael@0: lastTime = null; michael@0: continue; michael@0: } michael@0: if (lastTime) { michael@0: sampleCount++; michael@0: timeCount += data[i].extraInfo["time"] - lastTime; michael@0: } michael@0: lastTime = data[i].extraInfo["time"]; michael@0: } michael@0: var effectiveInterval = timeCount/sampleCount; michael@0: // Biggest diff michael@0: var biggestDiff = 0; michael@0: lastTime = null; michael@0: for (var i = 0; i < data.length; ++i) { michael@0: if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) { michael@0: lastTime = null; michael@0: continue; michael@0: } michael@0: if (lastTime) { michael@0: if (biggestDiff < Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime))) michael@0: biggestDiff = Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime)); michael@0: } michael@0: lastTime = data[i].extraInfo["time"]; michael@0: } michael@0: michael@0: if (effectiveInterval != effectiveInterval) michael@0: return "Time info not collected"; michael@0: michael@0: return (effectiveInterval).toFixed(2) + " ms ±" + biggestDiff.toFixed(2); michael@0: } michael@0: michael@0: function numberOfCurrentlyShownSamples() { michael@0: var data = gCurrentlyShownSampleData; michael@0: var num = 0; michael@0: for (var i = 0; i < data.length; ++i) { michael@0: if (data[i]) michael@0: num++; michael@0: } michael@0: return num; michael@0: } michael@0: michael@0: function avgResponsiveness() { michael@0: var data = gCurrentlyShownSampleData; michael@0: var totalRes = 0.0; michael@0: for (var i = 0; i < data.length; ++i) { michael@0: if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"]) michael@0: continue; michael@0: totalRes += data[i].extraInfo["responsiveness"]; michael@0: } michael@0: return totalRes / numberOfCurrentlyShownSamples(); michael@0: } michael@0: michael@0: function copyProfile() { michael@0: window.prompt ("Copy to clipboard: Ctrl+C, Enter", document.getElementById("data").value); michael@0: } michael@0: michael@0: function saveProfileToLocalStorage() { michael@0: Parser.getSerializedProfile(true, function (serializedProfile) { michael@0: gLocalStorage.storeLocalProfile(serializedProfile, gMeta.profileId, function profileSaved() { michael@0: michael@0: }); michael@0: }); michael@0: } michael@0: function downloadProfile() { michael@0: Parser.getSerializedProfile(true, function (serializedProfile) { michael@0: var blob = new Blob([serializedProfile], { "type": "application/octet-stream" }); michael@0: location.href = window.URL.createObjectURL(blob); michael@0: }); michael@0: } michael@0: michael@0: function promptUploadProfile(selected) { michael@0: var overlay = document.createElement("div"); michael@0: overlay.style.position = "absolute"; michael@0: overlay.style.top = 0; michael@0: overlay.style.left = 0; michael@0: overlay.style.width = "100%"; michael@0: overlay.style.height = "100%"; michael@0: overlay.style.backgroundColor = "transparent"; michael@0: michael@0: var bg = document.createElement("div"); michael@0: bg.style.position = "absolute"; michael@0: bg.style.top = 0; michael@0: bg.style.left = 0; michael@0: bg.style.width = "100%"; michael@0: bg.style.height = "100%"; michael@0: bg.style.opacity = "0.6"; michael@0: bg.style.backgroundColor = "#aaaaaa"; michael@0: overlay.appendChild(bg); michael@0: michael@0: var contentDiv = document.createElement("div"); michael@0: contentDiv.className = "sideBar"; michael@0: contentDiv.style.position = "absolute"; michael@0: contentDiv.style.top = "50%"; michael@0: contentDiv.style.left = "50%"; michael@0: contentDiv.style.width = "40em"; michael@0: contentDiv.style.height = "20em"; michael@0: contentDiv.style.marginLeft = "-20em"; michael@0: contentDiv.style.marginTop = "-10em"; michael@0: contentDiv.style.padding = "10px"; michael@0: contentDiv.style.border = "2px solid black"; michael@0: contentDiv.style.backgroundColor = "rgb(219, 223, 231)"; michael@0: overlay.appendChild(contentDiv); michael@0: michael@0: var noticeHTML = ""; michael@0: noticeHTML += "

Upload Profile - Privacy Notice

"; michael@0: noticeHTML += "You're about to upload your profile publicly where anyone will be able to access it. "; michael@0: noticeHTML += "To better diagnose performance problems profiles include the following information:"; michael@0: noticeHTML += "
"; michael@0: noticeHTML += "To view all the information you can download the full profile to a file and open the json structure with a text editor.

"; michael@0: contentDiv.innerHTML = noticeHTML; michael@0: michael@0: var cancelButton = document.createElement("input"); michael@0: cancelButton.style.position = "absolute"; michael@0: cancelButton.style.bottom = "10px"; michael@0: cancelButton.type = "button"; michael@0: cancelButton.value = "Cancel"; michael@0: cancelButton.onclick = function() { michael@0: document.body.removeChild(overlay); michael@0: } michael@0: contentDiv.appendChild(cancelButton); michael@0: michael@0: var uploadButton = document.createElement("input"); michael@0: uploadButton.style.position = "absolute"; michael@0: uploadButton.style.right = "10px"; michael@0: uploadButton.style.bottom = "10px"; michael@0: uploadButton.type = "button"; michael@0: uploadButton.value = "Upload"; michael@0: uploadButton.onclick = function() { michael@0: document.body.removeChild(overlay); michael@0: uploadProfile(selected); michael@0: } michael@0: contentDiv.appendChild(uploadButton); michael@0: michael@0: document.body.appendChild(overlay); michael@0: } michael@0: michael@0: function uploadProfile(selected) { michael@0: Parser.getSerializedProfile(!selected, function (dataToUpload) { michael@0: var oXHR = new XMLHttpRequest(); michael@0: oXHR.onload = function (oEvent) { michael@0: if (oXHR.status == 200) { michael@0: gReportID = oXHR.responseText; michael@0: updateDocumentURL(); michael@0: document.getElementById("upload_status").innerHTML = "Success! Use this link"; michael@0: document.getElementById("linkElem").href = document.URL; michael@0: } else { michael@0: document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file."; michael@0: } michael@0: }; michael@0: oXHR.onerror = function (oEvent) { michael@0: document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file."; michael@0: } michael@0: oXHR.upload.onprogress = function(oEvent) { michael@0: if (oEvent.lengthComputable) { michael@0: var progress = Math.round((oEvent.loaded / oEvent.total)*100); michael@0: if (progress == 100) { michael@0: document.getElementById("upload_status").innerHTML = "Uploading: Waiting for server side compression"; michael@0: } else { michael@0: document.getElementById("upload_status").innerHTML = "Uploading: " + Math.round((oEvent.loaded / oEvent.total)*100) + "%"; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: var dataSize; michael@0: if (dataToUpload.length > 1024*1024) { michael@0: dataSize = (dataToUpload.length/1024/1024).toFixed(1) + " MB(s)"; michael@0: } else { michael@0: dataSize = (dataToUpload.length/1024).toFixed(1) + " KB(s)"; michael@0: } michael@0: michael@0: var formData = new FormData(); michael@0: formData.append("file", dataToUpload); michael@0: document.getElementById("upload_status").innerHTML = "Uploading Profile (" + dataSize + ")"; michael@0: oXHR.open("POST", "http://profile-store.appspot.com/store", true); michael@0: oXHR.send(formData); michael@0: }); michael@0: } michael@0: michael@0: function populate_skip_symbol() { michael@0: var skipSymbolCtrl = document.getElementById('skipsymbol') michael@0: //skipSymbolCtrl.options = gSkipSymbols; michael@0: for (var i = 0; i < gSkipSymbols.length; i++) { michael@0: var elOptNew = document.createElement('option'); michael@0: elOptNew.text = gSkipSymbols[i]; michael@0: elOptNew.value = gSkipSymbols[i]; michael@0: elSel.add(elOptNew); michael@0: } michael@0: michael@0: } michael@0: michael@0: function delete_skip_symbol() { michael@0: var skipSymbol = document.getElementById('skipsymbol').value michael@0: } michael@0: michael@0: function add_skip_symbol() { michael@0: michael@0: } michael@0: michael@0: var gFilterChangeCallback = null; michael@0: var gFilterChangeDelay = 1200; michael@0: function filterOnChange() { michael@0: if (gFilterChangeCallback != null) { michael@0: clearTimeout(gFilterChangeCallback); michael@0: gFilterChangeCallback = null; michael@0: } michael@0: michael@0: gFilterChangeCallback = setTimeout(filterUpdate, gFilterChangeDelay); michael@0: } michael@0: function filterUpdate() { michael@0: gFilterChangeCallback = null; michael@0: michael@0: filtersChanged(); michael@0: michael@0: var filterNameInput = document.getElementById("filterName"); michael@0: if (filterNameInput != null) { michael@0: changeFocus(filterNameInput); michael@0: } michael@0: } michael@0: michael@0: // Maps document id to a tooltip description michael@0: var tooltip = { michael@0: "mergeFunctions" : "Ignore line information and merge samples based on function names.", michael@0: "showJank" : "Show only samples with >50ms responsiveness.", michael@0: "showJS" : "Show only samples which involve running chrome or content Javascript code.", michael@0: "mergeUnbranched" : "Collapse unbranched call paths in the call tree into a single node.", michael@0: "filterName" : "Show only samples with a frame containing the filter as a substring.", michael@0: "invertCallstack" : "Invert the callstack (Heavy view) to find the most expensive leaf functions.", michael@0: "upload" : "Upload the full profile to public cloud storage to share with others.", michael@0: "upload_select" : "Upload only the selected view.", michael@0: "download" : "Initiate a download of the full profile.", michael@0: } michael@0: michael@0: function addTooltips() { michael@0: for (var elemId in tooltip) { michael@0: var elem = document.getElementById(elemId); michael@0: if (!elem) michael@0: continue; michael@0: if (elem.parentNode.nodeName.toLowerCase() == "label") michael@0: elem = elem.parentNode; michael@0: elem.title = tooltip[elemId]; michael@0: } michael@0: } michael@0: michael@0: function InfoBar() { michael@0: this._container = document.createElement("div"); michael@0: this._container.id = "infoBar"; michael@0: this._container.className = "sideBar"; michael@0: } michael@0: michael@0: InfoBar.prototype = { michael@0: getContainer: function InfoBar_getContainer() { michael@0: return this._container; michael@0: }, michael@0: display: function InfoBar_display() { michael@0: function getMetaFeatureString() { michael@0: features = "
Stackwalk:
" + (gMeta.stackwalk ? "True" : "False") + "
"; michael@0: features += "
Jank:
" + (gMeta.stackwalk ? "True" : "False") + "
"; michael@0: return features; michael@0: } michael@0: function getPlatformInfo() { michael@0: return gMeta.oscpu + " (" + gMeta.toolkit + ")"; michael@0: } michael@0: var infobar = this._container; michael@0: var infoText = ""; michael@0: michael@0: if (gMeta) { michael@0: infoText += "

Profile Info

\n
\n"; michael@0: infoText += "
Product:
" + gMeta.product + "
"; michael@0: infoText += "
Platform:
" + getPlatformInfo() + "
"; michael@0: infoText += getMetaFeatureString(); michael@0: infoText += "
Interval:
" + gMeta.interval + " ms
"; michael@0: } michael@0: infoText += "

Selection Info

\n
\n"; michael@0: infoText += "
Avg. Responsiveness:
" + avgResponsiveness().toFixed(2) + " ms
\n"; michael@0: infoText += "
Max Responsiveness:
" + maxResponsiveness().toFixed(2) + " ms
\n"; michael@0: infoText += "
Real Interval:
" + effectiveInterval() + "
"; michael@0: infoText += "
\n"; michael@0: infoText += "

Pre Filtering

\n"; michael@0: // Disable for now since it's buggy and not useful michael@0: //infoText += "
\n"; michael@0: michael@0: var filterNameInputOld = document.getElementById("filterName"); michael@0: infoText += "Filter:\n"; michael@0: infoText += "\n"; michael@0: michael@0: infoText += "

Post Filtering

\n"; michael@0: infoText += "\n"; michael@0: infoText += "

View Options

\n"; michael@0: infoText += "
\n"; michael@0: infoText += "
\n"; michael@0: infoText += "
\n"; michael@0: michael@0: infoText += "

Share

\n"; michael@0: infoText += "
No upload in progress

\n"; michael@0: infoText += "\n"; michael@0: infoText += "
\n"; michael@0: infoText += "\n"; michael@0: michael@0: infoText += "

Compare

\n"; michael@0: infoText += "\n"; michael@0: michael@0: //infoText += "
\n"; michael@0: //infoText += "Skip functions:
\n"; michael@0: //infoText += "
" michael@0: //infoText += "
\n"; michael@0: //infoText += "
\n"; michael@0: michael@0: infobar.innerHTML = infoText; michael@0: addTooltips(); michael@0: michael@0: var filterNameInputNew = document.getElementById("filterName"); michael@0: if (filterNameInputOld != null && filterNameInputNew != null) { michael@0: filterNameInputNew.parentNode.replaceChild(filterNameInputOld, filterNameInputNew); michael@0: //filterNameInputNew.value = filterNameInputOld.value; michael@0: } else if (gQueryParamFilterName != null) { michael@0: filterNameInputNew.value = gQueryParamFilterName; michael@0: gQueryParamFilterName = null; michael@0: } michael@0: document.getElementById('compare').onclick = function() { michael@0: openProfileCompare(); michael@0: } michael@0: document.getElementById('upload').onclick = function() { michael@0: promptUploadProfile(false); michael@0: }; michael@0: document.getElementById('download').onclick = downloadProfile; michael@0: document.getElementById('upload_select').onclick = function() { michael@0: promptUploadProfile(true); michael@0: }; michael@0: //document.getElementById('delete_skipsymbol').onclick = delete_skip_symbol; michael@0: //document.getElementById('add_skipsymbol').onclick = add_skip_symbol; michael@0: michael@0: //populate_skip_symbol(); michael@0: } michael@0: } michael@0: michael@0: var gNumSamples = 0; michael@0: var gMeta = null; michael@0: var gSymbols = {}; michael@0: var gFunctions = {}; michael@0: var gResources = {}; michael@0: var gHighlightedCallstack = []; michael@0: var gFrameView = null; michael@0: var gTreeManager = null; michael@0: var gSampleBar = null; michael@0: var gBreadcrumbTrail = null; michael@0: var gHistogramView = null; michael@0: var gDiagnosticBar = null; michael@0: var gVideoPane = null; michael@0: var gPluginView = null; michael@0: var gFileList = null; michael@0: var gInfoBar = null; michael@0: var gMainArea = null; michael@0: var gCurrentlyShownSampleData = null; michael@0: var gSkipSymbols = ["test2", "test1"]; michael@0: var gAppendVideoCapture = null; michael@0: var gQueryParamFilterName = null; michael@0: var gRestoreSelection = null; michael@0: var gReportID = null; michael@0: michael@0: function getTextData() { michael@0: var data = []; michael@0: var samples = gCurrentlyShownSampleData; michael@0: for (var i = 0; i < samples.length; i++) { michael@0: data.push(samples[i].lines.join("\n")); michael@0: } michael@0: return data.join("\n"); michael@0: } michael@0: michael@0: function loadProfileFile(fileList) { michael@0: if (fileList.length == 0) michael@0: return; michael@0: var file = fileList[0]; michael@0: var reporter = enterProgressUI(); michael@0: var subreporters = reporter.addSubreporters({ michael@0: fileLoading: 1000, michael@0: parsing: 1000 michael@0: }); michael@0: michael@0: var reader = new FileReader(); michael@0: reader.onloadend = function () { michael@0: subreporters.fileLoading.finish(); michael@0: loadRawProfile(subreporters.parsing, reader.result); michael@0: }; michael@0: reader.onprogress = function (e) { michael@0: subreporters.fileLoading.setProgress(e.loaded / e.total); michael@0: }; michael@0: reader.readAsText(file, "utf-8"); michael@0: subreporters.fileLoading.begin("Reading local file..."); michael@0: } michael@0: michael@0: function loadLocalStorageProfile(profileKey) { michael@0: var reporter = enterProgressUI(); michael@0: var subreporters = reporter.addSubreporters({ michael@0: fileLoading: 1000, michael@0: parsing: 1000 michael@0: }); michael@0: michael@0: gLocalStorage.getProfile(profileKey, function(profile) { michael@0: subreporters.fileLoading.finish(); michael@0: loadRawProfile(subreporters.parsing, profile, profileKey); michael@0: }); michael@0: subreporters.fileLoading.begin("Reading local storage..."); michael@0: } michael@0: michael@0: function appendVideoCapture(videoCapture) { michael@0: if (videoCapture.indexOf("://") == -1) { michael@0: videoCapture = EIDETICKER_BASE_URL + videoCapture; michael@0: } michael@0: gAppendVideoCapture = videoCapture; michael@0: } michael@0: michael@0: function loadZippedProfileURL(url) { michael@0: var reporter = enterProgressUI(); michael@0: var subreporters = reporter.addSubreporters({ michael@0: fileLoading: 1000, michael@0: parsing: 1000 michael@0: }); michael@0: michael@0: // Crude way to detect if we're using a relative URL or not :( michael@0: if (url.indexOf("://") == -1) { michael@0: url = EIDETICKER_BASE_URL + url; michael@0: } michael@0: reporter.begin("Fetching " + url); michael@0: PROFILERTRACE("Fetch url: " + url); michael@0: michael@0: function onerror(e) { michael@0: PROFILERERROR("zip.js error"); michael@0: PROFILERERROR(JSON.stringify(e)); michael@0: } michael@0: michael@0: zip.workerScriptsPath = "js/zip.js/"; michael@0: zip.createReader(new zip.HttpReader(url), function(zipReader) { michael@0: subreporters.fileLoading.setProgress(0.4); michael@0: zipReader.getEntries(function(entries) { michael@0: for (var i = 0; i < entries.length; i++) { michael@0: var entry = entries[i]; michael@0: PROFILERTRACE("Zip file: " + entry.filename); michael@0: if (entry.filename === "symbolicated_profile.txt") { michael@0: reporter.begin("Decompressing " + url); michael@0: subreporters.fileLoading.setProgress(0.8); michael@0: entry.getData(new zip.TextWriter(), function(profileText) { michael@0: subreporters.fileLoading.finish(); michael@0: loadRawProfile(subreporters.parsing, profileText); michael@0: }); michael@0: return; michael@0: } michael@0: onerror("symbolicated_profile.txt not found in zip file."); michael@0: } michael@0: }); michael@0: }, onerror); michael@0: } michael@0: michael@0: function loadProfileURL(url) { michael@0: var reporter = enterProgressUI(); michael@0: var subreporters = reporter.addSubreporters({ michael@0: fileLoading: 1000, michael@0: parsing: 1000 michael@0: }); michael@0: michael@0: var xhr = new XMLHttpRequest(); michael@0: xhr.open("GET", url, true); michael@0: xhr.responseType = "text"; michael@0: xhr.onreadystatechange = function (e) { michael@0: if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) { michael@0: subreporters.fileLoading.finish(); michael@0: PROFILERLOG("Got profile from '" + url + "'."); michael@0: if (xhr.responseText == null || xhr.responseText === "") { michael@0: subreporters.fileLoading.begin("Profile '" + url + "' is empty. Did you set the CORS headers?"); michael@0: return; michael@0: } michael@0: loadRawProfile(subreporters.parsing, xhr.responseText, url); michael@0: } michael@0: }; michael@0: xhr.onerror = function (e) { michael@0: subreporters.fileLoading.begin("Error fetching profile :(. URL: '" + url + "'. Did you set the CORS headers?"); michael@0: } michael@0: xhr.onprogress = function (e) { michael@0: if (e.lengthComputable && (e.loaded <= e.total)) { michael@0: subreporters.fileLoading.setProgress(e.loaded / e.total); michael@0: } else { michael@0: subreporters.fileLoading.setProgress(NaN); michael@0: } michael@0: }; michael@0: xhr.send(null); michael@0: subreporters.fileLoading.begin("Loading remote file..."); michael@0: } michael@0: michael@0: function loadProfile(rawProfile) { michael@0: if (!rawProfile) michael@0: return; michael@0: var reporter = enterProgressUI(); michael@0: loadRawProfile(reporter, rawProfile); michael@0: } michael@0: michael@0: function loadRawProfile(reporter, rawProfile, profileId) { michael@0: PROFILERLOG("Parse raw profile: ~" + rawProfile.length + " bytes"); michael@0: reporter.begin("Parsing..."); michael@0: if (rawProfile == null || rawProfile.length === 0) { michael@0: reporter.begin("Profile is null or empty"); michael@0: return; michael@0: } michael@0: var startTime = Date.now(); michael@0: var parseRequest = Parser.parse(rawProfile, { michael@0: appendVideoCapture : gAppendVideoCapture, michael@0: profileId: profileId, michael@0: }); michael@0: parseRequest.addEventListener("progress", function (progress, action) { michael@0: if (action) michael@0: reporter.setAction(action); michael@0: reporter.setProgress(progress); michael@0: }); michael@0: parseRequest.addEventListener("finished", function (result) { michael@0: reporter.finish(); michael@0: gMeta = result.meta; michael@0: gNumSamples = result.numSamples; michael@0: gSymbols = result.symbols; michael@0: gFunctions = result.functions; michael@0: gResources = result.resources; michael@0: enterFinishedProfileUI(); michael@0: gFileList.profileParsingFinished(); michael@0: }); michael@0: } michael@0: michael@0: var gImportFromAddonSubreporters = null; michael@0: michael@0: window.addEventListener("message", function messageFromAddon(msg) { michael@0: // This is triggered by the profiler add-on. michael@0: var o = JSON.parse(msg.data); michael@0: switch (o.task) { michael@0: case "importFromAddonStart": michael@0: var totalReporter = enterProgressUI(); michael@0: gImportFromAddonSubreporters = totalReporter.addSubreporters({ michael@0: import: 10000, michael@0: parsing: 1000 michael@0: }); michael@0: gImportFromAddonSubreporters.import.begin("Symbolicating..."); michael@0: break; michael@0: case "importFromAddonProgress": michael@0: gImportFromAddonSubreporters.import.setProgress(o.progress); michael@0: if (o.action != null) { michael@0: gImportFromAddonSubreporters.import.setAction(o.action); michael@0: } michael@0: break; michael@0: case "importFromAddonFinish": michael@0: importFromAddonFinish(o.rawProfile); michael@0: break; michael@0: } michael@0: }); michael@0: michael@0: function importFromAddonFinish(rawProfile) { michael@0: gImportFromAddonSubreporters.import.finish(); michael@0: loadRawProfile(gImportFromAddonSubreporters.parsing, rawProfile); michael@0: } michael@0: michael@0: var gInvertCallstack = false; michael@0: function toggleInvertCallStack() { michael@0: gTreeManager.saveReverseSelectionSnapshot(gJavascriptOnly); michael@0: gInvertCallstack = !gInvertCallstack; michael@0: var startTime = Date.now(); michael@0: viewOptionsChanged(); michael@0: } michael@0: michael@0: var gMergeUnbranched = false; michael@0: function toggleMergeUnbranched() { michael@0: gMergeUnbranched = !gMergeUnbranched; michael@0: viewOptionsChanged(); michael@0: } michael@0: michael@0: var gMergeFunctions = true; michael@0: function toggleMergeFunctions() { michael@0: gMergeFunctions = !gMergeFunctions; michael@0: filtersChanged(); michael@0: } michael@0: michael@0: var gJankOnly = false; michael@0: var gJankThreshold = 50 /* ms */; michael@0: function toggleJank(/* optional */ threshold) { michael@0: // Currently we have no way to change the threshold in the UI michael@0: // once we add this we will need to change the tooltip. michael@0: gJankOnly = !gJankOnly; michael@0: if (threshold != null ) { michael@0: gJankThreshold = threshold; michael@0: } michael@0: filtersChanged(); michael@0: } michael@0: michael@0: var gJavascriptOnly = false; michael@0: function toggleJavascriptOnly() { michael@0: if (gJavascriptOnly) { michael@0: // When going from JS only to non js there's going to be new C++ michael@0: // frames in the selection so we need to restore the selection michael@0: // while allowing non contigous symbols to be in the stack (the c++ ones) michael@0: gTreeManager.setAllowNonContiguous(); michael@0: } michael@0: gJavascriptOnly = !gJavascriptOnly; michael@0: gTreeManager.saveSelectionSnapshot(gJavascriptOnly); michael@0: filtersChanged(); michael@0: } michael@0: michael@0: var gSampleFilters = []; michael@0: function focusOnSymbol(focusSymbol, name) { michael@0: var newFilterChain = gSampleFilters.concat([{type: "FocusedFrameSampleFilter", name: name, focusedSymbol: focusSymbol}]); michael@0: gBreadcrumbTrail.addAndEnter({ michael@0: title: name, michael@0: enterCallback: function () { michael@0: gSampleFilters = newFilterChain; michael@0: filtersChanged(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function focusOnCallstack(focusedCallstack, name, overwriteCallstack) { michael@0: var invertCallstack = gInvertCallstack; michael@0: michael@0: if (overwriteCallstack != null) { michael@0: invertCallstack = overwriteCallstack; michael@0: } michael@0: var filter = { michael@0: type: !invertCallstack ? "FocusedCallstackPostfixSampleFilter" : "FocusedCallstackPrefixSampleFilter", michael@0: name: name, michael@0: focusedCallstack: focusedCallstack, michael@0: appliesToJS: gJavascriptOnly michael@0: }; michael@0: var newFilterChain = gSampleFilters.concat([filter]); michael@0: gBreadcrumbTrail.addAndEnter({ michael@0: title: name, michael@0: enterCallback: function () { michael@0: gSampleFilters = newFilterChain; michael@0: filtersChanged(); michael@0: } michael@0: }) michael@0: } michael@0: michael@0: function focusOnPluginView(pluginName, param) { michael@0: var filter = { michael@0: type: "PluginView", michael@0: pluginName: pluginName, michael@0: param: param, michael@0: }; michael@0: var newFilterChain = gSampleFilters.concat([filter]); michael@0: gBreadcrumbTrail.addAndEnter({ michael@0: title: "Plugin View: " + pluginName, michael@0: enterCallback: function () { michael@0: gSampleFilters = newFilterChain; michael@0: filtersChanged(); michael@0: } michael@0: }) michael@0: } michael@0: michael@0: function viewJSSource(sample) { michael@0: var sourceView = new SourceView(); michael@0: sourceView.setScriptLocation(sample.scriptLocation); michael@0: sourceView.setSource(gMeta.js.source[sample.scriptLocation.scriptURI]); michael@0: gMainArea.appendChild(sourceView.getContainer()); michael@0: michael@0: } michael@0: michael@0: function setHighlightedCallstack(samples, heaviestSample) { michael@0: PROFILERTRACE("highlight: " + samples); michael@0: gHighlightedCallstack = samples; michael@0: gHistogramView.highlightedCallstackChanged(gHighlightedCallstack); michael@0: if (!gInvertCallstack) { michael@0: // Always show heavy michael@0: heaviestSample = heaviestSample.clone().reverse(); michael@0: } michael@0: michael@0: if (gSampleBar) { michael@0: gSampleBar.setSample(heaviestSample); michael@0: } michael@0: } michael@0: michael@0: function enterMainUI() { michael@0: var uiContainer = document.createElement("div"); michael@0: uiContainer.id = "ui"; michael@0: michael@0: gFileList = new FileList(); michael@0: uiContainer.appendChild(gFileList.getContainer()); michael@0: michael@0: //gFileList.addFile(); michael@0: gFileList.loadProfileListFromLocalStorage(); michael@0: michael@0: gInfoBar = new InfoBar(); michael@0: uiContainer.appendChild(gInfoBar.getContainer()); michael@0: michael@0: gMainArea = document.createElement("div"); michael@0: gMainArea.id = "mainarea"; michael@0: uiContainer.appendChild(gMainArea); michael@0: document.body.appendChild(uiContainer); michael@0: michael@0: var profileEntryPane = document.createElement("div"); michael@0: profileEntryPane.className = "profileEntryPane"; michael@0: profileEntryPane.innerHTML = '' + michael@0: '

Upload your profile here:

' + michael@0: '' + michael@0: '

Or, alternatively, enter your profile data here:

' + michael@0: '' + michael@0: '

' + michael@0: ''; michael@0: michael@0: gMainArea.appendChild(profileEntryPane); michael@0: } michael@0: michael@0: function enterProgressUI() { michael@0: var profileProgressPane = document.createElement("div"); michael@0: profileProgressPane.className = "profileProgressPane"; michael@0: michael@0: var progressLabel = document.createElement("a"); michael@0: profileProgressPane.appendChild(progressLabel); michael@0: michael@0: var progressBar = document.createElement("progress"); michael@0: profileProgressPane.appendChild(progressBar); michael@0: michael@0: var totalProgressReporter = new ProgressReporter(); michael@0: totalProgressReporter.addListener(function (r) { michael@0: var progress = r.getProgress(); michael@0: progressLabel.innerHTML = r.getAction(); michael@0: if (isNaN(progress)) michael@0: progressBar.removeAttribute("value"); michael@0: else michael@0: progressBar.value = progress; michael@0: }); michael@0: michael@0: gMainArea.appendChild(profileProgressPane); michael@0: michael@0: Parser.updateLogSetting(); michael@0: michael@0: return totalProgressReporter; michael@0: } michael@0: michael@0: function enterFinishedProfileUI() { michael@0: saveProfileToLocalStorage(); michael@0: michael@0: var finishedProfilePaneBackgroundCover = document.createElement("div"); michael@0: finishedProfilePaneBackgroundCover.className = "finishedProfilePaneBackgroundCover"; michael@0: michael@0: var finishedProfilePane = document.createElement("table"); michael@0: var rowIndex = 0; michael@0: var currRow; michael@0: finishedProfilePane.style.width = "100%"; michael@0: finishedProfilePane.style.height = "100%"; michael@0: finishedProfilePane.border = "0"; michael@0: finishedProfilePane.cellPadding = "0"; michael@0: finishedProfilePane.cellSpacing = "0"; michael@0: finishedProfilePane.borderCollapse = "collapse"; michael@0: finishedProfilePane.className = "finishedProfilePane"; michael@0: setTimeout(function() { michael@0: // Work around a webkit bug. For some reason the table doesn't show up michael@0: // until some actions happen such as focusing this box michael@0: var filterNameInput = document.getElementById("filterName"); michael@0: if (filterNameInput != null) { michael@0: changeFocus(filterNameInput); michael@0: } michael@0: }, 100); michael@0: michael@0: gBreadcrumbTrail = new BreadcrumbTrail(); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer()); michael@0: michael@0: gHistogramView = new HistogramView(); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.insertCell(0).appendChild(gHistogramView.getContainer()); michael@0: michael@0: if (false && gLocation.indexOf("file:") == 0) { michael@0: // Local testing for frameView michael@0: gFrameView = new FrameView(); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.insertCell(0).appendChild(gFrameView.getContainer()); michael@0: } michael@0: michael@0: gDiagnosticBar = new DiagnosticBar(); michael@0: gDiagnosticBar.setDetailsListener(function(details) { michael@0: if (details.indexOf("bug ") == 0) { michael@0: window.open('https://bugzilla.mozilla.org/show_bug.cgi?id=' + details.substring(4)); michael@0: } else { michael@0: var sourceView = new SourceView(); michael@0: sourceView.setText("Diagnostic", js_beautify(details)); michael@0: gMainArea.appendChild(sourceView.getContainer()); michael@0: } michael@0: }); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.insertCell(0).appendChild(gDiagnosticBar.getContainer()); michael@0: michael@0: // For testing: michael@0: //gMeta.videoCapture = { michael@0: // src: "http://videos-cdn.mozilla.net/brand/Mozilla_Firefox_Manifesto_v0.2_640.webm", michael@0: //}; michael@0: michael@0: if (gMeta && gMeta.videoCapture) { michael@0: gVideoPane = new VideoPane(gMeta.videoCapture); michael@0: gVideoPane.onTimeChange(videoPaneTimeChange); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.insertCell(0).appendChild(gVideoPane.getContainer()); michael@0: } michael@0: michael@0: var treeContainerDiv = document.createElement("div"); michael@0: treeContainerDiv.className = "treeContainer"; michael@0: treeContainerDiv.style.width = "100%"; michael@0: treeContainerDiv.style.height = "100%"; michael@0: michael@0: gTreeManager = new ProfileTreeManager(); michael@0: currRow = finishedProfilePane.insertRow(rowIndex++); michael@0: currRow.style.height = "100%"; michael@0: var cell = currRow.insertCell(0); michael@0: cell.appendChild(treeContainerDiv); michael@0: treeContainerDiv.appendChild(gTreeManager.getContainer()); michael@0: michael@0: gSampleBar = new SampleBar(); michael@0: treeContainerDiv.appendChild(gSampleBar.getContainer()); michael@0: michael@0: // sampleBar michael@0: michael@0: gPluginView = new PluginView(); michael@0: //currRow = finishedProfilePane.insertRow(4); michael@0: treeContainerDiv.appendChild(gPluginView.getContainer()); michael@0: michael@0: gMainArea.appendChild(finishedProfilePaneBackgroundCover); michael@0: gMainArea.appendChild(finishedProfilePane); michael@0: michael@0: var currentBreadcrumb = gSampleFilters; michael@0: gBreadcrumbTrail.add({ michael@0: title: gStrings["Complete Profile"], michael@0: enterCallback: function () { michael@0: gSampleFilters = []; michael@0: filtersChanged(); michael@0: } michael@0: }); michael@0: if (currentBreadcrumb == null || currentBreadcrumb.length == 0) { michael@0: gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection); michael@0: viewOptionsChanged(); michael@0: } michael@0: for (var i = 0; i < currentBreadcrumb.length; i++) { michael@0: var filter = currentBreadcrumb[i]; michael@0: var forceSelection = null; michael@0: if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) { michael@0: forceSelection = gRestoreSelection; michael@0: } michael@0: switch (filter.type) { michael@0: case "FocusedFrameSampleFilter": michael@0: focusOnSymbol(filter.name, filter.symbolName); michael@0: gBreadcrumbTrail.enterLastItem(forceSelection); michael@0: case "FocusedCallstackPrefixSampleFilter": michael@0: focusOnCallstack(filter.focusedCallstack, filter.name, false); michael@0: gBreadcrumbTrail.enterLastItem(forceSelection); michael@0: case "FocusedCallstackPostfixSampleFilter": michael@0: focusOnCallstack(filter.focusedCallstack, filter.name, true); michael@0: gBreadcrumbTrail.enterLastItem(forceSelection); michael@0: case "RangeSampleFilter": michael@0: gHistogramView.selectRange(filter.start, filter.end); michael@0: gBreadcrumbTrail.enterLastItem(forceSelection); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Make all focus change events go through this function. michael@0: // This function will mediate the focus changes in case michael@0: // that we're in a compare view. In a compare view an inactive michael@0: // instance of cleopatra should not steal focus from the active michael@0: // cleopatra instance. michael@0: function changeFocus(elem) { michael@0: if (window.comparator_changeFocus) { michael@0: window.comparator_changeFocus(elem); michael@0: } else { michael@0: PROFILERLOG("FOCUS\n\n\n\n\n\n\n\n\n"); michael@0: elem.focus(); michael@0: } michael@0: } michael@0: michael@0: function comparator_receiveSelection(snapshot, frameData) { michael@0: gTreeManager.restoreSerializedSelectionSnapshot(snapshot); michael@0: if (frameData) michael@0: gTreeManager.highlightFrame(frameData); michael@0: viewOptionsChanged(); michael@0: } michael@0: michael@0: function filtersChanged() { michael@0: if (window.comparator_setSelection) { michael@0: // window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), null); michael@0: } michael@0: updateDocumentURL(); michael@0: var data = { symbols: {}, functions: {}, samples: [] }; michael@0: michael@0: gHistogramView.dataIsOutdated(); michael@0: var filterNameInput = document.getElementById("filterName"); michael@0: var updateRequest = Parser.updateFilters({ michael@0: mergeFunctions: gMergeFunctions, michael@0: nameFilter: (filterNameInput && filterNameInput.value) || gQueryParamFilterName || "", michael@0: sampleFilters: gSampleFilters, michael@0: jankOnly: gJankOnly, michael@0: javascriptOnly: gJavascriptOnly michael@0: }); michael@0: var start = Date.now(); michael@0: updateRequest.addEventListener("finished", function (filteredSamples) { michael@0: gCurrentlyShownSampleData = filteredSamples; michael@0: gInfoBar.display(); michael@0: michael@0: if (gSampleFilters.length > 0 && gSampleFilters[gSampleFilters.length-1].type === "PluginView") { michael@0: start = Date.now(); michael@0: gPluginView.display(gSampleFilters[gSampleFilters.length-1].pluginName, gSampleFilters[gSampleFilters.length-1].param, michael@0: gCurrentlyShownSampleData, gHighlightedCallstack); michael@0: } else { michael@0: gPluginView.hide(); michael@0: } michael@0: }); michael@0: michael@0: var histogramRequest = Parser.calculateHistogramData(); michael@0: histogramRequest.addEventListener("finished", function (data) { michael@0: start = Date.now(); michael@0: gHistogramView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack); michael@0: if (gFrameView) michael@0: gFrameView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack); michael@0: }); michael@0: michael@0: if (gDiagnosticBar) { michael@0: var diagnosticsRequest = Parser.calculateDiagnosticItems(gMeta); michael@0: diagnosticsRequest.addEventListener("finished", function (diagnosticItems) { michael@0: start = Date.now(); michael@0: gDiagnosticBar.display(diagnosticItems); michael@0: }); michael@0: } michael@0: michael@0: viewOptionsChanged(); michael@0: } michael@0: michael@0: function viewOptionsChanged() { michael@0: gTreeManager.dataIsOutdated(); michael@0: var filterNameInput = document.getElementById("filterName"); michael@0: var updateViewOptionsRequest = Parser.updateViewOptions({ michael@0: invertCallstack: gInvertCallstack, michael@0: mergeUnbranched: gMergeUnbranched michael@0: }); michael@0: updateViewOptionsRequest.addEventListener("finished", function (calltree) { michael@0: var start = Date.now(); michael@0: gTreeManager.display(calltree, gSymbols, gFunctions, gResources, gMergeFunctions, filterNameInput && filterNameInput.value); michael@0: }); michael@0: } michael@0: michael@0: function loadQueryData(queryDataOriginal) { michael@0: var isFiltersChanged = false; michael@0: var queryData = {}; michael@0: for (var i in queryDataOriginal) { michael@0: queryData[i] = unQueryEscape(queryDataOriginal[i]); michael@0: } michael@0: if (queryData.search) { michael@0: gQueryParamFilterName = queryData.search; michael@0: isFiltersChanged = true; michael@0: } michael@0: if (queryData.jankOnly) { michael@0: gJankOnly = queryData.jankOnly; michael@0: isFiltersChanged = true; michael@0: } michael@0: if (queryData.javascriptOnly) { michael@0: gJavascriptOnly = queryData.javascriptOnly; michael@0: isFiltersChanged = true; michael@0: } michael@0: if (queryData.mergeUnbranched) { michael@0: gMergeUnbranched = queryData.mergeUnbranched; michael@0: isFiltersChanged = true; michael@0: } michael@0: if (queryData.invertCallback) { michael@0: gInvertCallstack = queryData.invertCallback; michael@0: isFiltersChanged = true; michael@0: } michael@0: if (queryData.report) { michael@0: gReportID = queryData.report; michael@0: } michael@0: if (queryData.filter) { michael@0: var filterChain = JSON.parse(queryData.filter); michael@0: gSampleFilters = filterChain; michael@0: } michael@0: if (queryData.selection) { michael@0: var selection = queryData.selection; michael@0: gRestoreSelection = selection; michael@0: } michael@0: michael@0: if (isFiltersChanged) { michael@0: //filtersChanged(); michael@0: } michael@0: } michael@0: michael@0: function unQueryEscape(str) { michael@0: return decodeURIComponent(str); michael@0: } michael@0: michael@0: function queryEscape(str) { michael@0: return encodeURIComponent(encodeURIComponent(str)); michael@0: } michael@0: michael@0: function updateDocumentURL() { michael@0: location.hash = getDocumentHashString(); michael@0: return document.location; michael@0: } michael@0: michael@0: function getDocumentHashString() { michael@0: var query = ""; michael@0: if (gReportID) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "report=" + queryEscape(gReportID); michael@0: } michael@0: if (document.getElementById("filterName") != null && michael@0: document.getElementById("filterName").value != null && michael@0: document.getElementById("filterName").value != "") { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "search=" + queryEscape(document.getElementById("filterName").value); michael@0: } michael@0: // For now don't restore the view rest michael@0: return query; michael@0: if (gJankOnly) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "jankOnly=" + queryEscape(gJankOnly); michael@0: } michael@0: if (gJavascriptOnly) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "javascriptOnly=" + queryEscape(gJavascriptOnly); michael@0: } michael@0: if (gMergeUnbranched) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "mergeUnbranched=" + queryEscape(gMergeUnbranched); michael@0: } michael@0: if (gInvertCallstack) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "invertCallback=" + queryEscape(gInvertCallstack); michael@0: } michael@0: if (gSampleFilters && gSampleFilters.length != 0) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "filter=" + queryEscape(JSON.stringify(gSampleFilters)); michael@0: } michael@0: if (gTreeManager.hasNonTrivialSelection()) { michael@0: if (query != "") michael@0: query += "&"; michael@0: query += "selection=" + queryEscape(gTreeManager.serializeCurrentSelectionSnapshot()); michael@0: } michael@0: if (!gReportID) { michael@0: query = "uploadProfileFirst!"; michael@0: } michael@0: michael@0: return query; michael@0: } michael@0: