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