From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../components/panel/Description.js | 63 +++++ .../components/panel/DevToolsPanel.js | 108 +++++++++ .../components/panel/DevToolsPresetSelection.js | 219 +++++++++++++++++ .../components/panel/OnboardingMessage.js | 138 +++++++++++ .../components/panel/ProfilerEventHandling.js | 128 ++++++++++ .../components/panel/RecordingButton.js | 265 +++++++++++++++++++++ .../components/panel/ToolboxHighlightController.js | 61 +++++ .../performance-new/components/panel/moz.build | 14 ++ 8 files changed, 996 insertions(+) create mode 100644 devtools/client/performance-new/components/panel/Description.js create mode 100644 devtools/client/performance-new/components/panel/DevToolsPanel.js create mode 100644 devtools/client/performance-new/components/panel/DevToolsPresetSelection.js create mode 100644 devtools/client/performance-new/components/panel/OnboardingMessage.js create mode 100644 devtools/client/performance-new/components/panel/ProfilerEventHandling.js create mode 100644 devtools/client/performance-new/components/panel/RecordingButton.js create mode 100644 devtools/client/performance-new/components/panel/ToolboxHighlightController.js create mode 100644 devtools/client/performance-new/components/panel/moz.build (limited to 'devtools/client/performance-new/components/panel') 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} + */ +class Description extends PureComponent { + /** + * @param {React.MouseEvent} 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

} ResolveThunks

+ */ + +/** + * @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} + */ +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

} ResolveThunks

+ */ + +/** + * @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} 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} + */ +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} 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} + */ +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

} ResolveThunks

+ */ + +/** + * @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} 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} + */ +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

} ResolveThunks

+ */ + +/** + * @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} 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} + */ +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} + */ +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", +) -- cgit v1.2.3