This commit is contained in:
Ryan VanderMeulen 2015-04-30 13:45:40 -04:00
Родитель c9167a6ece 8263d08189
Коммит 5e083ec726
86 изменённых файлов: 1780 добавлений и 910 удалений

Просмотреть файл

@ -239,6 +239,7 @@ Tools.performance = {
ordinal: 7,
icon: "chrome://browser/skin/devtools/tool-profiler.svg",
invertIconForLightTheme: true,
highlightedicon: "chrome://browser/skin/devtools/tool-profiler-active.svg",
url: "chrome://browser/content/devtools/performance.xul",
visibilityswitch: "devtools.performance.enabled",
label: l10n("profiler.label2", profilerStrings),

Просмотреть файл

@ -2264,6 +2264,7 @@ function TextEditor(aContainer, aNode, aTemplate) {
stopOnReturn: true,
trigger: "dblclick",
multiline: true,
trimOutput: false,
done: (aVal, aCommit) => {
if (!aCommit) {
return;

Просмотреть файл

@ -37,7 +37,8 @@ const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
// Events to pipe from PerformanceActorsConnection to the PerformanceFront
const CONNECTION_PIPE_EVENTS = [
"console-profile-start", "console-profile-ending", "console-profile-end",
"timeline-data", "profiler-already-active", "profiler-activated"
"timeline-data", "profiler-already-active", "profiler-activated",
"recording-started", "recording-stopped"
];
// Events to listen to from the profiler actor
@ -100,9 +101,9 @@ function PerformanceActorsConnection(target) {
PerformanceActorsConnection.prototype = {
// Properties set when mocks are being used
_usingMockMemory: false,
_usingMockTimeline: false,
// Properties set based off of server actor support
_memorySupported: true,
_timelineSupported: true,
/**
* Initializes a connection to the profiler and other miscellaneous actors.
@ -174,9 +175,9 @@ PerformanceActorsConnection.prototype = {
if (supported) {
this._timeline = new TimelineFront(this._target.client, this._target.form);
} else {
this._usingMockTimeline = true;
this._timeline = new compatibility.MockTimelineFront();
}
this._timelineSupported = supported;
},
/**
@ -187,9 +188,9 @@ PerformanceActorsConnection.prototype = {
if (supported) {
this._memory = new MemoryFront(this._target.client, this._target.form);
} else {
this._usingMockMemory = true;
this._memory = new compatibility.MockMemoryFront();
}
this._memorySupported = supported;
}),
/**
@ -413,6 +414,7 @@ PerformanceActorsConnection.prototype = {
model.populate(data);
this._recordings.push(model);
this.emit("recording-started", model);
return model;
}),
@ -467,6 +469,7 @@ PerformanceActorsConnection.prototype = {
memoryEndTime: memoryEndTime
});
this.emit("recording-stopped", model);
return model;
}),
@ -620,8 +623,8 @@ function PerformanceFront(connection) {
this._request = connection._request;
// Set when mocks are being used
this._usingMockMemory = connection._usingMockMemory;
this._usingMockTimeline = connection._usingMockTimeline;
this._memorySupported = connection._memorySupported;
this._timelineSupported = connection._timelineSupported;
// Pipe the console profile events from the connection
// to the front so that the UI can listen.
@ -636,7 +639,8 @@ PerformanceFront.prototype = {
*
* @param object options
* An options object to pass to the actors. Supported properties are
* `withTicks`, `withMemory` and `withAllocations`, `probability` and `maxLogLength`.
* `withTicks`, `withMemory` and `withAllocations`,
* `probability` and `maxLogLength`.
* @return object
* A promise that is resolved once recording has started.
*/
@ -658,13 +662,24 @@ PerformanceFront.prototype = {
},
/**
* Returns an object indicating if mock actors are being used or not.
* Returns an object indicating what server actors are available and
* initialized. A falsy value indicates that the server does not support
* that feature, or that mock actors were explicitly requested (tests).
*/
getMocksInUse: function () {
getActorSupport: function () {
return {
memory: this._usingMockMemory,
timeline: this._usingMockTimeline
memory: this._memorySupported,
timeline: this._timelineSupported
};
},
/**
* Returns a boolean indicating whether or not the current performance connection is recording.
*
* @return Boolean
*/
isRecording: function () {
return this._connection.isRecording();
}
};
@ -673,6 +688,7 @@ PerformanceFront.prototype = {
*/
function getRecordingModelPrefs () {
return {
withMarkers: true,
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),

Просмотреть файл

@ -0,0 +1,409 @@
/* 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";
/**
* This file contains the base line graph that all Performance line graphs use.
*/
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource:///modules/devtools/Graphs.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
const { colorUtils: { setAlpha }} = require("devtools/css-color");
const { getColor } = require("devtools/shared/theme");
loader.lazyRequireGetter(this, "ProfilerGlobal",
"devtools/shared/profiler/global");
loader.lazyRequireGetter(this, "TimelineGlobal",
"devtools/shared/timeline/global");
loader.lazyRequireGetter(this, "MarkersOverview",
"devtools/shared/timeline/markers-overview", true);
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
/**
* For line graphs
*/
const HEIGHT = 35; // px
const STROKE_WIDTH = 1; // px
const DAMPEN_VALUES = 0.95;
const CLIPHEAD_LINE_COLOR = "#666";
const SELECTION_LINE_COLOR = "#555";
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
/**
* For timeline overview
*/
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
/**
* A base class for performance graphs to inherit from.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
* @param string metric
* The unit of measurement for this graph.
*/
function PerformanceGraph(parent, metric) {
LineGraphWidget.call(this, parent, { metric });
this.setTheme();
}
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
strokeWidth: STROKE_WIDTH,
dampenValuesFactor: DAMPEN_VALUES,
fixedHeight: HEIGHT,
clipheadLineColor: CLIPHEAD_LINE_COLOR,
selectionLineColor: SELECTION_LINE_COLOR,
withTooltipArrows: false,
withFixedTooltipPositions: true,
/**
* Disables selection and empties this graph.
*/
clearView: function() {
this.selectionEnabled = false;
this.dropSelection();
this.setData([]);
},
/**
* Sets the theme via `theme` to either "light" or "dark",
* and updates the internal styling to match. Requires a redraw
* to see the effects.
*/
setTheme: function (theme) {
theme = theme || "light";
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
this.backgroundColor = getColor("body-background", theme);
this.strokeColor = mainColor;
this.backgroundGradientStart = setAlpha(mainColor, 0.2);
this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
this.maximumLineColor = setAlpha(mainColor, 0.4);
this.averageLineColor = setAlpha(mainColor, 0.7);
this.minimumLineColor = setAlpha(mainColor, 0.9);
}
});
/**
* Constructor for the framerate graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function FramerateGraph(parent) {
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
}
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: FRAMERATE_GRAPH_COLOR_NAME,
setPerformanceData: function ({ duration, ticks }, resolution) {
this.dataDuration = duration;
return this.setDataFromTimestamps(ticks, resolution, duration);
}
});
/**
* Constructor for the memory graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function MemoryGraph(parent) {
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
}
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: MEMORY_GRAPH_COLOR_NAME,
setPerformanceData: function ({ duration, memory }) {
this.dataDuration = duration;
return this.setData(memory);
}
});
function TimelineGraph(parent, blueprint) {
MarkersOverview.call(this, parent, blueprint);
}
TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, {
headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
setPerformanceData: MarkersOverview.prototype.setData
});
/**
* Definitions file for GraphsController, indicating the constructor,
* selector and other meta for each of the graphs controller by
* GraphsController.
*/
const GRAPH_DEFINITIONS = {
memory: {
constructor: MemoryGraph,
selector: "#memory-overview",
},
framerate: {
constructor: FramerateGraph,
selector: "#time-framerate",
},
timeline: {
constructor: TimelineGraph,
selector: "#markers-overview",
needsBlueprints: true,
primaryLink: true
}
};
/**
* A controller for orchestrating the performance's tool overview graphs. Constructs,
* syncs, toggles displays and defines the memory, framerate and timeline view.
*
* @param {object} definition
* @param {DOMElement} root
* @param {function} getBlueprint
* @param {function} getTheme
*/
function GraphsController ({ definition, root, getBlueprint, getTheme }) {
this._graphs = {};
this._enabled = new Set();
this._definition = definition || GRAPH_DEFINITIONS;
this._root = root;
this._getBlueprint = getBlueprint;
this._getTheme = getTheme;
this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
EventEmitter.decorate(this);
this._onSelecting = this._onSelecting.bind(this);
}
GraphsController.prototype = {
/**
* Returns the corresponding graph by `graphName`.
*/
get: function (graphName) {
return this._graphs[graphName];
},
/**
* Iterates through all graphs and renders the data
* from a RecordingModel. Takes a resolution value used in
* some graphs.
* Saves rendering progress as a promise to be consumed by `destroy`,
* to wait for cleaning up rendering during destruction.
*/
render: Task.async(function *(recordingData, resolution) {
// Get the previous render promise so we don't start rendering
// until the previous render cycle completes, which can occur
// especially when a recording is finished, and triggers a
// fresh rendering at a higher rate
yield (this._rendering && this._rendering.promise);
// Check after yielding to ensure we're not tearing down,
// as this can create a race condition in tests
if (this._destroyed) {
return;
}
this._rendering = Promise.defer();
for (let graph of (yield this._getEnabled())) {
yield graph.setPerformanceData(recordingData, resolution);
this.emit("rendered", graph.graphName);
}
this._rendering.resolve();
}),
/**
* Destroys the underlying graphs.
*/
destroy: Task.async(function *() {
let primary = this._getPrimaryLink();
this._destroyed = true;
if (primary) {
primary.off("selecting", this._onSelecting);
}
// If there was rendering, wait until the most recent render cycle
// has finished
if (this._rendering) {
yield this._rendering.promise;
}
for (let graphName in this._graphs) {
yield this._graphs[graphName].destroy();
}
}),
/**
* Applies the theme to the underlying graphs. Optionally takes
* a `redraw` boolean in the options to force redraw.
*/
setTheme: function (options={}) {
let theme = options.theme || this._getTheme();
for (let graph in this._graphs) {
this._graphs[graph].setTheme(theme);
this._graphs[graph].refresh({ force: options.redraw });
}
},
/**
* Sets up the graph, if needed. Returns a promise resolving
* to the graph if it is enabled once it's ready, or otherwise returns
* null if disabled.
*/
isAvailable: Task.async(function *(graphName) {
if (!this._enabled.has(graphName)) {
return null;
}
let graph = this.get(graphName);
if (!graph) {
graph = yield this._construct(graphName);
}
yield graph.ready();
return graph;
}),
/**
* Enable or disable a subgraph controlled by GraphsController.
* This determines what graphs are visible and get rendered.
*/
enable: function (graphName, isEnabled) {
let el = this.$(this._definition[graphName].selector);
el.hidden = !isEnabled;
// If no status change, just return
if (this._enabled.has(graphName) === isEnabled) {
return;
}
if (isEnabled) {
this._enabled.add(graphName);
} else {
this._enabled.delete(graphName);
}
// Invalidate our cache of ready-to-go graphs
this._enabledGraphs = null;
},
/**
* Disables all graphs controller by the GraphsController, and
* also hides the root element. This is a one way switch, and used
* when older platforms do not have any timeline data.
*/
disableAll: function () {
this._root.hidden = true;
// Hide all the subelements
Object.keys(this._definition).forEach(graphName => this.enable(graphName, false));
},
/**
* Sets a mapped selection on the graph that is the main controller
* for keeping the graphs' selections in sync.
*/
setMappedSelection: function (selection, { mapStart, mapEnd }) {
return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
},
getMappedSelection: function ({ mapStart, mapEnd }) {
return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
},
/**
* Drops the selection.
*/
dropSelection: function () {
if (this._getPrimaryLink()) {
return this._getPrimaryLink().dropSelection();
}
},
/**
* Makes sure the selection is enabled or disabled in all the graphs.
*/
selectionEnabled: Task.async(function *(enabled) {
for (let graph of (yield this._getEnabled())) {
graph.selectionEnabled = enabled;
}
}),
/**
* Creates the graph `graphName` and initializes it.
*/
_construct: Task.async(function *(graphName) {
let def = this._definition[graphName];
let el = this.$(def.selector);
let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0;
let graph = this._graphs[graphName] = new def.constructor(el, blueprint);
graph.graphName = graphName;
yield graph.ready();
// Sync the graphs' animations and selections together
if (def.primaryLink) {
graph.on("selecting", this._onSelecting);
} else {
CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
}
this.setTheme();
return graph;
}),
/**
* Returns the main graph for this collection, that all graphs
* are bound to for syncing and selection.
*/
_getPrimaryLink: function () {
return this.get(this._primaryLink);
},
/**
* Emitted when a selection occurs.
*/
_onSelecting: function () {
this.emit("selecting");
},
/**
* Resolves to an array with all graphs that are enabled, and
* creates them if needed. Different than just iterating over `this._graphs`,
* as those could be enabled. Uses caching, as rendering happens many times per second,
* compared to how often which graphs/features are changed (rarely).
*/
_getEnabled: Task.async(function *() {
if (this._enabledGraphs) {
return this._enabledGraphs;
}
let enabled = [];
for (let graphName of this._enabled) {
let graph;
if (graph = yield this.isAvailable(graphName)) {
enabled.push(graph);
}
}
return this._enabledGraphs = enabled;
}),
};
exports.FramerateGraph = FramerateGraph;
exports.MemoryGraph = MemoryGraph;
exports.TimelineGraph = TimelineGraph;
exports.GraphsController = GraphsController;

Просмотреть файл

@ -148,7 +148,15 @@ function convertLegacyData (legacyData) {
memory: [],
ticks: ticksData,
allocations: { sites: [], timestamps: [], frames: [], counts: [] },
profile: profilerData.profile
profile: profilerData.profile,
// Fake a configuration object here if there's tick data,
// so that it can be rendered
configuration: {
withTicks: !!ticksData.length,
withMarkers: false,
withMemory: false,
withAllocations: false
}
};
return data;

Просмотреть файл

@ -1,113 +0,0 @@
/* 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";
/**
* This file contains the base line graph that all Performance line graphs use.
*/
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource:///modules/devtools/Graphs.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const { colorUtils: { setAlpha }} = require("devtools/css-color");
const { getColor } = require("devtools/shared/theme");
loader.lazyRequireGetter(this, "ProfilerGlobal",
"devtools/shared/profiler/global");
loader.lazyRequireGetter(this, "TimelineGlobal",
"devtools/shared/timeline/global");
const HEIGHT = 35; // px
const STROKE_WIDTH = 1; // px
const DAMPEN_VALUES = 0.95;
const CLIPHEAD_LINE_COLOR = "#666";
const SELECTION_LINE_COLOR = "#555";
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
/**
* A base class for performance graphs to inherit from.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
* @param string metric
* The unit of measurement for this graph.
*/
function PerformanceGraph(parent, metric) {
LineGraphWidget.call(this, parent, { metric });
this.setTheme();
}
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
strokeWidth: STROKE_WIDTH,
dampenValuesFactor: DAMPEN_VALUES,
fixedHeight: HEIGHT,
clipheadLineColor: CLIPHEAD_LINE_COLOR,
selectionLineColor: SELECTION_LINE_COLOR,
withTooltipArrows: false,
withFixedTooltipPositions: true,
/**
* Disables selection and empties this graph.
*/
clearView: function() {
this.selectionEnabled = false;
this.dropSelection();
this.setData([]);
},
/**
* Sets the theme via `theme` to either "light" or "dark",
* and updates the internal styling to match. Requires a redraw
* to see the effects.
*/
setTheme: function (theme) {
theme = theme || "light";
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
this.backgroundColor = getColor("body-background", theme);
this.strokeColor = mainColor;
this.backgroundGradientStart = setAlpha(mainColor, 0.2);
this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
this.maximumLineColor = setAlpha(mainColor, 0.4);
this.averageLineColor = setAlpha(mainColor, 0.7);
this.minimumLineColor = setAlpha(mainColor, 0.9);
}
});
/**
* Constructor for the framerate graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function FramerateGraph(parent) {
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
}
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: FRAMERATE_GRAPH_COLOR_NAME
});
exports.FramerateGraph = FramerateGraph;
/**
* Constructor for the memory graph. Inherits from PerformanceGraph.
*
* @param nsIDOMNode parent
* The parent node holding the overview.
*/
function MemoryGraph(parent) {
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
}
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
mainColor: MEMORY_GRAPH_COLOR_NAME
});
exports.MemoryGraph = MemoryGraph;

Просмотреть файл

@ -22,6 +22,7 @@ const RecordingModel = function (options={}) {
this._console = options.console || false;
this._configuration = {
withMarkers: options.withMarkers || false,
withTicks: options.withTicks || false,
withMemory: options.withMemory || false,
withAllocations: options.withAllocations || false,
@ -70,6 +71,7 @@ RecordingModel.prototype = {
this._ticks = recordingData.ticks;
this._allocations = recordingData.allocations;
this._profile = recordingData.profile;
this._configuration = recordingData.configuration || {};
}),
/**
@ -226,7 +228,8 @@ RecordingModel.prototype = {
let ticks = this.getTicks();
let allocations = this.getAllocations();
let profile = this.getProfile();
return { label, duration, markers, frames, memory, ticks, allocations, profile };
let configuration = this.getConfiguration();
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
},
/**
@ -263,10 +266,13 @@ RecordingModel.prototype = {
return;
}
let config = this.getConfiguration();
switch (eventName) {
// Accumulate timeline markers into an array. Furthermore, the timestamps
// do not have a zero epoch, so offset all of them by the start time.
case "markers": {
if (!config.withMarkers) { break; }
let [markers] = data;
RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
Array.prototype.push.apply(this._markers, markers);
@ -274,6 +280,7 @@ RecordingModel.prototype = {
}
// Accumulate stack frames into an array.
case "frames": {
if (!config.withMarkers) { break; }
let [, frames] = data;
Array.prototype.push.apply(this._frames, frames);
break;
@ -281,6 +288,7 @@ RecordingModel.prototype = {
// Accumulate memory measurements into an array. Furthermore, the timestamp
// does not have a zero epoch, so offset it by the actor's start time.
case "memory": {
if (!config.withMemory) { break; }
let [currentTime, measurement] = data;
this._memory.push({
delta: currentTime - this._timelineStartTime,
@ -290,6 +298,7 @@ RecordingModel.prototype = {
}
// Save the accumulated refresh driver ticks.
case "ticks": {
if (!config.withTicks) { break; }
let [, timestamps] = data;
this._ticks = timestamps;
break;
@ -298,6 +307,7 @@ RecordingModel.prototype = {
// do not have a zero epoch, and are microseconds instead of milliseconds,
// so offset all of them by the start time, also converting from µs to ms.
case "allocations": {
if (!config.withAllocations) { break; }
let [{ sites, timestamps, frames, counts }] = data;
let timeOffset = this._memoryStartTime * 1000;
let timeScale = 1000;

Просмотреть файл

@ -6,8 +6,8 @@
EXTRA_JS_MODULES.devtools.performance += [
'modules/compatibility.js',
'modules/front.js',
'modules/graphs.js',
'modules/io.js',
'modules/performance-graphs.js',
'modules/recording-model.js',
'modules/recording-utils.js',
'panel.js'

Просмотреть файл

@ -34,6 +34,7 @@ PerformancePanel.prototype = {
open: Task.async(function*() {
this.panelWin.gToolbox = this._toolbox;
this.panelWin.gTarget = this.target;
this._onRecordingStartOrStop = this._onRecordingStartOrStop.bind(this);
// Connection is already created in the toolbox; reuse
// the same connection.
@ -43,6 +44,8 @@ PerformancePanel.prototype = {
yield this._connection.open();
this.panelWin.gFront = new PerformanceFront(this._connection);
this.panelWin.gFront.on("recording-started", this._onRecordingStartOrStop);
this.panelWin.gFront.on("recording-stopped", this._onRecordingStartOrStop);
yield this.panelWin.startupPerformance();
@ -61,8 +64,19 @@ PerformancePanel.prototype = {
return;
}
this.panelWin.gFront.off("recording-started", this._onRecordingStartOrStop);
this.panelWin.gFront.off("recording-stopped", this._onRecordingStartOrStop);
yield this.panelWin.shutdownPerformance();
this.emit("destroyed");
this._destroyed = true;
})
}),
_onRecordingStartOrStop: function () {
let front = this.panelWin.gFront;
if (front.isRecording()) {
this._toolbox.highlightTool("performance");
} else {
this._toolbox.unhighlightTool("performance");
}
}
};

Просмотреть файл

@ -27,12 +27,8 @@ devtools.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true);
devtools.lazyRequireGetter(this, "RecordingModel",
"devtools/performance/recording-model", true);
devtools.lazyRequireGetter(this, "FramerateGraph",
"devtools/performance/performance-graphs", true);
devtools.lazyRequireGetter(this, "MemoryGraph",
"devtools/performance/performance-graphs", true);
devtools.lazyRequireGetter(this, "MarkersOverview",
"devtools/shared/timeline/markers-overview", true);
devtools.lazyRequireGetter(this, "GraphsController",
"devtools/performance/graphs", true);
devtools.lazyRequireGetter(this, "Waterfall",
"devtools/shared/timeline/waterfall", true);
devtools.lazyRequireGetter(this, "MarkerDetails",
@ -52,8 +48,6 @@ devtools.lazyRequireGetter(this, "FlameGraphUtils",
devtools.lazyRequireGetter(this, "FlameGraph",
"devtools/shared/widgets/FlameGraph", true);
devtools.lazyImporter(this, "CanvasGraphUtils",
"resource:///modules/devtools/Graphs.jsm");
devtools.lazyImporter(this, "SideMenuWidget",
"resource:///modules/devtools/SideMenuWidget.jsm");
devtools.lazyImporter(this, "PluralForm",
@ -291,6 +285,7 @@ let PerformanceController = {
*/
startRecording: Task.async(function *() {
let options = {
withMarkers: true,
withMemory: this.getOption("enable-memory"),
withTicks: this.getOption("enable-framerate"),
withAllocations: this.getOption("enable-memory"),
@ -470,6 +465,42 @@ let PerformanceController = {
return this._recordings;
},
/**
* Utility method taking the currently selected recording item's features, or optionally passed
* in recording item, as well as the actor support on the server, returning a boolean
* indicating if the requirements pass or not. Used to toggle features' visibility mostly.
*
* @option {Array<string>} features
* An array of strings indicating what configuration is needed on the recording
* model, like `withTicks`, or `withMemory`.
* @option {Array<string>} actors
* An array of strings indicating what actors must exist.
* @option {boolean} isRecording
* A boolean indicating whether the recording must be either recording or not
* recording. Setting to undefined will allow either state.
* @param {RecordingModel} recording
* An optional recording model to use instead of the currently selected.
*
* @return boolean
*/
isFeatureSupported: function ({ features, actors, isRecording: shouldBeRecording }, recording) {
recording = recording || this.getCurrentRecording();
let recordingConfig = recording ? recording.getConfiguration() : {};
let currentRecordingState = recording ? recording.isRecording() : void 0;
let actorsSupported = gFront.getActorSupport();
if (shouldBeRecording != null && shouldBeRecording !== currentRecordingState) {
return false;
}
if (actors && !actors.every(a => actorsSupported[a])) {
return false;
}
if (features && !features.every(f => recordingConfig[f])) {
return false;
}
return true;
},
toString: () => "[object PerformanceController]"
};

Просмотреть файл

@ -102,22 +102,27 @@
<toolbarbutton id="select-waterfall-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.waterfall;"
hidden="true"
data-view="waterfall" />
<toolbarbutton id="select-js-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.js-calltree;"
hidden="true"
data-view="js-calltree" />
<toolbarbutton id="select-js-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.js-flamegraph;"
hidden="true"
data-view="js-flamegraph" />
<toolbarbutton id="select-memory-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.memory-calltree1;"
hidden="true"
data-view="memory-calltree" />
<toolbarbutton id="select-memory-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.memory-flamegraph1;"
hidden="true"
data-view="memory-flamegraph" />
</hbox>
<spacer flex="1"></spacer>

Просмотреть файл

@ -11,9 +11,8 @@ support-files =
# that need to be moved over to performance tool
[browser_perf-aaa-run-first-leaktest.js]
[browser_markers-gc.js]
[browser_markers-parse-html.js]
[browser_perf-allocations-to-samples.js]
[browser_perf-compatibility-01.js]
[browser_perf-compatibility-02.js]
@ -43,6 +42,7 @@ support-files =
[browser_perf-details-02.js]
[browser_perf-details-03.js]
[browser_perf-details-04.js]
[browser_perf-details-05.js]
[browser_perf-events-calltree.js]
[browser_perf-front-basic-profiler-01.js]
[browser_perf-front-basic-timeline-01.js]
@ -54,6 +54,7 @@ support-files =
#[browser_perf-front-profiler-06.js]
[browser_perf-front-01.js]
[browser_perf-front-02.js]
[browser_perf-highlighted.js]
[browser_perf-jit-view-01.js]
[browser_perf-jit-view-02.js]
[browser_perf-jit-model-01.js]

Просмотреть файл

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we get a "GarbageCollection" marker.
*/
const TIME_CLOSE_TO = 10000;
function* spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let markers;
front.on("timeline-data", handler);
let model = yield front.startRecording({ withTicks: true });
// Check async for markers found while GC/CCing between
yield waitUntil(() => {
forceCC();
return !!markers;
}, 100);
front.off("timeline-data", handler);
yield front.stopRecording(model);
info(`Got ${markers.length} markers.`);
let maxMarkerTime = model._timelineStartTime + model.getDuration() + TIME_CLOSE_TO;
ok(markers.every(({name}) => name === "GarbageCollection"), "All markers found are GC markers");
ok(markers.length > 0, "found atleast one GC marker");
ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
"All markers have a start time between the valid range.");
ok(markers.every(({end}) => typeof end === "number" && end > 0 && end < maxMarkerTime),
"All markers have an end time between the valid range.");
ok(markers.every(({causeName}) => typeof causeName === "string"),
"All markers have a causeName.");
yield removeTab(target.tab);
finish();
function handler (_, name, m) {
if (name === "markers" && m[0].name === "GarbageCollection") {
markers = m;
}
}
}

Просмотреть файл

@ -16,7 +16,7 @@ function* getMarkers(front) {
};
front.on("timeline-data", handler);
yield front.startRecording({ withTicks: true });
yield front.startRecording({ withMarkers: true, withTicks: true });
const markers = yield promise;
front.off("timeline-data", handler);

Просмотреть файл

@ -11,14 +11,13 @@ function spawnTest () {
// Enable platform data to show the `busyWait` function in the tree.
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(1000);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield rendered;
testCells($, $$, {

Просмотреть файл

@ -11,14 +11,13 @@ function spawnTest () {
// Enable memory to test.
Services.prefs.setBoolPref(MEMORY_PREF, true);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(1000);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield rendered;
testCells($, $$, {

Просмотреть файл

@ -14,12 +14,13 @@ function spawnTest () {
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { memory, timeline } = front.getMocksInUse();
ok(memory, "memory should be mocked.");
ok(timeline, "timeline should be mocked.");
let { memory, timeline } = front.getActorSupport();
ok(!memory, "memory should be mocked.");
ok(!timeline, "timeline should be mocked.");
let recording = yield front.startRecording({
withTicks: true,
withMarkers: true,
withMemory: true,
withAllocations: true,
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),

Просмотреть файл

@ -16,9 +16,9 @@ let test = Task.async(function*() {
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
ok(memoryMock, "memory should be mocked.");
ok(timelineMock, "timeline should be mocked.");
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
ok(!memorySupport, "memory should be mocked.");
ok(!timelineSupport, "timeline should be mocked.");
yield startRecording(panel, { waitForOverview: false });
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity

Просмотреть файл

@ -13,12 +13,13 @@ function spawnTest () {
});
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { memory, timeline } = front.getMocksInUse();
ok(memory, "memory should be mocked.");
ok(!timeline, "timeline should not be mocked.");
let { memory, timeline } = front.getActorSupport();
ok(!memory, "memory should be mocked.");
ok(timeline, "timeline should not be mocked.");
let recording = yield front.startRecording({
withTicks: true,
withMarkers: true,
withMemory: true,
withAllocations: true,
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),

Просмотреть файл

@ -16,9 +16,9 @@ let test = Task.async(function*() {
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
ok(memoryMock, "memory should be mocked.");
ok(!timelineMock, "timeline should not be mocked.");
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
ok(!memorySupport, "memory should be mocked.");
ok(timelineSupport, "timeline should not be mocked.");
yield startRecording(panel);
yield busyWait(100);

Просмотреть файл

@ -29,9 +29,7 @@ function spawnTest () {
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield consoleProfileEnd(panel.panelWin, "rust");
yield detailsRendered;
recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
@ -40,7 +38,7 @@ function spawnTest () {
is(RecordingsView.selectedItem.attachment.isRecording(), false,
"The first console recording should no longer be recording.");
detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield consoleProfileEnd(panel.panelWin, "golang");
yield detailsRendered;

Просмотреть файл

@ -8,6 +8,9 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, $, DetailsView, document: doc } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
info("views on startup");
checkViews(DetailsView, doc, "waterfall");

Просмотреть файл

@ -9,6 +9,9 @@ function spawnTest () {
let { EVENTS, DetailsView } = panel.panelWin;
let { WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
@ -18,7 +21,7 @@ function spawnTest () {
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(JsCallTreeView),
"The waterfall view is now selected in the details view.");
"The jscalltree view is now selected in the details view.");
selected = DetailsView.whenViewSelected(JsFlameGraphView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);

Просмотреть файл

@ -2,17 +2,23 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the details view hides the memory buttons when `enable-memory` is toggled,
* and that it switches to default panel if toggling while a memory panel is selected.
* Tests that the details view hides the memory buttons when a recording does not
* have memory data (withMemory: false), and that when a memory panel is selected,
* switching to a panel that does not have memory goes to a default panel instead.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, OverviewView, DetailsView } = panel.panelWin;
let { $, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
let { $, RecordingsView, WaterfallView, MemoryCallTreeView, MemoryFlameGraphView } = panel.panelWin;
Services.prefs.setBoolPref(MEMORY_PREF, false);
yield startRecording(panel);
yield stopRecording(panel);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
Services.prefs.setBoolPref(MEMORY_PREF, true);
// The toolbar buttons will always be hidden when a recording isn't available,
// so make sure we have one that's finished.
yield startRecording(panel);
@ -21,13 +27,8 @@ function spawnTest () {
let flameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
let callBtn = $("toolbarbutton[data-view='memory-calltree']");
Services.prefs.setBoolPref(MEMORY_PREF, false);
is(flameBtn.hidden, true, "memory-flamegraph button hidden when enable-memory=false");
is(callBtn.hidden, true, "memory-calltree button hidden when enable-memory=false");
Services.prefs.setBoolPref(MEMORY_PREF, true);
is(flameBtn.hidden, false, "memory-flamegraph button shown when enable-memory=true");
is(callBtn.hidden, false, "memory-calltree button shown when enable-memory=true");
is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
let selected = DetailsView.whenViewSelected(MemoryCallTreeView);
let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
@ -45,15 +46,21 @@ function spawnTest () {
ok(DetailsView.isViewSelected(MemoryFlameGraphView),
"The memory flamegraph view can now be selected.");
// Select the first recording with no memory data
selected = DetailsView.whenViewSelected(WaterfallView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
Services.prefs.setBoolPref(MEMORY_PREF, false);
RecordingsView.selectedIndex = 0;
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
"The waterfall view is now selected when switching back to a recording that does not have memory data");
is(flameBtn.hidden, true, "memory-flamegraph button hidden when recording does not have memory data");
is(callBtn.hidden, true, "memory-calltree button hidden when recording does not have memory data");
Services.prefs.setBoolPref(MEMORY_PREF, true);
// Go back to the recording with memory data
let render = WaterfallView.once(EVENTS.WATERFALL_RENDERED);
RecordingsView.selectedIndex = 1;
yield render;
selected = DetailsView.whenViewSelected(MemoryCallTreeView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
@ -61,23 +68,9 @@ function spawnTest () {
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(MemoryCallTreeView),
"The memory call tree view can be selected again after re-enabling memory.");
selected = DetailsView.whenViewSelected(MemoryFlameGraphView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
DetailsView.selectView("memory-flamegraph");
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(MemoryFlameGraphView),
"The memory flamegraph view can be selected again after re-enabling memory.");
selected = DetailsView.whenViewSelected(WaterfallView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
Services.prefs.setBoolPref(MEMORY_PREF, false);
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is now selected when toggling off enable-memory when a memory panel is selected.");
"The memory call tree view can be selected again after going back to the view with memory data");
is(flameBtn.hidden, false, "memory-flamegraph button shown when recording has memory data");
is(callBtn.hidden, false, "memory-calltree button shown when recording has memory data");
yield teardown(panel);
finish();

Просмотреть файл

@ -7,7 +7,7 @@
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, $, $$, PerformanceController, RecordingsView, DetailsView } = panel.panelWin;
let { EVENTS, $, $$, PerformanceController, RecordingsView, WaterfallView } = panel.panelWin;
let waterfallBtn = $("toolbarbutton[data-view='waterfall']");
let jsFlameBtn = $("toolbarbutton[data-view='js-flamegraph']");
@ -46,8 +46,9 @@ function spawnTest () {
is(memCallBtn.hidden, true, "memory-calltree button hidden when another recording starts.");
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
let render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
mousedown(panel.panelWin, $$(".recording-item")[0]);
yield select;
yield Promise.all([select, render]);
is(RecordingsView.selectedIndex, 0,
"The first recording was selected again.");
@ -71,7 +72,9 @@ function spawnTest () {
is(memFlameBtn.hidden, true, "memory-flamegraph button still hidden when second recording selected.");
is(memCallBtn.hidden, true, "memory-calltree button still hidden when second recording selected.");
render = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield stopRecording(panel);
yield render;
is(RecordingsView.selectedIndex, 1,
"The second recording is still selected.");

Просмотреть файл

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the details view utility functions work as advertised.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView } = panel.panelWin;
let { PerformanceController, WaterfallView, JsCallTreeView } = panel.panelWin;
yield startRecording(panel);
yield stopRecording(panel);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
let selected = DetailsView.whenViewSelected(JsCallTreeView);
let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
yield DetailsView.selectView("js-calltree");
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(JsCallTreeView),
"The jscalltree view is now selected in the details view.");
yield PerformanceController.clearRecordings();
yield startRecording(panel);
let render = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield render;
ok(DetailsView.isViewSelected(JsCallTreeView),
"The jscalltree view is still selected in the details view");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -8,14 +8,13 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield rendered;
ok(true, "JsCallTreeView rendered after recording is stopped.");

Просмотреть файл

@ -8,14 +8,13 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
yield DetailsView.selectView("js-flamegraph");
ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-flamegraph");
ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
yield rendered;
ok(true, "JsFlameGraphView rendered after recording is stopped.");

Просмотреть файл

@ -11,14 +11,13 @@ function spawnTest () {
// Enable memory to test.
Services.prefs.setBoolPref(MEMORY_PREF, true);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield rendered;
ok(true, "MemoryCallTreeView rendered after recording is stopped.");

Просмотреть файл

@ -11,14 +11,13 @@ function spawnTest () {
// Enable memory to test.
Services.prefs.setBoolPref(MEMORY_PREF, true);
yield DetailsView.selectView("memory-flamegraph");
ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-flamegraph");
ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
yield rendered;
ok(true, "MemoryFlameGraphView rendered after recording is stopped.");

Просмотреть файл

@ -8,14 +8,13 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, DetailsView, WaterfallView } = panel.panelWin;
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
yield startRecording(panel);
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield stopRecording(panel);
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
yield rendered;
ok(true, "WaterfallView rendered after recording is stopped.");

Просмотреть файл

@ -10,7 +10,7 @@ let WAIT_TIME = 1000;
function spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let config = { withMemory: true, withTicks: true };
let config = { withMarkers: true, withMemory: true, withTicks: true };
yield front._request("memory", "attach");

Просмотреть файл

@ -25,7 +25,7 @@ function spawnTest () {
front.on("timeline-data", handler);
yield front.startRecording({ withMemory: true, withTicks: true });
yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
yield front.stopRecording();
front.off("timeline-data", handler);

Просмотреть файл

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the toolbox tab for performance is highlighted when recording,
* whether already loaded, or via console.profile with an unloaded performance tools.
*/
let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
function spawnTest () {
let profilerConnected = waitForProfilerConnection();
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let tab = toolbox.doc.getElementById("toolbox-tab-performance");
let profileStart = once(connection, "console-profile-start");
console.profile("rust");
yield profileStart;
ok(tab.hasAttribute("highlighted"),
"performance tab is highlighted during recording from console.profile when unloaded");
let profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust");
yield profileEnd;
ok(!tab.hasAttribute("highlighted"),
"performance tab is no longer highlighted when console.profile recording finishes");
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
yield startRecording(panel);
ok(tab.hasAttribute("highlighted"),
"performance tab is highlighted during recording while in performance tool");
yield stopRecording(panel);
ok(!tab.hasAttribute("highlighted"),
"performance tab is no longer highlighted when recording finishes");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -8,22 +8,22 @@
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, $ } = panel.panelWin;
Services.prefs.setBoolPref(FRAMERATE_PREF, false);
ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
yield startRecording(panel);
yield stopRecording(panel);
is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, false,
"PerformanceFront started without ticks recording.");
ok($("#time-framerate").hidden, "fps graph is hidden when ticks disabled");
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled");
ok($("#time-framerate").hidden, "fps graph is still hidden if recording does not contain ticks.");
yield startRecording(panel);
yield stopRecording(panel);
ok(!$("#time-framerate").hidden, "fps graph is not hidden when ticks enabled before recording");
is(PerformanceController.getCurrentRecording().getConfiguration().withTicks, true,
"PerformanceFront started with ticks recording.");

Просмотреть файл

@ -10,7 +10,6 @@ function spawnTest () {
let { EVENTS, PerformanceController, $ } = panel.panelWin;
Services.prefs.setBoolPref(MEMORY_PREF, false);
ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
yield startRecording(panel);
yield stopRecording(panel);
@ -19,13 +18,16 @@ function spawnTest () {
"PerformanceFront started without memory recording.");
is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, false,
"PerformanceFront started without allocations recording.");
ok($("#memory-overview").hidden, "memory graph is hidden when memory disabled");
Services.prefs.setBoolPref(MEMORY_PREF, true);
ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled");
ok($("#memory-overview").hidden,
"memory graph is still hidden after enabling if recording did not start recording memory");
yield startRecording(panel);
yield stopRecording(panel);
ok(!$("#memory-overview").hidden, "memory graph is not hidden when memory enabled before recording");
is(PerformanceController.getCurrentRecording().getConfiguration().withMemory, true,
"PerformanceFront started with memory recording.");
is(PerformanceController.getCurrentRecording().getConfiguration().withAllocations, true,

Просмотреть файл

@ -10,13 +10,12 @@ function spawnTest () {
Services.prefs.setBoolPref(FLATTEN_PREF, true);
yield DetailsView.selectView("js-flamegraph");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield DetailsView.selectView("js-flamegraph");
yield rendered;
let samples1 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;

Просмотреть файл

@ -12,13 +12,12 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
Services.prefs.setBoolPref(FLATTEN_PREF, true);
yield DetailsView.selectView("memory-flamegraph");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-flamegraph");
yield rendered;
let allocations1 = PerformanceController.getCurrentRecording().getAllocations();

Просмотреть файл

@ -10,14 +10,13 @@ function spawnTest () {
Services.prefs.setBoolPref(INVERT_PREF, true);
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield rendered;
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);

Просмотреть файл

@ -12,14 +12,13 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
Services.prefs.setBoolPref(INVERT_PREF, true);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield rendered;
rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);

Просмотреть файл

@ -8,15 +8,14 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
yield DetailsView.selectView("js-flamegraph");
Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-flamegraph");
yield rendered;
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);

Просмотреть файл

@ -11,13 +11,12 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
Services.prefs.setBoolPref(INVERT_FLAME_PREF, true);
yield DetailsView.selectView("memory-flamegraph");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-flamegraph");
yield rendered;
rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);

Просмотреть файл

@ -10,13 +10,12 @@ function spawnTest () {
Services.prefs.setBoolPref(IDLE_PREF, true);
yield DetailsView.selectView("js-flamegraph");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-flamegraph");
yield rendered;
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);

Просмотреть файл

@ -12,13 +12,12 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
Services.prefs.setBoolPref(IDLE_PREF, true);
yield DetailsView.selectView("memory-flamegraph");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("memory-flamegraph");
yield rendered;
rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);

Просмотреть файл

@ -10,13 +10,12 @@ function spawnTest () {
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
yield DetailsView.selectView("js-calltree");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-calltree");
yield rendered;
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);

Просмотреть файл

@ -10,13 +10,12 @@ function spawnTest () {
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
yield DetailsView.selectView("js-flamegraph");
yield startRecording(panel);
yield busyWait(100);
yield stopRecording(panel);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield DetailsView.selectView("js-flamegraph");
yield rendered;
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);

Просмотреть файл

@ -21,25 +21,28 @@ function spawnTest () {
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
]);
ok("selectionEnabled" in OverviewView.framerateGraph,
let framerate = OverviewView.graphs.get("framerate");
ok("selectionEnabled" in framerate,
"The selection should not be enabled for the framerate overview (1).");
is(OverviewView.framerateGraph.selectionEnabled, false,
is(framerate.selectionEnabled, false,
"The selection should not be enabled for the framerate overview (2).");
is(OverviewView.framerateGraph.hasSelection(), false,
is(framerate.hasSelection(), false,
"The framerate overview shouldn't have a selection before recording.");
ok("selectionEnabled" in OverviewView.markersOverview,
let markers = OverviewView.graphs.get("timeline");
ok("selectionEnabled" in markers,
"The selection should not be enabled for the markers overview (1).");
is(OverviewView.markersOverview.selectionEnabled, false,
is(markers.selectionEnabled, false,
"The selection should not be enabled for the markers overview (2).");
is(OverviewView.markersOverview.hasSelection(), false,
is(markers.hasSelection(), false,
"The markers overview shouldn't have a selection before recording.");
ok("selectionEnabled" in OverviewView.memoryOverview,
let memory = OverviewView.graphs.get("memory");
ok("selectionEnabled" in memory,
"The selection should not be enabled for the memory overview (1).");
is(OverviewView.memoryOverview.selectionEnabled, false,
is(memory.selectionEnabled, false,
"The selection should not be enabled for the memory overview (2).");
is(OverviewView.memoryOverview.hasSelection(), false,
is(memory.hasSelection(), false,
"The memory overview shouldn't have a selection before recording.");
let updated = 0;
@ -48,36 +51,36 @@ function spawnTest () {
ok((yield waitUntil(() => updated > 10)),
"The overviews were updated several times.");
ok("selectionEnabled" in OverviewView.framerateGraph,
ok("selectionEnabled" in framerate,
"The selection should still not be enabled for the framerate overview (1).");
is(OverviewView.framerateGraph.selectionEnabled, false,
is(framerate.selectionEnabled, false,
"The selection should still not be enabled for the framerate overview (2).");
is(OverviewView.framerateGraph.hasSelection(), false,
is(framerate.hasSelection(), false,
"The framerate overview still shouldn't have a selection before recording.");
ok("selectionEnabled" in OverviewView.markersOverview,
ok("selectionEnabled" in markers,
"The selection should still not be enabled for the markers overview (1).");
is(OverviewView.markersOverview.selectionEnabled, false,
is(markers.selectionEnabled, false,
"The selection should still not be enabled for the markers overview (2).");
is(OverviewView.markersOverview.hasSelection(), false,
is(markers.hasSelection(), false,
"The markers overview still shouldn't have a selection before recording.");
ok("selectionEnabled" in OverviewView.memoryOverview,
ok("selectionEnabled" in memory,
"The selection should still not be enabled for the memory overview (1).");
is(OverviewView.memoryOverview.selectionEnabled, false,
is(memory.selectionEnabled, false,
"The selection should still not be enabled for the memory overview (2).");
is(OverviewView.memoryOverview.hasSelection(), false,
is(memory.hasSelection(), false,
"The memory overview still shouldn't have a selection before recording.");
yield stopRecording(panel);
is(OverviewView.framerateGraph.selectionEnabled, true,
is(framerate.selectionEnabled, true,
"The selection should now be enabled for the framerate overview.");
is(OverviewView.markersOverview.selectionEnabled, true,
is(markers.selectionEnabled, true,
"The selection should now be enabled for the markers overview.");
is(OverviewView.memoryOverview.selectionEnabled, true,
is(memory.selectionEnabled, true,
"The selection should now be enabled for the memory overview.");
yield teardown(panel);

Просмотреть файл

@ -24,41 +24,40 @@ function spawnTest () {
yield stopRecording(panel);
ok(OverviewView.markersOverview.width > 0,
"The overview's framerate graph has a width.");
ok(OverviewView.markersOverview.dataScaleX > 0,
"The overview's framerate graph has a data scale factor.");
let markers = OverviewView.graphs.get("timeline");
let framerate = OverviewView.graphs.get("framerate");
let memory = OverviewView.graphs.get("memory");
ok(OverviewView.memoryOverview.width > 0,
ok(markers.width > 0,
"The overview's markers graph has a width.");
ok(markers.dataScaleX > 0,
"The overview's markers graph has a data scale factor.");
ok(memory.width > 0,
"The overview's memory graph has a width.");
ok(memory.dataDuration > 0,
"The overview's memory graph has a data duration.");
ok(memory.dataScaleX > 0,
"The overview's memory graph has a data scale factor.");
ok(framerate.width > 0,
"The overview's framerate graph has a width.");
ok(OverviewView.memoryOverview.dataDuration > 0,
ok(framerate.dataDuration > 0,
"The overview's framerate graph has a data duration.");
ok(OverviewView.memoryOverview.dataScaleX > 0,
ok(framerate.dataScaleX > 0,
"The overview's framerate graph has a data scale factor.");
ok(OverviewView.framerateGraph.width > 0,
"The overview's framerate graph has a width.");
ok(OverviewView.framerateGraph.dataDuration > 0,
"The overview's framerate graph has a data duration.");
ok(OverviewView.framerateGraph.dataScaleX > 0,
"The overview's framerate graph has a data scale factor.");
is(OverviewView.markersOverview.width,
OverviewView.memoryOverview.width,
"The markers and memory graphs widths are the same.")
is(OverviewView.markersOverview.width,
OverviewView.framerateGraph.width,
is(markers.width, memory.width,
"The markers and memory graphs widths are the same.");
is(markers.width, framerate.width,
"The markers and framerate graphs widths are the same.");
is(OverviewView.memoryOverview.dataDuration,
OverviewView.framerateGraph.dataDuration,
is(memory.dataDuration, framerate.dataDuration,
"The memory and framerate graphs data duration are the same.");
is(OverviewView.markersOverview.dataScaleX,
OverviewView.memoryOverview.dataScaleX,
"The markers and memory graphs data scale are the same.")
is(OverviewView.markersOverview.dataScaleX,
OverviewView.framerateGraph.dataScaleX,
is(markers.dataScaleX, memory.dataScaleX,
"The markers and memory graphs data scale are the same.");
is(markers.dataScaleX, framerate.dataScaleX,
"The markers and framerate graphs data scale are the same.");
yield teardown(panel);

Просмотреть файл

@ -19,7 +19,7 @@ function spawnTest () {
yield stopRecording(panel);
let graph = OverviewView.markersOverview;
let graph = OverviewView.graphs.get("timeline");
let MAX = graph.width;
// Select the first half of the graph

Просмотреть файл

@ -20,9 +20,9 @@ function spawnTest () {
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
]);
let markersOverview = OverviewView.markersOverview;
let memoryOverview = OverviewView.memoryOverview;
let framerateGraph = OverviewView.framerateGraph;
let markersOverview = OverviewView.graphs.get("timeline");
let memoryOverview = OverviewView.graphs.get("memory");
let framerateGraph = OverviewView.graphs.get("framerate");
ok(markersOverview,
"The markers graph should have been created now.");

Просмотреть файл

@ -22,9 +22,9 @@ function spawnTest () {
yield stopRecording(panel);
let framerateGraph = OverviewView.framerateGraph;
let markersOverview = OverviewView.markersOverview;
let memoryOverview = OverviewView.memoryOverview;
let framerateGraph = OverviewView.graphs.get("framerate");
let markersOverview = OverviewView.graphs.get("timeline");
let memoryOverview = OverviewView.graphs.get("memory");
let MAX = framerateGraph.width;
// Perform a selection inside the framerate graph.

Просмотреть файл

@ -17,6 +17,9 @@ let test = Task.async(function*() {
// `waitForWidgetsRendered`.
DetailsSubview.canUpdateWhileHidden = true;
yield startRecording(panel);
yield stopRecording(panel);
// Cycle through all the views to initialize them, otherwise we can't use
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
// other views are created lazily, so won't emit any events.
@ -28,9 +31,6 @@ let test = Task.async(function*() {
yield startRecording(panel);
yield stopRecording(panel);
yield startRecording(panel);
yield stopRecording(panel);
let rerender = waitForWidgetsRendered(panel);
RecordingsView.selectedIndex = 0;
yield rerender;

Просмотреть файл

@ -11,15 +11,15 @@ function spawnTest () {
is(PerformanceView.getState(), "empty",
"The intial state of the performance panel view is correct.");
ok(!("markersOverview" in OverviewView),
ok(!(OverviewView.graphs.get("timeline")),
"The markers graph should not have been created yet.");
ok(!("memoryOverview" in OverviewView),
ok(!(OverviewView.graphs.get("memory")),
"The memory graph should not have been created yet.");
ok(!("framerateGraph" in OverviewView),
ok(!(OverviewView.graphs.get("framerate")),
"The framerate graph should not have been created yet.");
ok(DetailsView.components["waterfall"].initialized,
"The waterfall detail view should have been created by default.");
ok(!DetailsView.components["waterfall"].initialized,
"The waterfall detail view should not have been created yet.");
ok(!DetailsView.components["js-calltree"].initialized,
"The js-calltree detail view should not have been created yet.");
ok(!DetailsView.components["js-flamegraph"].initialized,
@ -31,11 +31,11 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
ok(!("markersOverview" in OverviewView),
ok(!(OverviewView.graphs.get("timeline")),
"The markers graph should still not have been created yet.");
ok(!("memoryOverview" in OverviewView),
ok(!(OverviewView.graphs.get("memory")),
"The memory graph should still not have been created yet.");
ok(!("framerateGraph" in OverviewView),
ok(!(OverviewView.graphs.get("framerate")),
"The framerate graph should still not have been created yet.");
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
@ -44,9 +44,9 @@ function spawnTest () {
is(PerformanceView.getState(), "recording",
"The current state of the performance panel view is 'recording'.");
ok(OverviewView.memoryOverview,
ok(OverviewView.graphs.get("memory"),
"The memory graph should have been created now.");
ok(OverviewView.framerateGraph,
ok(OverviewView.graphs.get("framerate"),
"The framerate graph should have been created now.");
stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);

Просмотреть файл

@ -17,20 +17,21 @@ function spawnTest () {
let { EVENTS, $, OverviewView, document: doc } = panel.panelWin;
yield startRecording(panel);
is(OverviewView.markersOverview.backgroundColor, DARK_BG,
let markers = OverviewView.graphs.get("timeline");
is(markers.backgroundColor, DARK_BG,
"correct theme on load for markers.");
yield stopRecording(panel);
let refreshed = once(OverviewView.markersOverview, "refresh");
let refreshed = once(markers, "refresh");
setTheme("light");
yield refreshed;
ok(true, "markers were rerendered after theme change.");
is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
is(markers.backgroundColor, LIGHT_BG,
"correct theme on after toggle for markers.");
// reset back to dark
refreshed = once(OverviewView.markersOverview, "refresh");
refreshed = once(markers, "refresh");
setTheme("dark");
yield refreshed;
@ -39,26 +40,27 @@ function spawnTest () {
Services.prefs.setBoolPref(MEMORY_PREF, true);
yield startRecording(panel);
is(OverviewView.memoryOverview.backgroundColor, DARK_BG,
let memory = OverviewView.graphs.get("memory");
is(memory.backgroundColor, DARK_BG,
"correct theme on load for memory.");
yield stopRecording(panel);
refreshed = Promise.all([
once(OverviewView.markersOverview, "refresh"),
once(OverviewView.memoryOverview, "refresh"),
once(markers, "refresh"),
once(memory, "refresh"),
]);
setTheme("light");
yield refreshed;
ok(true, "Both memory and markers were rerendered after theme change.");
is(OverviewView.markersOverview.backgroundColor, LIGHT_BG,
is(markers.backgroundColor, LIGHT_BG,
"correct theme on after toggle for markers.");
is(OverviewView.memoryOverview.backgroundColor, LIGHT_BG,
is(memory.backgroundColor, LIGHT_BG,
"correct theme on after toggle for memory.");
refreshed = Promise.all([
once(OverviewView.markersOverview, "refresh"),
once(OverviewView.memoryOverview, "refresh"),
once(markers, "refresh"),
once(memory, "refresh"),
]);
// Set theme back to light

Просмотреть файл

@ -11,6 +11,14 @@ let test = Task.async(function*() {
// Enable memory to test the memory-calltree and memory-flamegraph.
Services.prefs.setBoolPref(MEMORY_PREF, true);
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
// Need to allow widgets to be updated while hidden, otherwise we can't use
// `waitForWidgetsRendered`.
DetailsSubview.canUpdateWhileHidden = true;
yield startRecording(panel);
yield stopRecording(panel);
// Cycle through all the views to initialize them, otherwise we can't use
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
@ -20,12 +28,6 @@ let test = Task.async(function*() {
yield DetailsView.selectView("memory-calltree");
yield DetailsView.selectView("memory-flamegraph");
// Need to allow widgets to be updated while hidden, otherwise we can't use
// `waitForWidgetsRendered`.
DetailsSubview.canUpdateWhileHidden = true;
yield startRecording(panel);
yield stopRecording(panel);
// Verify original recording.
@ -60,19 +62,23 @@ let test = Task.async(function*() {
let importedData = PerformanceController.getCurrentRecording().getAllData();
is(importedData.label, originalData.label,
"The impored data is identical to the original data (1).");
"The imported data is identical to the original data (1).");
is(importedData.duration, originalData.duration,
"The impored data is identical to the original data (2).");
"The imported data is identical to the original data (2).");
is(importedData.markers.toSource(), originalData.markers.toSource(),
"The impored data is identical to the original data (3).");
"The imported data is identical to the original data (3).");
is(importedData.memory.toSource(), originalData.memory.toSource(),
"The impored data is identical to the original data (4).");
"The imported data is identical to the original data (4).");
is(importedData.ticks.toSource(), originalData.ticks.toSource(),
"The impored data is identical to the original data (5).");
"The imported data is identical to the original data (5).");
is(importedData.allocations.toSource(), originalData.allocations.toSource(),
"The impored data is identical to the original data (6).");
"The imported data is identical to the original data (6).");
is(importedData.profile.toSource(), originalData.profile.toSource(),
"The impored data is identical to the original data (7).");
"The imported data is identical to the original data (7).");
is(importedData.configuration.withTicks, originalData.configuration.withTicks,
"The imported data is identical to the original data (8).");
is(importedData.configuration.withMemory, originalData.configuration.withMemory,
"The imported data is identical to the original data (9).");
yield teardown(panel);
finish();

Просмотреть файл

@ -3,28 +3,16 @@
/**
* Tests if the performance tool can import profiler data from the
* original profiler tool.
* original profiler tool and the correct views and graphs are loaded.
*/
let test = Task.async(function*() {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, DetailsView, DetailsSubview } = panel.panelWin;
let { $, EVENTS, PerformanceController, DetailsView, OverviewView, JsCallTreeView } = panel.panelWin;
// Enable memory to test the memory-calltree and memory-flamegraph.
Services.prefs.setBoolPref(MEMORY_PREF, true);
// Cycle through all the views to initialize them, otherwise we can't use
// `waitForWidgetsRendered`. The waterfall is shown by default, but all the
// other views are created lazily, so won't emit any events.
yield DetailsView.selectView("js-calltree");
yield DetailsView.selectView("js-flamegraph");
yield DetailsView.selectView("memory-calltree");
yield DetailsView.selectView("memory-flamegraph");
// Need to allow widgets to be updated while hidden, otherwise we can't use
// `waitForWidgetsRendered`.
DetailsSubview.canUpdateWhileHidden = true;
yield startRecording(panel);
yield stopRecording(panel);
@ -49,16 +37,30 @@ let test = Task.async(function*() {
// Import recording.
let rerendered = waitForWidgetsRendered(panel);
let calltreeRendered = once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED);
let fpsRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
ok(true, "The original profiler data appears to have been successfully imported.");
yield rerendered;
yield calltreeRendered;
yield fpsRendered;
ok(true, "The imported data was re-rendered.");
// Ensure that only framerate and js calltree/flamegraph view are available
is($("#overview-pane").hidden, false, "overview graph container still shown");
is($("#memory-overview").hidden, true, "memory graph hidden");
is($("#markers-overview").hidden, true, "markers overview graph hidden");
is($("#time-framerate").hidden, false, "fps graph shown");
is($("#select-waterfall-view").hidden, true, "waterfall button hidden");
is($("#select-js-calltree-view").hidden, false, "jscalltree button shown");
is($("#select-js-flamegraph-view").hidden, false, "jsflamegraph button shown");
is($("#select-memory-calltree-view").hidden, true, "memorycalltree button hidden");
is($("#select-memory-flamegraph-view").hidden, true, "memoryflamegraph button hidden");
ok(DetailsView.isViewSelected(JsCallTreeView), "jscalltree view selected as its the only option");
// Verify imported recording.
let importedData = PerformanceController.getCurrentRecording().getAllData();
@ -79,6 +81,12 @@ let test = Task.async(function*() {
"The imported legacy data was successfully converted for the current tool (7).");
is(importedData.profile.toSource(), data.profile.toSource(),
"The imported legacy data was successfully converted for the current tool (8).");
is(importedData.configuration.withTicks, true,
"The imported legacy data was successfully converted for the current tool (9).");
is(importedData.configuration.withMemory, false,
"The imported legacy data was successfully converted for the current tool (10).");
is(importedData.configuration.sampleFrequency, void 0,
"The imported legacy data was successfully converted for the current tool (11).");
yield teardown(panel);
finish();

Просмотреть файл

@ -8,7 +8,8 @@
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
let { TimelineGraph } = devtools.require("devtools/performance/graphs");
let { rowHeight: MARKERS_GRAPH_ROW_HEIGHT } = TimelineGraph.prototype;
yield startRecording(panel);
ok(true, "Recording has started.");
@ -23,7 +24,7 @@ function spawnTest () {
yield stopRecording(panel);
let overview = OverviewView.markersOverview;
let overview = OverviewView.graphs.get("timeline");
let waterfall = WaterfallView.waterfall;
// Select everything

Просмотреть файл

@ -354,6 +354,7 @@ function* startRecording(panel, options = {
: Promise.resolve();
yield hasStarted;
let overviewRendered = options.waitForOverview
? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
: Promise.resolve();
@ -379,6 +380,7 @@ function* stopRecording(panel, options = {
let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
let button = win.$("#main-record-button");
let overviewRendered = null;
ok(button.hasAttribute("checked"),
"The record button should already be checked.");
@ -399,12 +401,17 @@ function* stopRecording(panel, options = {
: Promise.resolve();
yield hasStopped;
let overviewRendered = options.waitForOverview
? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
: Promise.resolve();
// Wait for the final rendering of the overview, not a low res
// incremental rendering and less likely to be from another rendering that was selected
while (!overviewRendered && options.waitForOverview) {
let [_, res] = yield onceSpread(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
if (res === win.FRAMERATE_GRAPH_HIGH_RES_INTERVAL) {
overviewRendered = true;
}
}
yield stateChanged;
yield overviewRendered;
is(win.PerformanceView.getState(), "recorded",
"The current state is 'recorded'.");
@ -490,3 +497,13 @@ function reload (aTarget, aEvent = "navigate") {
aTarget.activeTab.reload();
return once(aTarget, aEvent);
}
/**
* Forces cycle collection and GC, used in AudioNode destruction tests.
*/
function forceCC () {
info("Triggering GC/CC...");
SpecialPowers.DOMWindowUtils.cycleCollect();
SpecialPowers.DOMWindowUtils.garbageCollect();
SpecialPowers.DOMWindowUtils.garbageCollect();
}

Просмотреть файл

@ -16,7 +16,8 @@ let DetailsView = {
"waterfall": {
id: "waterfall-view",
view: WaterfallView,
requires: ["timeline"]
actors: ["timeline"],
features: ["withMarkers"]
},
"js-calltree": {
id: "js-profile-view",
@ -25,19 +26,19 @@ let DetailsView = {
"js-flamegraph": {
id: "js-flamegraph-view",
view: JsFlameGraphView,
requires: ["timeline"]
actors: ["timeline"]
},
"memory-calltree": {
id: "memory-calltree-view",
view: MemoryCallTreeView,
requires: ["memory"],
pref: "enable-memory"
actors: ["memory"],
features: ["withAllocations"]
},
"memory-flamegraph": {
id: "memory-flamegraph-view",
view: MemoryFlameGraphView,
requires: ["memory", "timeline"],
pref: "enable-memory"
actors: ["memory", "timeline"],
features: ["withAllocations"]
}
},
@ -56,7 +57,6 @@ let DetailsView = {
button.addEventListener("command", this._onViewToggle);
}
yield this.selectDefaultView();
yield this.setAvailableViews();
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
@ -84,32 +84,53 @@ let DetailsView = {
}),
/**
* Sets the possible views based off of prefs and server actor support by hiding/showing the
* buttons that select them and going to default view if currently selected.
* Called when a preference changes in `devtools.performance.ui.`.
* Sets the possible views based off of recording features and server actor support
* by hiding/showing the buttons that select them and going to default view
* if currently selected. Called when a preference changes in `devtools.performance.ui.`.
*/
setAvailableViews: Task.async(function* () {
let mocks = gFront.getMocksInUse();
let recording = PerformanceController.getCurrentRecording();
let isRecording = recording && recording.isRecording();
let invalidCurrentView = false;
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
let recording = PerformanceController.getCurrentRecording();
for (let [name, { view }] of Iterator(this.components)) {
let isSupported = this._isViewSupported(name, false);
let isRecorded = recording && !recording.isRecording();
// View is enabled by its corresponding pref
let isEnabled = !pref || PerformanceController.getOption(pref);
// View is supported by the server actor, and the requried actor is not being mocked
let isSupported = !requires || requires.every(r => !mocks[r]);
$(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
$(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
// If the view is currently selected and not enabled, go back to the
// If the view is currently selected and not supported, go back to the
// default view.
if (!isEnabled && this.isViewSelected(view)) {
yield this.selectDefaultView();
if (!isSupported && this.isViewSelected(view)) {
invalidCurrentView = true;
}
}
// Two scenarios in which we select the default view.
//
// 1: If we currently have selected a view that is no longer valid due
// to feature support, and this isn't the first view, and the current recording
// is not recording.
//
// 2. If we have a finished recording and no panel was selected yet,
// use a default now that we have the recording configurations
if ((this._initialized && !isRecording && invalidCurrentView) ||
(!this._initialized && !isRecording && recording)) {
yield this.selectDefaultView();
}
}),
/**
* Takes a view name and optionally if there must be a currently recording in progress.
*
* @param {string} viewName
* @param {boolean?} isRecording
* @return {boolean}
*/
_isViewSupported: function (viewName, isRecording) {
let { features, actors } = this.components[viewName];
return PerformanceController.isFeatureSupported({ features, actors, isRecording });
},
/**
* Select one of the DetailView's subviews to be rendered,
* hiding the others.
@ -131,6 +152,10 @@ let DetailsView = {
}
}
// Set a flag indicating that a view was explicitly set based on a
// recording's features.
this._initialized = true;
this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
}),
@ -139,14 +164,15 @@ let DetailsView = {
* and preferences enabled.
*/
selectDefaultView: function () {
let { timeline: mockTimeline } = gFront.getMocksInUse();
// If timelines are mocked, the first view available is the js-calltree.
if (mockTimeline) {
return this.selectView("js-calltree");
} else {
// In every other scenario with preferences and mocks, waterfall will
// be the default view.
// We want the waterfall to be default view in almost all cases, except when
// timeline actor isn't supported, or we have markers disabled (which should only
// occur temporarily via bug 1156499
if (this._isViewSupported("waterfall")) {
return this.selectView("waterfall");
} else {
// The JS CallTree should always be supported since the profiler
// actor is as old as the world.
return this.selectView("js-calltree");
}
},
@ -157,6 +183,12 @@ let DetailsView = {
* @return boolean
*/
isViewSelected: function(viewObject) {
// If not initialized, and we have no recordings,
// no views are selected (even though there's a selected panel)
if (!this._initialized) {
return false;
}
let selectedPanel = this.el.selectedPanel;
let selectedId = selectedPanel.id;

Просмотреть файл

@ -7,26 +7,45 @@
// backend. Make sure this isn't lower than DEFAULT_TIMELINE_DATA_PULL_TIMEOUT
// in toolkit/devtools/server/actors/timeline.js
const OVERVIEW_UPDATE_INTERVAL = 200; // ms
const FRAMERATE_GRAPH_LOW_RES_INTERVAL = 100; // ms
const FRAMERATE_GRAPH_HIGH_RES_INTERVAL = 16; // ms
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
const GRAPH_REQUIREMENTS = {
timeline: {
actors: ["timeline"],
features: ["withMarkers"]
},
framerate: {
actors: ["timeline"],
features: ["withTicks"]
},
memory: {
actors: ["memory"],
features: ["withMemory"]
},
}
/**
* View handler for the overview panel's time view, displaying
* framerate, markers and memory over time.
* framerate, timeline and memory over time.
*/
let OverviewView = {
/**
* Sets up the view with event binding.
*/
initialize: function () {
if (gFront.getMocksInUse().timeline) {
this.graphs = new GraphsController({
root: $("#overview-pane"),
getBlueprint: () => PerformanceController.getTimelineBlueprint(),
getTheme: () => PerformanceController.getTheme(),
});
// If no timeline support, shut it all down.
if (!gFront.getActorSupport().timeline) {
this.disable();
return;
}
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
@ -34,14 +53,12 @@ let OverviewView = {
this._onRecordingSelected = this._onRecordingSelected.bind(this);
this._onRecordingTick = this._onRecordingTick.bind(this);
this._onGraphSelecting = this._onGraphSelecting.bind(this);
this._onGraphRendered = this._onGraphRendered.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this._onThemeChanged = this._onThemeChanged.bind(this);
// Toggle the initial visibility of memory and framerate graph containers
// based off of prefs.
$("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
$("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
@ -52,22 +69,14 @@ let OverviewView = {
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
this.graphs.on("selecting", this._onGraphSelecting);
this.graphs.on("rendered", this._onGraphRendered);
},
/**
* Unbinds events.
*/
destroy: Task.async(function*() {
if (this.markersOverview) {
yield this.markersOverview.destroy();
}
if (this.memoryOverview) {
yield this.memoryOverview.destroy();
}
if (this.framerateGraph) {
yield this.framerateGraph.destroy();
}
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
@ -78,16 +87,19 @@ let OverviewView = {
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
this.graphs.off("selecting", this._onGraphSelecting);
this.graphs.off("rendered", this._onGraphRendered);
yield this.graphs.destroy();
}),
/**
* Disabled in the event we're using a Timeline mock, so we'll have no
* markers, ticks or memory data to show, so just block rendering and hide
* timeline, ticks or memory data to show, so just block rendering and hide
* the panel.
*/
disable: function () {
this._disabled = true;
$("#overview-pane").hidden = true;
this.graphs.disableAll();
},
/**
@ -99,28 +111,6 @@ let OverviewView = {
return this._disabled;
},
/**
* Sets the theme for the markers overview and memory overview.
*/
setTheme: function (options={}) {
let theme = options.theme || PerformanceController.getTheme();
if (this.framerateGraph) {
this.framerateGraph.setTheme(theme);
this.framerateGraph.refresh({ force: options.redraw });
}
if (this.markersOverview) {
this.markersOverview.setTheme(theme);
this.markersOverview.refresh({ force: options.redraw });
}
if (this.memoryOverview) {
this.memoryOverview.setTheme(theme);
this.memoryOverview.refresh({ force: options.redraw });
}
},
/**
* Sets the time interval selection for all graphs in this overview.
*
@ -139,7 +129,7 @@ let OverviewView = {
let mapEnd = () => recording.getDuration();
let selection = { start: interval.startTime, end: interval.endTime };
this._stopSelectionChangeEventPropagation = options.stopPropagation;
this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
this.graphs.setMappedSelection(selection, { mapStart, mapEnd });
this._stopSelectionChangeEventPropagation = false;
},
@ -159,80 +149,10 @@ let OverviewView = {
}
let mapStart = () => 0;
let mapEnd = () => recording.getDuration();
let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
return { startTime: selection.min, endTime: selection.max };
},
/**
* Sets up the markers overivew graph, if needed.
*
* @return object
* A promise resolved to `true` when the graph was initialized.
*/
_markersGraphAvailable: Task.async(function *() {
if (this.markersOverview) {
yield this.markersOverview.ready();
return true;
}
let blueprint = PerformanceController.getTimelineBlueprint();
this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
this.markersOverview.on("selecting", this._onGraphSelecting);
yield this.markersOverview.ready();
this.setTheme();
return true;
}),
/**
* Sets up the memory overview graph, if allowed and needed.
*
* @return object
* A promise resolved to `true` if the graph was initialized and is
* ready to use, `false` if the graph is disabled.
*/
_memoryGraphAvailable: Task.async(function *() {
if (!PerformanceController.getOption("enable-memory")) {
return false;
}
if (this.memoryOverview) {
yield this.memoryOverview.ready();
return true;
}
this.memoryOverview = new MemoryGraph($("#memory-overview"));
yield this.memoryOverview.ready();
this.setTheme();
CanvasGraphUtils.linkAnimation(this.markersOverview, this.memoryOverview);
CanvasGraphUtils.linkSelection(this.markersOverview, this.memoryOverview);
return true;
}),
/**
* Sets up the framerate graph, if allowed and needed.
*
* @return object
* A promise resolved to `true` if the graph was initialized and is
* ready to use, `false` if the graph is disabled.
*/
_framerateGraphAvailable: Task.async(function *() {
if (!PerformanceController.getOption("enable-framerate")) {
return false;
}
if (this.framerateGraph) {
yield this.framerateGraph.ready();
return true;
}
this.framerateGraph = new FramerateGraph($("#time-framerate"));
yield this.framerateGraph.ready();
this.setTheme();
CanvasGraphUtils.linkAnimation(this.markersOverview, this.framerateGraph);
CanvasGraphUtils.linkSelection(this.markersOverview, this.framerateGraph);
return true;
}),
/**
* Method for handling all the set up for rendering the overview graphs.
*
@ -244,29 +164,10 @@ let OverviewView = {
return;
}
let recording = PerformanceController.getCurrentRecording();
let duration = recording.getDuration();
let markers = recording.getMarkers();
let memory = recording.getMemory();
let timestamps = recording.getTicks();
// Empty or older recordings might yield no markers, memory or timestamps.
if (markers && (yield this._markersGraphAvailable())) {
this.markersOverview.setData({ markers, duration });
this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
}
if (memory && (yield this._memoryGraphAvailable())) {
this.memoryOverview.dataDuration = duration;
this.memoryOverview.setData(memory);
this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
}
if (timestamps && (yield this._framerateGraphAvailable())) {
this.framerateGraph.dataDuration = duration;
yield this.framerateGraph.setDataFromTimestamps(timestamps, resolution);
this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
}
yield this.graphs.render(recording.getAllData(), resolution);
// Finished rendering all graphs in this overview.
this.emit(EVENTS.OVERVIEW_RENDERED);
this.emit(EVENTS.OVERVIEW_RENDERED, resolution);
}),
/**
@ -290,24 +191,6 @@ let OverviewView = {
}
},
/**
* Fired when the graph selection has changed. Called by
* mouseup and scroll events.
*/
_onGraphSelecting: function () {
if (this._stopSelectionChangeEventPropagation) {
return;
}
// If the range is smaller than a pixel (which can happen when performing
// a click on the graphs), treat this as a cleared selection.
let interval = this.getTimeInterval();
if (interval.endTime - interval.startTime < 1) {
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
} else {
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
}
},
/**
* Called when recording will start. No recording because it does not
* exist yet, but can just disable from here. This will only trigger for
@ -316,7 +199,7 @@ let OverviewView = {
_onRecordingWillStart: Task.async(function* () {
this._onRecordingStateChange();
yield this._checkSelection();
this.markersOverview.dropSelection();
this.graphs.dropSelection();
}),
/**
@ -358,12 +241,14 @@ let OverviewView = {
return;
}
this._onRecordingStateChange();
this._setGraphVisibilityFromRecordingFeatures(recording);
// If this recording is complete, render the high res graph
if (!recording.isRecording()) {
yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
}
yield this._checkSelection(recording);
this.markersOverview.dropSelection();
this.graphs.dropSelection();
}),
/**
@ -407,49 +292,73 @@ let OverviewView = {
* based on whether a recording currently exists and is not in progress.
*/
_checkSelection: Task.async(function* (recording) {
let selectionEnabled = recording ? !recording.isRecording() : false;
if (yield this._markersGraphAvailable()) {
this.markersOverview.selectionEnabled = selectionEnabled;
}
if (yield this._memoryGraphAvailable()) {
this.memoryOverview.selectionEnabled = selectionEnabled;
}
if (yield this._framerateGraphAvailable()) {
this.framerateGraph.selectionEnabled = selectionEnabled;
}
let isEnabled = recording ? !recording.isRecording() : false;
yield this.graphs.selectionEnabled(isEnabled);
}),
/**
* Called whenever a preference in `devtools.performance.ui.` changes. Used
* to toggle the visibility of memory and framerate graphs.
* Fired when the graph selection has changed. Called by
* mouseup and scroll events.
*/
_onGraphSelecting: function () {
if (this._stopSelectionChangeEventPropagation) {
return;
}
// If the range is smaller than a pixel (which can happen when performing
// a click on the graphs), treat this as a cleared selection.
let interval = this.getTimeInterval();
if (interval.endTime - interval.startTime < 1) {
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
} else {
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
}
},
_onGraphRendered: function (_, graphName) {
switch (graphName) {
case "timeline":
this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
break;
case "memory":
this.emit(EVENTS.MEMORY_GRAPH_RENDERED);
break;
case "framerate":
this.emit(EVENTS.FRAMERATE_GRAPH_RENDERED);
break;
}
},
/**
* Called whenever a preference in `devtools.performance.ui.` changes.
* Does not care about the enabling of memory/framerate graphs,
* because those will set values on a recording model, and
* the graphs will render based on the existence.
*/
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
switch (prefName) {
case "enable-memory": {
$("#memory-overview").hidden = !prefValue;
break;
}
case "enable-framerate": {
$("#time-framerate").hidden = !prefValue;
break;
}
case "hidden-markers": {
if (yield this._markersGraphAvailable()) {
let graph;
if (graph = yield this.graphs.isAvailable("timeline")) {
let blueprint = PerformanceController.getTimelineBlueprint();
this.markersOverview.setBlueprint(blueprint);
this.markersOverview.refresh({ force: true });
graph.setBlueprint(blueprint);
graph.refresh({ force: true });
}
break;
}
}
}),
_setGraphVisibilityFromRecordingFeatures: function (recording) {
for (let [graphName, requirements] of Iterator(GRAPH_REQUIREMENTS)) {
this.graphs.enable(graphName, PerformanceController.isFeatureSupported(requirements));
}
},
/**
* Called when `devtools.theme` changes.
*/
_onThemeChanged: function (_, theme) {
this.setTheme({ theme, redraw: true });
this.graphs.setTheme({ theme, redraw: true });
},
toString: () => "[object OverviewView]"

Просмотреть файл

@ -83,6 +83,10 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
* focusable element.
* {string} trigger: The DOM event that should trigger editing,
* defaults to "click"
* {boolean} multiline: Should the editor be a multiline textarea?
* defaults to false
* {boolean} trimOutput: Should the returned string be trimmed?
* defaults to true
*/
function editableField(aOptions)
{
@ -189,6 +193,7 @@ function InplaceEditor(aOptions, aEvent)
this.destroy = aOptions.destroy;
this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
this.multiline = aOptions.multiline || false;
this.trimOutput = aOptions.trimOutput === undefined ? true : !!aOptions.trimOutput;
this.stopOnShiftTab = !!aOptions.stopOnShiftTab;
this.stopOnTab = !!aOptions.stopOnTab;
this.stopOnReturn = !!aOptions.stopOnReturn;
@ -254,6 +259,12 @@ exports.InplaceEditor = InplaceEditor;
InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
InplaceEditor.prototype = {
get currentInputValue() {
let val = this.trimOutput ? this.input.value.trim() : this.input.value;
return val;
},
_createInput: function InplaceEditor_createEditor()
{
this.input =
@ -398,7 +409,7 @@ InplaceEditor.prototype = {
// Call the user's change handler if available.
if (this.change) {
this.change(this.input.value.trim());
this.change(this.currentInputValue);
}
return true;
@ -790,8 +801,8 @@ InplaceEditor.prototype = {
this._applied = true;
if (this.done) {
let val = this.input.value.trim();
return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
let val = this.cancelled ? this.initial : this.currentInputValue;
return this.done(val, !this.cancelled, direction);
}
return null;
@ -1034,7 +1045,7 @@ InplaceEditor.prototype = {
// Call the user's change handler if available.
if (this.change) {
this.change(this.input.value.trim());
this.change(this.currentInputValue);
}
},

Просмотреть файл

@ -117,14 +117,20 @@ exports.filterPlatformData = function filterPlatformData (frames) {
*/
function nsIURL(url) {
let cached = gNSURLStore.get(url);
if (cached) {
// If we cached a valid URI, or `null` in the case
// of a failure, return it.
if (cached !== void 0) {
return cached;
}
let uri = null;
try {
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
// Access the host, because the constructor doesn't necessarily throw
// if it's invalid, but accessing the host can throw as well
uri.host;
} catch(e) {
// The passed url string is invalid.
uri = null;
}
gNSURLStore.set(url, uri);
return uri;

Просмотреть файл

@ -70,7 +70,9 @@ support-files =
[browser_graphs-12.js]
[browser_graphs-13.js]
[browser_graphs-14.js]
[browser_inplace-editor.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
[browser_graphs-15.js]
[browser_layoutHelpers.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_layoutHelpers-getBoxQuads.js]

Просмотреть файл

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that graph widgets correctly emit mouse input events.
const FAST_FPS = 60;
const SLOW_FPS = 10;
// Each element represents a second
const FRAMES= [FAST_FPS, FAST_FPS, FAST_FPS, SLOW_FPS, FAST_FPS];
const TEST_DATA = [];
const INTERVAL = 100;
const DURATION = 5000; // 5s
let t = 0;
for (let frameRate of FRAMES) {
for (let i = 0; i < frameRate; i++) {
let delta = Math.floor(1000 / frameRate); // Duration between frames at this rate
t += delta;
TEST_DATA.push(t);
}
}
let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
add_task(function*() {
yield promiseTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
});
function* performTest() {
let [host, win, doc] = yield createHost();
let graph = new LineGraphWidget(doc.body, "fps");
yield testGraph(graph);
yield graph.destroy();
host.destroy();
}
function* testGraph(graph) {
console.log("test data", TEST_DATA);
yield graph.setDataFromTimestamps(TEST_DATA, INTERVAL, DURATION);
is(graph._avgTooltip.querySelector("[text=value]").textContent, "50",
"The average tooltip displays the correct value.");
}

Просмотреть файл

@ -0,0 +1,91 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let {editableField, getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
// Test that the trimOutput option for the inplace editor works correctly.
add_task(function*() {
yield promiseTab("data:text/html;charset=utf-8,inline editor tests");
let [host, win, doc] = yield createHost();
yield testNonTrimmed(doc);
yield testTrimmed(doc);
host.destroy();
gBrowser.removeCurrentTab();
});
function testNonTrimmed(doc) {
info("Testing the trimOutput=false option");
let def = promise.defer();
let initial = "\nMultiple\nLines\n";
let changed = " \nMultiple\nLines\n with more whitespace ";
createInplaceEditorAndClick({
trimOutput: false,
multiline: true,
initial: initial,
start: function(editor) {
is(editor.input.value, initial, "Explicit initial value should be used.");
editor.input.value = changed;
EventUtils.sendKey("return");
},
done: onDone(changed, true, def)
}, doc);
return def.promise;
}
function testTrimmed(doc) {
info("Testing the trimOutput=true option (default value)");
let def = promise.defer();
let initial = "\nMultiple\nLines\n";
let changed = " \nMultiple\nLines\n with more whitespace ";
createInplaceEditorAndClick({
initial: initial,
multiline: true,
start: function(editor) {
is(editor.input.value, initial, "Explicit initial value should be used.");
editor.input.value = changed;
EventUtils.sendKey("return");
},
done: onDone(changed.trim(), true, def)
}, doc);
return def.promise;
}
function onDone(value, isCommit, def) {
return function(actualValue, actualCommit) {
info("Inplace-editor's done callback executed, checking its state");
is(actualValue, value, "The value is correct");
is(actualCommit, isCommit, "The commit boolean is correct");
def.resolve();
}
}
function createInplaceEditorAndClick(options, doc) {
doc.body.innerHTML = "";
let span = options.element = createSpan(doc);
info("Creating an inplace-editor field");
editableField(options);
info("Clicking on the inplace-editor field to turn to edit mode");
span.click();
}
function createSpan(doc) {
info("Creating a new span element");
let span = doc.createElement("span");
span.setAttribute("tabindex", "0");
span.textContent = "Edit Me!";
doc.body.appendChild(span);
return span;
}

Просмотреть файл

@ -21,7 +21,8 @@ const L10N = new ViewHelpers.L10N(STRINGS_URI);
* - label: the label used in the waterfall to identify the marker
* - colorName: the name of the DevTools color used for this marker. If adding
* a new color, be sure to check that there's an entry for
* `.marker-details-bullet.{COLORNAME}` for the equivilent entry.
* `.marker-details-bullet.{COLORNAME}` for the equivilent entry
* in ./browser/themes/shared/devtools/performance.inc.css
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
*
* Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
@ -68,6 +69,11 @@ const TIMELINE_BLUEPRINT = {
colorName: "highlight-bluegrey",
label: L10N.getStr("timeline.label.consoleTime")
},
"GarbageCollection": {
group: 1,
colorName: "highlight-red",
label: L10N.getStr("timeline.label.garbageCollection")
},
};
// Exported symbols.

Просмотреть файл

@ -1302,14 +1302,15 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
* represents the elapsed time on each refresh driver tick.
* @param number interval
* The maximum amount of time to wait between calculations.
* @param number duration
* The duration of the recording in milliseconds.
*/
setDataFromTimestamps: Task.async(function*(timestamps, interval) {
setDataFromTimestamps: Task.async(function*(timestamps, interval, duration) {
let {
plottedData,
plottedMinMaxSum
} = yield CanvasGraphUtils._performTaskInWorker("plotTimestampsGraph", {
timestamps: timestamps,
interval: interval
timestamps, interval, duration
});
this._tempMinMaxSum = plottedMinMaxSum;

Просмотреть файл

@ -21,10 +21,11 @@ self.onmessage = e => {
* @param number id
* @param array timestamps
* @param number interval
* @param number duration
*/
function plotTimestampsGraph(id, args) {
let plottedData = plotTimestamps(args.timestamps, args.interval);
let plottedMinMaxSum = getMinMaxSum(plottedData);
let plottedMinMaxSum = getMinMaxAvg(plottedData, args.timestamps, args.duration);
let response = { id, plottedData, plottedMinMaxSum };
self.postMessage(response);
@ -33,21 +34,25 @@ function plotTimestampsGraph(id, args) {
/**
* Gets the min, max and average of the values in an array.
* @param array source
* @param array timestamps
* @param number duration
* @return object
*/
function getMinMaxSum(source) {
function getMinMaxAvg(source, timestamps, duration) {
let totalTicks = source.length;
let totalFrames = timestamps.length;
let maxValue = Number.MIN_SAFE_INTEGER;
let minValue = Number.MAX_SAFE_INTEGER;
let avgValue = 0;
let sumValues = 0;
// Calculate the average by counting how many frames occurred
// in the duration of the recording, rather than average the frame points
// we have, as that weights higher FPS, as there'll be more timestamps for those
// values
let avgValue = totalFrames / (duration / 1000);
for (let { value } of source) {
maxValue = Math.max(value, maxValue);
minValue = Math.min(value, minValue);
sumValues += value;
}
avgValue = sumValues / totalTicks;
return { minValue, maxValue, avgValue };
}

Просмотреть файл

@ -44,6 +44,7 @@ timeline.label.parseHTML=Parse HTML
timeline.label.parseXML=Parse XML
timeline.label.domevent=DOM Event
timeline.label.consoleTime=Console
timeline.label.garbageCollection=GC Event
# LOCALIZATION NOTE (graphs.memory):
# This string is displayed in the memory graph of the Performance tool,

Просмотреть файл

@ -578,7 +578,7 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
.findbar-button > .toolbarbutton-text,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-container,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
-moz-margin-end: 0;
padding: 2px 6px;
@ -589,7 +589,7 @@ menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-container,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
padding: 3px 7px;
}
@ -614,7 +614,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open]:not([disabled=true]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-container,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-icon {
background: var(--toolbarbutton-hover-background);
border-width: 1px;
@ -632,7 +632,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open="true"]) > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-container,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon {
background: var(--toolbarbutton-active-background);
box-shadow: var(--toolbarbutton-active-boxshadow);

Просмотреть файл

@ -367,6 +367,7 @@ browser.jar:
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)

Просмотреть файл

@ -500,6 +500,7 @@ browser.jar:
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)

Просмотреть файл

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
<g fill="#71c054" fill-rule="evenodd">
<path d="m8,1c-3.9,0-7,3.1-7,7s3.1,7 7,7c3.9,0 7-3.1 7-7s-3.1-7-7-7zm-.1,12c-2.8,0-5-2.2-5-5 0-2.8 2.2-5 5-5s5,2.2 5,5c0,2.8-2.2,5-5,5z"/>
<path d="m8,6.9c.6,0 1.1,.5 1.1,1.1 0,.6-.5,1.1-1.1,1.1-.6,0-1.1-.5-1.1-1.1 0-.6 .5-1.1 1.1-1.1z"/>
<path d="m11.3,4.6l-3.9,2.5 1.5,1.4 2.4-3.9z"/>
<path opacity=".4" d="m4.6,10c.7,1.2 2,2 3.4,2 1.5,0 2.7-.8 3.4-2h-6.8z"/>
<g opacity=".3">
<path d="m7.1,5.1l-.6-1.3-.9,.4 .7,1.3c.2-.1 .5-.3 .8-.4z"/>
<path d="m9.8,5.6l.7-1.4-.9-.4-.7,1.3c.3,.2 .6,.3 .9,.5z"/>
<path d="m10.8,7c.1,.3 .2,.7 .2,1h2v-1h-2.2z"/>
<path d="m5,8c0-.3 .1-.7 .2-1h-2.2l-.1,1h2.1z"/>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 814 B

Просмотреть файл

@ -447,6 +447,13 @@
.waterfall-marker-bullet.highlight-lightorange {
background-color: var(--theme-highlight-lightorange);
}
#performance-filter-menupopup > menuitem.highlight-red:before,
.marker-details-bullet.highlight-red,
.waterfall-marker-bar.highlight-red,
.waterfall-marker-bullet.highlight-red {
background-color: var(--theme-highlight-red);
}
#waterfall-details > * {
padding-top: 3px;

Просмотреть файл

@ -442,6 +442,7 @@ browser.jar:
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
skin/classic/browser/devtools/tool-profiler-active.svg (../shared/devtools/images/tool-profiler-active.svg)
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/tool-webaudio.svg (../shared/devtools/images/tool-webaudio.svg)

Просмотреть файл

@ -2,6 +2,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.roboexample.test"
#ifdef MOZ_ANDROID_SHARED_ID
android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
#endif
android:versionCode="1"
android:versionName="1.0" >

Просмотреть файл

@ -39,14 +39,19 @@ java-tests := \
$(wildcard $(TESTPATH)/components/*.java) \
$(wildcard $(TESTPATH)/helpers/*.java)
PP_TARGETS += manifest
manifest := $(srcdir)/AndroidManifest.xml.in
manifest_TARGET := AndroidManifest.xml
PP_TARGETS += manifest
manifest := $(srcdir)/AndroidManifest.xml.in
manifest_TARGET := export
manifest_FLAGS += \
-DMOZ_ANDROID_SHARED_ID='$(ANDROID_PACKAGE_NAME).sharedID' \
-DMOZ_ANDROID_SHARED_ACCOUNT_TYPE='$(ANDROID_PACKAGE_NAME)_sync' \
$(NULL)
ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
# Install robocop configs and helper
INSTALL_TARGETS += robocop
robocop_TARGET := libs
robocop_TARGET := export
robocop_DEST := $(CURDIR)
robocop_FILES := \
$(TESTPATH)/robocop.ini \
@ -68,7 +73,8 @@ ROBOCOP_FILES := \
$(wildcard $(TESTPATH)/reader_mode_pages) \
$(NULL)
ROBOCOP_DEST = $(DEPTH)/_tests/testing/mochitest/tests/robocop/
ROBOCOP_DEST := $(DEPTH)/_tests/testing/mochitest/tests/robocop/
ROBOCOP_TARGET := export
INSTALL_TARGETS += ROBOCOP
GARBAGE += \

Просмотреть файл

@ -5192,6 +5192,7 @@ if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
MOZ_APPLEMEDIA=1
fi
if test "$COMPILE_ENVIRONMENT"; then
if test -n "$MOZ_APPLEMEDIA"; then
AC_DEFINE(MOZ_APPLEMEDIA)
# hack in frameworks for fmp4 - see bug 1029974
@ -5201,6 +5202,7 @@ if test -n "$MOZ_APPLEMEDIA"; then
AC_CHECK_HEADER([CoreMedia/CoreMedia.h], [],
[AC_MSG_ERROR([MacOS X 10.7 SDK or later is required])])
fi
fi # COMPILE_ENVIRONMENT
dnl ========================================================
dnl = DirectShow support

Просмотреть файл

@ -155,7 +155,7 @@ compartment.
and end events.
`reason`
: A very short string describing th reason why the collection was
: A very short string describing the reason why the collection was
triggered. Known values include the following:
* "API"
@ -199,7 +199,13 @@ compartment.
: If SpiderMonkey's collector determined it could not incrementally
collect garbage, and had to do a full GC all at once, this is a short
string describing the reason it determined the full GC was necessary.
Otherwise, `null` is returned.
Otherwise, `null` is returned. Known values include the following:
* "GC mode"
* "malloc bytes trigger"
* "allocation trigger"
* "requested"
Function Properties of the `Debugger.Memory.prototype` Object
-------------------------------------------------------------

Просмотреть файл

@ -54,6 +54,7 @@ class MachCommands(MachCommandBase):
ensure_exit_code=False, # Don't throw on non-zero exit code.
cwd=mozpath.join(self.topobjdir, 'mobile', 'android', 'gradle'))
@Command('gradle-install', category='devenv',
description='Install gradle environment.',
conditions=[conditions.is_android])
@ -115,6 +116,7 @@ class MachCommands(MachCommandBase):
srcdir('app/build.gradle', 'mobile/android/gradle/app/build.gradle')
objdir('app/src/main/AndroidManifest.xml', 'mobile/android/base/AndroidManifest.xml')
objdir('app/src/androidTest/AndroidManifest.xml', 'build/mobile/robocop/AndroidManifest.xml')
srcdir('app/src/androidTest/res', 'build/mobile/robocop/res')
srcdir('app/src/androidTest/assets', 'mobile/android/base/tests/assets')
objdir('app/src/debug/assets', 'dist/fennec/assets')

Просмотреть файл

@ -475,12 +475,20 @@
<xul:image class="popup-notification-icon"
xbl:inherits="popupid,src=icon"/>
</xul:vbox>
<xul:vbox flex="1">
<xul:label class="popup-notification-originHost header"
xbl:inherits="value=originhost"
crop="end"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label,popupid"/>
<xul:vbox>
<xul:hbox align="start">
<xul:vbox flex="1">
<xul:label class="popup-notification-originHost header"
xbl:inherits="value=originhost"
crop="end"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label,popupid"/>
</xul:vbox>
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
xbl:inherits="oncommand=closebuttoncommand"
tooltiptext="&closeNotification.tooltip;"/>
</xul:hbox>
<children includes="popupnotificationcontent"/>
<xul:label class="text-link popup-notification-learnmore-link"
xbl:inherits="href=learnmoreurl">&learnMore;</xul:label>
@ -502,12 +510,6 @@
</xul:button>
</xul:hbox>
</xul:vbox>
<xul:vbox pack="start">
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
xbl:inherits="oncommand=closebuttoncommand"
tooltiptext="&closeNotification.tooltip;"/>
</xul:vbox>
</content>
<resources>
<stylesheet src="chrome://global/skin/notification.css"/>

Просмотреть файл

@ -4,41 +4,22 @@
"use strict";
const { Cc, Ci, Cu } = require("chrome");
let protocol = require("devtools/server/protocol");
let { method, RetVal, Arg, types } = protocol;
const { reportException } = require("devtools/toolkit/DevToolsUtils");
const protocol = require("devtools/server/protocol");
const { method, RetVal, Arg, types } = protocol;
const { MemoryBridge } = require("./utils/memory-bridge");
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "StackFrameCache",
"devtools/server/actors/utils/stack", true);
/**
* A method decorator that ensures the actor is in the expected state before
* proceeding. If the actor is not in the expected state, the decorated method
* returns a rejected promise.
*
* @param String expectedState
* The expected state.
* @param String activity
* Additional info about what's going on.
* @param Function method
* The actor method to proceed with when the actor is in the expected
* state.
*
* @returns Function
* The decorated method.
* Proxies a call to the MemoryActor to the underlying MemoryBridge,
* allowing access to MemoryBridge features by defining the RDP
* request/response signature.
*/
function expectState(expectedState, method, activity) {
return function(...args) {
if (this.state !== expectedState) {
const msg = `Wrong state while ${activity}:` +
`Expected '${expectedState}',` +
`but current state is '${this.state}'.`;
return Promise.reject(new Error(msg));
}
return method.apply(this, args);
};
function linkBridge (methodName, definition) {
return method(function () {
return this.bridge[methodName].apply(this.bridge, arguments);
}, definition);
}
types.addDictType("AllocationsRecordingOptions", {
@ -66,6 +47,7 @@ let MemoryActor = protocol.ActorClass({
* The set of unsolicited events the MemoryActor emits that will be sent over
* the RDP (by protocol.js).
*/
events: {
// Same format as the data passed to the
// `Debugger.Memory.prototype.onGarbageCollection` hook. See
@ -76,37 +58,17 @@ let MemoryActor = protocol.ActorClass({
},
},
get dbg() {
if (!this._dbg) {
this._dbg = this.parent.makeDebugger();
}
return this._dbg;
},
initialize: function(conn, parent, frameCache = new StackFrameCache()) {
protocol.Actor.prototype.initialize.call(this, conn);
this.parent = parent;
this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
.getService(Ci.nsIMemoryReporterManager);
this.state = "detached";
this._dbg = null;
this._frameCache = frameCache;
this._onGarbageCollection = data =>
events.emit(this, "garbage-collection", data);
this._onWindowReady = this._onWindowReady.bind(this);
events.on(this.parent, "window-ready", this._onWindowReady);
this._onGarbageCollection = this._onGarbageCollection.bind(this);
this.bridge = new MemoryBridge(parent, frameCache);
this.bridge.on("garbage-collection", this._onGarbageCollection);
},
destroy: function() {
events.off(this.parent, "window-ready", this._onWindowReady);
this._mgr = null;
if (this.state === "attached") {
this.detach();
}
this.bridge.off("garbage-collection", this._onGarbageCollection);
this.bridge.destroy();
protocol.Actor.prototype.destroy.call(this);
},
@ -117,12 +79,7 @@ let MemoryActor = protocol.ActorClass({
* recording allocations or take a census of the heap. In addition, the
* MemoryActor will start emitting GC events.
*/
attach: method(expectState("detached", function() {
this.dbg.addDebuggees();
this.dbg.memory.onGarbageCollection = this._onGarbageCollection;
this.state = "attached";
},
`attaching to the debugger`), {
attach: linkBridge("attach", {
request: {},
response: {
type: "attached"
@ -132,13 +89,7 @@ let MemoryActor = protocol.ActorClass({
/**
* Detach from this MemoryActor.
*/
detach: method(expectState("attached", function() {
this._clearDebuggees();
this.dbg.enabled = false;
this._dbg = null;
this.state = "detached";
},
`detaching from the debugger`), {
detach: linkBridge("detach", {
request: {},
response: {
type: "detached"
@ -148,51 +99,17 @@ let MemoryActor = protocol.ActorClass({
/**
* Gets the current MemoryActor attach/detach state.
*/
getState: method(function() {
return this.state;
}, {
getState: linkBridge("getState", {
response: {
state: RetVal(0, "string")
}
}),
_clearDebuggees: function() {
if (this._dbg) {
if (this.dbg.memory.trackingAllocationSites) {
this.dbg.memory.drainAllocationsLog();
}
this._clearFrames();
this.dbg.removeAllDebuggees();
}
},
_clearFrames: function() {
if (this.dbg.memory.trackingAllocationSites) {
this._frameCache.clearFrames();
}
},
/**
* Handler for the parent actor's "window-ready" event.
*/
_onWindowReady: function({ isTopLevel }) {
if (this.state == "attached") {
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
this._clearDebuggees();
this._frameCache.initFrames();
}
this.dbg.addDebuggees();
}
},
/**
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
* more information.
*/
takeCensus: method(expectState("attached", function() {
return this.dbg.memory.takeCensus();
},
`taking census`), {
takeCensus: linkBridge("takeCensus", {
request: {},
response: RetVal("json")
}),
@ -203,24 +120,7 @@ let MemoryActor = protocol.ActorClass({
* @param AllocationsRecordingOptions options
* See the protocol.js definition of AllocationsRecordingOptions above.
*/
startRecordingAllocations: method(expectState("attached", function(options = {}) {
if (this.dbg.memory.trackingAllocationSites) {
return Date.now();
}
this._frameCache.initFrames();
this.dbg.memory.allocationSamplingProbability = options.probability != null
? options.probability
: 1.0;
if (options.maxLogLength != null) {
this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
}
this.dbg.memory.trackingAllocationSites = true;
return Date.now();
},
`starting recording allocations`), {
startRecordingAllocations: linkBridge("startRecordingAllocations", {
request: {
options: Arg(0, "nullable:AllocationsRecordingOptions")
},
@ -233,13 +133,7 @@ let MemoryActor = protocol.ActorClass({
/**
* Stop recording allocation sites.
*/
stopRecordingAllocations: method(expectState("attached", function() {
this.dbg.memory.trackingAllocationSites = false;
this._clearFrames();
return Date.now();
},
`stopping recording allocations`), {
stopRecordingAllocations: linkBridge("stopRecordingAllocations", {
request: {},
response: {
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
@ -251,115 +145,14 @@ let MemoryActor = protocol.ActorClass({
* Return settings used in `startRecordingAllocations` for `probability`
* and `maxLogLength`. Currently only uses in tests.
*/
getAllocationsSettings: method(expectState("attached", function() {
return {
maxLogLength: this.dbg.memory.maxAllocationsLogLength,
probability: this.dbg.memory.allocationSamplingProbability
};
},
`getting allocations settings`), {
getAllocationsSettings: linkBridge("getAllocationsSettings", {
request: {},
response: {
options: RetVal(0, "json")
}
}),
/**
* Get a list of the most recent allocations since the last time we got
* allocations, as well as a summary of all allocations since we've been
* recording.
*
* @returns Object
* An object of the form:
*
* {
* allocations: [<index into "frames" below>, ...],
* allocationsTimestamps: [
* <timestamp for allocations[0]>,
* <timestamp for allocations[1]>,
* ...
* ],
* frames: [
* {
* line: <line number for this frame>,
* column: <column number for this frame>,
* source: <filename string for this frame>,
* functionDisplayName: <this frame's inferred function name function or null>,
* parent: <index into "frames">
* },
* ...
* ],
* counts: [
* <number of allocations in frames[0]>,
* <number of allocations in frames[1]>,
* <number of allocations in frames[2]>,
* ...
* ]
* }
*
* The timestamps' unit is microseconds since the epoch.
*
* Subsequent `getAllocations` request within the same recording and
* tab navigation will always place the same stack frames at the same
* indices as previous `getAllocations` requests in the same
* recording. In other words, it is safe to use the index as a
* unique, persistent id for its frame.
*
* Additionally, the root node (null) is always at index 0.
*
* Note that the allocation counts include "self" allocations only,
* and don't account for allocations in child frames.
*
* We use the indices into the "frames" array to avoid repeating the
* description of duplicate stack frames both when listing
* allocations, and when many stacks share the same tail of older
* frames. There shouldn't be any duplicates in the "frames" array,
* as that would defeat the purpose of this compression trick.
*
* In the future, we might want to split out a frame's "source" and
* "functionDisplayName" properties out the same way we have split
* frames out with the "frames" array. While this would further
* compress the size of the response packet, it would increase CPU
* usage to build the packet, and it should, of course, be guided by
* profiling and done only when necessary.
*/
getAllocations: method(expectState("attached", function() {
if (this.dbg.memory.allocationsLogOverflowed) {
// Since the last time we drained the allocations log, there have been
// more allocations than the log's capacity, and we lost some data. There
// isn't anything actionable we can do about this, but put a message in
// the browser console so we at least know that it occurred.
reportException("MemoryActor.prototype.getAllocations",
"Warning: allocations log overflowed and lost some data.");
}
const allocations = this.dbg.memory.drainAllocationsLog()
const packet = {
allocations: [],
allocationsTimestamps: []
};
for (let { frame: stack, timestamp } of allocations) {
if (stack && Cu.isDeadWrapper(stack)) {
continue;
}
// Safe because SavedFrames are frozen/immutable.
let waived = Cu.waiveXrays(stack);
// Ensure that we have a form, count, and index for new allocations
// because we potentially haven't seen some or all of them yet. After this
// loop, we can rely on the fact that every frame we deal with already has
// its metadata stored.
let index = this._frameCache.addFrame(waived);
packet.allocations.push(index);
packet.allocationsTimestamps.push(timestamp);
}
return this._frameCache.updateFramePacket(packet);
},
`getting allocations`), {
getAllocations: linkBridge("getAllocations", {
request: {},
response: RetVal("json")
}),
@ -367,11 +160,7 @@ let MemoryActor = protocol.ActorClass({
/*
* Force a browser-wide GC.
*/
forceGarbageCollection: method(function() {
for (let i = 0; i < 3; i++) {
Cu.forceGC();
}
}, {
forceGarbageCollection: linkBridge("forceGarbageCollection", {
request: {},
response: {}
}),
@ -381,9 +170,7 @@ let MemoryActor = protocol.ActorClass({
* collection, see
* https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
*/
forceCycleCollection: method(function() {
Cu.forceCC();
}, {
forceCycleCollection: linkBridge("forceCycleCollection", {
request: {},
response: {}
}),
@ -394,47 +181,23 @@ let MemoryActor = protocol.ActorClass({
*
* @returns object
*/
measure: method(function() {
let result = {};
let jsObjectsSize = {};
let jsStringsSize = {};
let jsOtherSize = {};
let domSize = {};
let styleSize = {};
let otherSize = {};
let totalSize = {};
let jsMilliseconds = {};
let nonJSMilliseconds = {};
try {
this._mgr.sizeOfTab(this.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
result.total = totalSize.value;
result.domSize = domSize.value;
result.styleSize = styleSize.value;
result.jsObjectsSize = jsObjectsSize.value;
result.jsStringsSize = jsStringsSize.value;
result.jsOtherSize = jsOtherSize.value;
result.otherSize = otherSize.value;
result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
} catch (e) {
reportException("MemoryActor.prototype.measure", e);
}
return result;
}, {
measure: linkBridge("measure", {
request: {},
response: RetVal("json"),
}),
residentUnique: method(function() {
return this._mgr.residentUnique;
}, {
residentUnique: linkBridge("residentUnique", {
request: {},
response: { value: RetVal("number") }
})
}),
/**
* Called when the underlying MemoryBridge fires a "garbage-collection" events.
* Propagates over RDP.
*/
_onGarbageCollection: function (data) {
events.emit(this, "garbage-collection", data);
},
});
exports.MemoryActor = MemoryActor;

Просмотреть файл

@ -24,8 +24,9 @@ const protocol = require("devtools/server/protocol");
const {method, Arg, RetVal, Option} = protocol;
const events = require("sdk/event/core");
const {setTimeout, clearTimeout} = require("sdk/timers");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {MemoryActor} = require("devtools/server/actors/memory");
const {MemoryBridge} = require("devtools/server/actors/utils/memory-bridge");
const {FramerateActor} = require("devtools/server/actors/framerate");
const {StackFrameCache} = require("devtools/server/actors/utils/stack");
@ -109,9 +110,11 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
this._isRecording = false;
this._stackFrames = null;
this._memoryBridge = null;
// Make sure to get markers from new windows as they become available
this._onWindowReady = this._onWindowReady.bind(this);
this._onGarbageCollection = this._onGarbageCollection.bind(this);
events.on(this.tabActor, "window-ready", this._onWindowReady);
},
@ -132,6 +135,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
events.off(this.tabActor, "window-ready", this._onWindowReady);
this.tabActor = null;
this._memoryBridge = null;
protocol.Actor.prototype.destroy.call(this);
},
@ -173,10 +177,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
* markers, memory, tick and frames events, if any.
*/
_pullTimelineData: function() {
if (!this._isRecording) {
return;
}
if (!this.docShells.length) {
if (!this._isRecording || !this.docShells.length) {
return;
}
@ -209,10 +210,10 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
if (markers.length > 0) {
events.emit(this, "markers", markers, endTime);
}
if (this._memoryActor) {
events.emit(this, "memory", endTime, this._memoryActor.measure());
if (this._withMemory) {
events.emit(this, "memory", endTime, this._memoryBridge.measure());
}
if (this._framerateActor) {
if (this._withTicks) {
events.emit(this, "ticks", endTime, this._framerateActor.getPendingTicks());
}
@ -235,9 +236,20 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
/**
* Start recording profile markers.
*
* @option {boolean} withMemory
* Boolean indiciating whether we want memory measurements sampled. A memory actor
* will be created regardless (to hook into GC events), but this determines
* whether or not a `memory` event gets fired.
* @option {boolean} withTicks
* Boolean indicating whether a `ticks` event is fired and a FramerateActor
* is created.
*/
start: method(function({ withMemory, withTicks }) {
var startTime = this.docShells[0].now();
start: method(Task.async(function *({ withMemory, withTicks }) {
var startTime = this._startTime = this.docShells[0].now();
// Store the start time from unix epoch so we can normalize
// markers from the memory actor
this._unixStartTime = Date.now();
if (this._isRecording) {
return startTime;
@ -246,14 +258,16 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
this._isRecording = true;
this._stackFrames = new StackFrameCache();
this._stackFrames.initFrames();
this._withMemory = withMemory;
this._withTicks = withTicks;
for (let docShell of this.docShells) {
docShell.recordProfileTimelineMarkers = true;
}
if (withMemory) {
this._memoryActor = new MemoryActor(this.conn, this.tabActor, this._stackFrames);
}
this._memoryBridge = new MemoryBridge(this.tabActor, this._stackFrames);
this._memoryBridge.attach();
events.on(this._memoryBridge, "garbage-collection", this._onGarbageCollection);
if (withTicks) {
this._framerateActor = new FramerateActor(this.conn, this.tabActor);
@ -262,7 +276,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
this._pullTimelineData();
return startTime;
}, {
}), {
request: {
withMemory: Option(0, "boolean"),
withTicks: Option(0, "boolean")
@ -275,16 +289,15 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
/**
* Stop recording profile markers.
*/
stop: method(function() {
stop: method(Task.async(function *() {
if (!this._isRecording) {
return;
}
this._isRecording = false;
this._stackFrames = null;
if (this._memoryActor) {
this._memoryActor = null;
}
events.off(this._memoryBridge, "garbage-collection", this._onGarbageCollection);
this._memoryBridge.detach();
if (this._framerateActor) {
this._framerateActor.stopRecording();
@ -297,7 +310,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
clearTimeout(this._dataPullTimeout);
return this.docShells[0].now();
}, {
}), {
response: {
// Set as possibly nullable due to the end time possibly being
// undefined during destruction
@ -316,7 +329,38 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
.QueryInterface(Ci.nsIDocShell);
docShell.recordProfileTimelineMarkers = true;
}
}
},
/**
* Fired when the MemoryActor emits a `garbage-collection` event. Used to
* emit the data to the front end and in similar format to other markers.
*
* A GC "marker" here represents a full GC cycle, which may contain several incremental
* events within its `collection` array. The marker contains a `reason` field, indicating
* why there was a GC, and may contain a `nonincrementalReason` when SpiderMonkey could
* not incrementally collect garbage.
*/
_onGarbageCollection: function ({ collections, reason, nonincrementalReason }) {
if (!this._isRecording || !this.docShells.length) {
return;
}
// Normalize the start time to docshell start time, and convert it
// to microseconds.
let startTime = (this._unixStartTime - this._startTime) * 1000;
let endTime = this.docShells[0].now();
events.emit(this, "markers", collections.map(({ startTimestamp: start, endTimestamp: end }) => {
return {
name: "GarbageCollection",
causeName: reason,
nonincrementalReason: nonincrementalReason,
// Both timestamps are in microseconds -- convert to milliseconds to match other markers
start: (start - startTime) / 1000,
end: (end - startTime) / 1000
};
}), endTime);
},
});
exports.TimelineFront = protocol.FrontClass(TimelineActor, {

Просмотреть файл

@ -0,0 +1,367 @@
/* 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";
const { Cc, Ci, Cu } = require("chrome");
const { reportException } = require("devtools/toolkit/DevToolsUtils");
const { Class } = require("sdk/core/heritage");
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "EventTarget", "sdk/event/target", true);
loader.lazyRequireGetter(this, "StackFrameCache",
"devtools/server/actors/utils/stack", true);
/**
* A method decorator that ensures the actor is in the expected state before
* proceeding. If the actor is not in the expected state, the decorated method
* returns a rejected promise.
*
* @param String expectedState
* The expected state.
* @param String activity
* Additional info about what's going on.
* @param Function method
* The actor method to proceed with when the actor is in the expected
* state.
*
* @returns Function
* The decorated method.
*/
function expectState(expectedState, method, activity) {
return function(...args) {
if (this.state !== expectedState) {
const msg = `Wrong state while ${activity}:` +
`Expected '${expectedState}',` +
`but current state is '${this.state}'.`;
return Promise.reject(new Error(msg));
}
return method.apply(this, args);
};
}
/**
* A class that returns memory data for a parent actor's window.
* Using a tab-scoped actor with this instance will measure the memory footprint of its
* parent tab. Using a global-scoped actor instance however, will measure the memory
* footprint of the chrome window referenced by its root actor.
*
* To be consumed by actor's, like MemoryActor using MemoryBridge to
* send information over RDP, and TimelineActor for using more light-weight
* utilities like GC events and measuring memory consumption.
*/
let MemoryBridge = Class({
extends: EventTarget,
/**
* Requires a root actor and a StackFrameCache.
*/
initialize: function (parent, frameCache = new StackFrameCache()) {
this.parent = parent;
this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
.getService(Ci.nsIMemoryReporterManager);
this.state = "detached";
this._dbg = null;
this._frameCache = frameCache;
this._onGarbageCollection = this._onGarbageCollection.bind(this);
this._onWindowReady = this._onWindowReady.bind(this);
events.on(this.parent, "window-ready", this._onWindowReady);
},
destroy: function() {
events.off(this.parent, "window-ready", this._onWindowReady);
this._mgr = null;
if (this.state === "attached") {
this.detach();
}
},
get dbg() {
if (!this._dbg) {
this._dbg = this.parent.makeDebugger();
}
return this._dbg;
},
/**
* Attach to this MemoryBridge.
*
* This attaches the MemoryBridge's Debugger instance so that you can start
* recording allocations or take a census of the heap. In addition, the
* MemoryBridge will start emitting GC events.
*/
attach: expectState("detached", function() {
this.dbg.addDebuggees();
this.dbg.memory.onGarbageCollection = this._onGarbageCollection.bind(this);
this.state = "attached";
}, `attaching to the debugger`),
/**
* Detach from this MemoryBridge.
*/
detach: expectState("attached", function() {
this._clearDebuggees();
this.dbg.enabled = false;
this._dbg = null;
this.state = "detached";
}, `detaching from the debugger`),
/**
* Gets the current MemoryBridge attach/detach state.
*/
getState: function () {
return this.state;
},
_clearDebuggees: function() {
if (this._dbg) {
if (this.dbg.memory.trackingAllocationSites) {
this.dbg.memory.drainAllocationsLog();
}
this._clearFrames();
this.dbg.removeAllDebuggees();
}
},
_clearFrames: function() {
if (this.dbg.memory.trackingAllocationSites) {
this._frameCache.clearFrames();
}
},
/**
* Handler for the parent actor's "window-ready" event.
*/
_onWindowReady: function({ isTopLevel }) {
if (this.state == "attached") {
if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
this._clearDebuggees();
this._frameCache.initFrames();
}
this.dbg.addDebuggees();
}
},
/**
* Handler for GC events on the Debugger.Memory instance.
*/
_onGarbageCollection: function (data) {
events.emit(this, "garbage-collection", data);
},
/**
* Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
* more information.
*/
takeCensus: expectState("attached", function() {
return this.dbg.memory.takeCensus();
}, `taking census`),
/**
* Start recording allocation sites.
*
* @param AllocationsRecordingOptions options
* See the protocol.js definition of AllocationsRecordingOptions above.
*/
startRecordingAllocations: expectState("attached", function(options = {}) {
if (this.dbg.memory.trackingAllocationSites) {
return Date.now();
}
this._frameCache.initFrames();
this.dbg.memory.allocationSamplingProbability = options.probability != null
? options.probability
: 1.0;
if (options.maxLogLength != null) {
this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
}
this.dbg.memory.trackingAllocationSites = true;
return Date.now();
}, `starting recording allocations`),
/**
* Stop recording allocation sites.
*/
stopRecordingAllocations: expectState("attached", function() {
this.dbg.memory.trackingAllocationSites = false;
this._clearFrames();
return Date.now();
}, `stopping recording allocations`),
/**
* Return settings used in `startRecordingAllocations` for `probability`
* and `maxLogLength`. Currently only uses in tests.
*/
getAllocationsSettings: expectState("attached", function() {
return {
maxLogLength: this.dbg.memory.maxAllocationsLogLength,
probability: this.dbg.memory.allocationSamplingProbability
};
}, `getting allocations settings`),
/**
* Get a list of the most recent allocations since the last time we got
* allocations, as well as a summary of all allocations since we've been
* recording.
*
* @returns Object
* An object of the form:
*
* {
* allocations: [<index into "frames" below>, ...],
* allocationsTimestamps: [
* <timestamp for allocations[0]>,
* <timestamp for allocations[1]>,
* ...
* ],
* frames: [
* {
* line: <line number for this frame>,
* column: <column number for this frame>,
* source: <filename string for this frame>,
* functionDisplayName: <this frame's inferred function name function or null>,
* parent: <index into "frames">
* },
* ...
* ],
* counts: [
* <number of allocations in frames[0]>,
* <number of allocations in frames[1]>,
* <number of allocations in frames[2]>,
* ...
* ]
* }
*
* The timestamps' unit is microseconds since the epoch.
*
* Subsequent `getAllocations` request within the same recording and
* tab navigation will always place the same stack frames at the same
* indices as previous `getAllocations` requests in the same
* recording. In other words, it is safe to use the index as a
* unique, persistent id for its frame.
*
* Additionally, the root node (null) is always at index 0.
*
* Note that the allocation counts include "self" allocations only,
* and don't account for allocations in child frames.
*
* We use the indices into the "frames" array to avoid repeating the
* description of duplicate stack frames both when listing
* allocations, and when many stacks share the same tail of older
* frames. There shouldn't be any duplicates in the "frames" array,
* as that would defeat the purpose of this compression trick.
*
* In the future, we might want to split out a frame's "source" and
* "functionDisplayName" properties out the same way we have split
* frames out with the "frames" array. While this would further
* compress the size of the response packet, it would increase CPU
* usage to build the packet, and it should, of course, be guided by
* profiling and done only when necessary.
*/
getAllocations: expectState("attached", function() {
if (this.dbg.memory.allocationsLogOverflowed) {
// Since the last time we drained the allocations log, there have been
// more allocations than the log's capacity, and we lost some data. There
// isn't anything actionable we can do about this, but put a message in
// the browser console so we at least know that it occurred.
reportException("MemoryBridge.prototype.getAllocations",
"Warning: allocations log overflowed and lost some data.");
}
const allocations = this.dbg.memory.drainAllocationsLog()
const packet = {
allocations: [],
allocationsTimestamps: []
};
for (let { frame: stack, timestamp } of allocations) {
if (stack && Cu.isDeadWrapper(stack)) {
continue;
}
// Safe because SavedFrames are frozen/immutable.
let waived = Cu.waiveXrays(stack);
// Ensure that we have a form, count, and index for new allocations
// because we potentially haven't seen some or all of them yet. After this
// loop, we can rely on the fact that every frame we deal with already has
// its metadata stored.
let index = this._frameCache.addFrame(waived);
packet.allocations.push(index);
packet.allocationsTimestamps.push(timestamp);
}
return this._frameCache.updateFramePacket(packet);
}, `getting allocations`),
/*
* Force a browser-wide GC.
*/
forceGarbageCollection: function () {
for (let i = 0; i < 3; i++) {
Cu.forceGC();
}
},
/**
* Force an XPCOM cycle collection. For more information on XPCOM cycle
* collection, see
* https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
*/
forceCycleCollection: function () {
Cu.forceCC();
},
/**
* A method that returns a detailed breakdown of the memory consumption of the
* associated window.
*
* @returns object
*/
measure: function () {
let result = {};
let jsObjectsSize = {};
let jsStringsSize = {};
let jsOtherSize = {};
let domSize = {};
let styleSize = {};
let otherSize = {};
let totalSize = {};
let jsMilliseconds = {};
let nonJSMilliseconds = {};
try {
this._mgr.sizeOfTab(this.parent.window, jsObjectsSize, jsStringsSize, jsOtherSize,
domSize, styleSize, otherSize, totalSize, jsMilliseconds, nonJSMilliseconds);
result.total = totalSize.value;
result.domSize = domSize.value;
result.styleSize = styleSize.value;
result.jsObjectsSize = jsObjectsSize.value;
result.jsStringsSize = jsStringsSize.value;
result.jsOtherSize = jsOtherSize.value;
result.otherSize = otherSize.value;
result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
} catch (e) {
reportException("MemoryBridge.prototype.measure", e);
}
return result;
},
residentUnique: function () {
return this._mgr.residentUnique;
}
});
exports.MemoryBridge = MemoryBridge;

Просмотреть файл

@ -79,6 +79,7 @@ EXTRA_JS_MODULES.devtools.server.actors.utils += [
'actors/utils/automation-timeline.js',
'actors/utils/make-debugger.js',
'actors/utils/map-uri-to-addon-id.js',
'actors/utils/memory-bridge.js',
'actors/utils/ScriptStore.js',
'actors/utils/stack.js',
'actors/utils/TabSources.js'