tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RecordingButton.js (7839B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 // @ts-check
      5 
      6 /**
      7 * @template P
      8 * @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
      9 */
     10 
     11 /**
     12 * @typedef {object} StateProps
     13 * @property {RecordingState} recordingState
     14 * @property {boolean | null} isSupportedPlatform
     15 * @property {boolean} recordingUnexpectedlyStopped
     16 * @property {PageContext} pageContext
     17 */
     18 
     19 /**
     20 * @typedef {object} OwnProps
     21 * @property {import("../../@types/perf").OnProfileReceived} onProfileReceived
     22 * @property {import("../../@types/perf").PerfFront} perfFront
     23 */
     24 
     25 /**
     26 * @typedef {object} ThunkDispatchProps
     27 * @property {typeof actions.startRecording} startRecording
     28 * @property {typeof actions.getProfileAndStopProfiler} getProfileAndStopProfiler
     29 * @property {typeof actions.stopProfilerAndDiscardProfile} stopProfilerAndDiscardProfile
     30 */
     31 
     32 /**
     33 * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
     34 * @typedef {StateProps & DispatchProps & OwnProps} Props
     35 * @typedef {import("../../@types/perf").RecordingState} RecordingState
     36 * @typedef {import("../../@types/perf").State} StoreState
     37 * @typedef {import("../../@types/perf").PageContext} PageContext
     38 */
     39 
     40 "use strict";
     41 
     42 const {
     43  createFactory,
     44  PureComponent,
     45 } = require("resource://devtools/client/shared/vendor/react.mjs");
     46 const {
     47  div,
     48  button,
     49  span,
     50  img,
     51 } = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     52 const {
     53  connect,
     54 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     55 const actions = require("resource://devtools/client/performance-new/store/actions.js");
     56 const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
     57 const Localized = createFactory(
     58  require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
     59 );
     60 
     61 /**
     62 * This component is not responsible for the full life cycle of recording a profile. It
     63 * is only responsible for the actual act of stopping and starting recordings. It
     64 * also reacts to the changes of the recording state from external changes.
     65 *
     66 * @augments {React.PureComponent<Props>}
     67 */
     68 class RecordingButton extends PureComponent {
     69  _onStartButtonClick = () => {
     70    const { startRecording, perfFront } = this.props;
     71    startRecording(perfFront);
     72  };
     73 
     74  _onCaptureButtonClick = async () => {
     75    const { getProfileAndStopProfiler, onProfileReceived, perfFront } =
     76      this.props;
     77    try {
     78      const profileAndAdditionalInformation =
     79        await getProfileAndStopProfiler(perfFront);
     80      onProfileReceived(profileAndAdditionalInformation);
     81    } catch (e) {
     82      const assertedError = /** @type {Error | string} */ (e);
     83      onProfileReceived(null, assertedError);
     84    }
     85  };
     86 
     87  _onStopButtonClick = () => {
     88    const { stopProfilerAndDiscardProfile, perfFront } = this.props;
     89    stopProfilerAndDiscardProfile(perfFront);
     90  };
     91 
     92  render() {
     93    const {
     94      recordingState,
     95      isSupportedPlatform,
     96      recordingUnexpectedlyStopped,
     97    } = this.props;
     98 
     99    if (!isSupportedPlatform) {
    100      return renderButton({
    101        label: startRecordingLabel(),
    102        isPrimary: true,
    103        disabled: true,
    104        additionalMessage:
    105          // No need to localize as this string is not displayed to Tier-1 platforms.
    106          "Your platform is not supported. The Gecko Profiler only " +
    107          "supports Tier-1 platforms.",
    108      });
    109    }
    110 
    111    switch (recordingState) {
    112      case "not-yet-known":
    113        return null;
    114 
    115      case "available-to-record":
    116        return renderButton({
    117          onClick: this._onStartButtonClick,
    118          isPrimary: true,
    119          label: startRecordingLabel(),
    120          additionalMessage: recordingUnexpectedlyStopped
    121            ? Localized(
    122                { id: "perftools-status-recording-stopped-by-another-tool" },
    123                div(null, "The recording was stopped by another tool.")
    124              )
    125            : null,
    126        });
    127 
    128      case "request-to-stop-profiler":
    129        return renderButton({
    130          label: Localized(
    131            { id: "perftools-request-to-stop-profiler" },
    132            "Stopping recording"
    133          ),
    134          disabled: true,
    135        });
    136 
    137      case "request-to-get-profile-and-stop-profiler":
    138        return renderButton({
    139          label: Localized(
    140            { id: "perftools-request-to-get-profile-and-stop-profiler" },
    141            "Capturing profile"
    142          ),
    143          disabled: true,
    144        });
    145 
    146      case "request-to-start-recording":
    147      case "recording":
    148        return renderButton({
    149          label: span(
    150            null,
    151            Localized(
    152              { id: "perftools-button-capture-recording" },
    153              "Capture recording"
    154            ),
    155            img({
    156              className: "perf-button-image",
    157              alt: "",
    158              /* This icon is actually the "open in new page" icon. */
    159              src: "chrome://devtools/skin/images/dock-undock.svg",
    160            })
    161          ),
    162          isPrimary: true,
    163          onClick: this._onCaptureButtonClick,
    164          disabled: recordingState === "request-to-start-recording",
    165          additionalButton: {
    166            label: Localized(
    167              { id: "perftools-button-cancel-recording" },
    168              "Cancel recording"
    169            ),
    170            onClick: this._onStopButtonClick,
    171          },
    172        });
    173 
    174      default:
    175        throw new Error("Unhandled recording state");
    176    }
    177  }
    178 }
    179 
    180 /**
    181 * @param {{
    182 *   disabled?: boolean,
    183 *   label?: React.ReactNode,
    184 *   onClick?: any,
    185 *   additionalMessage?: React.ReactNode,
    186 *   isPrimary?: boolean,
    187 *   pageContext?: PageContext,
    188 *   additionalButton?: {
    189 *     label: React.ReactNode,
    190 *     onClick: any,
    191 *   },
    192 * }} buttonSettings
    193 */
    194 function renderButton(buttonSettings) {
    195  const {
    196    disabled,
    197    label,
    198    onClick,
    199    additionalMessage,
    200    isPrimary,
    201    // pageContext,
    202    additionalButton,
    203  } = buttonSettings;
    204 
    205  const buttonClass = isPrimary ? "primary" : "default";
    206 
    207  return div(
    208    { className: "perf-button-container" },
    209    div(
    210      null,
    211      button(
    212        {
    213          className: `perf-photon-button perf-photon-button-${buttonClass} perf-button`,
    214          disabled,
    215          onClick,
    216        },
    217        label
    218      ),
    219      additionalButton
    220        ? button(
    221            {
    222              className: `perf-photon-button perf-photon-button-default perf-button`,
    223              onClick: additionalButton.onClick,
    224              disabled,
    225            },
    226            additionalButton.label
    227          )
    228        : null
    229    ),
    230    additionalMessage
    231      ? div({ className: "perf-additional-message" }, additionalMessage)
    232      : null
    233  );
    234 }
    235 
    236 function startRecordingLabel() {
    237  return span(
    238    null,
    239    Localized({ id: "perftools-button-start-recording" }, "Start recording"),
    240    img({
    241      className: "perf-button-image",
    242      alt: "",
    243      /* This icon is actually the "open in new page" icon. */
    244      src: "chrome://devtools/skin/images/dock-undock.svg",
    245    })
    246  );
    247 }
    248 
    249 /**
    250 * @param {StoreState} state
    251 * @returns {StateProps}
    252 */
    253 function mapStateToProps(state) {
    254  return {
    255    recordingState: selectors.getRecordingState(state),
    256    isSupportedPlatform: selectors.getIsSupportedPlatform(state),
    257    recordingUnexpectedlyStopped:
    258      selectors.getRecordingUnexpectedlyStopped(state),
    259    pageContext: selectors.getPageContext(state),
    260  };
    261 }
    262 
    263 /** @type {ThunkDispatchProps} */
    264 const mapDispatchToProps = {
    265  startRecording: actions.startRecording,
    266  stopProfilerAndDiscardProfile: actions.stopProfilerAndDiscardProfile,
    267  getProfileAndStopProfiler: actions.getProfileAndStopProfiler,
    268 };
    269 
    270 module.exports = connect(mapStateToProps, mapDispatchToProps)(RecordingButton);