summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance-new/components/panel
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/performance-new/components/panel
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/performance-new/components/panel')
-rw-r--r--devtools/client/performance-new/components/panel/Description.js63
-rw-r--r--devtools/client/performance-new/components/panel/DevToolsPanel.js108
-rw-r--r--devtools/client/performance-new/components/panel/DevToolsPresetSelection.js219
-rw-r--r--devtools/client/performance-new/components/panel/OnboardingMessage.js138
-rw-r--r--devtools/client/performance-new/components/panel/ProfilerEventHandling.js128
-rw-r--r--devtools/client/performance-new/components/panel/RecordingButton.js265
-rw-r--r--devtools/client/performance-new/components/panel/ToolboxHighlightController.js61
-rw-r--r--devtools/client/performance-new/components/panel/moz.build14
8 files changed, 996 insertions, 0 deletions
diff --git a/devtools/client/performance-new/components/panel/Description.js b/devtools/client/performance-new/components/panel/Description.js
new file mode 100644
index 0000000000..f2fb3620f0
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/Description.js
@@ -0,0 +1,63 @@
+/* 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 {{}} Props - This is an empty object.
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ button,
+ p,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+/**
+ * This component provides a helpful description for what is going on in the component
+ * and provides some external links.
+ * @extends {React.PureComponent<Props>}
+ */
+class Description extends PureComponent {
+ /**
+ * @param {React.MouseEvent<HTMLButtonElement>} event
+ */
+ handleLinkClick = event => {
+ const {
+ openDocLink,
+ } = require("resource://devtools/client/shared/link.js");
+
+ /** @type HTMLButtonElement */
+ const target = /** @type {any} */ (event.target);
+
+ openDocLink(target.value, {});
+ };
+
+ render() {
+ return div(
+ { className: "perf-description" },
+ Localized(
+ {
+ id: "perftools-description-intro",
+ a: button({
+ className: "perf-external-link",
+ onClick: this.handleLinkClick,
+ value: "https://profiler.firefox.com",
+ }),
+ },
+ p({})
+ )
+ );
+ }
+}
+
+module.exports = Description;
diff --git a/devtools/client/performance-new/components/panel/DevToolsPanel.js b/devtools/client/performance-new/components/panel/DevToolsPanel.js
new file mode 100644
index 0000000000..8f1b8fb344
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/DevToolsPanel.js
@@ -0,0 +1,108 @@
+/* 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
+ */
+
+/**
+ * @typedef {Object} OwnProps
+ * @property {import("../../@types/perf").PerfFront} perfFront
+ * @property {import("../../@types/perf").OnProfileReceived} onProfileReceived
+ * @property {() => void} onEditSettingsLinkClicked
+ */
+
+/**
+ * @typedef {StateProps & OwnProps} Props
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").RecordingState} RecordingState
+ * @typedef {import("../../@types/perf").PanelWindow} PanelWindow
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const {
+ div,
+ hr,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const RecordingButton = createFactory(
+ require("resource://devtools/client/performance-new/components/panel/RecordingButton.js")
+);
+const Description = createFactory(
+ require("resource://devtools/client/performance-new/components/panel/Description.js")
+);
+const DevToolsPresetSelection = createFactory(
+ require("resource://devtools/client/performance-new/components/panel/DevToolsPresetSelection.js")
+);
+const OnboardingMessage = createFactory(
+ require("resource://devtools/client/performance-new/components/panel/OnboardingMessage.js")
+);
+const ToolboxHighlightController = createFactory(
+ require("resource://devtools/client/performance-new/components/panel/ToolboxHighlightController.js")
+);
+
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+const anyWindow = /** @type {any} */ (window);
+const panelWindow = /** @type {PanelWindow} */ (anyWindow);
+
+/**
+ * This is the top level component for the DevTools panel.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class DevToolsPanel extends PureComponent {
+ render() {
+ const {
+ isSupportedPlatform,
+ perfFront,
+ onProfileReceived,
+ onEditSettingsLinkClicked,
+ } = this.props;
+
+ if (isSupportedPlatform === null) {
+ // We don't know yet if this is a supported platform, wait for a response.
+ return null;
+ }
+
+ return [
+ OnboardingMessage(),
+ div(
+ { className: `perf perf-devtools` },
+ RecordingButton({ perfFront, onProfileReceived }),
+ panelWindow.gToolbox
+ ? ToolboxHighlightController({ toolbox: panelWindow.gToolbox })
+ : null,
+ Description(),
+ hr({ className: "perf-presets-hr" }),
+ DevToolsPresetSelection({ onEditSettingsLinkClicked })
+ ),
+ ];
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ isSupportedPlatform: selectors.getIsSupportedPlatform(state),
+ };
+}
+
+module.exports = connect(mapStateToProps)(DevToolsPanel);
diff --git a/devtools/client/performance-new/components/panel/DevToolsPresetSelection.js b/devtools/client/performance-new/components/panel/DevToolsPresetSelection.js
new file mode 100644
index 0000000000..8b6cd44727
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/DevToolsPresetSelection.js
@@ -0,0 +1,219 @@
+/* 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 {string} presetName
+ * @property {number} interval
+ * @property {string[]} threads
+ * @property {string[]} features
+ * @property {import("../../@types/perf").Presets} presets
+ */
+
+/**
+ * @typedef {Object} ThunkDispatchProps
+ * @property {typeof actions.changePreset} changePreset
+ */
+
+/**
+ * @typedef {Object} OwnProps
+ * @property {() => void} onEditSettingsLinkClicked
+ */
+
+/**
+ * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
+ * @typedef {StateProps & DispatchProps & OwnProps} Props
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").FeatureDescription} FeatureDescription
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ select,
+ option,
+ button,
+ ul,
+ li,
+ span,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const actions = require("resource://devtools/client/performance-new/store/actions.js");
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+const {
+ featureDescriptions,
+} = require("resource://devtools/client/performance-new/shared/utils.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+/**
+ * This component displays the preset selection for the DevTools panel. It should be
+ * basically the same implementation as the popup, but done in React. The popup
+ * is written using vanilla JS and browser chrome elements in order to be more
+ * performant.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class DevToolsPresetSelection extends PureComponent {
+ /** @param {Props} props */
+ constructor(props) {
+ super(props);
+
+ /**
+ * Create an object map to easily look up feature description.
+ * @type {{[key: string]: FeatureDescription}}
+ */
+ this.featureDescriptionMap = {};
+ for (const feature of featureDescriptions) {
+ this.featureDescriptionMap[feature.value] = feature;
+ }
+ }
+
+ /**
+ * Handle the select change.
+ * @param {React.ChangeEvent<HTMLSelectElement>} event
+ */
+ onPresetChange = event => {
+ const { presets } = this.props;
+ this.props.changePreset(presets, event.target.value);
+ };
+
+ render() {
+ const { presetName, presets, onEditSettingsLinkClicked } = this.props;
+
+ let presetDescription;
+ const currentPreset = presets[presetName];
+ if (currentPreset) {
+ // Display the current preset's description.
+ presetDescription = Localized({
+ id: currentPreset.l10nIds.devtools.description,
+ });
+ } else {
+ // Build up a display of the details of the custom preset.
+ const { interval, threads, features } = this.props;
+ presetDescription = div(
+ null,
+ ul(
+ { className: "perf-presets-custom" },
+ li(
+ null,
+ Localized(
+ { id: "perftools-devtools-interval-label" },
+ span({ className: "perf-presets-custom-bold" })
+ ),
+ " ",
+ Localized({
+ id: "perftools-range-interval-milliseconds",
+ $interval: interval,
+ })
+ ),
+ li(
+ null,
+ Localized(
+ { id: "perftools-devtools-threads-label" },
+ span({ className: "perf-presets-custom-bold" })
+ ),
+ " ",
+ threads.join(", ")
+ ),
+ features.map(feature => {
+ const description = this.featureDescriptionMap[feature];
+ if (!description) {
+ throw new Error(
+ "Could not find the feature description for " + feature
+ );
+ }
+ return li(
+ { key: feature },
+ description ? description.name : feature
+ );
+ })
+ )
+ );
+ }
+
+ return div(
+ { className: "perf-presets" },
+ div(
+ { className: "perf-presets-settings" },
+ Localized({ id: "perftools-devtools-settings-label" })
+ ),
+ div(
+ { className: "perf-presets-details" },
+ div(
+ { className: "perf-presets-details-row" },
+ select(
+ {
+ className: "perf-presets-select",
+ onChange: this.onPresetChange,
+ value: presetName,
+ },
+ Object.entries(presets).map(([name, preset]) =>
+ Localized(
+ { id: preset.l10nIds.devtools.label },
+ option({ key: name, value: name })
+ )
+ ),
+ Localized(
+ { id: "perftools-presets-custom-label" },
+ option({ value: "custom" })
+ )
+ )
+ // The overhead component will go here.
+ ),
+ div(
+ { className: "perf-presets-details-row perf-presets-description" },
+ presetDescription
+ ),
+ button(
+ {
+ className: "perf-external-link",
+ onClick: onEditSettingsLinkClicked,
+ },
+ Localized({ id: "perftools-button-edit-settings" })
+ )
+ )
+ );
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ presetName: selectors.getPresetName(state),
+ presets: selectors.getPresets(state),
+ interval: selectors.getInterval(state),
+ threads: selectors.getThreads(state),
+ features: selectors.getFeatures(state),
+ };
+}
+
+/**
+ * @type {ThunkDispatchProps}
+ */
+const mapDispatchToProps = {
+ changePreset: actions.changePreset,
+};
+
+module.exports = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(DevToolsPresetSelection);
diff --git a/devtools/client/performance-new/components/panel/OnboardingMessage.js b/devtools/client/performance-new/components/panel/OnboardingMessage.js
new file mode 100644
index 0000000000..614215c6e6
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/OnboardingMessage.js
@@ -0,0 +1,138 @@
+/* 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 {{}} Props - This is an empty object.
+ */
+
+/**
+ * @typedef {Object} State
+ * @property {boolean} isOnboardingEnabled
+ */
+
+/**
+ * @typedef {import("../../@types/perf").PanelWindow} PanelWindow
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ b,
+ button,
+ div,
+ p,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+const { openDocLink } = require("resource://devtools/client/shared/link.js");
+
+const LEARN_MORE_URL = "https://profiler.firefox.com/docs";
+const ONBOARDING_PREF = "devtools.performance.new-panel-onboarding";
+
+/**
+ * This component provides a temporary onboarding message for users migrating
+ * from the old DevTools performance panel.
+ * @extends {React.PureComponent<Props>}
+ */
+class OnboardingMessage extends PureComponent {
+ /**
+ * @param {Props} props
+ */
+ constructor(props) {
+ super(props);
+
+ // The preference has no default value for new profiles.
+ // If it is missing, default to true to show the message by default.
+ const isOnboardingEnabled = Services.prefs.getBoolPref(
+ ONBOARDING_PREF,
+ true
+ );
+
+ /** @type {State} */
+ this.state = { isOnboardingEnabled };
+ }
+
+ componentDidMount() {
+ Services.prefs.addObserver(ONBOARDING_PREF, this.onPreferenceUpdated);
+ }
+
+ componentWillUnmount() {
+ Services.prefs.removeObserver(ONBOARDING_PREF, this.onPreferenceUpdated);
+ }
+
+ handleCloseIconClick = () => {
+ Services.prefs.setBoolPref(ONBOARDING_PREF, false);
+ };
+
+ handleLearnMoreClick = () => {
+ openDocLink(LEARN_MORE_URL, {});
+ };
+
+ /**
+ * Update the state whenever the devtools.performance.new-panel-onboarding
+ * preference is updated.
+ */
+ onPreferenceUpdated = () => {
+ const value = Services.prefs.getBoolPref(ONBOARDING_PREF, true);
+ this.setState({ isOnboardingEnabled: value });
+ };
+
+ render() {
+ const { isOnboardingEnabled } = this.state;
+ if (!isOnboardingEnabled) {
+ return null;
+ }
+
+ /** @type {any} */
+ const anyWindow = window;
+
+ // If gToolbox is not defined on window, the component is rendered in
+ // about:debugging, and no onboarding message should be displayed.
+ if (!anyWindow.gToolbox) {
+ return null;
+ }
+
+ const learnMoreLink = button({
+ className: "perf-external-link",
+ onClick: this.handleLearnMoreClick,
+ });
+
+ const closeButton = Localized(
+ {
+ id: "perftools-onboarding-close-button",
+ attrs: { "aria-label": true },
+ },
+ button({
+ className:
+ "perf-onboarding-close-button perf-photon-button perf-photon-button-ghost",
+ onClick: this.handleCloseIconClick,
+ })
+ );
+
+ return div(
+ { className: "perf-onboarding" },
+ div(
+ { className: "perf-onboarding-message" },
+ Localized(
+ {
+ id: "perftools-onboarding-message",
+ b: b(),
+ a: learnMoreLink,
+ },
+ p({ className: "perf-onboarding-message-row" })
+ )
+ ),
+ closeButton
+ );
+ }
+}
+
+module.exports = OnboardingMessage;
diff --git a/devtools/client/performance-new/components/panel/ProfilerEventHandling.js b/devtools/client/performance-new/components/panel/ProfilerEventHandling.js
new file mode 100644
index 0000000000..1b415bdf19
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/ProfilerEventHandling.js
@@ -0,0 +1,128 @@
+/* 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").PerfFront} PerfFront
+ * @typedef {import("../../@types/perf").RecordingState} RecordingState
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").RootTraits} RootTraits
+ * @typedef {import("../../@types/perf").PanelWindow} PanelWindow
+ */
+
+/**
+ * @template P
+ * @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
+ */
+
+/**
+ * @typedef {Object} StateProps
+ * @property {RecordingState} recordingState
+ * @property {boolean?} isSupportedPlatform
+ */
+
+/**
+ * @typedef {Object} ThunkDispatchProps
+ * @property {typeof actions.reportProfilerReady} reportProfilerReady
+ * @property {typeof actions.reportProfilerStarted} reportProfilerStarted
+ * @property {typeof actions.reportProfilerStopped} reportProfilerStopped
+ */
+
+/**
+ * @typedef {Object} OwnProps
+ * @property {PerfFront} perfFront
+ * @property {RootTraits} traits
+ */
+
+/**
+ * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
+ * @typedef {StateProps & DispatchProps & OwnProps} Props
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const actions = require("resource://devtools/client/performance-new/store/actions.js");
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+
+/**
+ * This component state changes for the performance recording. e.g. If the profiler
+ * suddenly becomes unavailable, it needs to react to those changes, and update the
+ * recordingState in the store.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class ProfilerEventHandling extends PureComponent {
+ componentDidMount() {
+ const {
+ perfFront,
+ isSupportedPlatform,
+ reportProfilerReady,
+ reportProfilerStarted,
+ reportProfilerStopped,
+ } = this.props;
+
+ if (!isSupportedPlatform) {
+ return;
+ }
+
+ // Ask for the initial state of the profiler.
+ perfFront.isActive().then(isActive => reportProfilerReady(isActive));
+
+ // Handle when the profiler changes state. It might be us, it might be someone else.
+ this.props.perfFront.on("profiler-started", reportProfilerStarted);
+ this.props.perfFront.on("profiler-stopped", reportProfilerStopped);
+ }
+
+ componentWillUnmount() {
+ switch (this.props.recordingState) {
+ case "not-yet-known":
+ case "available-to-record":
+ case "request-to-stop-profiler":
+ case "request-to-get-profile-and-stop-profiler":
+ // 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.");
+ }
+ }
+
+ render() {
+ return null;
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ recordingState: selectors.getRecordingState(state),
+ isSupportedPlatform: selectors.getIsSupportedPlatform(state),
+ };
+}
+
+/** @type {ThunkDispatchProps} */
+const mapDispatchToProps = {
+ reportProfilerReady: actions.reportProfilerReady,
+ reportProfilerStarted: actions.reportProfilerStarted,
+ reportProfilerStopped: actions.reportProfilerStopped,
+};
+
+module.exports = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ProfilerEventHandling);
diff --git a/devtools/client/performance-new/components/panel/RecordingButton.js b/devtools/client/performance-new/components/panel/RecordingButton.js
new file mode 100644
index 0000000000..352468cbd1
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/RecordingButton.js
@@ -0,0 +1,265 @@
+/* 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 {RecordingState} recordingState
+ * @property {boolean | null} isSupportedPlatform
+ * @property {boolean} recordingUnexpectedlyStopped
+ * @property {PageContext} pageContext
+ */
+
+/**
+ * @typedef {Object} OwnProps
+ * @property {import("../../@types/perf").OnProfileReceived} onProfileReceived
+ * @property {import("../../@types/perf").PerfFront} perfFront
+ */
+
+/**
+ * @typedef {Object} ThunkDispatchProps
+ * @property {typeof actions.startRecording} startRecording
+ * @property {typeof actions.getProfileAndStopProfiler} getProfileAndStopProfiler
+ * @property {typeof actions.stopProfilerAndDiscardProfile} stopProfilerAndDiscardProfile
+
+ */
+
+/**
+ * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
+ * @typedef {StateProps & DispatchProps & OwnProps} Props
+ * @typedef {import("../../@types/perf").RecordingState} RecordingState
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").PageContext} PageContext
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ button,
+ span,
+ img,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const actions = require("resource://devtools/client/performance-new/store/actions.js");
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+const React = require("resource://devtools/client/shared/vendor/react.js");
+const Localized = React.createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+/**
+ * This component is not responsible for the full life cycle of recording a profile. It
+ * is only responsible for the actual act of stopping and starting recordings. It
+ * also reacts to the changes of the recording state from external changes.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class RecordingButton extends PureComponent {
+ _onStartButtonClick = () => {
+ const { startRecording, perfFront } = this.props;
+ startRecording(perfFront);
+ };
+
+ _onCaptureButtonClick = async () => {
+ const { getProfileAndStopProfiler, onProfileReceived, perfFront } =
+ this.props;
+ const profile = await getProfileAndStopProfiler(perfFront);
+ onProfileReceived(profile);
+ };
+
+ _onStopButtonClick = () => {
+ const { stopProfilerAndDiscardProfile, perfFront } = this.props;
+ stopProfilerAndDiscardProfile(perfFront);
+ };
+
+ render() {
+ const {
+ recordingState,
+ isSupportedPlatform,
+ recordingUnexpectedlyStopped,
+ } = this.props;
+
+ if (!isSupportedPlatform) {
+ return renderButton({
+ label: startRecordingLabel(),
+ isPrimary: true,
+ disabled: true,
+ additionalMessage:
+ // No need to localize as this string is not displayed to Tier-1 platforms.
+ "Your platform is not supported. The Gecko Profiler only " +
+ "supports Tier-1 platforms.",
+ });
+ }
+
+ switch (recordingState) {
+ case "not-yet-known":
+ return null;
+
+ case "available-to-record":
+ return renderButton({
+ onClick: this._onStartButtonClick,
+ isPrimary: true,
+ label: startRecordingLabel(),
+ additionalMessage: recordingUnexpectedlyStopped
+ ? Localized(
+ { id: "perftools-status-recording-stopped-by-another-tool" },
+ div(null, "The recording was stopped by another tool.")
+ )
+ : null,
+ });
+
+ case "request-to-stop-profiler":
+ return renderButton({
+ label: Localized(
+ { id: "perftools-request-to-stop-profiler" },
+ "Stopping recording"
+ ),
+ disabled: true,
+ });
+
+ case "request-to-get-profile-and-stop-profiler":
+ return renderButton({
+ label: Localized(
+ { id: "perftools-request-to-get-profile-and-stop-profiler" },
+ "Capturing profile"
+ ),
+ disabled: true,
+ });
+
+ case "request-to-start-recording":
+ case "recording":
+ return renderButton({
+ label: span(
+ null,
+ Localized(
+ { id: "perftools-button-capture-recording" },
+ "Capture recording"
+ ),
+ img({
+ className: "perf-button-image",
+ alt: "",
+ /* This icon is actually the "open in new page" icon. */
+ src: "chrome://devtools/skin/images/dock-undock.svg",
+ })
+ ),
+ isPrimary: true,
+ onClick: this._onCaptureButtonClick,
+ disabled: recordingState === "request-to-start-recording",
+ additionalButton: {
+ label: Localized(
+ { id: "perftools-button-cancel-recording" },
+ "Cancel recording"
+ ),
+ onClick: this._onStopButtonClick,
+ },
+ });
+
+ default:
+ throw new Error("Unhandled recording state");
+ }
+ }
+}
+
+/**
+ * @param {{
+ * disabled?: boolean,
+ * label?: React.ReactNode,
+ * onClick?: any,
+ * additionalMessage?: React.ReactNode,
+ * isPrimary?: boolean,
+ * pageContext?: PageContext,
+ * additionalButton?: {
+ * label: React.ReactNode,
+ * onClick: any,
+ * },
+ * }} buttonSettings
+ */
+function renderButton(buttonSettings) {
+ const {
+ disabled,
+ label,
+ onClick,
+ additionalMessage,
+ isPrimary,
+ // pageContext,
+ additionalButton,
+ } = buttonSettings;
+
+ const buttonClass = isPrimary ? "primary" : "default";
+
+ return div(
+ { className: "perf-button-container" },
+ div(
+ null,
+ button(
+ {
+ className: `perf-photon-button perf-photon-button-${buttonClass} perf-button`,
+ disabled,
+ onClick,
+ },
+ label
+ ),
+ additionalButton
+ ? button(
+ {
+ className: `perf-photon-button perf-photon-button-default perf-button`,
+ onClick: additionalButton.onClick,
+ disabled,
+ },
+ additionalButton.label
+ )
+ : null
+ ),
+ additionalMessage
+ ? div({ className: "perf-additional-message" }, additionalMessage)
+ : null
+ );
+}
+
+function startRecordingLabel() {
+ return span(
+ null,
+ Localized({ id: "perftools-button-start-recording" }, "Start recording"),
+ img({
+ className: "perf-button-image",
+ alt: "",
+ /* This icon is actually the "open in new page" icon. */
+ src: "chrome://devtools/skin/images/dock-undock.svg",
+ })
+ );
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ recordingState: selectors.getRecordingState(state),
+ isSupportedPlatform: selectors.getIsSupportedPlatform(state),
+ recordingUnexpectedlyStopped:
+ selectors.getRecordingUnexpectedlyStopped(state),
+ pageContext: selectors.getPageContext(state),
+ };
+}
+
+/** @type {ThunkDispatchProps} */
+const mapDispatchToProps = {
+ startRecording: actions.startRecording,
+ stopProfilerAndDiscardProfile: actions.stopProfilerAndDiscardProfile,
+ getProfileAndStopProfiler: actions.getProfileAndStopProfiler,
+};
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(RecordingButton);
diff --git a/devtools/client/performance-new/components/panel/ToolboxHighlightController.js b/devtools/client/performance-new/components/panel/ToolboxHighlightController.js
new file mode 100644
index 0000000000..9d9d594aa0
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/ToolboxHighlightController.js
@@ -0,0 +1,61 @@
+/* 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 {Object} StateProps
+ * @property {RecordingState} recordingState
+ */
+
+/**
+ * @typedef {Object} OwnProps
+ * @property {any} toolbox
+ */
+
+/**
+ * @typedef {StateProps & OwnProps} Props
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").RecordingState} RecordingState
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+
+/**
+ * @extends {React.PureComponent<Props>}
+ */
+class ToolboxHighlightController extends PureComponent {
+ /** @param {Props} prevProps */
+ componentDidUpdate(prevProps) {
+ const { recordingState, toolbox } = this.props;
+ if (recordingState === "recording") {
+ toolbox.highlightTool("performance");
+ } else if (prevProps.recordingState === "recording") {
+ toolbox.unhighlightTool("performance");
+ }
+ }
+
+ render() {
+ return null;
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ recordingState: selectors.getRecordingState(state),
+ };
+}
+
+module.exports = connect(mapStateToProps)(ToolboxHighlightController);
diff --git a/devtools/client/performance-new/components/panel/moz.build b/devtools/client/performance-new/components/panel/moz.build
new file mode 100644
index 0000000000..4063f5d333
--- /dev/null
+++ b/devtools/client/performance-new/components/panel/moz.build
@@ -0,0 +1,14 @@
+# 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(
+ "Description.js",
+ "DevToolsPanel.js",
+ "DevToolsPresetSelection.js",
+ "OnboardingMessage.js",
+ "ProfilerEventHandling.js",
+ "RecordingButton.js",
+ "ToolboxHighlightController.js",
+)