Files
palemoon27/toolkit/devtools/profiler/ui-profile.js
T
roytam1 a7bc0406ee import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1155006: Fix unified build sensitivities in js/src/jit. r=shu (6e24e1af1)
- Bug 1162766 - Fix more bad implicit constructors in js. r=evilpie (39961b06d)
- Bug 1151606 - Stream atoms instead of raw pointers for native functions in tracked optimizations. (r=djvj) (7641ee9d6)
- pointer style (540728104)
- Bug 1154997 - Deal with self-hosted builtins when stringifying tracked optimization type info. (r=djvj) (92f9a54e6)
- pointer style (45742d820)
- Bug 1154115 - Rewrite the JSAPI profiling API to use a FrameHandle, as to avoid multiple lookups in JitcodeGlobalTable. (r=djvj) (4d202ba9e)
- Bug 1119023 - Timeline in new perf tool should filter out markers, r=jsantell (6fc1a8bbe)
- Bug 1132755 - Allocations tree has a bunch of columns that don't make sense, r=jsantell (1ae9ee7e2)
- Bug 1142744 - Fix tests broken by bug 1132755, r=me (cc495f72d)
- Bug 1133058 - OptionsView button, when clicked, should have an 'open' attribute. r=vp (65a78d896)
- Bug 1132765 - Pass through performance memory options for 'probability' and 'maxLogLength' from the front to the memory actor. r=vp (f9bbbe098)
- Bug 1141817 - Fix yield statement to correctly return memory actor state so that the performance tool can poll for allocations during recording. r=vp (2ddf7d528)
- Bug 1141817 - Followup to fix additional intermittents like bug 1132370, r=vp (eab962f01)
- Bug 1142748 - Use a single configuration for starting/stopping recordings, r=jsantell (0181b319a)
- bit of Bug 879008 - New UI for the sampling Profiler (32c4d0fe8)
- Bug 1123815 - Merge gum into fx-team to enable the Performance++ tool, r=me (84aabbd61)
- Bug 1143933 - Expose raw JIT optimization information in performance front end. r=vp,shu (f68a6df50)
- Bug 1143915 - Allow multiple calls to memory and timeline actor's start methods, to return the local start time from the actor. r=vp (028ac4187)
- Bug 978948 - Add animation generator support for setTimeout in the canvas debugger. r=vp (42d623452)
- Bug 985488 - Allow canvas debugger to time out and stop recording frames. Canvas debugger 'wait' style now matches other media styles. Update labels in canvas debugger to explicitly state that it's waiting for rAF cycles, rather than appearing as if something went wrong. r=vporof (b4670d843)
- Bug 1144163 - Add a rulers highlighter; added unit test. r=pbrosset (5811a67d0)
- Bug 1144163 - Add a rulers highlighter; added highlighter. r=pbrosset (779f88bdd)
- Bug 1144163 - Add a rulers highlighter; added gcli command and button. r=pbrosset (d0d13da51)
- Bug 1110550 - Enable performance overview graphs to rerender and change on devtools theme switch. r=vp (bd91ca7cf)
- Bug 1149630 - Performance graphs should inherit from a common graph and be similarly styled. r=vporof (481c841f1)
- Bug 1150733 - Correctly internationalize jit samples label. r=vporof, r=flod (b5612d1a6)
- Bug 1137518 - FlameGraph's destroy function should be async, r=jsantell (f103e4c15)
- Bug 1137503 - Avoid potential infinite loops in `findOptimalTickInterval` functions, r=jsantell (95df6c04a)
- Bug 1121194 - Support vertical panning for the flamegraph in the new performance tool, r=jsantell (06241b5b2)
- Bug 1121180 - Support dark theme in flamecharts for the performance tool. r=vp (c76abe237)
-  Bug 1059308 - Add Target.isTabActor to tell if the remote tab actor supports attach/detach requests. r=jryans (e03dcef93)
- Bug 1132370 - Wrong State: Expected 'attached', but current state is 'detached', r=jsantell (e884e8db9)
- No Bug - Fix documentation for _startMemory and _stopMemory in performance/modules/front.js, r=me DONTBUILD (d79090b31)
- Bug 1147656 - Remove duplicate profiler defaults from the front end and just use on the server. r=vp (35c015dd0)
- Bug 1046234 - Add more DevTools Telemetry measures (display size etc) r=pbrosset, r=gijs (a235681b4)
- actually package telemetry.js (e8f3a58a4)
- Bug 1077464 - Wire console.profile/profileEnd to the new performance tool. Move most of the recording-model logic from the front end into the PerformanceFront and PerformanceActorConnection so it can manage recordings without the front end being viewed. r=vp,jryans,pbrosset (eef8e18c3)
- Bug 1144363 - Fix this._telemetry is undefined in gDevTools. r=bgrins (ba7d02902)
- init telemetry, missing parts of Bug 866642 (1e70df975)
- do not use sysctl.h on Linux anymore, since it is not provided by recent glibc (b2467d7ce)
- clean up some telemetry issues of histogram, parts of  Bug 974171 (d30c8d0ad)
- move devtools to browser - part 1 (9a856f452)
- Bug 1291423: Explicitly qualify the destructor call that we invoke in Maybe::reset. r=Waldo (944904a7d)
- Bug 1148075 - Dynamically add XUL commands for the debugger frontend. r=vporof (60bc91f8f)
- Bug 1147945 - Let the profiler's buffer size and sample rate be configurable via prefs. r=vp (acebcbdd9)
- Bug 1124326 - Improve packageDir support for Cordova. r=ochameau (4b736580a)
- Bug 1124326 - Support Cordova w/o build file. r=ochameau (d4b50aeae)
- Bug 1134029 - Fix 'Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIURI.host]' timeouts, r=jsantell (18d16a5d0)
- Bug 1147806 - Content frame filtering is confused when profiling FxOS, r=jsantell (b3c62c552)
- Bug 1108843 - Generalize platform data in call tree view when platform data is hidden. r=vporof (354553ed7)
- Bug 1138928 - Display only function name and file, instead of full url, in flame graphs. r=vp (4169689c1)
- Bug 1152605 - Should not show host names for chrome URIs. r=vporof (c6dcf9e78)
- Bug 1147604 - Inverted call trees should list (root) as leaves. r=jsantell (01768267f)
- Bug 1075450 - Disable some Awesomebar actions for private windows r=mak (21d5586e7)
- Bug 1120616 - Part 1: Implement filter styles in rule view r=bgrins (b66ee0282)
- Bug 1120616 - Part 2: Add unit tests for filter styles in rule view r=bgrins (2892503d8)
- Bug 1120616 - Part 3: Adjust the styles in the computed view's filter style search r=bgrins (41f8fae1b)
- Bug 1120616 - Part 4: Add textbox context menu for rule and computed view r=bgrins (ff3f868ad)
- Bug 1120616 - Part 5: Refactor style inspector tests to use synthesizeKeys r=bgrins (41db021d7)
- Bug 1102219 - Part 5: Replace more `String.prototype.contains` with `String.prototype.includes` in chrome code. r=till (86ed03588)
- Bug 1154018 - Check to see that nsIURI's host exists when parsing location for framenodes, and cache failures. r=vp (9494d52e7)
- Bug 1160691 - Optimize FrameUtils.isContent and FrameUtils.parseLocation. (r=jsantell) (09118fd5d)
- Bug 1154115 - Make the performance devtool handle the new profiler JSON format. (r=jsantell,vporof) (e3e5be7a4)
- Bug 1059308 - Make frame selection button to work in browser toolbox. r=jryans,past (30fe6e61e)
- Bug 1059308 - Fix tests to support chrome actor. r=jryans (01cf3926c)
- Bug 1147042 - Rename attachProcess to getProcess. r=ochameau (0393ffb80)
- Bug 1145824 - Profiler actor and performance tools now handle passing in a startTime to filter out SPS profiles on platform rather than client. r=vp,fitzgen (f225116ba)
- Bug 1157718 - Do not use Array.prototype.includes in production code that leaves nightly in performance tool. r=fitzgen (ff06d284e)
- Bug 1140728 - Rename 'Memory' to 'Allocations' in the new performance tool. r=jsantell (f584e720f)
- Bug 1137500 - Always wait for the overview to be rendered in tests after a recording finishes, unless otherwise specified, r=jsantell (59825e179)
- Bug 1137487 - AbstractCanvasGraph's destroy function should be async, r=jsantell (a17ae00b5)
- Bug 1132758 - Performance feature visibility now based on a per recording-basis, dependent on features enabled and server support. r=vp (0d080a7c2)
- Bug 1147035 - Make DeveloperToolbar.jsm use the gBrowser.contentDocumentAsCPOW shortcut. r=past. (251eff125)
- Bug 1151168 - Don't flush profiled threads that are pending deletion on JS shutdown and don't delete expired markers when resetting the profile buffer. (r=djvj) (90721313a)
2020-08-29 08:02:13 +08:00

835 lines
28 KiB
JavaScript

/* 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";
/**
* Functions handling the profile inspection UI, showing the framerate and
* cateogry graphs, along with a call tree view.
*
* A profile view is a tabbed browser, so recording data will be displayed in
* tabs. Certain messages like 'Loading' or 'Recording...' may also be shown.
*/
let ProfileView = {
/**
* Initialization function, called when the tool is started.
*/
initialize: function() {
this._tabs = $("#profile-content tabs");
this._panels = $("#profile-content tabpanels");
this._tabTemplate = $("#profile-content-tab-template");
this._panelTemplate = $("#profile-content-tabpanel-template");
this._newtabButton = $("#profile-newtab-button");
this._invertTree = $("#invert-tree");
this._recordingInfoByPanel = new WeakMap();
this._framerateGraphByPanel = new Map();
this._categoriesGraphByPanel = new Map();
this._callTreeRootByPanel = new Map();
this._onTabSelect = this._onTabSelect.bind(this);
this._onNewTabClick = this._onNewTabClick.bind(this);
this._onInvertTree = this._onInvertTree.bind(this);
this._onGraphLegendSelection = this._onGraphLegendSelection.bind(this);
this._onGraphMouseUp = this._onGraphMouseUp.bind(this);
this._onGraphScroll = this._onGraphScroll.bind(this);
this._onCallViewFocus = this._onCallViewFocus.bind(this);
this._onCallViewLink = this._onCallViewLink.bind(this);
this._onCallViewZoom = this._onCallViewZoom.bind(this);
this._panels.addEventListener("select", this._onTabSelect, false);
this._newtabButton.addEventListener("click", this._onNewTabClick, false);
this._invertTree.addEventListener("command", this._onInvertTree, false);
},
/**
* Destruction function, called when the tool is closed.
*/
destroy: function() {
this.removeAllTabs();
this._panels.removeEventListener("select", this._onTabSelect, false);
this._newtabButton.removeEventListener("click", this._onNewTabClick, false);
this._invertTree.removeEventListener("command", this._onInvertTree, false);
},
/**
* Shows a message detailing that there are is no data available.
* The tabbed browser will also be hidden.
*/
showEmptyNotice: function() {
$("#profile-pane").selectedPanel = $("#empty-notice");
window.emit(EVENTS.EMPTY_NOTICE_SHOWN);
},
/**
* Shows a message detailing that a recording is currently in progress.
* The tabbed browser will also be hidden.
*/
showRecordingNotice: function() {
$("#profile-pane").selectedPanel = $("#recording-notice");
window.emit(EVENTS.RECORDING_NOTICE_SHOWN);
},
/**
* Shows a message detailing that a finished recording is being loaded.
* The tabbed browser will also be hidden.
*/
showLoadingNotice: function() {
$("#profile-pane").selectedPanel = $("#loading-notice");
window.emit(EVENTS.LOADING_NOTICE_SHOWN);
},
/**
* Shows the tabbed browser displaying recording data.
*/
showTabbedBrowser: function() {
$("#profile-pane").selectedPanel = $("#profile-content");
window.emit(EVENTS.TABBED_BROWSER_SHOWN);
},
/**
* Selects the tab at the specified index in this tabbed browser.
*
* @param number tabIndex
* The index of the tab to select. If no tab is available at the
* specified index, all tabs will be deselected.
*/
selectTab: function(tabIndex) {
$("#profile-content").selectedIndex = tabIndex;
},
/**
* Adds an empty tab in this tabbed browser.
*
* @return number
* The newly created tab's index.
*/
addTab: function() {
let tab = this._tabs.appendChild(this._tabTemplate.cloneNode(true));
let panel = this._panels.appendChild(this._panelTemplate.cloneNode(true));
// "Uncover" the tab via a CSS animation.
tab.removeAttribute("covered");
let tabIndex = this._tabs.itemCount - 1;
return tabIndex;
},
/**
* Sets the title of a tab in this tabbed browser.
*
* @param number tabIndex
* The index of the tab to name.
* @param number beginAt, endAt
* The 'start → stop' components of the tab title.
*/
nameTab: function(tabIndex, beginAt, endAt) {
let tab = this._getTab(tabIndex);
let a = L10N.numberWithDecimals(beginAt, 2);
let b = L10N.numberWithDecimals(endAt, 2);
let labelNode = $(".tab-title-label", tab);
labelNode.setAttribute("value", L10N.getFormatStr("profile.tab", a, b));
},
/**
* Populates the panel for a tab in this tabbed browser with the provided
* recording data.
*
* @param number tabIndex
* The index of the tab to populate.
* @param object recordingData
* The profiler and refresh driver ticks data received from the front.
* @param number beginAt
* The earliest time in the recording data to start at (in milliseconds).
* @param number endAt
* The latest time in the recording data to end at (in milliseconds).
* @param object options
* Additional options supported by this operation.
* @see ProfileView._populatePanelWidgets
*/
populateTab: Task.async(function*(tabIndex, recordingData, beginAt, endAt, options) {
let tab = this._getTab(tabIndex);
let panel = this._getPanel(tabIndex);
if (!tab || !panel) {
return;
}
this._recordingInfoByPanel.set(panel, {
recordingData: recordingData,
displayRange: { beginAt: beginAt, endAt: endAt }
});
let { profilerData, ticksData } = recordingData;
let categoriesData = RecordingUtils.plotCategoriesFor(profilerData, beginAt, endAt);
let framerateData = RecordingUtils.plotFramerateFor(ticksData, beginAt, endAt);
RecordingUtils.syncCategoriesWithFramerate(categoriesData, framerateData);
yield this._populatePanelWidgets(panel, {
profilerData: profilerData,
framerateData: framerateData,
categoriesData: categoriesData
}, beginAt, endAt, options);
}),
/**
* Adds a new tab in this tabbed browser, populates it with the provided
* recording data and automatically selects it.
*
* @param object recordingData
* The profiler and refresh driver ticks data received from the front.
* @param number beginAt
* The earliest time in the recording data to start at (in milliseconds).
* @param number endAt
* The latest time in the recording data to end at (in milliseconds).
* @param object options
* Additional options supported by this operation.
* @see ProfileView._populatePanelWidgets
*/
addTabAndPopulate: Task.async(function*(recordingData, beginAt, endAt, options) {
let tabIndex = this.addTab();
this.nameTab(tabIndex, beginAt, endAt);
// Wait for a few milliseconds before presenting the recording data,
// to allow the 'Loading' panel to finish being drawn (if there is one).
yield DevToolsUtils.waitForTime(RECORDING_DATA_DISPLAY_DELAY);
yield this.populateTab(tabIndex, recordingData, beginAt, endAt, options);
this.selectTab(tabIndex);
}),
/**
* Removes all tabs and corresponding views from this tabbed browser.
*/
removeAllTabs: function() {
for (let [, graph] of this._framerateGraphByPanel) graph.destroy();
for (let [, graph] of this._categoriesGraphByPanel) graph.destroy();
for (let [, root] of this._callTreeRootByPanel) root.remove();
this._recordingInfoByPanel = new WeakMap();
this._framerateGraphByPanel.clear();
this._categoriesGraphByPanel.clear();
this._callTreeRootByPanel.clear();
while (this._tabs.hasChildNodes()) {
this._tabs.firstChild.remove();
}
while (this._panels.hasChildNodes()) {
this._panels.firstChild.remove();
}
},
/**
* Removes all tabs exclusively after the one at the specified index.
*
* @param number tabIndex
* The "leftmost" tab to still keep. Remaining tabs will be removed.
*/
removeTabsAfter: function(tabIndex) {
tabIndex++;
while (tabIndex < this._tabs.itemCount) {
let tab = this._getTab(tabIndex);
let panel = this._getPanel(tabIndex);
this._framerateGraphByPanel.delete(panel);
this._categoriesGraphByPanel.delete(panel);
this._callTreeRootByPanel.delete(panel);
tab.remove();
panel.remove();
}
},
/**
* Gets the total number of tabs displayed in this tabbed browser.
* @return number
*/
get tabCount() {
let tabs = this._tabs.childNodes.length;
let tabpanels = this._panels.childNodes.length;
if (tabs != tabpanels) {
throw "The number of tabs isn't equal to the number of tabpanels.";
}
return tabs;
},
/**
* Adds a new tab in this tabbed browser, populates it based on the current
* selection range in the displayed data and automatically selects it.
*/
_spawnTabFromSelection: Task.async(function*() {
let { recordingData } = this._getRecordingInfo();
let categoriesGraph = this._getCategoriesGraph();
// A selection is assumed to be available in the current tab.
let { min: beginAt, max: endAt } = categoriesGraph.getMappedSelection();
// Hide the "new tab" button since a selection won't implicitly be made
// in the newly created tab.
this._newtabButton.hidden = true;
yield this.addTabAndPopulate(recordingData, beginAt, endAt);
// Signal that a new tab was spawned from a graph's selection.
window.emit(EVENTS.TAB_SPAWNED_FROM_SELECTION);
}),
/**
* Adds a new tab in this tabbed browser, populates it based on the provided
* frame node and automatically selects it.
*
* @param FrameNode frameNode
* Information about the function call node in the tree.
*/
_spawnTabFromFrameNode: Task.async(function*(frameNode) {
let { recordingData } = this._getRecordingInfo();
let sampleTimes = frameNode.sampleTimes;
let beginAt = sampleTimes[0].start;
let endAt = sampleTimes[sampleTimes.length - 1].end;
// Hide the "new tab" button since a selection won't implicitly be made
// in the newly created tab.
this._newtabButton.hidden = true;
yield this.addTabAndPopulate(recordingData, beginAt, endAt, { skipCallTree: true });
this._populateCallTreeFromFrameNode(this._getPanel(), frameNode);
// Signal that a new tab was spawned from a node in the call tree.
window.emit(EVENTS.TAB_SPAWNED_FROM_FRAME_NODE);
}),
/**
* Filters the recording data displayed in the call tree view to match
* the current selection range in the graphs.
*
* @param object options
* Additional options supported by this operation.
* @see ProfileView._populatePanelWidgets
*/
_rebuildTreeFromSelection: function(options) {
let { recordingData, displayRange } = this._getRecordingInfo();
let categoriesGraph = this._getCategoriesGraph();
let selectedPanel = this._getPanel();
// If there's no selection, get the original display range and hide the
// "new tab" button.
if (!categoriesGraph.hasSelection()) {
let { beginAt, endAt } = displayRange;
this._newtabButton.hidden = true;
this._populateCallTree(selectedPanel, recordingData.profilerData, beginAt, endAt, options);
}
// Otherwise, just get the selected display range and only show the
// "new tab" button if the selection is wide enough.
else {
let { min: beginAt, max: endAt } = categoriesGraph.getMappedSelection();
this._newtabButton.hidden = (endAt - beginAt) < GRAPH_ZOOM_MIN_TIMESPAN;
this._populateCallTree(selectedPanel, recordingData.profilerData, beginAt, endAt, options);
}
},
/**
* Highlights certain areas in the categories graph to match the currently
* selected frame node's sample times in the tree view.
*
* @param ThreadNode | FrameNode frameNode
* The root node data source for this tree.
*/
_highlightAreaFromFrameNode: function(frameNode) {
let categoriesGraph = this._getCategoriesGraph();
if (categoriesGraph) {
categoriesGraph.setMask(frameNode.sampleTimes);
}
},
/**
* Populates all the widgets in the specified tab's panel with the provided
* data. The already existing widgets will be removed.
*
* @param nsIDOMNode panel
* The <panel> element in this <tabbox>.
* @param object dataSource
* The profiler, framerate and categories data source.
* @param number beginAt
* The earliest allowed time for tree nodes (in milliseconds).
* @param number endAt
* The latest allowed time for tree nodes (in milliseconds).
* @param object options
* Additional options supported by this operation:
* - skipCallTree: true if the call tree should not be populated
* - skipCallTreeFocus: true if the root node shouldn't be focused
*/
_populatePanelWidgets: Task.async(function*(panel, dataSource, beginAt, endAt, options = {}) {
let { profilerData, framerateData, categoriesData } = dataSource;
let framerateGraph = yield this._populateFramerateGraph(panel, framerateData, beginAt);
let categoriesGraph = yield this._populateCategoriesGraph(panel, categoriesData, beginAt);
CanvasGraphUtils.linkAnimation(framerateGraph, categoriesGraph);
CanvasGraphUtils.linkSelection(framerateGraph, categoriesGraph);
if (!options.skipCallTree) {
this._populateCallTree(panel, profilerData, beginAt, endAt, options);
}
}),
/**
* Populates the framerate graph in the specified tab's panel with the
* provided data. The already existing graph will be removed.
*
* @param nsIDOMNode panel
* The <panel> element in this <tabbox>.
* @param object framerateData
* The data source for this graph.
* @param number beginAt
* The earliest time in the recording data to start at (in milliseconds).
*/
_populateFramerateGraph: Task.async(function*(panel, framerateData, beginAt) {
let oldGraph = this._getFramerateGraph(panel);
if (oldGraph) {
oldGraph.destroy();
}
// Don't create a graph if there's not enough data to show.
if (!framerateData || framerateData.length < 2) {
return null;
}
let graph = new LineGraphWidget($(".framerate", panel), {
metric: L10N.getStr("graphs.fps")
});
graph.fixedHeight = FRAMERATE_GRAPH_HEIGHT;
graph.dataOffsetX = beginAt;
yield graph.setDataWhenReady(framerateData);
graph.on("mouseup", this._onGraphMouseUp);
graph.on("scroll", this._onGraphScroll);
this._framerateGraphByPanel.set(panel, graph);
return graph;
}),
/**
* Populates the categories graph in the specified tab's panel with the
* provided data. The already existing graph will be removed.
*
* @param nsIDOMNode panel
* The <panel> element in this <tabbox>.
* @param object categoriesData
* The data source for this graph.
* @param number beginAt
* The earliest time in the recording data to start at (in milliseconds).
*/
_populateCategoriesGraph: Task.async(function*(panel, categoriesData, beginAt) {
let oldGraph = this._getCategoriesGraph(panel);
if (oldGraph) {
oldGraph.destroy();
}
// Don't create a graph if there's not enough data to show.
if (!categoriesData || categoriesData.length < 2) {
return null;
}
let graph = new BarGraphWidget($(".categories", panel));
graph.fixedHeight = CATEGORIES_GRAPH_HEIGHT;
graph.minBarsWidth = CATEGORIES_GRAPH_MIN_BARS_WIDTH;
graph.format = CATEGORIES.sort((a, b) => a.ordinal > b.ordinal);
graph.dataOffsetX = beginAt;
yield graph.setDataWhenReady(categoriesData);
graph.on("legend-selection", this._onGraphLegendSelection);
graph.on("mouseup", this._onGraphMouseUp);
graph.on("scroll", this._onGraphScroll);
this._categoriesGraphByPanel.set(panel, graph);
return graph;
}),
/**
* Populates the call tree view in the specified tab's panel with the
* provided data. The already existing tree will be removed.
*
* @param nsIDOMNode panel
* The <panel> element in this <tabbox>.
* @param object profilerData
* The data source for this tree.
* @param number startTime
* The earliest time in the data source to start at (in milliseconds).
* @param number endTime
* The latest time in the data source to end at (in milliseconds).
* @param object options
* Additional options supported by this operation.
* @see ProfileView._populatePanelWidgets
*/
_populateCallTree: function(panel, profilerData, startTime, endTime, options = {}) {
let threadSamples = profilerData.profile.threads[0].samples;
let contentOnly = !Prefs.showPlatformData;
let invertChecked = this._invertTree.hasAttribute("checked");
let threadNode = new ThreadNode(threadSamples,
{ startTime, endTime, contentOnly, invertChecked });
// If we have an empty profile (no samples), then don't invert the tree, as
// it would hide the root node and a completely blank call tree space can be
// mis-interpreted as an error.
options.inverted = invertChecked && threadNode.samples > 0;
this._populateCallTreeFromFrameNode(panel, threadNode, options);
},
/**
* Populates the call tree view in the specified tab's panel with the
* provided frame node. The already existing tree will be removed.
*
* @param nsIDOMNode panel
* The <panel> element in this <tabbox>.
* @param ThreadNode | FrameNode frameNode
* The root node data source for this tree.
* @param object options
* Additional options supported by this operation.
* @see ProfileView._populatePanelWidgets
*/
_populateCallTreeFromFrameNode: function(panel, frameNode, options = {}) {
let oldRoot = this._getCallTreeRoot(panel);
if (oldRoot) {
oldRoot.remove();
}
let callTreeRoot = new CallView({
autoExpandDepth: options.inverted ? 0 : undefined,
frame: frameNode,
hidden: options.inverted,
inverted: options.inverted
});
callTreeRoot.on("focus", this._onCallViewFocus);
callTreeRoot.on("link", this._onCallViewLink);
callTreeRoot.on("zoom", this._onCallViewZoom);
callTreeRoot.attachTo($(".call-tree-cells-container", panel));
if (!options.skipCallTreeFocus) {
callTreeRoot.focus();
}
let contentOnly = !Prefs.showPlatformData;
callTreeRoot.toggleCategories(!contentOnly);
this._callTreeRootByPanel.set(panel, callTreeRoot);
},
/**
* Shortcuts for accessing the recording info or widgets for a <panel>.
* @param nsIDOMNode panel [optional]
* @return object
*/
_getRecordingInfo: function(panel = this._getPanel()) {
return this._recordingInfoByPanel.get(panel);
},
_getFramerateGraph: function(panel = this._getPanel()) {
return this._framerateGraphByPanel.get(panel);
},
_getCategoriesGraph: function(panel = this._getPanel()) {
return this._categoriesGraphByPanel.get(panel);
},
_getCallTreeRoot: function(panel = this._getPanel()) {
return this._callTreeRootByPanel.get(panel);
},
_getTab: function(tabIndex = this._getSelectedIndex()) {
return this._tabs.childNodes[tabIndex];
},
_getPanel: function(tabIndex = this._getSelectedIndex()) {
return this._panels.childNodes[tabIndex];
},
_getSelectedIndex: function() {
return $("#profile-content").selectedIndex;
},
/**
* Listener handling the tab "select" event in this container.
*/
_onTabSelect: function() {
let categoriesGraph = this._getCategoriesGraph();
if (categoriesGraph) {
this._newtabButton.hidden = !categoriesGraph.hasSelection();
} else {
this._newtabButton.hidden = true;
}
this.removeTabsAfter(this._getSelectedIndex());
},
/**
* Listener handling the new tab "click" event in this container.
*/
_onNewTabClick: function() {
this._spawnTabFromSelection();
},
_onInvertTree: function() {
this._rebuildTreeFromSelection();
},
/**
* Listener handling the "legend-selection" event for the graphs in this container.
*/
_onGraphLegendSelection: function() {
this._rebuildTreeFromSelection({ skipCallTreeFocus: true });
},
/**
* Listener handling the "mouseup" event for the graphs in this container.
*/
_onGraphMouseUp: function() {
this._rebuildTreeFromSelection();
},
/**
* Listener handling the "scroll" event for the graphs in this container.
*/
_onGraphScroll: function() {
setNamedTimeout("graph-scroll", GRAPH_SCROLL_EVENTS_DRAIN, () => {
this._rebuildTreeFromSelection();
});
},
/**
* Listener handling the "focus" event for the call tree in this container.
*/
_onCallViewFocus: function(event, treeItem) {
setNamedTimeout("graph-focus", CALL_VIEW_FOCUS_EVENTS_DRAIN, () => {
this._highlightAreaFromFrameNode(treeItem.frame);
});
},
/**
* Listener handling the "link" event for the call tree in this container.
*/
_onCallViewLink: function(event, treeItem) {
let { url, line } = treeItem.frame.getInfo();
viewSourceInDebugger(url, line);
},
/**
* Listener handling the "zoom" event for the call tree in this container.
*/
_onCallViewZoom: function(event, treeItem) {
this._spawnTabFromFrameNode(treeItem.frame);
}
};
/**
* Utility functions handling recording data.
*/
let RecordingUtils = {
_frameratePlotsCache: new WeakMap(),
/**
* Creates an appropriate data source to be displayed in a categories graph
* from on the provided profiler data.
*
* @param object profilerData
* The profiler data received from the front.
* @param number beginAt
* The earliest time in the profiler data to start at (in milliseconds).
* @param number endAt
* The latest time in the profiler data to end at (in milliseconds).
* @return array
* A data source useful for a BarGraphWidget.
*/
plotCategoriesFor: function(profilerData, beginAt, endAt) {
let categoriesData = [];
let profile = profilerData.profile;
let samples = profile.threads[0].samples;
// Accumulate the category of each frame for every sample.
for (let { frames, time } of samples) {
if (!time || time < beginAt || time > endAt) continue;
let blocks = [];
for (let { category: bitmask } of frames) {
if (!bitmask) continue;
let category = CATEGORY_MAPPINGS[bitmask];
// Guard against categories that aren't found in the frontend mappings.
// This usually means that a new category was added in the platform,
// but browser/devtools/profiler/utils/global.js wasn't updated yet.
if (!category) {
category = CATEGORY_MAPPINGS[CATEGORY_OTHER];
}
if (!blocks[category.ordinal]) {
blocks[category.ordinal] = 1;
} else {
blocks[category.ordinal]++;
}
}
// If no categories were found in the frames, default to using a
// single block using the stack depth as height.
if (blocks.length == 0) {
blocks[CATEGORY_MAPPINGS[CATEGORY_OTHER].ordinal] = frames.length;
}
categoriesData.push({
delta: time,
values: blocks
});
}
return categoriesData;
},
/**
* Creates an appropriate data source to be displayed in a framerate graph
* from on the provided refresh driver ticks data.
*
* @param object ticksData
* The refresh driver ticks received from the front.
* @param number beginAt
* The earliest time in the ticks data to start at (in milliseconds).
* @param number endAt
* The latest time in the ticks data to end at (in milliseconds).
* @return array
* A data source useful for a LineGraphWidget.
*/
plotFramerateFor: function(ticksData, beginAt, endAt) {
// Older Gecko versions don't have a framerate actor implementation,
// in which case the returned ticks data is null.
if (ticksData == null) {
return [];
}
let framerateData = this._frameratePlotsCache.get(ticksData);
if (framerateData == null) {
framerateData = FramerateFront.plotFPS(ticksData, FRAMERATE_CALC_INTERVAL);
this._frameratePlotsCache.set(ticksData, framerateData);
}
// Quickly find the earliest and oldest valid index in the plotted
// framerate data based on the specified beginAt and endAt time. Sure,
// using [].findIndex would be more elegant, but also slower.
let earliestValidIndex = findFirstIndex(framerateData, e => e.delta >= beginAt);
let oldestValidIndex = findLastIndex(framerateData, e => e.delta <= endAt);
let totalValues = framerateData.length;
// If all the plotted framerate data fits inside the specified time range,
// simply return it.
if (earliestValidIndex == 0 && oldestValidIndex == totalValues - 1) {
return framerateData;
}
// Otherwise, a slice will need to be made. Be very careful here, the
// beginAt and endAt timestamps can refer to a point in *between* two
// entries in the framerate data, so we'll need to insert new values where
// the cuts are made.
let slicedData = framerateData.slice(earliestValidIndex, oldestValidIndex + 1);
if (earliestValidIndex > 0) {
slicedData.unshift({
delta: beginAt,
value: framerateData[earliestValidIndex - 1].value
});
}
if (oldestValidIndex < totalValues - 1) {
slicedData.push({
delta: endAt,
value: framerateData[oldestValidIndex + 1].value
});
}
return slicedData;
},
/**
* Makes sure the data sources for the categories and framerate graphs
* have the same beginning and ending, time-wise.
*
* @param array categoriesData
* Data source generated by `RecordingUtils.plotCategoriesFor`.
* @param array framerateData
* Data source generated by `RecordingUtils.plotFramerateFor`.
*/
syncCategoriesWithFramerate: function(categoriesData, framerateData) {
if (categoriesData.length < 2 || framerateData.length < 2) {
return;
}
let categoryBegin = categoriesData[0];
let categoryEnd = categoriesData[categoriesData.length - 1];
let framerateBegin = framerateData[0];
let framerateEnd = framerateData[framerateData.length - 1];
if (categoryBegin.delta > framerateBegin.delta) {
categoriesData.unshift({
delta: framerateBegin.delta,
values: categoryBegin.values
});
} else {
framerateData.unshift({
delta: categoryBegin.delta,
value: framerateBegin.value
});
}
if (categoryEnd.delta < framerateEnd.delta) {
categoriesData.push({
delta: framerateEnd.delta,
values: categoryEnd.values
});
} else {
framerateData.push({
delta: categoryEnd.delta,
value: framerateEnd.value
});
}
}
};
/**
* Finds the index of the first element in an array that validates a predicate.
* @param array
* @param function predicate
* @return number
*/
function findFirstIndex(array, predicate) {
for (let i = 0, len = array.length; i < len; i++) {
if (predicate(array[i])) return i;
}
}
/**
* Finds the last of the first element in an array that validates a predicate.
* @param array
* @param function predicate
* @return number
*/
function findLastIndex(array, predicate) {
for (let i = array.length - 1; i >= 0; i--) {
if (predicate(array[i])) return i;
}
}
/**
* Opens/selects the debugger in this toolbox and jumps to the specified
* file name and line number.
* @param string url
* @param number line
*/
function viewSourceInDebugger(url, line) {
let showSource = ({ DebuggerView }) => {
let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === url);
if (item) {
DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true }).then(() => {
window.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
}, () => {
window.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
});
}
};
// If the Debugger was already open, switch to it and try to show the
// source immediately. Otherwise, initialize it and wait for the sources
// to be added first.
let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger");
gToolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
if (debuggerAlreadyOpen) {
showSource(dbg);
} else {
dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
}
});
}