/* 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);