Bug 1159052 - Performance recording should stop rendering and recording as soon as the recording stops. r=vp

This commit is contained in:
Jordan Santell 2015-05-02 16:47:41 -07:00
parent 726123824a
commit 8593e43ebd
17 changed files with 260 additions and 171 deletions

View File

@ -223,10 +223,17 @@ MemoryFrontFacade.prototype = {
yield this.attach();
let startTime = yield this.startRecordingAllocations({
probability: options.allocationsSampleProbability,
maxLogLength: options.allocationsMaxLogLength
});
// Reconstruct our options because the server actor fails when being passed
// undefined values in objects.
let allocationOptions = {};
if (options.allocationsSampleProbability !== void 0) {
allocationOptions.probability = options.allocationsSampleProbability;
}
if (options.allocationsMaxLogLength !== void 0) {
allocationOptions.maxLogLength = options.allocationsMaxLogLength;
}
let startTime = yield this.startRecordingAllocations(allocationOptions);
yield this._pullAllocationSites();

View File

@ -27,9 +27,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",
"recording-started", "recording-stopped"
"recording-starting", "recording-started", "recording-stopping", "recording-stopped"
];
/**
@ -227,8 +226,6 @@ PerformanceActorsConnection.prototype = {
console: true,
label: profileLabel
}));
this.emit("console-profile-start", model);
}),
/**
@ -271,9 +268,7 @@ PerformanceActorsConnection.prototype = {
return;
}
this.emit("console-profile-ending", model);
yield this.stopRecording(model);
this.emit("console-profile-end", model);
}),
/**
@ -309,6 +304,8 @@ PerformanceActorsConnection.prototype = {
*/
startRecording: Task.async(function*(options = {}) {
let model = new RecordingModel(options);
this.emit("recording-starting", model);
// All actors are started asynchronously over the remote debugging protocol.
// Get the corresponding start times from each one of them.
// The timeline and memory actors are target-dependent, so start those as well,
@ -343,6 +340,13 @@ PerformanceActorsConnection.prototype = {
return;
}
// Flag the recording as no longer recording, so that `model.isRecording()`
// is false. Do this before we fetch all the data, and then subsequently
// the recording can be considered "completed".
let endTime = Date.now();
model._onStoppingRecording(endTime);
this.emit("recording-stopping", model);
// Currently there are two ways profiles stop recording. Either manually in the
// performance tool, or via console.profileEnd. Once a recording is done,
// we want to deliver the model to the performance tool (either as a return
@ -485,7 +489,9 @@ PerformanceFront.prototype = {
}
let actor = this._connection[`_${actorName}`];
return actor[method].apply(actor, args);
}
},
toString: () => "[object PerformanceFront]"
};
/**

View File

@ -38,6 +38,7 @@ RecordingModel.prototype = {
_console: false,
_imported: false,
_recording: false,
_completed: false,
_profilerStartTime: 0,
_timelineStartTime: 0,
_memoryStartTime: 0,
@ -94,7 +95,7 @@ RecordingModel.prototype = {
// However, we also want to update the view with the elapsed time
// even when the actor is not generating data. To do this we get
// the local time and use it to compute a reasonable elapsed time.
this._localStartTime = Date.now()
this._localStartTime = Date.now();
this._profilerStartTime = info.profilerStartTime;
this._timelineStartTime = info.timelineStartTime;
@ -108,19 +109,30 @@ RecordingModel.prototype = {
this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
},
/**
* Called when the signal was sent to the front to no longer record more
* data, and begin fetching the data. There's some delay during fetching,
* even though the recording is stopped, the model is not yet completed until
* all the data is fetched.
*/
_onStoppingRecording: function (endTime) {
this._duration = endTime - this._localStartTime;
this._recording = false;
},
/**
* Sets results available from stopping a recording from SharedPerformanceConnection.
* Should only be called by SharedPerformanceConnection.
*/
_onStopRecording: Task.async(function *(info) {
this._profile = info.profile;
this._duration = info.profilerEndTime - this._profilerStartTime;
this._recording = false;
this._completed = true;
// We filter out all samples that fall out of current profile's range
// since the profiler is continuously running. Because of this, sample
// times are not guaranteed to have a zero epoch, so offset the
// timestamps.
// TODO move this into FakeProfilerFront in ./actors.js after bug 1154115
RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
// Markers need to be sorted ascending by time, to be properly displayed
@ -248,9 +260,21 @@ RecordingModel.prototype = {
return this._console;
},
/**
* Returns a boolean indicating whether or not this recording model
* has finished recording.
* There is some delay in fetching data between when the recording stops, and
* when the recording is considered completed once it has all the profiler and timeline data.
*/
isCompleted: function () {
return this._completed || this.isImported();
},
/**
* Returns a boolean indicating whether or not this recording model
* is recording.
* A model may no longer be recording, yet still not have the profiler data. In that
* case, use `isCompleted()`.
*/
isRecording: function () {
return this._recording;
@ -262,7 +286,7 @@ RecordingModel.prototype = {
addTimelineData: function (eventName, ...data) {
// If this model isn't currently recording,
// ignore the timeline data.
if (!this._recording) {
if (!this.isRecording()) {
return;
}

View File

@ -63,13 +63,6 @@ const EVENTS = {
// Fired by the PerformanceController when the devtools theme changes.
THEME_CHANGED: "Performance:ThemeChanged",
// When the SharedPerformanceConnection handles profiles created via `console.profile()`,
// the controller handles those events and emits the below events for consumption
// by other views.
CONSOLE_RECORDING_STARTED: "Performance:ConsoleRecordingStarted",
CONSOLE_RECORDING_WILL_STOP: "Performance:ConsoleRecordingWillStop",
CONSOLE_RECORDING_STOPPED: "Performance:ConsoleRecordingStopped",
// Emitted by the PerformanceView when the state (display mode) changes,
// for example when switching between "empty", "recording" or "recorded".
// This causes certain panels to be hidden or visible.
@ -190,9 +183,7 @@ let PerformanceController = {
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this._onThemeChanged = this._onThemeChanged.bind(this);
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
this._onConsoleProfileEnding = this._onConsoleProfileEnding.bind(this);
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
// All boolean prefs should be handled via the OptionsView in the
// ToolbarView, so that they may be accessible via the "gear" menu.
@ -208,9 +199,10 @@ let PerformanceController = {
this._nonBooleanPrefs.registerObserver();
this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
gFront.on("console-profile-start", this._onConsoleProfileStart);
gFront.on("console-profile-ending", this._onConsoleProfileEnding);
gFront.on("console-profile-end", this._onConsoleProfileEnd);
gFront.on("recording-starting", this._onRecordingStateChange);
gFront.on("recording-started", this._onRecordingStateChange);
gFront.on("recording-stopping", this._onRecordingStateChange);
gFront.on("recording-stopped", this._onRecordingStateChange);
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -229,9 +221,10 @@ let PerformanceController = {
this._nonBooleanPrefs.unregisterObserver();
this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
gFront.off("console-profile-start", this._onConsoleProfileStart);
gFront.off("console-profile-ending", this._onConsoleProfileEnding);
gFront.off("console-profile-end", this._onConsoleProfileEnd);
gFront.off("recording-starting", this._onRecordingStateChange);
gFront.off("recording-started", this._onRecordingStateChange);
gFront.off("recording-stopping", this._onRecordingStateChange);
gFront.off("recording-stopped", this._onRecordingStateChange);
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -300,12 +293,7 @@ let PerformanceController = {
sampleFrequency: this.getPref("profiler-sample-frequency")
};
this.emit(EVENTS.RECORDING_WILL_START);
let recording = yield gFront.startRecording(options);
this._recordings.push(recording);
this.emit(EVENTS.RECORDING_STARTED, recording);
yield gFront.startRecording(options);
}),
/**
@ -315,9 +303,7 @@ let PerformanceController = {
stopRecording: Task.async(function *() {
let recording = this.getLatestManualRecording();
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
yield gFront.stopRecording(recording);
this.emit(EVENTS.RECORDING_STOPPED, recording);
}),
/**
@ -344,6 +330,11 @@ let PerformanceController = {
if (latest && latest.isRecording()) {
yield this.stopRecording();
}
// If last recording is not recording, but finalizing itself,
// wait for that to finish
if (latest && !latest.isCompleted()) {
yield this.once(EVENTS.RECORDING_STOPPED);
}
this._recordings.length = 0;
this.setCurrentRecording(null);
@ -440,27 +431,33 @@ let PerformanceController = {
},
/**
* Fired when `console.profile()` is executed.
* Fired when a recording model changes state.
*
* @param {string} state
* @param {RecordingModel} model
*/
_onConsoleProfileStart: function (_, recording) {
this._recordings.push(recording);
this.emit(EVENTS.CONSOLE_RECORDING_STARTED, recording);
},
/**
* Fired when `console.profileEnd()` is executed, and the profile
* is stopping soon, as it fetches profiler data.
*/
_onConsoleProfileEnding: function (_, recording) {
this.emit(EVENTS.CONSOLE_RECORDING_WILL_STOP, recording);
},
/**
* Fired when `console.profileEnd()` is executed, and
* has a corresponding `console.profile()` session.
*/
_onConsoleProfileEnd: function (_, recording) {
this.emit(EVENTS.CONSOLE_RECORDING_STOPPED, recording);
_onRecordingStateChange: function (state, model) {
switch (state) {
// Fired when a RecordingModel was just created from the front
case "recording-starting":
// When a recording is just starting, store it internally
this._recordings.push(model);
this.emit(EVENTS.RECORDING_WILL_START, model);
break;
// Fired when a RecordingModel has started recording
case "recording-started":
this.emit(EVENTS.RECORDING_STARTED, model);
break;
// Fired when a RecordingModel is no longer recording, and
// starting to fetch all the profiler data
case "recording-stopping":
this.emit(EVENTS.RECORDING_WILL_STOP, model);
break;
// Fired when a RecordingModel is finished fetching all of its data
case "recording-stopped":
this.emit(EVENTS.RECORDING_STOPPED, model);
break;
}
},
/**
@ -480,21 +477,21 @@ let PerformanceController = {
* 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.
* @option {boolean} mustBeCompleted
* A boolean indicating whether the recording must be either completed or not.
* 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) {
isFeatureSupported: function ({ features, actors, mustBeCompleted }, recording) {
recording = recording || this.getCurrentRecording();
let recordingConfig = recording ? recording.getConfiguration() : {};
let currentRecordingState = recording ? recording.isRecording() : void 0;
let currentCompletedState = recording ? recording.isCompleted() : void 0;
let actorsSupported = gFront.getActorSupport();
if (shouldBeRecording != null && shouldBeRecording !== currentRecordingState) {
if (mustBeCompleted != null && mustBeCompleted !== currentCompletedState) {
return false;
}
if (actors && !actors.every(a => actorsSupported[a])) {

View File

@ -41,12 +41,11 @@ let PerformanceView = {
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
this._onImportButtonClick = this._onImportButtonClick.bind(this);
this._onClearButtonClick = this._onClearButtonClick.bind(this);
this._lockRecordButton = this._lockRecordButton.bind(this);
this._unlockRecordButton = this._unlockRecordButton.bind(this);
this._lockRecordButtons = this._lockRecordButtons.bind(this);
this._unlockRecordButtons = this._unlockRecordButtons.bind(this);
this._onRecordingSelected = this._onRecordingSelected.bind(this);
this._onRecordingStopped = this._onRecordingStopped.bind(this);
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
this._onRecordingStarted = this._onRecordingStarted.bind(this);
for (let button of $$(".record-button")) {
button.addEventListener("click", this._onRecordButtonClick);
@ -55,11 +54,8 @@ let PerformanceView = {
this._clearButton.addEventListener("click", this._onClearButtonClick);
// Bind to controller events to unlock the record button
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
this.setState("empty");
@ -82,11 +78,8 @@ let PerformanceView = {
this._importButton.removeEventListener("click", this._onImportButtonClick);
this._clearButton.removeEventListener("click", this._onClearButtonClick);
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
yield ToolbarView.destroy();
@ -130,31 +123,30 @@ let PerformanceView = {
* Adds the `locked` attribute on the record button. This prevents it
* from being clicked while recording is started or stopped.
*/
_lockRecordButton: function () {
this._recordButton.setAttribute("locked", "true");
_lockRecordButtons: function () {
for (let button of $$(".record-button")) {
button.setAttribute("locked", "true");
}
},
/**
* Removes the `locked` attribute on the record button.
*/
_unlockRecordButton: function () {
this._recordButton.removeAttribute("locked");
_unlockRecordButtons: function () {
for (let button of $$(".record-button")) {
button.removeAttribute("locked");
}
},
/**
* Fired when a recording is starting, but not yet completed.
* When a recording has started.
*/
_onRecordingWillStart: function () {
this._lockRecordButton();
this._recordButton.setAttribute("checked", "true");
},
/**
* Fired when a recording is stopping, but not yet completed.
*/
_onRecordingWillStop: function () {
this._lockRecordButton();
this._recordButton.removeAttribute("checked");
_onRecordingStarted: function (_, recording) {
// A stopped recording can be from `console.profileEnd` -- only unlock
// the button if it's the main recording that was started via UI.
if (!recording.isConsole()) {
this._unlockRecordButtons();
}
},
/**
@ -164,7 +156,7 @@ let PerformanceView = {
// A stopped recording can be from `console.profileEnd` -- only unlock
// the button if it's the main recording that was started via UI.
if (!recording.isConsole()) {
this._unlockRecordButton();
this._unlockRecordButtons();
}
// If the currently selected recording is the one that just stopped,
@ -187,7 +179,15 @@ let PerformanceView = {
_onRecordButtonClick: function (e) {
if (this._recordButton.hasAttribute("checked")) {
this.emit(EVENTS.UI_STOP_RECORDING);
this._lockRecordButtons();
for (let button of $$(".record-button")) {
button.removeAttribute("checked");
}
} else {
this._lockRecordButtons();
for (let button of $$(".record-button")) {
button.setAttribute("checked", "true");
}
this.emit(EVENTS.UI_START_RECORDING);
}
},

View File

@ -77,7 +77,7 @@
<hbox id="recordings-controls"
class="devtools-toolbarbutton-group">
<toolbarbutton id="main-record-button"
class="devtools-toolbarbutton record-button"
class="devtools-toolbarbutton record-button devtools-thobber"
tooltiptext="&profilerUI.recordButton.tooltip;"/>
<toolbarbutton id="import-button"
class="devtools-toolbarbutton"
@ -159,8 +159,7 @@
flex="1">
<label value="&profilerUI.stopNotice1;"/>
<button class="devtools-toolbarbutton record-button"
standalone="true"
checked="true" />
standalone="true" />
<label value="&profilerUI.stopNotice2;"/>
</hbox>
<hbox id="console-recording-notice"

View File

@ -89,6 +89,7 @@ support-files =
[browser_perf-states.js]
[browser_perf-refresh.js]
[browser_perf-ui-recording.js]
[browser_perf-recording-model-01.js]
[browser_perf-recording-notices-01.js]
[browser_perf-recording-notices-02.js]
[browser_perf_recordings-io-01.js]

View File

@ -15,12 +15,12 @@ function spawnTest () {
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
let profileStart = once(connection, "recording-started");
console.profile("rust");
yield profileStart;
busyWait(WAIT_TIME);
let profileEnd = once(connection, "console-profile-end");
let profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust");
yield profileEnd;

View File

@ -15,10 +15,10 @@ function spawnTest () {
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
let profileStart = once(connection, "recording-started");
console.profile("rust");
yield profileStart;
profileStart = once(connection, "console-profile-start");
profileStart = once(connection, "recording-started");
console.profile("rust2");
yield profileStart;
@ -38,10 +38,10 @@ function spawnTest () {
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
let profileEnd = once(connection, "console-profile-end");
let profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust");
yield profileEnd;
profileEnd = once(connection, "console-profile-end");
profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust2");
yield profileEnd;

View File

@ -15,15 +15,15 @@ function spawnTest () {
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
let profileStart = once(connection, "recording-started");
console.profile("rust");
yield profileStart;
let profileEnd = once(connection, "console-profile-end");
let profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust");
yield profileEnd;
profileStart = once(connection, "console-profile-start");
profileStart = once(connection, "recording-started");
console.profile("rust2");
yield profileStart;
@ -43,7 +43,7 @@ function spawnTest () {
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
profileEnd = once(connection, "console-profile-end");
profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust2");
yield profileEnd;

View File

@ -15,14 +15,14 @@ function spawnTest () {
let connection = getPerformanceActorsConnection(target);
let tab = toolbox.doc.getElementById("toolbox-tab-performance");
let profileStart = once(connection, "console-profile-start");
let profileStart = once(connection, "recording-started");
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");
let profileEnd = once(connection, "recording-stopped");
console.profileEnd("rust");
yield profileEnd;

View File

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the state of a recording rec from start to finish for recording,
* completed, and rec data.
*/
function spawnTest () {
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
let { EVENTS, gFront: front, PerformanceController } = panel.panelWin;
let rec = yield front.startRecording({ withMarkers: true, withTicks: true, withMemory: true });
ok(rec.isRecording(), "RecordingModel is recording when created");
yield busyWait(100);
yield waitUntil(() => rec.getMemory().length);
ok(true, "RecordingModel populates memory while recording");
yield waitUntil(() => rec.getTicks().length);
ok(true, "RecordingModel populates ticks while recording");
yield waitUntil(() => rec.getMarkers().length);
ok(true, "RecordingModel populates markers while recording");
ok(!rec.isCompleted(), "RecordingModel is not completed when still recording");
let stopping = once(front, "recording-stopping");
let stopped = once(front, "recording-stopped");
front.stopRecording(rec);
yield stopping;
ok(!rec.isRecording(), "on 'recording-stopping', model is no longer recording");
// This handler should be called BEFORE "recording-stopped" is called, as
// there is some delay, but in the event where "recording-stopped" finishes
// before we check here, ensure that we're atleast in the right state
if (rec.getProfile()) {
ok(rec.isCompleted(), "recording is completed once it has profile data");
} else {
ok(!rec.isCompleted(), "recording is not yet completed on 'recording-stopping'");
}
yield stopped;
ok(!rec.isRecording(), "on 'recording-stopped', model is still no longer recording");
ok(rec.isCompleted(), "on 'recording-stopped', model is considered 'complete'");
// Export and import a rec, and ensure it has the correct state.
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
yield PerformanceController.exportRecording("", rec, file);
yield exported;
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
yield PerformanceController.importRecording("", file);
yield imported;
let importedModel = PerformanceController.getCurrentRecording();
ok(importedModel.isCompleted(), "All imported recordings should be completed");
ok(!importedModel.isRecording(), "All imported recordings should not be recording");
yield teardown(panel);
finish();
}

View File

@ -142,7 +142,7 @@ function handleError(aError) {
}
function once(aTarget, aEventName, aUseCapture = false, spread = false) {
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
info(`Waiting for event: '${aEventName}' on ${aTarget}`);
let deferred = Promise.defer();
@ -153,6 +153,7 @@ function once(aTarget, aEventName, aUseCapture = false, spread = false) {
]) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
info(`Received event: '${aEventName}' on ${aTarget}`);
aTarget[remove](aEventName, onEvent, aUseCapture);
deferred.resolve(spread ? aArgs : aArgs[0]);
}, aUseCapture);
@ -305,13 +306,13 @@ function consoleMethod (...args) {
}
function* consoleProfile(win, label) {
let profileStart = once(win.PerformanceController, win.EVENTS.CONSOLE_RECORDING_STARTED);
let profileStart = once(win.PerformanceController, win.EVENTS.RECORDING_STARTED);
consoleMethod("profile", label);
yield profileStart;
}
function* consoleProfileEnd(win, label) {
let ended = once(win.PerformanceController, win.EVENTS.CONSOLE_RECORDING_STOPPED);
let ended = once(win.PerformanceController, win.EVENTS.RECORDING_STOPPED);
consoleMethod("profileEnd", label);
yield ended;
}

View File

@ -17,7 +17,6 @@ let DetailsSubview = {
this._onPrefChanged = this._onPrefChanged.bind(this);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
@ -32,7 +31,6 @@ let DetailsSubview = {
clearNamedTimeout("range-change-debounce");
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
@ -81,7 +79,7 @@ let DetailsSubview = {
* Called when recording stops or is selected.
*/
_onRecordingStoppedOrSelected: function(_, recording) {
if (!recording || recording.isRecording()) {
if (!recording || !recording.isCompleted()) {
return;
}
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
@ -131,7 +129,7 @@ let DetailsSubview = {
// All detail views require a recording to be complete, so do not
// attempt to render if recording is in progress or does not exist.
let recording = PerformanceController.getCurrentRecording();
if (!recording || recording.isRecording()) {
if (!recording || !recording.isCompleted()) {
return;
}

View File

@ -59,7 +59,6 @@ let DetailsView = {
yield this.setAvailableViews();
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
@ -77,7 +76,6 @@ let DetailsView = {
component.initialized && (yield component.view.destroy());
}
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
@ -90,13 +88,13 @@ let DetailsView = {
*/
setAvailableViews: Task.async(function* () {
let recording = PerformanceController.getCurrentRecording();
let isRecording = recording && recording.isRecording();
let isCompleted = recording && recording.isCompleted();
let invalidCurrentView = false;
for (let [name, { view }] of Iterator(this.components)) {
// TODO bug 1160313 get rid of retro mode checks.
let isRetro = PerformanceController.getOption("retro-mode");
let isSupported = isRetro ? name === "js-calltree" : this._isViewSupported(name, false);
let isSupported = isRetro ? name === "js-calltree" : this._isViewSupported(name, true);
// TODO bug 1160313 hide all view buttons, but let js-calltree still be "supported"
$(`toolbarbutton[data-view=${name}]`).hidden = isRetro ? true : !isSupported;
@ -112,12 +110,12 @@ let DetailsView = {
//
// 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.
// is completed.
//
// 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)) {
if ((this._initialized && isCompleted && invalidCurrentView) ||
(!this._initialized && isCompleted && recording)) {
yield this.selectDefaultView();
}
}),
@ -126,12 +124,12 @@ let DetailsView = {
* Takes a view name and optionally if there must be a currently recording in progress.
*
* @param {string} viewName
* @param {boolean?} isRecording
* @param {boolean?} mustBeCompleted
* @return {boolean}
*/
_isViewSupported: function (viewName, isRecording) {
_isViewSupported: function (viewName, mustBeCompleted) {
let { features, actors } = this.components[viewName];
return PerformanceController.isFeatureSupported({ features, actors, isRecording });
return PerformanceController.isFeatureSupported({ features, actors, mustBeCompleted });
},
/**
@ -238,7 +236,7 @@ let DetailsView = {
// All detail views require a recording to be complete, so do not
// attempt to render if recording is in progress or does not exist.
let recording = PerformanceController.getCurrentRecording();
if (recording && !recording.isRecording()) {
if (recording && recording.isCompleted()) {
component.view.shouldUpdateWhenShown = true;
}
}),

View File

@ -66,9 +66,6 @@ let OverviewView = {
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
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);
},
@ -84,9 +81,6 @@ let OverviewView = {
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
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();
@ -207,31 +201,25 @@ let OverviewView = {
* exist yet, but can just disable from here. This will only trigger for
* manual recordings.
*/
_onRecordingWillStart: Task.async(function* () {
this._onRecordingStateChange();
_onRecordingWillStart: OverviewViewOnStateChange(Task.async(function* () {
yield this._checkSelection();
this.graphs.dropSelection();
}),
})),
/**
* Called when recording actually starts.
*/
_onRecordingStarted: function (_, recording) {
this._onRecordingStateChange();
},
_onRecordingStarted: OverviewViewOnStateChange(),
/**
* Called when recording will stop.
*/
_onRecordingWillStop: function(_, recording) {
this._onRecordingStateChange();
},
_onRecordingWillStop: OverviewViewOnStateChange(),
/**
* Called when recording actually stops.
*/
_onRecordingStopped: Task.async(function* (_, recording) {
this._onRecordingStateChange();
_onRecordingStopped: OverviewViewOnStateChange(Task.async(function* (_, recording) {
// Check to see if the recording that just stopped is the current recording.
// If it is, render the high-res graphs. For manual recordings, it will also
// be the current recording, but profiles generated by `console.profile` can stop
@ -242,39 +230,21 @@ let OverviewView = {
}
this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
yield this._checkSelection(recording);
}),
})),
/**
* Called when a new recording is selected.
*/
_onRecordingSelected: Task.async(function* (_, recording) {
if (!recording) {
return;
}
this._onRecordingStateChange();
_onRecordingSelected: OverviewViewOnStateChange(Task.async(function* (_, recording) {
this._setGraphVisibilityFromRecordingFeatures(recording);
// If this recording is complete, render the high res graph
if (!recording.isRecording()) {
if (recording.isCompleted()) {
yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
}
yield this._checkSelection(recording);
this.graphs.dropSelection();
}),
/**
* Called when a recording is starting, stopping, or about to start/stop.
* Checks the current recording displayed to determine whether or not
* the polling for rendering the overview graph needs to start or stop.
*/
_onRecordingStateChange: function () {
let currentRecording = PerformanceController.getCurrentRecording();
if (!currentRecording || (this.isRendering() && !currentRecording.isRecording())) {
this._stopPolling();
} else if (currentRecording.isRecording() && !this.isRendering()) {
this._startPolling();
}
},
})),
/**
* Start the polling for rendering the overview graph.
@ -303,7 +273,7 @@ let OverviewView = {
* based on whether a recording currently exists and is not in progress.
*/
_checkSelection: Task.async(function* (recording) {
let isEnabled = recording ? !recording.isRecording() : false;
let isEnabled = recording ? recording.isCompleted() : false;
yield this.graphs.selectionEnabled(isEnabled);
}),
@ -375,5 +345,35 @@ let OverviewView = {
toString: () => "[object OverviewView]"
};
/**
* Utility that can wrap a method of OverviewView that
* handles a recording state change like when a recording is starting,
* stopping, or about to start/stop, and determines whether or not
* the polling for rendering the overview graphs needs to start or stop.
* Must be called with the OverviewView context.
*
* @param {function?} fn
* @return {function}
*/
function OverviewViewOnStateChange (fn) {
return function _onRecordingStateChange () {
let currentRecording = PerformanceController.getCurrentRecording();
// All these methods require a recording to exist.
if (!currentRecording) {
return;
}
if (this.isRendering() && !currentRecording.isRecording()) {
this._stopPolling();
} else if (currentRecording.isRecording() && !this.isRendering()) {
this._startPolling();
}
if (fn) {
fn.apply(this, arguments);
}
}
}
// Decorates the OverviewView as an EventEmitter
EventEmitter.decorate(OverviewView);

View File

@ -24,8 +24,6 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.on(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.addEventListener("select", this._onSelect, false);
@ -37,8 +35,6 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
destroy: function() {
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.off(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.removeEventListener("select", this._onSelect, false);