tor-browser

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

Settings.js (18386B)


      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 {object} StateProps
      8 * @property {number} interval
      9 * @property {number} entries
     10 * @property {string[]} features
     11 * @property {string[]} threads
     12 * @property {string} threadsString
     13 * @property {string[]} objdirs
     14 * @property {string[]} supportedFeatures
     15 */
     16 
     17 /**
     18 * @typedef {object} ThunkDispatchProps
     19 * @property {typeof actions.changeInterval} changeInterval
     20 * @property {typeof actions.changeEntries} changeEntries
     21 * @property {typeof actions.changeFeatures} changeFeatures
     22 * @property {typeof actions.changeThreads} changeThreads
     23 * @property {typeof actions.changeObjdirs} changeObjdirs
     24 */
     25 
     26 /**
     27 * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
     28 */
     29 
     30 /**
     31 * @typedef {object} State
     32 * @property {null | string} temporaryThreadText
     33 */
     34 
     35 /**
     36 * @typedef {import("../../@types/perf").State} StoreState
     37 * @typedef {import("../../@types/perf").FeatureDescription} FeatureDescription
     38 *
     39 * @typedef {StateProps & DispatchProps} Props
     40 */
     41 
     42 /**
     43 * @template P
     44 * @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
     45 */
     46 
     47 /**
     48 * @template InjectedProps
     49 * @template NeededProps
     50 * @typedef {import("react-redux")
     51 *    .InferableComponentEnhancerWithProps<InjectedProps, NeededProps>
     52 * } InferableComponentEnhancerWithProps<InjectedProps, NeededProps>
     53 */
     54 "use strict";
     55 
     56 const {
     57  PureComponent,
     58  createFactory,
     59 } = require("resource://devtools/client/shared/vendor/react.mjs");
     60 const {
     61  code,
     62  div,
     63  label,
     64  input,
     65  h1,
     66  h2,
     67  h3,
     68  section,
     69  p,
     70  span,
     71 } = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     72 const Range = createFactory(
     73  require("resource://devtools/client/performance-new/components/aboutprofiling/Range.js")
     74 );
     75 const DirectoryPicker = createFactory(
     76  require("resource://devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js")
     77 );
     78 const {
     79  makeLinear10Scale,
     80  makePowerOf2Scale,
     81  formatFileSize,
     82  featureDescriptions,
     83 } = require("resource://devtools/client/performance-new/shared/utils.js");
     84 const {
     85  connect,
     86 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     87 const actions = require("resource://devtools/client/performance-new/store/actions.js");
     88 const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
     89 const {
     90  openFilePickerForObjdir,
     91 } = require("resource://devtools/client/performance-new/shared/browser.js");
     92 const Localized = createFactory(
     93  require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
     94 );
     95 
     96 // The Gecko Profiler interprets the "entries" setting as 8 bytes per entry.
     97 const PROFILE_ENTRY_SIZE = 8;
     98 
     99 /**
    100 * @typedef {{ name: string, id: string, l10nId: string }} ThreadColumn
    101 */
    102 
    103 /** @type {Array<ThreadColumn[]>} */
    104 const threadColumns = [
    105  [
    106    {
    107      name: "GeckoMain",
    108      id: "gecko-main",
    109      // The l10nId take the form `perf-thread-${id}`, but isn't done programmatically
    110      // so that it is easy to search in the codebase.
    111      l10nId: "perftools-thread-gecko-main",
    112    },
    113    {
    114      name: "Compositor",
    115      id: "compositor",
    116      l10nId: "perftools-thread-compositor",
    117    },
    118    {
    119      name: "DOM Worker",
    120      id: "dom-worker",
    121      l10nId: "perftools-thread-dom-worker",
    122    },
    123    {
    124      name: "Renderer",
    125      id: "renderer",
    126      l10nId: "perftools-thread-renderer",
    127    },
    128  ],
    129  [
    130    {
    131      name: "RenderBackend",
    132      id: "render-backend",
    133      l10nId: "perftools-thread-render-backend",
    134    },
    135    {
    136      name: "Timer",
    137      id: "timer",
    138      l10nId: "perftools-thread-timer",
    139    },
    140    {
    141      name: "StyleThread",
    142      id: "style-thread",
    143      l10nId: "perftools-thread-style-thread",
    144    },
    145    {
    146      name: "Socket Thread",
    147      id: "socket-thread",
    148      l10nId: "perftools-thread-socket-thread",
    149    },
    150  ],
    151  [
    152    {
    153      name: "StreamTrans",
    154      id: "stream-trans",
    155      l10nId: "pref-thread-stream-trans",
    156    },
    157    {
    158      name: "ImgDecoder",
    159      id: "img-decoder",
    160      l10nId: "perftools-thread-img-decoder",
    161    },
    162    {
    163      name: "DNS Resolver",
    164      id: "dns-resolver",
    165      l10nId: "perftools-thread-dns-resolver",
    166    },
    167    {
    168      // Threads that are part of XPCOM's TaskController thread pool.
    169      name: "TaskController",
    170      id: "task-controller",
    171      l10nId: "perftools-thread-task-controller",
    172    },
    173  ],
    174 ];
    175 
    176 /** @type {Array<ThreadColumn[]>} */
    177 const jvmThreadColumns = [
    178  [
    179    {
    180      name: "Gecko",
    181      id: "gecko",
    182      l10nId: "perftools-thread-jvm-gecko",
    183    },
    184    {
    185      name: "Nimbus",
    186      id: "nimbus",
    187      l10nId: "perftools-thread-jvm-nimbus",
    188    },
    189  ],
    190  [
    191    {
    192      name: "DefaultDispatcher",
    193      id: "default-dispatcher",
    194      l10nId: "perftools-thread-jvm-default-dispatcher",
    195    },
    196    {
    197      name: "Glean",
    198      id: "glean",
    199      l10nId: "perftools-thread-jvm-glean",
    200    },
    201  ],
    202  [
    203    {
    204      name: "arch_disk_io",
    205      id: "arch-disk-io",
    206      l10nId: "perftools-thread-jvm-arch-disk-io",
    207    },
    208    {
    209      name: "pool-",
    210      id: "pool",
    211      l10nId: "perftools-thread-jvm-pool",
    212    },
    213  ],
    214 ];
    215 
    216 /**
    217 * This component manages the settings for recording a performance profile.
    218 *
    219 * @augments {React.PureComponent<Props, State>}
    220 */
    221 class Settings extends PureComponent {
    222  /**
    223   * @param {Props} props
    224   */
    225  constructor(props) {
    226    super(props);
    227    /** @type {State} */
    228    this.state = {
    229      // Allow the textbox to have a temporary tracked value.
    230      temporaryThreadText: null,
    231    };
    232 
    233    this._intervalExponentialScale = makeLinear10Scale(0.01, 1000);
    234    this._entriesExponentialScale = makePowerOf2Scale(
    235      128 * 1024,
    236      256 * 1024 * 1024
    237    );
    238  }
    239 
    240  /**
    241   * Handle the checkbox change.
    242   *
    243   * @param {React.ChangeEvent<HTMLInputElement>} event
    244   */
    245  _handleThreadCheckboxChange = event => {
    246    const { threads, changeThreads } = this.props;
    247    const { checked, value } = event.target;
    248 
    249    if (checked) {
    250      if (!threads.includes(value)) {
    251        changeThreads([...threads, value]);
    252      }
    253    } else {
    254      changeThreads(threads.filter(thread => thread !== value));
    255    }
    256  };
    257 
    258  /**
    259   * Handle the checkbox change.
    260   *
    261   * @param {React.ChangeEvent<HTMLInputElement>} event
    262   */
    263  _handleFeaturesCheckboxChange = event => {
    264    const { features, changeFeatures } = this.props;
    265    const { checked, value } = event.target;
    266 
    267    if (checked) {
    268      if (!features.includes(value)) {
    269        changeFeatures([value, ...features]);
    270      }
    271    } else {
    272      changeFeatures(features.filter(feature => feature !== value));
    273    }
    274  };
    275 
    276  _handleAddObjdir = () => {
    277    const { objdirs, changeObjdirs } = this.props;
    278    openFilePickerForObjdir(window, objdirs, changeObjdirs);
    279  };
    280 
    281  /**
    282   * @param {number} index
    283   * @return {void}
    284   */
    285  _handleRemoveObjdir = index => {
    286    const { objdirs, changeObjdirs } = this.props;
    287    const newObjdirs = [...objdirs];
    288    newObjdirs.splice(index, 1);
    289    changeObjdirs(newObjdirs);
    290  };
    291 
    292  /**
    293   * @param {React.ChangeEvent<HTMLInputElement>} event
    294   */
    295  _setThreadTextFromInput = event => {
    296    this.setState({ temporaryThreadText: event.target.value });
    297  };
    298 
    299  /**
    300   * @param {React.ChangeEvent<HTMLInputElement>} event
    301   */
    302  _handleThreadTextCleanup = event => {
    303    this.setState({ temporaryThreadText: null });
    304    this.props.changeThreads(_threadTextToList(event.target.value));
    305  };
    306 
    307  /**
    308   * @param {ThreadColumn[]} threadDisplay
    309   * @param {number} index
    310   * @return {React.ReactNode}
    311   */
    312  _renderThreadsColumns(threadDisplay, index) {
    313    const { threads } = this.props;
    314    const areAllThreadsIncluded = threads.includes("*");
    315    return div(
    316      { className: "perf-settings-thread-column", key: index },
    317      threadDisplay.map(({ name, id, l10nId }) =>
    318        Localized(
    319          // The title is localized with a description of the thread.
    320          { id: l10nId, attrs: { title: true }, key: name },
    321          label(
    322            {
    323              className: `perf-settings-checkbox-label perf-settings-thread-label toggle-container-with-text ${
    324                areAllThreadsIncluded
    325                  ? "perf-settings-checkbox-label-disabled"
    326                  : ""
    327              }`,
    328            },
    329            input({
    330              className: "perf-settings-checkbox",
    331              id: `perf-settings-thread-checkbox-${id}`,
    332              type: "checkbox",
    333              // Do not localize the value, this is used internally by the profiler.
    334              value: name,
    335              checked: threads.includes(name),
    336              disabled: areAllThreadsIncluded,
    337              onChange: this._handleThreadCheckboxChange,
    338            }),
    339            span(null, name)
    340          )
    341        )
    342      )
    343    );
    344  }
    345  _renderThreads() {
    346    const { temporaryThreadText } = this.state;
    347    const { threads } = this.props;
    348 
    349    return renderSection(
    350      "perf-settings-threads-summary",
    351      Localized({ id: "perftools-heading-threads" }, "Threads"),
    352      div(
    353        null,
    354        div(
    355          { className: "perf-settings-thread-columns" },
    356          threadColumns.map((threadDisplay, index) =>
    357            this._renderThreadsColumns(threadDisplay, index)
    358          )
    359        ),
    360        this._renderJvmThreads(),
    361        div(
    362          {
    363            className: "perf-settings-checkbox-label perf-settings-all-threads",
    364          },
    365          label(
    366            {
    367              className: "toggle-container-with-text",
    368            },
    369            input({
    370              id: "perf-settings-thread-checkbox-all-threads",
    371              type: "checkbox",
    372              value: "*",
    373              checked: threads.includes("*"),
    374              onChange: this._handleThreadCheckboxChange,
    375            }),
    376            Localized({ id: "perftools-record-all-registered-threads" })
    377          )
    378        ),
    379        div(
    380          { className: "perf-settings-row" },
    381          Localized(
    382            { id: "perftools-tools-threads-input-label" },
    383            label(
    384              { className: "perf-settings-text-label" },
    385              div(
    386                null,
    387                Localized(
    388                  { id: "perftools-custom-threads-label" },
    389                  "Add custom threads by name:"
    390                )
    391              ),
    392              input({
    393                className: "perf-settings-text-input",
    394                id: "perftools-settings-thread-text",
    395                type: "text",
    396                value:
    397                  temporaryThreadText === null
    398                    ? threads.join(",")
    399                    : temporaryThreadText,
    400                onBlur: this._handleThreadTextCleanup,
    401                onFocus: this._setThreadTextFromInput,
    402                onChange: this._setThreadTextFromInput,
    403              })
    404            )
    405          )
    406        )
    407      )
    408    );
    409  }
    410 
    411  _renderJvmThreads() {
    412    if (!this.props.supportedFeatures.includes("java")) {
    413      return null;
    414    }
    415 
    416    return [
    417      h2(
    418        null,
    419        Localized({ id: "perftools-heading-threads-jvm" }, "JVM Threads")
    420      ),
    421      div(
    422        { className: "perf-settings-thread-columns" },
    423        jvmThreadColumns.map((threadDisplay, index) =>
    424          this._renderThreadsColumns(threadDisplay, index)
    425        )
    426      ),
    427    ];
    428  }
    429 
    430  /**
    431   * @param {React.ReactNode} sectionTitle
    432   * @param {FeatureDescription[]} features
    433   * @param {boolean} isSupported
    434   */
    435  _renderFeatureSection(sectionTitle, features, isSupported) {
    436    if (features.length === 0) {
    437      return null;
    438    }
    439 
    440    // Note: This area is not localized. This area is pretty deep in the UI, and is mostly
    441    // geared towards Firefox engineers. It may not be worth localizing. This decision
    442    // can be tracked in Bug 1682333.
    443 
    444    return div(
    445      null,
    446      h3(null, sectionTitle),
    447      features.map(featureDescription => {
    448        const { name, value, title, disabledReason } = featureDescription;
    449        const extraClassName = isSupported
    450          ? ""
    451          : "perf-settings-checkbox-label-disabled";
    452        return label(
    453          {
    454            className: `perf-settings-checkbox-label perf-toggle-label ${extraClassName}`,
    455            key: value,
    456          },
    457          input({
    458            id: `perf-settings-feature-checkbox-${value}`,
    459            type: "checkbox",
    460            value,
    461            checked: isSupported && this.props.features.includes(value),
    462            onChange: this._handleFeaturesCheckboxChange,
    463            disabled: !isSupported,
    464          }),
    465          div(
    466            { className: "perf-toggle-text-label" },
    467            !isSupported && featureDescription.experimental
    468              ? // Note when unsupported features are experimental.
    469                `${name} (Experimental)`
    470              : name,
    471            span(
    472              { className: "perf-toggle-feature-value" },
    473              "(",
    474              code(null, value),
    475              ")"
    476            )
    477          ),
    478          div(
    479            { className: "perf-toggle-description" },
    480            title,
    481            !isSupported && disabledReason
    482              ? div(
    483                  { className: "perf-settings-feature-disabled-reason" },
    484                  disabledReason
    485                )
    486              : null
    487          )
    488        );
    489      })
    490    );
    491  }
    492 
    493  _renderFeatures() {
    494    const { supportedFeatures } = this.props;
    495 
    496    // Divvy up the features into their respective groups.
    497    const recommended = [];
    498    const supported = [];
    499    const unsupported = [];
    500    const experimental = [];
    501 
    502    for (const feature of featureDescriptions) {
    503      if (supportedFeatures.includes(feature.value)) {
    504        if (feature.experimental) {
    505          experimental.push(feature);
    506        } else if (feature.recommended) {
    507          recommended.push(feature);
    508        } else {
    509          supported.push(feature);
    510        }
    511      } else {
    512        unsupported.push(feature);
    513      }
    514    }
    515 
    516    return div(
    517      { className: "perf-settings-sections" },
    518      div(
    519        null,
    520        this._renderFeatureSection(
    521          Localized(
    522            { id: "perftools-heading-features-default" },
    523            "Features (Recommended on by default)"
    524          ),
    525          recommended,
    526          true
    527        ),
    528        this._renderFeatureSection(
    529          Localized({ id: "perftools-heading-features" }, "Features"),
    530          supported,
    531          true
    532        ),
    533        this._renderFeatureSection(
    534          Localized(
    535            { id: "perftools-heading-features-experimental" },
    536            "Experimental"
    537          ),
    538          experimental,
    539          true
    540        ),
    541        this._renderFeatureSection(
    542          Localized(
    543            { id: "perftools-heading-features-disabled" },
    544            "Disabled Features"
    545          ),
    546          unsupported,
    547          false
    548        )
    549      )
    550    );
    551  }
    552 
    553  _renderLocalBuildSection() {
    554    const { objdirs } = this.props;
    555    return renderSection(
    556      "perf-settings-local-build-summary",
    557      Localized({ id: "perftools-heading-local-build" }),
    558      div(
    559        null,
    560        p(null, Localized({ id: "perftools-description-local-build" })),
    561        DirectoryPicker({
    562          dirs: objdirs,
    563          onAdd: this._handleAddObjdir,
    564          onRemove: this._handleRemoveObjdir,
    565        })
    566      )
    567    );
    568  }
    569 
    570  render() {
    571    return section(
    572      { className: "perf-settings" },
    573      h1(null, Localized({ id: "perftools-heading-settings" })),
    574      h2(
    575        { className: "perf-settings-title" },
    576        Localized({ id: "perftools-heading-buffer" })
    577      ),
    578      Range({
    579        label: Localized({ id: "perftools-range-interval-label" }),
    580        value: this.props.interval,
    581        id: "perf-range-interval",
    582        scale: this._intervalExponentialScale,
    583        display: _intervalTextDisplay,
    584        onChange: this.props.changeInterval,
    585      }),
    586      Range({
    587        label: Localized({ id: "perftools-range-entries-label" }),
    588        value: this.props.entries,
    589        id: "perf-range-entries",
    590        scale: this._entriesExponentialScale,
    591        display: _entriesTextDisplay,
    592        onChange: this.props.changeEntries,
    593      }),
    594      this._renderThreads(),
    595      this._renderFeatures(),
    596      this._renderLocalBuildSection()
    597    );
    598  }
    599 }
    600 
    601 /**
    602 * Clean up the thread list string into a list of values.
    603 *
    604 * @param {string} threads - Comma separated values.
    605 * @return {string[]}
    606 */
    607 function _threadTextToList(threads) {
    608  return (
    609    threads
    610      // Split on commas
    611      .split(",")
    612      // Clean up any extraneous whitespace
    613      .map(string => string.trim())
    614      // Filter out any blank strings
    615      .filter(string => string)
    616  );
    617 }
    618 
    619 /**
    620 * Format the interval number for display.
    621 *
    622 * @param {number} value
    623 * @return {React.ReactNode}
    624 */
    625 function _intervalTextDisplay(value) {
    626  return Localized({
    627    id: "perftools-range-interval-milliseconds",
    628    $interval: value,
    629  });
    630 }
    631 
    632 /**
    633 * Format the entries number for display.
    634 *
    635 * @param {number} value
    636 * @return {string}
    637 */
    638 function _entriesTextDisplay(value) {
    639  return formatFileSize(value * PROFILE_ENTRY_SIZE);
    640 }
    641 
    642 /**
    643 * Renders a section for about:profiling.
    644 *
    645 * @param {string} id Unused.
    646 * @param {React.ReactNode} title
    647 * @param {React.ReactNode} children
    648 * @returns React.ReactNode
    649 */
    650 function renderSection(id, title, children) {
    651  return div(
    652    { className: "perf-settings-sections" },
    653    div(null, h2(null, title), children)
    654  );
    655 }
    656 
    657 /**
    658 * @param {StoreState} state
    659 * @returns {StateProps}
    660 */
    661 function mapStateToProps(state) {
    662  return {
    663    interval: selectors.getInterval(state),
    664    entries: selectors.getEntries(state),
    665    features: selectors.getFeatures(state),
    666    threads: selectors.getThreads(state),
    667    threadsString: selectors.getThreadsString(state),
    668    objdirs: selectors.getObjdirs(state),
    669    supportedFeatures: selectors.getSupportedFeatures(state),
    670  };
    671 }
    672 
    673 /** @type {ThunkDispatchProps} */
    674 const mapDispatchToProps = {
    675  changeInterval: actions.changeInterval,
    676  changeEntries: actions.changeEntries,
    677  changeFeatures: actions.changeFeatures,
    678  changeThreads: actions.changeThreads,
    679  changeObjdirs: actions.changeObjdirs,
    680 };
    681 
    682 const SettingsConnected = connect(
    683  mapStateToProps,
    684  mapDispatchToProps
    685 )(Settings);
    686 
    687 module.exports = SettingsConnected;