Commit 2ade1a1b authored by Greg Tatum's avatar Greg Tatum
Browse files

Bug 1597376 - Implement the new about:profiling page; r=julienw

This patch adds a new context for the performance-new components. It's
eventually the only place where the settings will live, but for now
the components share the settings between the popup, the devtools panel,
and the about:profiling page.

This page uses the base styling that the preferences page uses. The styles
should obey the theming for the browser, and ignore the devtools theming
colors.

Differential Revision: https://phabricator.services.mozilla.com/D55010

--HG--
extra : moz-landing-system : lando
parent c29911fd
......@@ -72,7 +72,8 @@ static const RedirEntry kRedirMap[] = {
#if !defined(ANDROID) && defined(NIGHTLY_BUILD)
// about:profiling is Nightly-only while it is in active development. Once
// the feature matures, it will be released across all desktop channels.
{"profiling", "chrome://devtools/content/performance-new/popup/popup.xhtml",
{"profiling",
"chrome://devtools/content/performance-new/aboutprofiling/index.xhtml",
nsIAboutModule::ALLOW_SCRIPT},
#endif
{"rights", "chrome://global/content/aboutRights.xhtml",
......
......@@ -27,6 +27,8 @@ devtools.jar:
content/performance-new/frame-script.js (performance-new/frame-script.js)
content/performance-new/popup/initializer.js (performance-new/popup/initializer.js)
content/performance-new/popup/popup.xhtml (performance-new/popup/popup.xhtml)
content/performance-new/aboutprofiling/initializer.js (performance-new/aboutprofiling/initializer.js)
content/performance-new/aboutprofiling/index.xhtml (performance-new/aboutprofiling/index.xhtml)
content/memory/index.xhtml (memory/index.xhtml)
content/framework/toolbox-window.xhtml (framework/toolbox-window.xhtml)
content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)
......@@ -128,6 +130,7 @@ devtools.jar:
skin/images/webconsole/run.svg (themes/images/webconsole/run.svg)
skin/images/breadcrumbs-scrollbutton.svg (themes/images/breadcrumbs-scrollbutton.svg)
skin/animation.css (themes/animation.css)
skin/aboutprofiling.css (themes/aboutprofiling.css)
skin/perf.css (themes/perf.css)
skin/performance.css (themes/performance.css)
skin/memory.css (themes/memory.css)
......
<!-- 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>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src chrome: resource:; img-src chrome: resource:; script-src chrome: resource:" />
<link rel="icon" type="image/png" href="chrome://devtools/skin/images/profiler-stopwatch.svg" />
<link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/aboutprofiling.css" type="text/css"/>
</head>
<body class="theme-body">
<div id="root"></div>
<script src="chrome://devtools/content/performance-new/aboutprofiling/initializer.js"></script>
</body>
</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/. */
// @ts-check
/**
* @typedef {import("../@types/perf").InitializeStoreValues} InitializeStoreValues
* @typedef {import("../@types/perf").PopupWindow} PopupWindow
*/
"use strict";
/**
* This file initializes the about:profiling page, which can be used to tweak the
* profiler's settings.
*/
{
// Create the browser loader, but take care not to conflict with
// TypeScript. See devtools/client/performance-new/typescript.md and
// the section on "Do not overload require" for more information.
const { BrowserLoader } = ChromeUtils.import(
"resource://devtools/client/shared/browser-loader.js"
);
const browserLoader = BrowserLoader({
baseURI: "resource://devtools/client/performance-new/aboutprofiling",
window,
});
/**
* @type {any} - Coerce the current scope into an `any`, and assign the
* loaders to the scope. They can then be used freely below.
*/
const scope = this;
scope.require = browserLoader.require;
scope.loader = browserLoader.loader;
}
/**
* The background.jsm.js manages the profiler state, and can be loaded multiple time
* for various components. This page needs a copy, and it is also used by the
* profiler shortcuts. In order to do this, the background code needs to live in a
* JSM module, that can be shared with the DevTools keyboard shortcut manager.
*/
const {
getRecordingPreferencesFromBrowser,
setRecordingPreferencesOnBrowser,
getSymbolsFromThisBrowser,
} = ChromeUtils.import(
"resource://devtools/client/performance-new/popup/background.jsm.js"
);
const { receiveProfile } = require("devtools/client/performance-new/browser");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const React = require("devtools/client/shared/vendor/react");
const AboutProfiling = React.createFactory(
require("devtools/client/performance-new/components/AboutProfiling")
);
const ProfilerEventHandling = React.createFactory(
require("devtools/client/performance-new/components/ProfilerEventHandling")
);
const createStore = require("devtools/client/shared/redux/create-store");
const reducers = require("devtools/client/performance-new/store/reducers");
const actions = require("devtools/client/performance-new/store/actions");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const {
ActorReadyGeckoProfilerInterface,
} = require("devtools/shared/performance-new/gecko-profiler-interface");
/**
* Initialize the panel by creating a redux store, and render the root component.
*/
document.addEventListener("DOMContentLoaded", async () => {
const store = createStore(reducers);
const perfFrontInterface = new ActorReadyGeckoProfilerInterface();
const supportedFeatures = await perfFrontInterface.getSupportedFeatures();
// Do some initialization, especially with privileged things that are part of the
// the browser.
store.dispatch(
actions.initializeStore({
perfFront: perfFrontInterface,
receiveProfile,
supportedFeatures,
// Get the preferences from the current browser
recordingPreferences: getRecordingPreferencesFromBrowser(),
// In the popup, the preferences are stored directly on the current browser.
setRecordingPreferences: setRecordingPreferencesOnBrowser,
// The popup doesn't need to support remote symbol tables from the debuggee.
// Only get the symbols from this browser.
getSymbolTableGetter: () => getSymbolsFromThisBrowser,
pageContext: "aboutprofiling",
})
);
ReactDOM.render(
React.createElement(
Provider,
{ store },
React.createElement(
React.Fragment,
null,
ProfilerEventHandling(),
AboutProfiling()
)
),
document.querySelector("#root")
);
window.addEventListener("unload", function() {
// The perf front interface needs to be unloaded in order to remove event handlers.
// Not doing so leads to leaks.
perfFrontInterface.destroy();
});
});
# 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/.
with Files('**'):
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
/* 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/. */
// @ts-check
/**
* @template P
* @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
*/
/**
* @typedef {Object} StateProps
* @property {boolean?} isSupportedPlatform
* @property {PageContext} pageContext
* @property {string | null} promptEnvRestart
*/
/**
* @typedef {StateProps} Props
* @typedef {import("../@types/perf").State} StoreState
* @typedef {import("../@types/perf").PanelWindow} PanelWindow
*@typedef {import("../@types/perf").PageContext} PageContext
*/
"use strict";
const {
PureComponent,
createFactory,
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
div,
h1,
button,
} = require("devtools/client/shared/vendor/react-dom-factories");
const Settings = createFactory(
require("devtools/client/performance-new/components/Settings.js")
);
const selectors = require("devtools/client/performance-new/store/selectors");
const {
restartBrowserWithEnvironmentVariable,
} = require("devtools/client/performance-new/browser");
/**
* This is the top level component for the about:profiling page. It shares components
* with the popup and DevTools page.
*
* @extends {React.PureComponent<Props>}
*/
class AboutProfiling extends PureComponent {
/** @param {Props} props */
constructor(props) {
super(props);
this.handleRestart = this.handleRestart.bind(this);
}
handleRestart() {
const { promptEnvRestart } = this.props;
if (!promptEnvRestart) {
throw new Error(
"handleRestart() should only be called when promptEnvRestart exists."
);
}
restartBrowserWithEnvironmentVariable(promptEnvRestart, "1");
}
render() {
const { isSupportedPlatform, pageContext, promptEnvRestart } = this.props;
if (isSupportedPlatform === null) {
// We don't know yet if this is a supported platform, wait for a response.
return null;
}
return div(
{ className: `perf perf-${pageContext}` },
promptEnvRestart
? div(
{ className: "perf-env-restart" },
div(
{
className:
"perf-photon-message-bar perf-photon-message-bar-warning perf-env-restart-fixed",
},
div({ className: "perf-photon-message-bar-warning-icon" }),
"The browser must be restarted to enable this feature.",
button(
{
className: "perf-photon-button perf-photon-button-micro",
type: "button",
onClick: this.handleRestart,
},
"Restart"
)
)
)
: null,
div(
{ className: "perf-intro" },
h1({ className: "perf-intro-title" }, "Profiler Settings"),
div(
{ className: "perf-intro-row" },
div({}, div({ className: "perf-intro-icon" })),
div(
{ className: "perf-intro-text" },
"Recordings launch profiler.firefox.com in a new tab. All data is stored " +
"locally, but you can choose to upload it for sharing."
)
)
),
// The presets aren't built yet, but this div serves as a place to put the
// visual divider that is in the UX mock-up.
div({ className: "perf-presets" }),
Settings()
);
}
}
/**
* @param {StoreState} state
* @returns {StateProps}
*/
function mapStateToProps(state) {
return {
isSupportedPlatform: selectors.getIsSupportedPlatform(state),
pageContext: selectors.getPageContext(state),
promptEnvRestart: selectors.getPromptEnvRestart(state),
};
}
module.exports = connect(mapStateToProps)(AboutProfiling);
......@@ -408,58 +408,6 @@ class Settings extends PureComponent {
this.props.changeThreads(_threadTextToList(event.target.value));
}
/**
* about:profiling doesn't need to collapse the children into details/summary,
* but the popup and devtools do (for now).
*
* @param {string} id
* @param {React.ReactNode} title
* @param {React.ReactNode} children
* @returns React.ReactNode
*/
_renderSection(id, title, children) {
const { pageContext } = this.props;
switch (pageContext) {
case "popup":
case "devtools":
// Render the section with a dropdown summary.
return details(
{
className: "perf-settings-details",
// @ts-ignore - The React type definitions don't know about onToggle.
onToggle: _handleToggle,
},
summary(
{
className: "perf-settings-summary",
id,
},
// Concatenating strings like this isn't very localizable, but it should go
// away by the time we localize these components.
title + ":"
),
// Contain the overflow of the slide down animation with the first div.
div(
{ className: "perf-settings-details-contents" },
// Provide a second <div> element for the contents of the slide down
// animation.
div(
{ className: "perf-settings-details-contents-slider" },
children
)
)
);
case "aboutprofiling":
// Render the section without a dropdown summary.
return div(
{ className: "perf-settings-sections" },
div(null, h2(null, title), children)
);
default:
throw new UnhandledCaseError(pageContext, "PageContext");
}
}
/**
* @param {ThreadColumn[]} threadDisplay
* @param {number} index
......@@ -472,7 +420,8 @@ class Settings extends PureComponent {
threadDisplay.map(({ name, title, id }) =>
label(
{
className: "perf-settings-checkbox-label",
className:
"perf-settings-checkbox-label perf-settings-thread-label",
key: name,
title,
},
......@@ -492,9 +441,12 @@ class Settings extends PureComponent {
_renderThreads() {
const { temporaryThreadText } = this.state;
return this._renderSection(
const { pageContext, threads } = this.props;
return renderSection(
"perf-settings-threads-summary",
"Threads",
pageContext,
div(
null,
div(
......@@ -519,7 +471,7 @@ class Settings extends PureComponent {
type: "text",
value:
temporaryThreadText === null
? this.props.threads.join(",")
? threads.join(",")
: temporaryThreadText,
onBlur: this._handleThreadTextCleanup,
onFocus: this._setThreadTextFromInput,
......@@ -590,9 +542,10 @@ class Settings extends PureComponent {
}
_renderFeatures() {
return this._renderSection(
return renderSection(
"perf-settings-features-summary",
"Features",
this.props.pageContext,
div(
null,
// Render the supported features first.
......@@ -612,10 +565,11 @@ class Settings extends PureComponent {
}
_renderLocalBuildSection() {
const { objdirs } = this.props;
return this._renderSection(
const { objdirs, pageContext } = this.props;
return renderSection(
"perf-settings-local-build-summary",
"Local build",
pageContext,
div(
null,
p(
......@@ -737,6 +691,55 @@ function _handleToggle() {
}
}
/**
* about:profiling doesn't need to collapse the children into details/summary,
* but the popup and devtools do (for now).
*
* @param {string} id
* @param {React.ReactNode} title
* @param {PageContext} pageContext
* @param {React.ReactNode} children
* @returns React.ReactNode
*/
function renderSection(id, title, pageContext, children) {
switch (pageContext) {
case "popup":
case "devtools":
// Render the section with a dropdown summary.
return details(
{
className: "perf-settings-details",
// @ts-ignore - The React type definitions don't know about onToggle.
onToggle: _handleToggle,
},
summary(
{
className: "perf-settings-summary",
id,
},
// Concatenating strings like this isn't very localizable, but it should go
// away by the time we localize these components.
title + ":"
),
// Contain the overflow of the slide down animation with the first div.
div(
{ className: "perf-settings-details-contents" },
// Provide a second <div> element for the contents of the slide down
// animation.
div({ className: "perf-settings-details-contents-slider" }, children)
)
);
case "aboutprofiling":
// Render the section without a dropdown summary.
return div(
{ className: "perf-settings-sections" },
div(null, h2(null, title), children)
);
default:
throw new UnhandledCaseError(pageContext, "PageContext");
}
}
/**
* @param {StoreState} state
* @returns {StateProps}
......
......@@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'AboutProfiling.js',
'Description.js',
'DevToolsAndPopup.js',
'DirectoryPicker.js',
......
......@@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'aboutprofiling',
'components',
'store',
'popup',
......
......@@ -9,4 +9,4 @@ DevToolsModules(
)
with Files('**'):
BUG_COMPONENT = ('Core', 'Gecko Profiler')
BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
/* 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 {
max-width: 670px;
margin: 10vh auto;
padding: 0 5%;
}
/* This styling was originally taken from the about:preferences page. */
h1 {
margin: 0 0 8px;
font-size: 1.46em;
font-weight: 300;
line-height: 1.3em;
}
/* This styling was originally taken from the about:preferences page. */
h2 {
/* This margin was enlarged */
margin: 30px 0 15px;
font-size: 1.14em;
line-height: normal;
}
.perf-intro-row {
margin: 1.7em 0;
display: flex;
line-height: 1.8em;
align-items: center;
}
.perf-intro-icon {
width: 4em;
height: 4em;
margin-inline-end: 1.3em;
background-image: url(chrome://devtools/skin/images/tool-profiler.svg);
background-size: 100%;
-moz-context-properties: fill;
fill: currentColor;
opacity: 0.5;
}
.perf-presets {
margin: 2em 0;
border-bottom: 1px solid var(--in-content-border-color);
}
.perf-settings-row {
display: flex;
line-height: 1.8;
}
.perf-settings-label {
height: 30px;
min-width: 140px;
}
.perf-settings-value {
display: flex;
flex: 1;
}
.perf-settings-range-input {
flex: 1;
}
.perf-settings-range-input-el {
width: 100%;
}
.perf-settings-range-value {
min-width: 90px;
text-align: end;
}
.perf-settings-thread-columns {
margin-bottom: 20px;
display: flex;
line-height: 1.3;
}
.perf-settings-thread-column {
flex: 1;
}
.perf-settings-feature-label {
display: block;
}
.perf-settings-thread-label {
display: flex;
margin: 15px 0;
}
.perf-settings-text-label {
flex: 1;
}
.perf-settings-text-input {