Bug 1196947 - Performance tools should display a message in private browsing, r=jsantell

This commit is contained in:
Victor Porof 2015-09-26 06:21:09 +02:00
parent 9bfc1cf93e
commit 94946f8488
18 changed files with 344 additions and 60 deletions

View File

@ -35,7 +35,12 @@
<!ENTITY performanceUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown
- in the call list view while loading a profile. -->
- in the details view while the profiler is unavailable, for example, while
- in Private Browsing mode. -->
<!ENTITY performanceUI.unavailableNoticePB "Recording a profile is currently unavailable. Please close all private browsing windows and try again.">
<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown
- in the details view while loading a profile. -->
<!ENTITY performanceUI.loadingNotice "Loading…">
<!-- LOCALIZATION NOTE (performanceUI.recordButton): This string is displayed

View File

@ -30,6 +30,9 @@ module.exports = {
// When a new recording is being tracked in the panel.
NEW_RECORDING: "Performance:NewRecording",
// When a new recording can't be successfully created when started.
NEW_RECORDING_FAILED: "Performance:NewRecordingFailed",
// When a recording is started or stopped or stopping via the PerformanceController
RECORDING_STATE_CHANGE: "Performance:RecordingStateChange",

View File

@ -89,6 +89,7 @@ var gToolbox, gTarget, gFront;
var startupPerformance = Task.async(function*() {
yield PerformanceController.initialize();
yield PerformanceView.initialize();
PerformanceController.enableFrontEventListeners();
});
/**
@ -97,6 +98,7 @@ var startupPerformance = Task.async(function*() {
var shutdownPerformance = Task.async(function*() {
yield PerformanceController.destroy();
yield PerformanceView.destroy();
PerformanceController.disableFrontEventListeners();
});
/**
@ -131,7 +133,6 @@ var PerformanceController = {
this._prefs = require("devtools/client/performance/modules/global").PREFS;
this._prefs.on("pref-changed", this._onPrefChanged);
gFront.on("*", this._onFrontEvent);
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -151,7 +152,6 @@ var PerformanceController = {
this._telemetry.destroy();
this._prefs.off("pref-changed", this._onPrefChanged);
gFront.off("*", this._onFrontEvent);
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -164,6 +164,27 @@ var PerformanceController = {
gDevTools.off("pref-changed", this._onThemeChanged);
},
/**
* Enables front event listeners.
*
* The rationale behind this is given by the async intialization of all the
* frontend components. Even though the panel is considered "open" only after
* both the controller and the view are created, and even though their
* initialization is sequential (controller, then view), the controller might
* start handling backend events before the view finishes if the event
* listeners are added too soon.
*/
enableFrontEventListeners: function() {
gFront.on("*", this._onFrontEvent);
},
/**
* Disables front event listeners.
*/
disableFrontEventListeners: function() {
gFront.off("*", this._onFrontEvent);
},
/**
* Returns the current devtools theme.
*/
@ -205,6 +226,27 @@ var PerformanceController = {
this._prefs[prefName] = prefValue;
},
/**
* Checks whether or not a new recording is supported by the PerformanceFront.
* @return Promise:boolean
*/
canCurrentlyRecord: Task.async(function*() {
// If we're testing the legacy front, the performance actor will exist,
// with `canCurrentlyRecord` method; this ensures we test the legacy path.
if (gFront.LEGACY_FRONT) {
return true;
}
let hasActor = yield gTarget.hasActor("performance");
if (!hasActor) {
return true;
}
let actorCanCheck = yield gTarget.actorHasMethod("performance", "canCurrentlyRecord");
if (!actorCanCheck) {
return true;
}
return (yield gFront.canCurrentlyRecord()).success;
}),
/**
* Starts recording with the PerformanceFront.
*/
@ -221,7 +263,13 @@ var PerformanceController = {
sampleFrequency: this.getPref("profiler-sample-frequency")
};
yield gFront.startRecording(options);
// In some cases, like when the target has a private browsing tab,
// recording is not currently supported because of the profiler module.
// Present a notification in this case alerting the user of this issue.
if (!(yield gFront.startRecording(options))) {
this.emit(EVENTS.NEW_RECORDING_FAILED);
PerformanceView.setState("unavailable");
}
}),
/**

View File

@ -14,27 +14,30 @@ var PerformanceView = {
// that the server has support for determining buffer status.
_bufferStatusSupported: false,
// Mapping of state to selectors for different panes
// of the main profiler view. Used in `PerformanceView.setState()`
// Mapping of state to selectors for different properties and their values,
// from the main profiler view. Used in `PerformanceView.setState()`
states: {
empty: [
{ deck: "#performance-view", pane: "#empty-notice" }
"unavailable": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#unavailable-notice") },
],
recording: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#recording-notice" }
"empty": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#empty-notice") }
],
"recording": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#recording-notice") }
],
"console-recording": [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#console-recording-notice" }
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#console-recording-notice") }
],
recorded: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#details-pane" }
"recorded": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#details-pane") }
],
loading: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#loading-notice" }
"loading": [
{ sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") },
{ sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#loading-notice") }
]
},
@ -52,6 +55,7 @@ var PerformanceView = {
this._onRecordingSelected = this._onRecordingSelected.bind(this);
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
this._onNewRecordingFailed = this._onNewRecordingFailed.bind(this);
for (let button of $$(".record-button")) {
button.addEventListener("click", this._onRecordButtonClick);
@ -64,8 +68,13 @@ var PerformanceView = {
PerformanceController.on(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
PerformanceController.on(EVENTS.NEW_RECORDING, this._onRecordingStateChange);
PerformanceController.on(EVENTS.NEW_RECORDING_FAILED, this._onNewRecordingFailed);
this.setState("empty");
if (yield PerformanceController.canCurrentlyRecord()) {
this.setState("empty");
} else {
this.setState("unavailable");
}
// Initialize the ToolbarView first, because other views may need access
// to the OptionsView via the controller, to read prefs.
@ -89,6 +98,7 @@ var PerformanceView = {
PerformanceController.off(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
PerformanceController.off(EVENTS.NEW_RECORDING, this._onRecordingStateChange);
PerformanceController.off(EVENTS.NEW_RECORDING_FAILED, this._onNewRecordingFailed);
yield ToolbarView.destroy();
yield RecordingsView.destroy();
@ -97,16 +107,18 @@ var PerformanceView = {
}),
/**
* Sets the state of the profiler view. Possible options are "empty",
* "recording", "console-recording", "recorded".
* Sets the state of the profiler view. Possible options are "unavailable",
* "empty", "recording", "console-recording", "recorded".
*/
setState: function (state) {
let viewConfig = this.states[state];
if (!viewConfig) {
throw new Error(`Invalid state for PerformanceView: ${state}`);
}
for (let { deck, pane } of viewConfig) {
$(deck).selectedPanel = $(pane);
for (let { sel, opt, val } of viewConfig) {
for (let el of $$(sel)) {
el[opt] = val();
}
}
this._state = state;
@ -114,6 +126,7 @@ var PerformanceView = {
if (state === "console-recording") {
let recording = PerformanceController.getCurrentRecording();
let label = recording.getLabel() || "";
// Wrap the label in quotes if it exists for the commands.
label = label ? `"${label}"` : "";
@ -192,7 +205,7 @@ var PerformanceView = {
*
* @param {boolean} activate
*/
_activateRecordButtons: function (activate) {
_toggleRecordButtons: function (activate) {
for (let button of $$(".record-button")) {
if (activate) {
button.setAttribute("checked", "true");
@ -209,7 +222,7 @@ var PerformanceView = {
let currentRecording = PerformanceController.getCurrentRecording();
let recordings = PerformanceController.getRecordings();
this._activateRecordButtons(recordings.find(r => !r.isConsole() && r.isRecording()));
this._toggleRecordButtons(recordings.find(r => !r.isConsole() && r.isRecording()));
this._lockRecordButtons(recordings.find(r => !r.isConsole() && r.isFinalizing()));
if (currentRecording && currentRecording.isFinalizing()) {
@ -223,6 +236,14 @@ var PerformanceView = {
}
},
/**
* When starting a recording has failed.
*/
_onNewRecordingFailed: function (e) {
this._lockRecordButtons(false);
this._toggleRecordButtons(false);
},
/**
* Handler for clicking the clear button.
*/
@ -238,7 +259,7 @@ var PerformanceView = {
this.emit(EVENTS.UI_STOP_RECORDING);
} else {
this._lockRecordButtons(true);
this._activateRecordButtons(true);
this._toggleRecordButtons(true);
this.emit(EVENTS.UI_START_RECORDING);
}
},

View File

@ -157,6 +157,30 @@
<!-- Recording contents and general notice messages -->
<deck id="performance-view" flex="1">
<!-- A default notice, shown while initially opening the tool.
Keep this element the first child of #performance-view. -->
<hbox id="tool-loading-notice"
class="notice-container"
flex="1">
</hbox>
<!-- "Unavailable" notice, shown when the entire tool is disabled,
for example, when in private browsing mode. -->
<vbox id="unavailable-notice"
class="notice-container"
align="center"
pack="center"
flex="1">
<hbox class="devtools-toolbarbutton-group"
pack="center">
<toolbarbutton class="devtools-toolbarbutton record-button"
label="&performanceUI.startRecording;"
standalone="true"/>
</hbox>
<label class="tool-disabled-message"
value="&performanceUI.unavailableNoticePB;"/>
</vbox>
<!-- "Empty" notice, shown when there's no recordings available -->
<hbox id="empty-notice"
class="notice-container"

View File

@ -8,9 +8,6 @@ support-files =
doc_simple-test.html
head.js
# Commented out tests are profiler tests
# that need to be moved over to performance tool
[browser_aaa-run-first-leaktest.js]
[browser_perf-categories-js-calltree.js]
[browser_perf-clear-01.js]
@ -81,6 +78,7 @@ skip-if = os == 'linux' # bug 1186322
[browser_perf-overview-selection-02.js]
[browser_perf-overview-selection-03.js]
[browser_perf-overview-time-interval.js]
[browser_perf-private-browsing.js]
[browser_perf-states.js]
skip-if = debug # bug 1203888
[browser_perf-refresh.js]

View File

@ -22,7 +22,7 @@ function* spawnTest() {
yield profileEnd;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let panel = yield toolbox.getCurrentPanel().open();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
let recordings = PerformanceController.getRecordings();

View File

@ -6,8 +6,6 @@
* when it is opened.
*/
var WAIT_TIME = 10;
function* spawnTest() {
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
let front = toolbox.performance;
@ -15,12 +13,13 @@ function* spawnTest() {
let profileStart = once(front, "recording-started");
console.profile("rust");
yield profileStart;
profileStart = once(front, "recording-started");
console.profile("rust2");
yield profileStart;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let panel = yield toolbox.getCurrentPanel().open();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
yield waitUntil(() => PerformanceController.getRecordings().length === 2);
@ -39,6 +38,7 @@ function* spawnTest() {
let profileEnd = once(front, "recording-stopped");
console.profileEnd("rust");
yield profileEnd;
profileEnd = once(front, "recording-stopped");
console.profileEnd("rust2");
yield profileEnd;

View File

@ -6,8 +6,6 @@
* also console recordings that have finished before it was opened.
*/
var WAIT_TIME = 10;
function* spawnTest() {
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
let front = toolbox.performance;
@ -25,7 +23,7 @@ function* spawnTest() {
yield profileStart;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let panel = yield toolbox.getCurrentPanel().open();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
yield waitUntil(() => PerformanceController.getRecordings().length === 2);

View File

@ -28,7 +28,7 @@ function* spawnTest() {
"performance tab is no longer highlighted when console.profile recording finishes");
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let panel = yield toolbox.getCurrentPanel().open();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
yield startRecording(panel);

View File

@ -20,7 +20,7 @@ function* spawnTest() {
yield profileStart;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let panel = yield toolbox.getCurrentPanel().open();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
yield waitUntil(() => PerformanceController.getRecordings().length === 2);

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that disables the frontend when in private browsing mode.
*/
let gPanelWinTuples = [];
function* spawnTest() {
yield testNormalWindow();
yield testPrivateWindow();
yield testRecordingFailingInWindow(0);
yield testRecordingFailingInWindow(1);
yield teardownPerfInWindow(1);
yield testRecordingSucceedingInWindow(0);
yield teardownPerfInWindow(0);
gPanelWinTuples = null;
finish();
}
function* createPanelInWindow(options) {
let win = yield addWindow(options);
let tab = yield addTab(SIMPLE_URL, win);
let target = TargetFactory.forTab(tab);
yield target.makeRemote();
let toolbox = yield gDevTools.showToolbox(target, "performance");
yield toolbox.initPerformance();
let panel = yield toolbox.getCurrentPanel().open();
gPanelWinTuples.push({ panel, win });
return { panel, win };
}
function* testNormalWindow() {
let { panel } = yield createPanelInWindow({ private: false });
let { PerformanceView } = panel.panelWin;
is(PerformanceView.getState(), "empty",
"The initial state of the performance panel view is correct (1).");
}
function* testPrivateWindow() {
let { panel } = yield createPanelInWindow({ private: true });
let { PerformanceView } = panel.panelWin;
is(PerformanceView.getState(), "unavailable",
"The initial state of the performance panel view is correct (2).");
}
function* testRecordingFailingInWindow(index) {
let { panel } = gPanelWinTuples[index];
let { EVENTS, PerformanceController } = panel.panelWin;
let onRecordingStarted = () => {
ok(false, "Recording should not start while a private window is present.");
};
PerformanceController.on(EVENTS.RECORDING_STARTED, onRecordingStarted);
let whenFailed = once(PerformanceController, EVENTS.NEW_RECORDING_FAILED);
PerformanceController.startRecording();
yield whenFailed;
ok(true, "Recording has failed.");
PerformanceController.off(EVENTS.RECORDING_STARTED, onRecordingStarted);
}
function* testRecordingSucceedingInWindow(index) {
let { panel } = gPanelWinTuples[index];
let { EVENTS, PerformanceController } = panel.panelWin;
let onRecordingFailed = () => {
ok(false, "Recording should start while now private windows are present.");
};
PerformanceController.on(EVENTS.NEW_RECORDING_FAILED, onRecordingFailed);
yield startRecording(panel);
yield stopRecording(panel);
ok(true, "Recording has succeeded.");
PerformanceController.off(EVENTS.RECORDING_STARTED, onRecordingFailed);
}
function* teardownPerfInWindow(index) {
let { panel, win } = gPanelWinTuples[index];
yield teardown(panel, win);
win.close();
}

View File

@ -105,6 +105,29 @@ registerCleanupFunction(() => {
Cu.forceGC();
});
function whenDelayedStartupFinished(aWindow, aCallback) {
Services.obs.addObserver(function observer(aSubject, aTopic) {
if (aWindow == aSubject) {
Services.obs.removeObserver(observer, aTopic);
executeSoon(aCallback);
}
}, "browser-delayed-startup-finished", false);
}
function addWindow(windowOptions) {
let deferred = Promise.defer();
let win = OpenBrowserWindow(windowOptions);
whenDelayedStartupFinished(win, () => {
executeSoon(() => {
deferred.resolve(win);
});
});
return deferred.promise;
}
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
@ -233,6 +256,8 @@ function initPerformance(aUrl, tool="performance", targetOps={}) {
// Wait for the performance tool to be spun up
yield toolbox.initPerformance();
// Panel is already initialized after `showToolbox` and `initPerformance`,
// no need to wait for `open` here.
let panel = toolbox.getCurrentPanel();
return { target, panel, toolbox };
});
@ -276,12 +301,12 @@ function consoleExecute (console, method, val) {
return promise;
}
function* teardown(panel) {
function* teardown(panel, win = window) {
info("Destroying the performance tool.");
let tab = panel.target.tab;
yield panel._toolbox.destroy();
yield removeTab(tab);
yield removeTab(tab, win);
}
function idleWait(time) {

View File

@ -107,10 +107,19 @@ var PerformanceActor = exports.PerformanceActor = protocol.ActorClass({
response: RetVal("json")
}),
canCurrentlyRecord: method(function() {
return this.bridge.canCurrentlyRecord();
}, {
response: { value: RetVal("json") }
}),
startRecording: method(Task.async(function *(options={}) {
if (!this.bridge.canCurrentlyRecord().success) {
return null;
}
let normalizedOptions = normalizePerformanceFeatures(options, this.traits.features);
let recording = yield this.bridge.startRecording(normalizedOptions);
this.manage(recording);
return recording;
@ -118,14 +127,18 @@ var PerformanceActor = exports.PerformanceActor = protocol.ActorClass({
request: {
options: Arg(0, "nullable:json"),
},
response: RetVal("performance-recording"),
response: {
recording: RetVal("nullable:performance-recording")
}
}),
stopRecording: actorBridge("stopRecording", {
request: {
options: Arg(0, "performance-recording"),
},
response: RetVal("performance-recording"),
response: {
recording: RetVal("performance-recording")
}
}),
isRecording: actorBridge("isRecording", {

View File

@ -282,6 +282,25 @@ const PerformanceRecorder = exports.PerformanceRecorder = Class({
}
},
/**
* Checks whether or not recording is currently supported. At the moment,
* this is only influenced by private browsing mode and the profiler.
*/
canCurrentlyRecord: function() {
let success = true;
let reasons = [];
if (!Profiler.canProfile()) {
success = false,
reasons.push("profiler-unavailable");
}
// Check other factors that will affect the possibility of successfully
// starting a recording here.
return { success, reasons };
},
/**
* Begins a recording session
*

View File

@ -55,7 +55,10 @@ const ProfilerManager = (function () {
* The nsIProfiler is target agnostic and interacts with the whole platform.
* Therefore, special care needs to be given to make sure different profiler
* consumers (i.e. "toolboxes") don't interfere with each other. Register
* the instance here.
* the profiler actor instances here.
*
* @param Profiler instance
* A profiler actor class.
*/
addInstance: function (instance) {
consumers.add(instance);
@ -64,6 +67,12 @@ const ProfilerManager = (function () {
this.registerEventListeners();
},
/**
* Remove the profiler actor instances here.
*
* @param Profiler instance
* A profiler actor class.
*/
removeInstance: function (instance) {
consumers.delete(instance);
@ -101,25 +110,36 @@ const ProfilerManager = (function () {
// interested in.
let currentTime = nsIProfilerModule.getElapsedTime();
nsIProfilerModule.StartProfiler(
config.entries,
config.interval,
config.features,
config.features.length,
config.threadFilters,
config.threadFilters.length
);
let { position, totalSize, generation } = this.getBufferInfo();
try {
nsIProfilerModule.StartProfiler(
config.entries,
config.interval,
config.features,
config.features.length,
config.threadFilters,
config.threadFilters.length
);
} catch (e) {
// For some reason, the profiler couldn't be started. This could happen,
// for example, when in private browsing mode.
Cu.reportError(`Could not start the profiler module: ${e.message}`);
return { started: false, reason: e, currentTime };
}
this._updateProfilerStatusPolling();
let { position, totalSize, generation } = this.getBufferInfo();
return { started: true, position, totalSize, generation, currentTime };
},
/**
* Attempts to stop the nsIProfiler module.
*/
stop: function () {
// Actually stop the profiler only if the last client has stopped profiling.
// Since this is used as a root actor, and the profiler module interacts with the
// whole platform, we need to avoid a case in which the profiler is stopped
// when there might be other clients still profiling.
// Since this is used as a root actor, and the profiler module interacts
// with the whole platform, we need to avoid a case in which the profiler
// is stopped when there might be other clients still profiling.
if (this.length <= 1) {
nsIProfilerModule.StopProfiler();
}
@ -306,7 +326,8 @@ const ProfilerManager = (function () {
*/
unregisterEventListeners: function () {
if (this._eventsRegistered) {
PROFILER_SYSTEM_EVENTS.forEach(eventName => Services.obs.removeObserver(this, eventName));
PROFILER_SYSTEM_EVENTS.forEach(eventName =>
Services.obs.removeObserver(this, eventName));
this._eventsRegistered = false;
}
},
@ -482,6 +503,14 @@ var Profiler = exports.Profiler = Class({
},
});
/**
* Checks whether or not the profiler module can currently run.
* @return boolean
*/
Profiler.canProfile = function() {
return nsIProfilerModule.CanProfile();
};
/**
* JSON.stringify callback used in Profiler.prototype.observe.
*/

View File

@ -2,7 +2,7 @@
/* 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/. */
#include "nsISupports.idl"
%{C++
@ -12,9 +12,10 @@ class nsCString;
[ref] native StringArrayRef(const nsTArray<nsCString>);
[scriptable, uuid(921e1223-b1ea-4906-bb26-a846e6b6835b)]
[scriptable, uuid(ff398a14-df1c-4966-9ab2-772ea6a6da6c)]
interface nsIProfiler : nsISupports
{
boolean CanProfile();
void StartProfiler(in uint32_t aEntries, in double aInterval,
[array, size_is(aFeatureCount)] in string aFeatures,
in uint32_t aFeatureCount,

View File

@ -70,6 +70,13 @@ nsProfiler::Observe(nsISupports *aSubject,
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::CanProfile(bool *aCanProfile)
{
*aCanProfile = !mLockedForPrivateBrowsing;
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::StartProfiler(uint32_t aEntries, double aInterval,
const char** aFeatures, uint32_t aFeatureCount,