tor-browser

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

AboutProfiling.js (10363B)


      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 * @typedef {import("../../@types/perf").State} StoreState
      8 * @typedef {import("../../@types/perf").PerformancePref} PerformancePref
      9 */
     10 
     11 "use strict";
     12 
     13 const {
     14  PureComponent,
     15  createFactory,
     16  createElement: h,
     17  Fragment,
     18  createRef,
     19 } = require("resource://devtools/client/shared/vendor/react.mjs");
     20 const {
     21  connect,
     22 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     23 const {
     24  div,
     25  h1,
     26  button,
     27 } = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     28 const Localized = createFactory(
     29  require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
     30 );
     31 const Settings = createFactory(
     32  require("resource://devtools/client/performance-new/components/aboutprofiling/Settings.js")
     33 );
     34 const Presets = createFactory(
     35  require("resource://devtools/client/performance-new/components/aboutprofiling/Presets.js")
     36 );
     37 
     38 const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
     39 const {
     40  restartBrowserWithEnvironmentVariable,
     41 } = require("resource://devtools/client/performance-new/shared/browser.js");
     42 
     43 /** @type {PerformancePref["AboutProfilingHasDeveloperOptions"]} */
     44 const ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF =
     45  "devtools.performance.aboutprofiling.has-developer-options";
     46 
     47 /**
     48 * This function encodes the parameter so that it can be used as an environment
     49 * variable value.
     50 * Basically it uses single quotes, but replacing any single quote by '"'"':
     51 * 1. close the previous single-quoted string,
     52 * 2. add a double-quoted string containing only a single quote
     53 * 3. start a single-quoted string again.
     54 * so that it's properly retained.
     55 *
     56 * @param {string} value
     57 * @returns {string}
     58 */
     59 function encodeShellValue(value) {
     60  return "'" + value.replaceAll("'", `'"'"'`) + "'";
     61 }
     62 
     63 /**
     64 * @typedef {import("../../@types/perf").RecordingSettings} RecordingSettings
     65 *
     66 * @typedef {object} ButtonStateProps
     67 * @property {RecordingSettings} recordingSettings
     68 *
     69 * @typedef {ButtonStateProps} ButtonProps
     70 *
     71 * @typedef {object} ButtonState
     72 * @property {boolean} hasDeveloperOptions
     73 */
     74 
     75 /**
     76 * This component implements the button that triggers the menu that makes it
     77 * possible to show more actions.
     78 *
     79 * @augments {React.PureComponent<ButtonProps, ButtonState>}
     80 */
     81 class MoreActionsButtonImpl extends PureComponent {
     82  state = {
     83    hasDeveloperOptions: Services.prefs.getBoolPref(
     84      ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
     85      false
     86    ),
     87  };
     88 
     89  componentDidMount() {
     90    Services.prefs.addObserver(
     91      ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
     92      this.onHasDeveloperOptionsPrefChanges
     93    );
     94  }
     95 
     96  componentWillUnmount() {
     97    Services.prefs.removeObserver(
     98      ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
     99      this.onHasDeveloperOptionsPrefChanges
    100    );
    101  }
    102  _menuRef = createRef();
    103 
    104  onHasDeveloperOptionsPrefChanges = () => {
    105    this.setState({
    106      hasDeveloperOptions: Services.prefs.getBoolPref(
    107        ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
    108        false
    109      ),
    110    });
    111  };
    112 
    113  /**
    114   * See the part "Showing the menu" in
    115   * https://searchfox.org/mozilla-central/rev/4bacdbc8ac088f2ee516daf42c535fab2bc24a04/toolkit/content/widgets/panel-list/README.stories.md
    116   * Strangely our React's type doesn't have the `detail` property for
    117   * MouseEvent, so we're defining it manually.
    118   *
    119   * @param {React.MouseEvent & { detail: number }} e
    120   */
    121  handleClickOrMousedown = e => {
    122    // The menu is toggled either for a "mousedown", or for a keyboard enter
    123    // (which triggers a "click" event with 0 clicks (detail == 0)).
    124    if (this._menuRef.current && (e.type == "mousedown" || e.detail === 0)) {
    125      this._menuRef.current.toggle(e.nativeEvent, e.currentTarget);
    126    }
    127  };
    128 
    129  /**
    130   * @returns {Record<string, string>}
    131   */
    132  getEnvironmentVariablesForStartupFromRecordingSettings = () => {
    133    const { interval, entries, threads, features } =
    134      this.props.recordingSettings;
    135    return {
    136      MOZ_PROFILER_STARTUP: "1",
    137      MOZ_PROFILER_STARTUP_INTERVAL: String(interval),
    138      MOZ_PROFILER_STARTUP_ENTRIES: String(entries),
    139      MOZ_PROFILER_STARTUP_FEATURES: features.join(","),
    140      MOZ_PROFILER_STARTUP_FILTERS: threads.join(","),
    141    };
    142  };
    143 
    144  onRestartWithProfiling = () => {
    145    const envVariables =
    146      this.getEnvironmentVariablesForStartupFromRecordingSettings();
    147    restartBrowserWithEnvironmentVariable(envVariables);
    148  };
    149 
    150  onCopyEnvVariables = async () => {
    151    const envVariables =
    152      this.getEnvironmentVariablesForStartupFromRecordingSettings();
    153    const envString = Object.entries(envVariables)
    154      .map(([key, value]) => `${key}=${encodeShellValue(value)}`)
    155      .join(" ");
    156    await navigator.clipboard.writeText(envString);
    157  };
    158 
    159  onCopyTestVariables = async () => {
    160    const { interval, entries, threads, features } =
    161      this.props.recordingSettings;
    162 
    163    const envString =
    164      "--gecko-profile" +
    165      ` --gecko-profile-interval ${interval}` +
    166      ` --gecko-profile-entries ${entries}` +
    167      ` --gecko-profile-features ${encodeShellValue(features.join(","))}` +
    168      ` --gecko-profile-threads ${encodeShellValue(threads.join(","))}`;
    169    await navigator.clipboard.writeText(envString);
    170  };
    171 
    172  render() {
    173    return h(
    174      Fragment,
    175      null,
    176      Localized(
    177        {
    178          id: "perftools-menu-more-actions-button",
    179          attrs: { title: true },
    180        },
    181        h("moz-button", {
    182          iconsrc: "chrome://global/skin/icons/more.svg",
    183          "aria-expanded": "false",
    184          "aria-haspopup": "menu",
    185          onClick: this.handleClickOrMousedown,
    186          onMouseDown: this.handleClickOrMousedown,
    187        })
    188      ),
    189      h(
    190        "panel-list",
    191        { ref: this._menuRef },
    192        Localized(
    193          { id: "perftools-menu-more-actions-restart-with-profiling" },
    194          h(
    195            "panel-item",
    196            { onClick: this.onRestartWithProfiling },
    197            "Restart Firefox with startup profiling enabled"
    198          )
    199        ),
    200        this.state.hasDeveloperOptions
    201          ? Localized(
    202              { id: "perftools-menu-more-actions-copy-for-startup" },
    203              h(
    204                "panel-item",
    205                { onClick: this.onCopyEnvVariables },
    206                "Copy environment variables for startup profiling"
    207              )
    208            )
    209          : null,
    210        this.state.hasDeveloperOptions
    211          ? Localized(
    212              { id: "perftools-menu-more-actions-copy-for-perf-tests" },
    213              h(
    214                "panel-item",
    215                { onClick: this.onCopyTestVariables },
    216                "Copy parameters for mach try perf"
    217              )
    218            )
    219          : null
    220      )
    221    );
    222  }
    223 }
    224 
    225 /**
    226 * @param {StoreState} state
    227 * @returns {ButtonStateProps}
    228 */
    229 function mapStateToButtonProps(state) {
    230  return {
    231    recordingSettings: selectors.getRecordingSettings(state),
    232  };
    233 }
    234 const MoreActionsButton = connect(mapStateToButtonProps)(MoreActionsButtonImpl);
    235 
    236 /**
    237 * @typedef {import("../../@types/perf").PageContext} PageContext
    238 *
    239 * @typedef {object} StateProps
    240 * @property {boolean?} isSupportedPlatform
    241 * @property {PageContext} pageContext
    242 * @property {string | null} promptEnvRestart
    243 * @property {(() => void) | undefined} openRemoteDevTools
    244 *
    245 * @typedef {StateProps} Props
    246 */
    247 
    248 /**
    249 * This is the top level component for the about:profiling page. It shares components
    250 * with the popup and DevTools page.
    251 *
    252 * @augments {React.PureComponent<Props>}
    253 */
    254 class AboutProfiling extends PureComponent {
    255  render() {
    256    const {
    257      isSupportedPlatform,
    258      pageContext,
    259      promptEnvRestart,
    260      openRemoteDevTools,
    261    } = this.props;
    262 
    263    if (isSupportedPlatform === null) {
    264      // We don't know yet if this is a supported platform, wait for a response.
    265      return null;
    266    }
    267 
    268    return div(
    269      { className: `perf perf-${pageContext}` },
    270      promptEnvRestart
    271        ? div(
    272            { className: "perf-env-restart" },
    273            div(
    274              {
    275                className:
    276                  "perf-photon-message-bar perf-photon-message-bar-warning perf-env-restart-fixed",
    277              },
    278              div({ className: "perf-photon-message-bar-warning-icon" }),
    279              Localized({ id: "perftools-status-restart-required" }),
    280              button(
    281                {
    282                  className: "perf-photon-button perf-photon-button-micro",
    283                  type: "button",
    284                  onClick: () => {
    285                    restartBrowserWithEnvironmentVariable({
    286                      [promptEnvRestart]: "1",
    287                    });
    288                  },
    289                },
    290                Localized({ id: "perftools-button-restart" })
    291              )
    292            )
    293          )
    294        : null,
    295 
    296      openRemoteDevTools
    297        ? div(
    298            { className: "perf-back" },
    299            button(
    300              {
    301                className: "perf-back-button",
    302                type: "button",
    303                onClick: openRemoteDevTools,
    304              },
    305              Localized({ id: "perftools-button-save-settings" })
    306            )
    307          )
    308        : null,
    309 
    310      div(
    311        { className: "perf-intro" },
    312        div(
    313          { className: "perf-intro-title-bar" },
    314          h1(
    315            { className: "perf-intro-title" },
    316            Localized({ id: "perftools-intro-title" })
    317          ),
    318          h(MoreActionsButton)
    319        ),
    320        div(
    321          { className: "perf-intro-row" },
    322          div({}, div({ className: "perf-intro-icon" })),
    323          Localized({
    324            className: "perf-intro-text",
    325            id: "perftools-intro-description",
    326          })
    327        )
    328      ),
    329      Presets(),
    330      Settings()
    331    );
    332  }
    333 }
    334 
    335 /**
    336 * @param {StoreState} state
    337 * @returns {StateProps}
    338 */
    339 function mapStateToProps(state) {
    340  return {
    341    isSupportedPlatform: selectors.getIsSupportedPlatform(state),
    342    pageContext: selectors.getPageContext(state),
    343    promptEnvRestart: selectors.getPromptEnvRestart(state),
    344    openRemoteDevTools: selectors.getOpenRemoteDevTools(state),
    345  };
    346 }
    347 
    348 module.exports = connect(mapStateToProps)(AboutProfiling);