зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1408124 - Create a new perf actor and recording panel; r=julienw
This patch adds a new performance recording panel that interfaes with perf.html. It is enabled through the new preferences: "devtools.performance.new-panel-enabled" MozReview-Commit-ID: 1HBLsbREDPk --HG-- extra : rebase_source : 22cc2826138c4891024c34947f145574f55b4541
This commit is contained in:
Родитель
c86e3d1c91
Коммит
5912015212
|
@ -19,6 +19,7 @@ loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/client/ca
|
|||
loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/client/webaudioeditor/panel").WebAudioEditorPanel);
|
||||
loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
|
||||
loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
|
||||
loader.lazyGetter(this, "NewPerformancePanel", () => require("devtools/client/performance-new/panel").PerformancePanel);
|
||||
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
|
||||
loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
|
||||
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
|
||||
|
@ -253,29 +254,49 @@ Tools.canvasDebugger = {
|
|||
};
|
||||
|
||||
Tools.performance = {
|
||||
id: "performance",
|
||||
ordinal: 7,
|
||||
icon: "chrome://devtools/skin/images/tool-profiler.svg",
|
||||
url: "chrome://devtools/content/performance/performance.xul",
|
||||
visibilityswitch: "devtools.performance.enabled",
|
||||
label: l10n("performance.label"),
|
||||
panelLabel: l10n("performance.panelLabel"),
|
||||
get tooltip() {
|
||||
return l10n("performance.tooltip", "Shift+" +
|
||||
functionkey(l10n("performance.commandkey")));
|
||||
},
|
||||
accesskey: l10n("performance.accesskey"),
|
||||
inMenu: true,
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
return target.hasActor("performance");
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
return new PerformancePanel(frame, target);
|
||||
}
|
||||
id: "performance",
|
||||
ordinal: 7,
|
||||
icon: "chrome://devtools/skin/images/tool-profiler.svg",
|
||||
visibilityswitch: "devtools.performance.enabled",
|
||||
label: l10n("performance.label"),
|
||||
panelLabel: l10n("performance.panelLabel"),
|
||||
get tooltip() {
|
||||
return l10n("performance.tooltip", "Shift+" +
|
||||
functionkey(l10n("performance.commandkey")));
|
||||
},
|
||||
accesskey: l10n("performance.accesskey"),
|
||||
inMenu: true,
|
||||
};
|
||||
|
||||
function switchPerformancePanel() {
|
||||
if (Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
|
||||
Tools.performance.url = "chrome://devtools/content/performance-new/perf.xhtml";
|
||||
Tools.performance.build = function (frame, target) {
|
||||
return new NewPerformancePanel(frame, target);
|
||||
};
|
||||
Tools.performance.isTargetSupported = function (target) {
|
||||
// Root actors are lazily initialized, so we can't check if the target has
|
||||
// the perf actor yet. Also this function is not async, so we can't initialize
|
||||
// the actor yet.
|
||||
return true;
|
||||
};
|
||||
} else {
|
||||
Tools.performance.url = "chrome://devtools/content/performance/performance.xul";
|
||||
Tools.performance.build = function (frame, target) {
|
||||
return new PerformancePanel(frame, target);
|
||||
};
|
||||
Tools.performance.isTargetSupported = function (target) {
|
||||
return target.hasActor("performance");
|
||||
};
|
||||
}
|
||||
}
|
||||
switchPerformancePanel();
|
||||
|
||||
Services.prefs.addObserver(
|
||||
"devtools.performance.new-panel-enabled",
|
||||
{ observe: switchPerformancePanel }
|
||||
);
|
||||
|
||||
Tools.memory = {
|
||||
id: "memory",
|
||||
ordinal: 8,
|
||||
|
|
|
@ -328,6 +328,11 @@ OptionsPanel.prototype = {
|
|||
label: L10N.getStr("toolbox.options.enableNewDebugger.label"),
|
||||
id: "devtools-new-debugger",
|
||||
parentId: "debugger-options"
|
||||
}, {
|
||||
pref: "devtools.performance.new-panel-enabled",
|
||||
label: "Enable new performance recorder (then re-open DevTools)",
|
||||
id: "devtools-new-performance",
|
||||
parentId: "context-options"
|
||||
}];
|
||||
|
||||
let createPreferenceOption = ({pref, label, id}) => {
|
||||
|
|
|
@ -77,6 +77,7 @@ function setPrefDefaults() {
|
|||
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
|
||||
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
|
||||
Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
|
||||
Services.prefs.setBoolPref("devtools.preference.new-panel-enabled", false);
|
||||
}
|
||||
window.addEventListener("load", function () {
|
||||
let cmdClose = document.getElementById("toolbox-cmd-close");
|
||||
|
|
|
@ -63,6 +63,9 @@ devtools.jar:
|
|||
content/performance/performance.xul (performance/performance.xul)
|
||||
content/performance/performance-controller.js (performance/performance-controller.js)
|
||||
content/performance/performance-view.js (performance/performance-view.js)
|
||||
content/performance-new/perf.xhtml (performance-new/perf.xhtml)
|
||||
content/performance-new/frame-script.js (performance-new/frame-script.js)
|
||||
content/performance-new/initializer.js (performance-new/initializer.js)
|
||||
content/performance/views/overview.js (performance/views/overview.js)
|
||||
content/performance/views/toolbar.js (performance/views/toolbar.js)
|
||||
content/performance/views/details.js (performance/views/details.js)
|
||||
|
@ -155,6 +158,7 @@ devtools.jar:
|
|||
skin/animationinspector.css (themes/animationinspector.css)
|
||||
skin/canvasdebugger.css (themes/canvasdebugger.css)
|
||||
skin/debugger.css (themes/debugger.css)
|
||||
skin/perf.css (themes/perf.css)
|
||||
skin/performance.css (themes/performance.css)
|
||||
skin/memory.css (themes/memory.css)
|
||||
skin/scratchpad.css (themes/scratchpad.css)
|
||||
|
|
|
@ -20,6 +20,7 @@ DIRS += [
|
|||
'memory',
|
||||
'netmonitor',
|
||||
'performance',
|
||||
'performance-new',
|
||||
'preferences',
|
||||
'responsive.html',
|
||||
'scratchpad',
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||
const { div, button } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
/**
|
||||
* The recordingState is one of the following:
|
||||
**/
|
||||
|
||||
// The initial state before we've queried the PerfActor
|
||||
const NOT_YET_KNOWN = "not-yet-known";
|
||||
// The profiler is available, we haven't started recording yet.
|
||||
const AVAILABLE_TO_RECORD = "available-to-record";
|
||||
// An async request has been sent to start the profiler.
|
||||
const REQUEST_TO_START_RECORDING = "request-to-start-recording";
|
||||
// An async request has been sent to get the profile and stop the profiler.
|
||||
const REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER =
|
||||
"request-to-get-profile-and-stop-profiler";
|
||||
// An async request has been sent to stop the profiler.
|
||||
const REQUEST_TO_STOP_PROFILER = "request-to-stop-profiler";
|
||||
// The profiler notified us that our request to start it actually started it.
|
||||
const RECORDING = "recording";
|
||||
// Some other code with access to the profiler started it.
|
||||
const OTHER_IS_RECORDING = "other-is-recording";
|
||||
// Profiling is not available when in private browsing mode.
|
||||
const LOCKED_BY_PRIVATE_BROWSING = "locked-by-private-browsing";
|
||||
|
||||
class Perf extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
perfFront: PropTypes.object.isRequired,
|
||||
receiveProfile: PropTypes.func.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
recordingState: NOT_YET_KNOWN,
|
||||
recordingUnexpectedlyStopped: false,
|
||||
// The following is either "null" for unknown, or a boolean value.
|
||||
isSupportedPlatform: null
|
||||
};
|
||||
this.startRecording = this.startRecording.bind(this);
|
||||
this.getProfileAndStopProfiler = this.getProfileAndStopProfiler.bind(this);
|
||||
this.stopProfilerAndDiscardProfile = this.stopProfilerAndDiscardProfile.bind(this);
|
||||
this.handleProfilerStarting = this.handleProfilerStarting.bind(this);
|
||||
this.handleProfilerStopping = this.handleProfilerStopping.bind(this);
|
||||
this.handlePrivateBrowsingStarting = this.handlePrivateBrowsingStarting.bind(this);
|
||||
this.handlePrivateBrowsingEnding = this.handlePrivateBrowsingEnding.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { perfFront } = this.props;
|
||||
|
||||
// Ask for the initial state of the profiler.
|
||||
Promise.all([
|
||||
perfFront.isActive(),
|
||||
perfFront.isSupportedPlatform(),
|
||||
perfFront.isLockedForPrivateBrowsing(),
|
||||
]).then((results) => {
|
||||
const [
|
||||
isActive,
|
||||
isSupportedPlatform,
|
||||
isLockedForPrivateBrowsing
|
||||
] = results;
|
||||
|
||||
let recordingState = this.state.recordingState;
|
||||
// It's theoretically possible we got an event that already let us know about
|
||||
// the current state of the profiler.
|
||||
if (recordingState === NOT_YET_KNOWN && isSupportedPlatform) {
|
||||
if (isLockedForPrivateBrowsing) {
|
||||
recordingState = LOCKED_BY_PRIVATE_BROWSING;
|
||||
} else {
|
||||
recordingState = isActive
|
||||
? OTHER_IS_RECORDING
|
||||
: AVAILABLE_TO_RECORD;
|
||||
}
|
||||
}
|
||||
this.setState({ isSupportedPlatform, recordingState });
|
||||
});
|
||||
|
||||
// Handle when the profiler changes state. It might be us, it might be someone else.
|
||||
this.props.perfFront.on("profiler-started", this.handleProfilerStarting);
|
||||
this.props.perfFront.on("profiler-stopped", this.handleProfilerStopping);
|
||||
this.props.perfFront.on("profile-locked-by-private-browsing",
|
||||
this.handlePrivateBrowsingStarting);
|
||||
this.props.perfFront.on("profile-unlocked-from-private-browsing",
|
||||
this.handlePrivateBrowsingEnding);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
switch (this.state.recordingState) {
|
||||
case NOT_YET_KNOWN:
|
||||
case AVAILABLE_TO_RECORD:
|
||||
case REQUEST_TO_STOP_PROFILER:
|
||||
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
|
||||
case LOCKED_BY_PRIVATE_BROWSING:
|
||||
case OTHER_IS_RECORDING:
|
||||
// Do nothing for these states.
|
||||
break;
|
||||
|
||||
case RECORDING:
|
||||
case REQUEST_TO_START_RECORDING:
|
||||
this.props.perfFront.stopProfilerAndDiscardProfile();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Unhandled recording state.");
|
||||
}
|
||||
}
|
||||
|
||||
getRecordingStateForTesting() {
|
||||
return this.state.recordingState;
|
||||
}
|
||||
|
||||
handleProfilerStarting() {
|
||||
switch (this.state.recordingState) {
|
||||
case NOT_YET_KNOWN:
|
||||
// We couldn't have started it yet, so it must have been someone
|
||||
// else. (fallthrough)
|
||||
case AVAILABLE_TO_RECORD:
|
||||
// We aren't recording, someone else started it up. (fallthrough)
|
||||
case REQUEST_TO_STOP_PROFILER:
|
||||
// We requested to stop the profiler, but someone else already started
|
||||
// it up. (fallthrough)
|
||||
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
|
||||
// Someone re-started the profiler while we were asking for the completed
|
||||
// profile.
|
||||
|
||||
this.setState({
|
||||
recordingState: OTHER_IS_RECORDING,
|
||||
recordingUnexpectedlyStopped: false
|
||||
});
|
||||
break;
|
||||
|
||||
case REQUEST_TO_START_RECORDING:
|
||||
// Wait for the profiler to tell us that it has started.
|
||||
this.setState({
|
||||
recordingState: RECORDING,
|
||||
recordingUnexpectedlyStopped: false
|
||||
});
|
||||
break;
|
||||
|
||||
case LOCKED_BY_PRIVATE_BROWSING:
|
||||
case OTHER_IS_RECORDING:
|
||||
case RECORDING:
|
||||
// These state cases don't make sense to happen, and means we have a logical
|
||||
// fallacy somewhere.
|
||||
throw new Error(
|
||||
"The profiler started recording, when it shouldn't have " +
|
||||
`been able to. Current state: "${this.state.recordingState}"`);
|
||||
default:
|
||||
throw new Error("Unhandled recording state");
|
||||
}
|
||||
}
|
||||
|
||||
handleProfilerStopping() {
|
||||
switch (this.state.recordingState) {
|
||||
case NOT_YET_KNOWN:
|
||||
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
|
||||
case REQUEST_TO_STOP_PROFILER:
|
||||
case OTHER_IS_RECORDING:
|
||||
this.setState({
|
||||
recordingState: AVAILABLE_TO_RECORD,
|
||||
recordingUnexpectedlyStopped: false
|
||||
});
|
||||
break;
|
||||
|
||||
case REQUEST_TO_START_RECORDING:
|
||||
// Highly unlikely, but someone stopped the recorder, this is fine.
|
||||
// Do nothing (fallthrough).
|
||||
case LOCKED_BY_PRIVATE_BROWSING:
|
||||
// The profiler is already locked, so we know about this already.
|
||||
break;
|
||||
|
||||
case RECORDING:
|
||||
this.setState({
|
||||
recordingState: AVAILABLE_TO_RECORD,
|
||||
recordingUnexpectedlyStopped: true
|
||||
});
|
||||
break;
|
||||
|
||||
case AVAILABLE_TO_RECORD:
|
||||
throw new Error(
|
||||
"The profiler stopped recording, when it shouldn't have been able to.");
|
||||
default:
|
||||
throw new Error("Unhandled recording state");
|
||||
}
|
||||
}
|
||||
|
||||
handlePrivateBrowsingStarting() {
|
||||
switch (this.state.recordingState) {
|
||||
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
|
||||
// This one is a tricky case. Go ahead and act like nothing went wrong, maybe
|
||||
// it will resolve correctly? (fallthrough)
|
||||
case REQUEST_TO_STOP_PROFILER:
|
||||
case AVAILABLE_TO_RECORD:
|
||||
case OTHER_IS_RECORDING:
|
||||
case NOT_YET_KNOWN:
|
||||
this.setState({
|
||||
recordingState: LOCKED_BY_PRIVATE_BROWSING,
|
||||
recordingUnexpectedlyStopped: false
|
||||
});
|
||||
break;
|
||||
|
||||
case REQUEST_TO_START_RECORDING:
|
||||
case RECORDING:
|
||||
this.setState({
|
||||
recordingState: LOCKED_BY_PRIVATE_BROWSING,
|
||||
recordingUnexpectedlyStopped: true
|
||||
});
|
||||
break;
|
||||
|
||||
case LOCKED_BY_PRIVATE_BROWSING:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Unhandled recording state");
|
||||
}
|
||||
}
|
||||
|
||||
handlePrivateBrowsingEnding() {
|
||||
// No matter the state, go ahead and set this as ready to record. This should
|
||||
// be the only logical state to go into.
|
||||
this.setState({
|
||||
recordingState: AVAILABLE_TO_RECORD,
|
||||
recordingUnexpectedlyStopped: false
|
||||
});
|
||||
}
|
||||
|
||||
startRecording() {
|
||||
this.setState({
|
||||
recordingState: REQUEST_TO_START_RECORDING,
|
||||
// Reset this error state since it's no longer valid.
|
||||
recordingUnexpectedlyStopped: false,
|
||||
});
|
||||
this.props.perfFront.startProfiler();
|
||||
}
|
||||
|
||||
async getProfileAndStopProfiler() {
|
||||
this.setState({ recordingState: REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER });
|
||||
const profile = await this.props.perfFront.getProfileAndStopProfiler();
|
||||
this.setState({ recordingState: AVAILABLE_TO_RECORD });
|
||||
console.log("getProfileAndStopProfiler");
|
||||
this.props.receiveProfile(profile);
|
||||
}
|
||||
|
||||
stopProfilerAndDiscardProfile() {
|
||||
this.setState({ recordingState: REQUEST_TO_STOP_PROFILER });
|
||||
this.props.perfFront.stopProfilerAndDiscardProfile();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { recordingState, isSupportedPlatform } = this.state;
|
||||
|
||||
// Handle the cases of platform support.
|
||||
switch (isSupportedPlatform) {
|
||||
case null:
|
||||
// We don't know yet if this is a supported platform, wait for a response.
|
||||
return null;
|
||||
case false:
|
||||
return renderButton({
|
||||
label: "Start recording",
|
||||
disabled: true,
|
||||
additionalMessage: "Your platform is not supported. The Gecko Profiler only " +
|
||||
"supports Tier-1 platforms."
|
||||
});
|
||||
case true:
|
||||
// Continue on and render the panel.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - L10N all of the messages. Bug 1418056
|
||||
switch (recordingState) {
|
||||
case NOT_YET_KNOWN:
|
||||
return null;
|
||||
|
||||
case AVAILABLE_TO_RECORD:
|
||||
return renderButton({
|
||||
onClick: this.startRecording,
|
||||
label: "Start recording",
|
||||
additionalMessage: this.state.recordingUnexpectedlyStopped
|
||||
? div(null, "The recording was stopped by another tool.")
|
||||
: null
|
||||
});
|
||||
|
||||
case REQUEST_TO_STOP_PROFILER:
|
||||
return renderButton({
|
||||
label: "Stopping the recording",
|
||||
disabled: true
|
||||
});
|
||||
|
||||
case REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER:
|
||||
return renderButton({
|
||||
label: "Stopping the recording, and capturing the profile",
|
||||
disabled: true
|
||||
});
|
||||
|
||||
case REQUEST_TO_START_RECORDING:
|
||||
case RECORDING:
|
||||
return renderButton({
|
||||
label: "Stop and grab the recording",
|
||||
onClick: this.getProfileAndStopProfiler,
|
||||
disabled: this.state.recordingState === REQUEST_TO_START_RECORDING
|
||||
});
|
||||
|
||||
case OTHER_IS_RECORDING:
|
||||
return renderButton({
|
||||
label: "Stop and discard the other recording",
|
||||
onClick: this.stopProfilerAndDiscardProfile,
|
||||
additionalMessage: "Another tool is currently recording."
|
||||
});
|
||||
|
||||
case LOCKED_BY_PRIVATE_BROWSING:
|
||||
return renderButton({
|
||||
label: "Start recording",
|
||||
disabled: true,
|
||||
additionalMessage: `The profiler is disabled when Private Browsing is enabled.
|
||||
Close all Private Windows to re-enable the profiler`
|
||||
});
|
||||
|
||||
default:
|
||||
throw new Error("Unhandled recording state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Perf;
|
||||
|
||||
function renderButton(props) {
|
||||
const { disabled, label, onClick, additionalMessage } = props;
|
||||
const nbsp = "\u00A0";
|
||||
|
||||
return div(
|
||||
{ className: "perf" },
|
||||
div({ className: "perf-additional-message" }, additionalMessage || nbsp),
|
||||
div(
|
||||
null,
|
||||
button(
|
||||
{
|
||||
className: "devtools-button perf-button",
|
||||
"data-standalone": true,
|
||||
disabled,
|
||||
onClick
|
||||
},
|
||||
label
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'Perf.js',
|
||||
)
|
|
@ -0,0 +1,106 @@
|
|||
/* 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";
|
||||
/* global addMessageListener, addEventListener, content */
|
||||
|
||||
/**
|
||||
* This frame script injects itself into perf-html.io and injects the profile
|
||||
* into the page. It is mostly taken from the Gecko Profiler Addon implementation.
|
||||
*/
|
||||
|
||||
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
||||
|
||||
let gProfile = null;
|
||||
|
||||
addMessageListener(TRANSFER_EVENT, e => {
|
||||
gProfile = e.data;
|
||||
// Eagerly try and see if the framescript was evaluated after perf loaded its scripts.
|
||||
connectToPage();
|
||||
// If not try again at DOMContentLoaded which should be called after the script
|
||||
// tag was synchronously loaded in.
|
||||
addEventListener("DOMContentLoaded", connectToPage);
|
||||
});
|
||||
|
||||
function connectToPage() {
|
||||
const unsafeWindow = content.wrappedJSObject;
|
||||
if (unsafeWindow.connectToGeckoProfiler) {
|
||||
unsafeWindow.connectToGeckoProfiler(makeAccessibleToPage({
|
||||
getProfile: () => Promise.resolve(gProfile),
|
||||
getSymbolTable: (debugName, breakpadId) => getSymbolTable(debugName, breakpadId),
|
||||
}, unsafeWindow));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For now, do not try to symbolicate. Reject any attempt.
|
||||
*/
|
||||
function getSymbolTable(debugName, breakpadId) {
|
||||
// Errors will not properly clone into the content page as they bring privileged
|
||||
// stacks information into the page. In this case provide a mock object to maintain
|
||||
// the Error type object shape.
|
||||
const error = {
|
||||
message: `The DevTools' "perf" actor does not support symbolication.`
|
||||
};
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// The following functions handle the security of cloning the object into the page.
|
||||
// The code was taken from the original Gecko Profiler Add-on to maintain
|
||||
// compatibility with the existing profile importing mechanism:
|
||||
// See: https://github.com/devtools-html/Gecko-Profiler-Addon/blob/78138190b42565f54ce4022a5b28583406489ed2/data/tab-framescript.js
|
||||
|
||||
/**
|
||||
* Create a promise that can be used in the page.
|
||||
*/
|
||||
function createPromiseInPage(fun, contentGlobal) {
|
||||
function funThatClonesObjects(resolve, reject) {
|
||||
return fun(result => resolve(Components.utils.cloneInto(result, contentGlobal)),
|
||||
error => reject(Components.utils.cloneInto(error, contentGlobal)));
|
||||
}
|
||||
return new contentGlobal.Promise(Components.utils.exportFunction(funThatClonesObjects,
|
||||
contentGlobal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that calls the original function and tries to make the
|
||||
* return value available to the page.
|
||||
*/
|
||||
function wrapFunction(fun, contentGlobal) {
|
||||
return function () {
|
||||
let result = fun.apply(this, arguments);
|
||||
if (typeof result === "object") {
|
||||
if (("then" in result) && (typeof result.then === "function")) {
|
||||
// fun returned a promise.
|
||||
return createPromiseInPage((resolve, reject) =>
|
||||
result.then(resolve, reject), contentGlobal);
|
||||
}
|
||||
return Components.utils.cloneInto(result, contentGlobal);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a simple object containing values that are objects or functions.
|
||||
* The objects or functions are wrapped in such a way that they can be
|
||||
* consumed by the page.
|
||||
*/
|
||||
function makeAccessibleToPage(obj, contentGlobal) {
|
||||
let result = Components.utils.createObjectIn(contentGlobal);
|
||||
for (let field in obj) {
|
||||
switch (typeof obj[field]) {
|
||||
case "function":
|
||||
Components.utils.exportFunction(
|
||||
wrapFunction(obj[field], contentGlobal), result, { defineAs: field });
|
||||
break;
|
||||
case "object":
|
||||
Components.utils.cloneInto(obj[field], result, { defineAs: field });
|
||||
break;
|
||||
default:
|
||||
result[field] = obj[field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* 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";
|
||||
|
||||
/* exported gInit, gDestroy */
|
||||
|
||||
const BrowserLoaderModule = {};
|
||||
Components.utils.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
|
||||
const { require } = BrowserLoaderModule.BrowserLoader({
|
||||
baseURI: "resource://devtools/client/memory/",
|
||||
window
|
||||
});
|
||||
const Perf = require("devtools/client/performance-new/components/Perf");
|
||||
const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { createElement } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
/**
|
||||
* Perform a simple initialization on the panel. Hook up event listeners.
|
||||
*
|
||||
* @param perfFront - The Perf actor's front. Used to start and stop recordings.
|
||||
*/
|
||||
function gInit(perfFront) {
|
||||
const props = {
|
||||
perfFront,
|
||||
receiveProfile: profile => {
|
||||
// Open up a new tab and send a message with the profile.
|
||||
const browser = top.gBrowser;
|
||||
const tab = browser.addTab("https://perf-html.io/from-addon");
|
||||
browser.selectedTab = tab;
|
||||
const mm = tab.linkedBrowser.messageManager;
|
||||
mm.loadFrameScript(
|
||||
"chrome://devtools/content/performance-new/frame-script.js",
|
||||
false
|
||||
);
|
||||
mm.sendAsyncMessage("devtools:perf-html-transfer-profile", profile);
|
||||
}
|
||||
};
|
||||
render(createElement(Perf, props), document.querySelector("#root"));
|
||||
}
|
||||
|
||||
function gDestroy() {
|
||||
unmountComponentAtNode(document.querySelector("#root"));
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'components',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'panel.js',
|
||||
)
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
|
|
@ -0,0 +1,59 @@
|
|||
/* 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 { PerfFront } = require("devtools/shared/fronts/perf");
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/shared/old-event-emitter");
|
||||
|
||||
class PerformancePanel {
|
||||
constructor(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this.toolbox = toolbox;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open is effectively an asynchronous constructor.
|
||||
* @return {Promise} Resolves when the Perf tool completes opening.
|
||||
*/
|
||||
open() {
|
||||
if (!this._opening) {
|
||||
this._opening = this._doOpen();
|
||||
}
|
||||
return this._opening;
|
||||
}
|
||||
|
||||
async _doOpen() {
|
||||
this.panelWin.gToolbox = this.toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
|
||||
const rootForm = await this.target.root;
|
||||
const perfFront = new PerfFront(this.target.client, rootForm);
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
this.panelWin.gInit(perfFront);
|
||||
return this;
|
||||
}
|
||||
|
||||
// DevToolPanel API:
|
||||
|
||||
get target() {
|
||||
return this.toolbox.target;
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
// Make sure this panel is not already destroyed.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
this.panelWin.gDestroy();
|
||||
this.emit("destroyed");
|
||||
this._destroyed = true;
|
||||
}
|
||||
}
|
||||
exports.PerformancePanel = PerformancePanel;
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="">
|
||||
<head>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/widgets.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/perf.css" type="text/css"/>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
<div id="root"></div>
|
||||
<script type="application/javascript" src="initializer.js"></script>
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"
|
||||
defer="true">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_perf-state-01.html]
|
||||
[test_perf-state-02.html]
|
||||
[test_perf-state-03.html]
|
||||
[test_perf-state-04.html]
|
|
@ -0,0 +1,133 @@
|
|||
/* 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";
|
||||
|
||||
/* exported addPerfTest, MockPerfFront */
|
||||
/* globals URL_ROOT */
|
||||
|
||||
const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
var { require } = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/performance-new/",
|
||||
window
|
||||
});
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { perfDescription } = require("devtools/shared/specs/perf");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const flags = require("devtools/shared/flags");
|
||||
|
||||
flags.testing = true;
|
||||
let EXPECTED_DTU_ASSERT_FAILURE_COUNT = 0;
|
||||
SimpleTest.registerCleanupFunction(function () {
|
||||
if (DevToolsUtils.assertionFailureCount !== EXPECTED_DTU_ASSERT_FAILURE_COUNT) {
|
||||
ok(false, "Should have had the expected number of DevToolsUtils.assert() failures." +
|
||||
"Expected " + EXPECTED_DTU_ASSERT_FAILURE_COUNT +
|
||||
", got " + DevToolsUtils.assertionFailureCount);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle test setup and teardown while catching errors.
|
||||
*/
|
||||
function addPerfTest(asyncTest) {
|
||||
window.onload = async () => {
|
||||
try {
|
||||
await asyncTest();
|
||||
} catch (e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The Gecko Profiler is a rather heavy-handed component that uses a lot of resources.
|
||||
* In order to get around that, and have quick component tests we provide a mock of
|
||||
* the performance front. It also has a method called flushAsyncQueue() that will
|
||||
* flush any queued async calls to deterministically run our tests.
|
||||
*/
|
||||
class MockPerfFront extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this._isActive = false;
|
||||
this._asyncQueue = [];
|
||||
|
||||
// Tests can update these two values directly as needed.
|
||||
this.mockIsSupported = true;
|
||||
this.mockIsLocked = false;
|
||||
|
||||
// Wrap all async methods in a flushable queue, so that tests can control
|
||||
// when the responses come back.
|
||||
this.isActive = this._wrapInAsyncQueue(this.isActive);
|
||||
this.startProfiler = this._wrapInAsyncQueue(this.startProfiler);
|
||||
this.stopProfilerAndDiscardProfile = this._wrapInAsyncQueue(
|
||||
this.stopProfilerAndDiscardProfile);
|
||||
this.getProfileAndStopProfiler = this._wrapInAsyncQueue(
|
||||
this.getProfileAndStopProfiler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a flushable queue mechanism for all async work. The work piles up
|
||||
* and then is evaluated at once when _flushPendingQueue is called.
|
||||
*/
|
||||
_wrapInAsyncQueue(fn) {
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error("_wrapInAsyncQueue requires a function");
|
||||
}
|
||||
return (...args) => {
|
||||
return new Promise(resolve => {
|
||||
this._asyncQueue.push(() => {
|
||||
resolve(fn.apply(this, args));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
flushAsyncQueue() {
|
||||
const pending = this._asyncQueue;
|
||||
this._asyncQueue = [];
|
||||
pending.forEach(fn => fn());
|
||||
// Ensure this is async.
|
||||
return new Promise(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
startProfiler() {
|
||||
this._isActive = true;
|
||||
this.emit("profiler-started");
|
||||
}
|
||||
|
||||
getProfileAndStopProfiler() {
|
||||
this._isActive = false;
|
||||
this.emit("profiler-stopped");
|
||||
// Return a fake profile.
|
||||
return {};
|
||||
}
|
||||
|
||||
stopProfilerAndDiscardProfile() {
|
||||
this._isActive = false;
|
||||
this.emit("profiler-stopped");
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
isSupportedPlatform() {
|
||||
return this.mockIsSupported;
|
||||
}
|
||||
|
||||
isLockedForPrivateBrowsing() {
|
||||
return this.mockIsLocked;
|
||||
}
|
||||
}
|
||||
|
||||
// Do a quick validation to make sure that our Mock has the same methods as a spec.
|
||||
const mockKeys = Object.getOwnPropertyNames(MockPerfFront.prototype);
|
||||
Object.getOwnPropertyNames(perfDescription.methods).forEach(methodName => {
|
||||
if (!mockKeys.includes(methodName)) {
|
||||
throw new Error(`The MockPerfFront is missing the method "${methodName}" from the ` +
|
||||
"actor's spec. It should be added to the mock.");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- 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/. -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Perf component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the normal workflow of starting and stopping the profiler through the
|
||||
* Perf component.
|
||||
*/
|
||||
addPerfTest(async () => {
|
||||
const Perf = require("devtools/client/performance-new/components/Perf");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const perfFront = new MockPerfFront();
|
||||
const container = document.querySelector("#container");
|
||||
|
||||
// Inject a function which will allow us to receive the profile.
|
||||
let profile;
|
||||
function receiveProfile(profileIn) {
|
||||
profile = profileIn;
|
||||
}
|
||||
|
||||
const element = React.createElement(Perf, { perfFront, receiveProfile });
|
||||
const perfComponent = ReactDOM.render(element, container);
|
||||
is(perfComponent.state.recordingState, "not-yet-known",
|
||||
"The component at first is in an unknown state.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"After talking to the actor, we're ready to record.");
|
||||
|
||||
const button = container.querySelector("button");
|
||||
ok(button, "Selected the button to click.");
|
||||
button.click();
|
||||
is(perfComponent.state.recordingState, "request-to-start-recording",
|
||||
"Sent in a request to start recording.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "recording",
|
||||
"The actor has started its recording");
|
||||
|
||||
button.click();
|
||||
is(perfComponent.state.recordingState,
|
||||
"request-to-get-profile-and-stop-profiler",
|
||||
"We have requested to stop the profiler.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"The profiler is available to record again.");
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(typeof profile, "object", "Got a profile");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- 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/. -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Perf component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the perf component when the profiler is already started.
|
||||
*/
|
||||
addPerfTest(async () => {
|
||||
const Perf = require("devtools/client/performance-new/components/Perf");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const perfFront = new MockPerfFront();
|
||||
const container = document.querySelector("#container");
|
||||
|
||||
ok(true, "Start the profiler before initiliazing the component, to simulate" +
|
||||
"the profiler being controlled by another tool.");
|
||||
|
||||
perfFront.startProfiler();
|
||||
await perfFront.flushAsyncQueue();
|
||||
|
||||
const receiveProfile = () => {};
|
||||
const element = React.createElement(Perf, { perfFront, receiveProfile });
|
||||
const perfComponent = ReactDOM.render(element, container);
|
||||
is(perfComponent.state.recordingState, "not-yet-known",
|
||||
"The component at first is in an unknown state.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "other-is-recording",
|
||||
"The profiler is not available to record.");
|
||||
|
||||
const button = container.querySelector("button");
|
||||
ok(button, "Selected a button on the component");
|
||||
button.click();
|
||||
is(perfComponent.state.recordingState, "request-to-stop-profiler",
|
||||
"We can request to stop the profiler.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"The profiler is now available to record.");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- 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/. -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Perf component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the perf component for when the profiler is already started.
|
||||
*/
|
||||
addPerfTest(async () => {
|
||||
const Perf = require("devtools/client/performance-new/components/Perf");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const perfFront = new MockPerfFront();
|
||||
const container = document.querySelector("#container");
|
||||
|
||||
const receiveProfile = () => {};
|
||||
const element = React.createElement(Perf, { perfFront, receiveProfile });
|
||||
const perfComponent = ReactDOM.render(element, container);
|
||||
|
||||
is(perfComponent.state.recordingState, "not-yet-known",
|
||||
"The component at first is in an unknown state.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"After talking to the actor, we're ready to record.");
|
||||
|
||||
document.querySelector("button").click();
|
||||
is(perfComponent.state.recordingState, "request-to-start-recording",
|
||||
"Sent in a request to start recording.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "recording",
|
||||
"The actor has started its recording");
|
||||
|
||||
ok(true, "Simulate a third party stopping the profiler.");
|
||||
perfFront.stopProfilerAndDiscardProfile();
|
||||
await perfFront.flushAsyncQueue();
|
||||
|
||||
ok(perfComponent.state.recordingUnexpectedlyStopped,
|
||||
"The profiler unexpectedly stopped.");
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"However, the profiler is available to record again.");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- 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/. -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Perf component test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test that the profiler gets disabled during private browsing.
|
||||
*/
|
||||
addPerfTest(async () => {
|
||||
const Perf = require("devtools/client/performance-new/components/Perf");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const perfFront = new MockPerfFront();
|
||||
const container = document.querySelector("#container");
|
||||
|
||||
perfFront.mockIsLocked = true;
|
||||
|
||||
const receiveProfile = () => {};
|
||||
const element = React.createElement(Perf, { perfFront, receiveProfile });
|
||||
const perfComponent = ReactDOM.render(element, container);
|
||||
|
||||
is(perfComponent.state.recordingState, "not-yet-known",
|
||||
"The component at first is in an unknown state.");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "locked-by-private-browsing",
|
||||
"After talking to the actor, it's locked for private browsing.");
|
||||
|
||||
perfFront.mockIsLocked = false;
|
||||
perfFront.emit("profile-unlocked-from-private-browsing");
|
||||
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "available-to-record",
|
||||
"After the profiler is unlocked, it's available to record.");
|
||||
|
||||
document.querySelector("button").click();
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "recording",
|
||||
"The actor has started its recording");
|
||||
|
||||
perfFront.mockIsLocked = true;
|
||||
perfFront.emit("profile-locked-by-private-browsing");
|
||||
await perfFront.flushAsyncQueue();
|
||||
is(perfComponent.state.recordingState, "locked-by-private-browsing",
|
||||
"The recording stops when going into private browsing mode.");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -305,6 +305,9 @@ pref("devtools.webconsole.new-frontend-enabled", true);
|
|||
// Enable the webconsole sidebar toggle
|
||||
pref("devtools.webconsole.sidebarToggle", false);
|
||||
|
||||
// Disable the new performance recording panel by default
|
||||
pref("devtools.performance.new-panel-enabled", false);
|
||||
|
||||
// Enable client-side mapping service for source maps
|
||||
pref("devtools.source-map.client-service.enabled", true);
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* 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/. */
|
||||
|
||||
.perf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.devtools-button.perf-button {
|
||||
padding: 5px;
|
||||
margin: auto;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
.perf-additional-message {
|
||||
margin: 10px;
|
||||
margin-top: 65px;
|
||||
}
|
|
@ -42,6 +42,7 @@ DevToolsModules(
|
|||
'memory.js',
|
||||
'monitor.js',
|
||||
'object.js',
|
||||
'perf.js',
|
||||
'performance-recording.js',
|
||||
'performance.js',
|
||||
'preference.js',
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/* 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 protocol = require("devtools/shared/protocol");
|
||||
const { ActorClassWithSpec, Actor } = protocol;
|
||||
const { perfSpec } = require("devtools/shared/specs/perf");
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyGetter(this, "geckoProfiler", () => {
|
||||
return Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
});
|
||||
|
||||
loader.lazyImporter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
// Some platforms are built without the Gecko Profiler.
|
||||
const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci;
|
||||
|
||||
/**
|
||||
* The PerfActor wraps the Gecko Profiler interface
|
||||
*/
|
||||
exports.PerfActor = ActorClassWithSpec(perfSpec, {
|
||||
initialize(conn) {
|
||||
Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
// Only setup the observers on a supported platform.
|
||||
if (IS_SUPPORTED_PLATFORM) {
|
||||
this._observer = {
|
||||
observe: this._observe.bind(this)
|
||||
};
|
||||
Services.obs.addObserver(this._observer, "profiler-started");
|
||||
Services.obs.addObserver(this._observer, "profiler-stopped");
|
||||
Services.obs.addObserver(this._observer, "chrome-document-global-created");
|
||||
Services.obs.addObserver(this._observer, "last-pb-context-exited");
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this._observer, "profiler-started");
|
||||
Services.obs.removeObserver(this._observer, "profiler-stopped");
|
||||
Services.obs.removeObserver(this._observer, "chrome-document-global-created");
|
||||
Services.obs.removeObserver(this._observer, "last-pb-context-exited");
|
||||
Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
startProfiler() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a quick implementation, decide on some default values. These may need
|
||||
// to be tweaked or made configurable as needed.
|
||||
const settings = {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js", "stackwalk", "threads", "leaf"],
|
||||
threads: ["GeckoMain", "Compositor"]
|
||||
};
|
||||
|
||||
try {
|
||||
// This can throw an error if the profiler is in the wrong state.
|
||||
geckoProfiler.StartProfiler(
|
||||
settings.entries,
|
||||
settings.interval,
|
||||
settings.features,
|
||||
settings.features.length,
|
||||
settings.threads,
|
||||
settings.threads.length
|
||||
);
|
||||
} catch (e) {
|
||||
// In case any errors get triggered, bailout with a false.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
stopProfilerAndDiscardProfile() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return;
|
||||
}
|
||||
geckoProfiler.StopProfiler();
|
||||
},
|
||||
|
||||
async getProfileAndStopProfiler() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return null;
|
||||
}
|
||||
let profile;
|
||||
try {
|
||||
// Attempt to pull out the data.
|
||||
profile = await geckoProfiler.getProfileDataAsync();
|
||||
|
||||
// Stop and discard the buffers.
|
||||
geckoProfiler.StopProfiler();
|
||||
} catch (e) {
|
||||
// If there was any kind of error, bailout with no profile.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Gecko Profiler errors can return an empty object, return null for this case
|
||||
// as well.
|
||||
if (Object.keys(profile).length === 0) {
|
||||
return null;
|
||||
}
|
||||
return profile;
|
||||
},
|
||||
|
||||
isActive() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
return geckoProfiler.IsActive();
|
||||
},
|
||||
|
||||
isSupportedPlatform() {
|
||||
return IS_SUPPORTED_PLATFORM;
|
||||
},
|
||||
|
||||
isLockedForPrivateBrowsing() {
|
||||
if (!IS_SUPPORTED_PLATFORM) {
|
||||
return false;
|
||||
}
|
||||
return !geckoProfiler.CanProfile();
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch for events that happen within the browser. These can affect the current
|
||||
* availability and state of the Gecko Profiler.
|
||||
*/
|
||||
_observe(subject, topic, _data) {
|
||||
switch (topic) {
|
||||
case "chrome-document-global-created":
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(subject)) {
|
||||
this.emit("profile-locked-by-private-browsing");
|
||||
}
|
||||
break;
|
||||
case "last-pb-context-exited":
|
||||
this.emit("profile-unlocked-from-private-browsing");
|
||||
break;
|
||||
case "profiler-started":
|
||||
case "profiler-stopped":
|
||||
this.emit(topic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -443,6 +443,15 @@ var DebuggerServer = {
|
|||
constructor: "HeapSnapshotFileActor",
|
||||
type: { global: true }
|
||||
});
|
||||
// Always register this as a global module, even while there is a pref turning
|
||||
// on and off the other performance actor. This actor shouldn't conflict with
|
||||
// the other one. These are also lazily loaded so there shouldn't be a performance
|
||||
// impact.
|
||||
this.registerModule("devtools/server/actors/perf", {
|
||||
prefix: "perf",
|
||||
constructor: "PerfActor",
|
||||
type: { global: true }
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -534,7 +543,8 @@ var DebuggerServer = {
|
|||
constructor: "TimelineActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
if ("nsIProfiler" in Ci) {
|
||||
if ("nsIProfiler" in Ci &&
|
||||
!Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
|
||||
this.registerModule("devtools/server/actors/performance", {
|
||||
prefix: "performance",
|
||||
constructor: "PerformanceActor",
|
||||
|
|
|
@ -72,6 +72,9 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
|
|||
[browser_markers-timestamp.js]
|
||||
[browser_navigateEvents.js]
|
||||
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
|
||||
[browser_perf-01.js]
|
||||
[browser_perf-02.js]
|
||||
[browser_perf-03.js]
|
||||
[browser_perf-allocation-data.js]
|
||||
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
|
||||
[browser_perf-profiler-01.js]
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Run through a series of basic recording actions for the perf actor.
|
||||
*/
|
||||
add_task(async function () {
|
||||
const {front, client} = await initPerfFront();
|
||||
|
||||
// Assert the initial state.
|
||||
is(await front.isSupportedPlatform(), true,
|
||||
"This test only runs on supported platforms.");
|
||||
is(await front.isLockedForPrivateBrowsing(), false,
|
||||
"The browser is not in private browsing mode.");
|
||||
is(await front.isActive(), false,
|
||||
"The profiler is not active yet.");
|
||||
|
||||
// Start the profiler.
|
||||
const profilerStarted = once(front, "profiler-started");
|
||||
await front.startProfiler();
|
||||
await profilerStarted;
|
||||
is(await front.isActive(), true, "The profiler was started.");
|
||||
|
||||
// Stop the profiler and assert the results.
|
||||
const profilerStopped1 = once(front, "profiler-stopped");
|
||||
const profile = await front.getProfileAndStopProfiler();
|
||||
await profilerStopped1;
|
||||
is(await front.isActive(), false, "The profiler was stopped.");
|
||||
ok("threads" in profile, "The actor was used to record a profile.");
|
||||
|
||||
// Restart the profiler.
|
||||
await front.startProfiler();
|
||||
is(await front.isActive(), true, "The profiler was re-started.");
|
||||
|
||||
// Stop and discard.
|
||||
const profilerStopped2 = once(front, "profiler-stopped");
|
||||
await front.stopProfilerAndDiscardProfile();
|
||||
await profilerStopped2;
|
||||
is(await front.isActive(), false,
|
||||
"The profiler was stopped and the profile discarded.");
|
||||
|
||||
// Clean up.
|
||||
await front.destroy();
|
||||
await client.close();
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Test what happens when other tools control the profiler.
|
||||
*/
|
||||
add_task(async function () {
|
||||
const {front, client} = await initPerfFront();
|
||||
|
||||
// Simulate other tools by getting an independent handle on the Gecko Profiler.
|
||||
const geckoProfiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
|
||||
|
||||
is(await front.isActive(), false, "The profiler hasn't been started yet.");
|
||||
|
||||
// Start the profiler.
|
||||
await front.startProfiler();
|
||||
is(await front.isActive(), true, "The profiler was started.");
|
||||
|
||||
// Stop the profiler manually through the Gecko Profiler interface.
|
||||
const profilerStopped = once(front, "profiler-stopped");
|
||||
geckoProfiler.StopProfiler();
|
||||
await profilerStopped;
|
||||
is(await front.isActive(), false, "The profiler was stopped by another tool.");
|
||||
|
||||
// Clean up.
|
||||
await front.destroy();
|
||||
await client.close();
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Test that the profiler emits events when private browsing windows are opened
|
||||
* and closed.
|
||||
*/
|
||||
add_task(async function () {
|
||||
const {front, client} = await initPerfFront();
|
||||
|
||||
is(await front.isLockedForPrivateBrowsing(), false,
|
||||
"The profiler is not locked for private browsing.");
|
||||
|
||||
// Open up a new private browser window, and assert the correct events are fired.
|
||||
const profilerLocked = once(front, "profile-locked-by-private-browsing");
|
||||
const privateWindow = await BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
await profilerLocked;
|
||||
is(await front.isLockedForPrivateBrowsing(), true,
|
||||
"The profiler is now locked because of private browsing.");
|
||||
|
||||
// Close the private browser window, and assert the correct events are fired.
|
||||
const profilerUnlocked = once(front, "profile-unlocked-from-private-browsing");
|
||||
await BrowserTestUtils.closeWindow(privateWindow);
|
||||
await profilerUnlocked;
|
||||
is(await front.isLockedForPrivateBrowsing(), false,
|
||||
"The profiler is available again after closing the private browsing window.");
|
||||
|
||||
// Clean up.
|
||||
await front.destroy();
|
||||
await client.close();
|
||||
});
|
|
@ -108,6 +108,41 @@ function initDebuggerServer() {
|
|||
DebuggerServer.registerAllActors();
|
||||
}
|
||||
|
||||
async function initPerfFront() {
|
||||
const {PerfFront} = require("devtools/shared/fronts/perf");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
await waitUntilClientConnected(client);
|
||||
const rootForm = await getRootForm(client);
|
||||
const front = PerfFront(client, rootForm);
|
||||
return {front, client};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RootActor form from a DebuggerClient.
|
||||
* @param {DebuggerClient} client
|
||||
* @return {RootActor} Resolves when connected.
|
||||
*/
|
||||
function getRootForm(client) {
|
||||
return new Promise(resolve => {
|
||||
client.listTabs(rootForm => {
|
||||
resolve(rootForm);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a DebuggerClient is connected.
|
||||
* @param {DebuggerClient} client
|
||||
* @return {Promise} Resolves when connected.
|
||||
*/
|
||||
function waitUntilClientConnected(client) {
|
||||
return new Promise(resolve => {
|
||||
client.addOneTimeListener("connected", resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a debugger client.
|
||||
* @param {DebuggerClient}
|
||||
|
|
|
@ -23,6 +23,7 @@ DevToolsModules(
|
|||
'layout.js',
|
||||
'memory.js',
|
||||
'node.js',
|
||||
'perf.js',
|
||||
'performance-recording.js',
|
||||
'performance.js',
|
||||
'preference.js',
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* 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 { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
|
||||
const { perfSpec } = require("devtools/shared/specs/perf");
|
||||
|
||||
exports.PerfFront = FrontClassWithSpec(perfSpec, {
|
||||
initialize: function (client, form) {
|
||||
Front.prototype.initialize.call(this, client, form);
|
||||
this.actorID = form.perfActor;
|
||||
this.manage(this);
|
||||
}
|
||||
});
|
|
@ -132,6 +132,11 @@ const Types = exports.__TypesForTests = [
|
|||
spec: "devtools/shared/specs/node",
|
||||
front: "devtools/shared/fronts/node",
|
||||
},
|
||||
{
|
||||
types: ["perf"],
|
||||
spec: "devtools/shared/specs/perf",
|
||||
front: "devtools/shared/fronts/perf",
|
||||
},
|
||||
{
|
||||
types: ["performance"],
|
||||
spec: "devtools/shared/specs/performance",
|
||||
|
|
|
@ -28,6 +28,7 @@ DevToolsModules(
|
|||
'layout.js',
|
||||
'memory.js',
|
||||
'node.js',
|
||||
'perf.js',
|
||||
'performance-recording.js',
|
||||
'performance.js',
|
||||
'preference.js',
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* 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 { RetVal, generateActorSpec } = require("devtools/shared/protocol");
|
||||
|
||||
const perfDescription = {
|
||||
typeName: "perf",
|
||||
|
||||
events: {
|
||||
"profiler-started": {
|
||||
type: "profiler-started"
|
||||
},
|
||||
"profiler-stopped": {
|
||||
type: "profiler-stopped"
|
||||
},
|
||||
"profile-locked-by-private-browsing": {
|
||||
type: "profile-locked-by-private-browsing"
|
||||
},
|
||||
"profile-unlocked-from-private-browsing": {
|
||||
type: "profile-unlocked-from-private-browsing"
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
startProfiler: {
|
||||
request: {},
|
||||
response: { value: RetVal("boolean") }
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns null when unable to return the profile.
|
||||
*/
|
||||
getProfileAndStopProfiler: {
|
||||
request: {},
|
||||
response: RetVal("nullable:json")
|
||||
},
|
||||
|
||||
stopProfilerAndDiscardProfile: {
|
||||
request: {},
|
||||
response: {}
|
||||
},
|
||||
|
||||
isActive: {
|
||||
request: {},
|
||||
response: { value: RetVal("boolean") }
|
||||
},
|
||||
|
||||
isSupportedPlatform: {
|
||||
request: {},
|
||||
response: { value: RetVal("boolean") }
|
||||
},
|
||||
|
||||
isLockedForPrivateBrowsing: {
|
||||
request: {},
|
||||
response: { value: RetVal("boolean") }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.perfDescription = perfDescription;
|
||||
|
||||
const perfSpec = generateActorSpec(perfDescription);
|
||||
|
||||
exports.perfSpec = perfSpec;
|
Загрузка…
Ссылка в новой задаче