summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance-new/components/aboutprofiling
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/performance-new/components/aboutprofiling
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/performance-new/components/aboutprofiling')
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/AboutProfiling.js156
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js119
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/Presets.js167
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/Range.js78
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/Settings.js674
-rw-r--r--devtools/client/performance-new/components/aboutprofiling/moz.build12
6 files changed, 1206 insertions, 0 deletions
diff --git a/devtools/client/performance-new/components/aboutprofiling/AboutProfiling.js b/devtools/client/performance-new/components/aboutprofiling/AboutProfiling.js
new file mode 100644
index 0000000000..0d205093ba
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/AboutProfiling.js
@@ -0,0 +1,156 @@
+/* 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<P>} ResolveThunks<P>
+ */
+
+/**
+ * @typedef {Object} StateProps
+ * @property {boolean?} isSupportedPlatform
+ * @property {PageContext} pageContext
+ * @property {string | null} promptEnvRestart
+ * @property {(() => void) | undefined} openRemoteDevTools
+ */
+
+/**
+ * @typedef {StateProps} Props
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").PageContext} PageContext
+ */
+
+"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,
+ h1,
+ button,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+const Settings = createFactory(
+ require("resource://devtools/client/performance-new/components/aboutprofiling/Settings.js")
+);
+const Presets = createFactory(
+ require("resource://devtools/client/performance-new/components/aboutprofiling/Presets.js")
+);
+
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+const {
+ restartBrowserWithEnvironmentVariable,
+} = require("resource://devtools/client/performance-new/shared/browser.js");
+
+/**
+ * This is the top level component for the about:profiling page. It shares components
+ * with the popup and DevTools page.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class AboutProfiling extends PureComponent {
+ handleRestart = () => {
+ const { promptEnvRestart } = this.props;
+ if (!promptEnvRestart) {
+ throw new Error(
+ "handleRestart() should only be called when promptEnvRestart exists."
+ );
+ }
+ restartBrowserWithEnvironmentVariable(promptEnvRestart, "1");
+ };
+
+ render() {
+ const {
+ isSupportedPlatform,
+ pageContext,
+ promptEnvRestart,
+ openRemoteDevTools,
+ } = this.props;
+
+ if (isSupportedPlatform === null) {
+ // We don't know yet if this is a supported platform, wait for a response.
+ return null;
+ }
+
+ return div(
+ { className: `perf perf-${pageContext}` },
+ promptEnvRestart
+ ? div(
+ { className: "perf-env-restart" },
+ div(
+ {
+ className:
+ "perf-photon-message-bar perf-photon-message-bar-warning perf-env-restart-fixed",
+ },
+ div({ className: "perf-photon-message-bar-warning-icon" }),
+ Localized({ id: "perftools-status-restart-required" }),
+ button(
+ {
+ className: "perf-photon-button perf-photon-button-micro",
+ type: "button",
+ onClick: this.handleRestart,
+ },
+ Localized({ id: "perftools-button-restart" })
+ )
+ )
+ )
+ : null,
+
+ openRemoteDevTools
+ ? div(
+ { className: "perf-back" },
+ button(
+ {
+ className: "perf-back-button",
+ type: "button",
+ onClick: openRemoteDevTools,
+ },
+ Localized({ id: "perftools-button-save-settings" })
+ )
+ )
+ : null,
+
+ div(
+ { className: "perf-intro" },
+ h1(
+ { className: "perf-intro-title" },
+ Localized({ id: "perftools-intro-title" })
+ ),
+ div(
+ { className: "perf-intro-row" },
+ div({}, div({ className: "perf-intro-icon" })),
+ Localized({
+ className: "perf-intro-text",
+ id: "perftools-intro-description",
+ })
+ )
+ ),
+ Presets(),
+ Settings()
+ );
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ isSupportedPlatform: selectors.getIsSupportedPlatform(state),
+ pageContext: selectors.getPageContext(state),
+ promptEnvRestart: selectors.getPromptEnvRestart(state),
+ openRemoteDevTools: selectors.getOpenRemoteDevTools(state),
+ };
+}
+
+module.exports = connect(mapStateToProps)(AboutProfiling);
diff --git a/devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js b/devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js
new file mode 100644
index 0000000000..4d9ea8373d
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js
@@ -0,0 +1,119 @@
+/* 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} Props
+ * @property {string[]} dirs
+ * @property {() => void} onAdd
+ * @property {(index: number) => void} onRemove
+ */
+
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ button,
+ select,
+ option,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const {
+ withCommonPathPrefixRemoved,
+} = require("resource://devtools/client/performance-new/shared/utils.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+/**
+ * A list of directories with add and remove buttons.
+ * Looks like this:
+ *
+ * +---------------------------------------------+
+ * | code/obj-m-android-opt |
+ * | code/obj-m-android-debug |
+ * | test/obj-m-test |
+ * | |
+ * +---------------------------------------------+
+ *
+ * [+] [-]
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class DirectoryPicker extends PureComponent {
+ /** @param {Props} props */
+ constructor(props) {
+ super(props);
+ this._listBox = null;
+ }
+
+ /**
+ * @param {HTMLSelectElement} element
+ */
+ _takeListBoxRef = element => {
+ this._listBox = element;
+ };
+
+ _handleAddButtonClick = () => {
+ this.props.onAdd();
+ };
+
+ _handleRemoveButtonClick = () => {
+ if (this._listBox && this._listBox.selectedIndex !== -1) {
+ this.props.onRemove(this._listBox.selectedIndex);
+ }
+ };
+
+ render() {
+ const { dirs } = this.props;
+ const truncatedDirs = withCommonPathPrefixRemoved(dirs);
+ return [
+ select(
+ {
+ className: "perf-settings-dir-list",
+ size: 4,
+ ref: this._takeListBoxRef,
+ key: "directory-picker-select",
+ },
+ dirs.map((fullPath, i) =>
+ option(
+ {
+ key: fullPath,
+ className: "pref-settings-dir-list-item",
+ title: fullPath,
+ },
+ truncatedDirs[i]
+ )
+ )
+ ),
+ div(
+ {
+ className: "perf-settings-dir-list-button-group",
+ key: "directory-picker-div",
+ },
+ button(
+ {
+ type: "button",
+ className: `perf-photon-button perf-photon-button-default perf-button`,
+ onClick: this._handleAddButtonClick,
+ },
+ Localized({ id: "perftools-button-add-directory" })
+ ),
+ button(
+ {
+ type: "button",
+ className: `perf-photon-button perf-photon-button-default perf-button`,
+ onClick: this._handleRemoveButtonClick,
+ },
+ Localized({ id: "perftools-button-remove-directory" })
+ )
+ ),
+ ];
+ }
+}
+
+module.exports = DirectoryPicker;
diff --git a/devtools/client/performance-new/components/aboutprofiling/Presets.js b/devtools/client/performance-new/components/aboutprofiling/Presets.js
new file mode 100644
index 0000000000..4dc97d7e32
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/Presets.js
@@ -0,0 +1,167 @@
+/* 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<P>} ResolveThunks<P>
+ */
+
+"use strict";
+const {
+ PureComponent,
+ createElement,
+ createFactory,
+ Fragment,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ label,
+ input,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const selectors = require("resource://devtools/client/performance-new/store/selectors.js");
+const actions = require("resource://devtools/client/performance-new/store/actions.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+/**
+ * @typedef {Object} PresetProps
+ * @property {string} presetName
+ * @property {boolean} selected
+ * @property {import("../../@types/perf").PresetDefinition | null} preset
+ * @property {(presetName: string) => void} onChange
+ */
+
+/**
+ * Switch between various profiler presets, which will override the individualized
+ * settings for the profiler.
+ *
+ * @extends {React.PureComponent<PresetProps>}
+ */
+class Preset extends PureComponent {
+ /**
+ * Handle the checkbox change.
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ onChange = event => {
+ this.props.onChange(event.target.value);
+ };
+
+ render() {
+ const { preset, presetName, selected } = this.props;
+ const presetLabelAndDescription = preset
+ ? createElement(
+ Fragment,
+ null,
+ Localized(
+ { id: preset.l10nIds.devtools.label },
+ div({ className: "perf-toggle-text-label" })
+ ),
+ Localized(
+ { id: preset.l10nIds.devtools.description },
+ div({ className: "perf-toggle-description" })
+ )
+ )
+ : Localized(
+ { id: "perftools-presets-custom-label" },
+ div({ className: "perf-toggle-text-label" }, "Custom")
+ );
+
+ return label(
+ { className: "perf-toggle-label" },
+ input({
+ className: "perf-presets-radio-button",
+ type: "radio",
+ name: "presets",
+ value: presetName,
+ checked: selected,
+ onChange: this.onChange,
+ }),
+ presetLabelAndDescription
+ );
+ }
+}
+
+/**
+ * @typedef {Object} StateProps
+ * @property {string} selectedPresetName
+ * @property {import("../../@types/perf").Presets} presets
+ */
+
+/**
+ * @typedef {Object} ThunkDispatchProps
+ * @property {typeof actions.changePreset} changePreset
+ */
+
+/**
+ * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
+ * @typedef {StateProps & DispatchProps} Props
+ * @typedef {import("../../@types/perf").State} StoreState
+ */
+
+/**
+ * Switch between various profiler presets, which will override the individualized
+ * settings for the profiler.
+ *
+ * @extends {React.PureComponent<Props>}
+ */
+class Presets extends PureComponent {
+ /**
+ * Handle the checkbox change.
+ * @param {string} presetName
+ */
+ onChange = presetName => {
+ const { presets } = this.props;
+ this.props.changePreset(presets, presetName);
+ };
+
+ render() {
+ const { presets, selectedPresetName } = this.props;
+
+ return div(
+ { className: "perf-presets" },
+ Object.entries(presets).map(([presetName, preset]) =>
+ createElement(Preset, {
+ key: presetName,
+ presetName,
+ preset,
+ selected: presetName === selectedPresetName,
+ onChange: this.onChange,
+ })
+ ),
+ createElement(Preset, {
+ key: "custom",
+ presetName: "custom",
+ selected: selectedPresetName == "custom",
+ preset: null,
+ onChange: this.onChange,
+ })
+ );
+ }
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ selectedPresetName: selectors.getPresetName(state),
+ presets: selectors.getPresets(state),
+ };
+}
+
+/**
+ * @type {ThunkDispatchProps}
+ */
+const mapDispatchToProps = {
+ changePreset: actions.changePreset,
+};
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(Presets);
diff --git a/devtools/client/performance-new/components/aboutprofiling/Range.js b/devtools/client/performance-new/components/aboutprofiling/Range.js
new file mode 100644
index 0000000000..ce787ba334
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/Range.js
@@ -0,0 +1,78 @@
+/* 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").ScaleFunctions} ScaleFunctions
+ */
+
+/**
+ * @typedef {Object} Props
+ * @property {number} value
+ * @property {React.ReactNode} label
+ * @property {string} id
+ * @property {ScaleFunctions} scale
+ * @property {(value: number) => unknown} onChange
+ * @property {(value: number) => React.ReactNode} display
+ */
+"use strict";
+const {
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ input,
+ label,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+
+/**
+ * Provide a numeric range slider UI that works off of custom numeric scales.
+ * @extends React.PureComponent<Props>
+ */
+class Range extends PureComponent {
+ /**
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ handleInput = event => {
+ event.preventDefault();
+ const { scale, onChange } = this.props;
+ const frac = Number(event.target.value) / scale.steps;
+ onChange(scale.fromFractionToSingleDigitValue(frac));
+ };
+
+ render() {
+ const { label: labelText, scale, id, value, display } = this.props;
+
+ const min = "0";
+ const max = scale.steps;
+ // Convert the value to the current range.
+ const rangeValue = scale.fromValueToFraction(value) * max;
+
+ return div(
+ { className: "perf-settings-range-row" },
+ label(
+ {
+ className: "perf-settings-label",
+ htmlFor: id,
+ },
+ labelText
+ ),
+ input({
+ type: "range",
+ className: `perf-settings-range-input`,
+ min,
+ "aria-valuemin": scale.fromFractionToValue(0),
+ max,
+ "aria-valuemax": scale.fromFractionToValue(1),
+ value: rangeValue,
+ "aria-valuenow": value,
+ onChange: this.handleInput,
+ id,
+ }),
+ div({ className: `perf-settings-range-value` }, display(value))
+ );
+ }
+}
+
+module.exports = Range;
diff --git a/devtools/client/performance-new/components/aboutprofiling/Settings.js b/devtools/client/performance-new/components/aboutprofiling/Settings.js
new file mode 100644
index 0000000000..3afe3a2d94
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/Settings.js
@@ -0,0 +1,674 @@
+/* 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 {number} interval
+ * @property {number} entries
+ * @property {string[]} features
+ * @property {string[]} threads
+ * @property {string} threadsString
+ * @property {string[]} objdirs
+ * @property {string[]} supportedFeatures
+ */
+
+/**
+ * @typedef {Object} ThunkDispatchProps
+ * @property {typeof actions.changeInterval} changeInterval
+ * @property {typeof actions.changeEntries} changeEntries
+ * @property {typeof actions.changeFeatures} changeFeatures
+ * @property {typeof actions.changeThreads} changeThreads
+ * @property {typeof actions.changeObjdirs} changeObjdirs
+ */
+
+/**
+ * @typedef {ResolveThunks<ThunkDispatchProps>} DispatchProps
+ */
+
+/**
+ * @typedef {Object} State
+ * @property {null | string} temporaryThreadText
+ */
+
+/**
+ * @typedef {import("../../@types/perf").State} StoreState
+ * @typedef {import("../../@types/perf").FeatureDescription} FeatureDescription
+ *
+ * @typedef {StateProps & DispatchProps} Props
+ */
+
+/**
+ * @template P
+ * @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
+ */
+
+/**
+ * @template InjectedProps
+ * @template NeededProps
+ * @typedef {import("react-redux")
+ * .InferableComponentEnhancerWithProps<InjectedProps, NeededProps>
+ * } InferableComponentEnhancerWithProps<InjectedProps, NeededProps>
+ */
+"use strict";
+
+const {
+ PureComponent,
+ createFactory,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ div,
+ label,
+ input,
+ h1,
+ h2,
+ h3,
+ section,
+ p,
+ span,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const Range = createFactory(
+ require("resource://devtools/client/performance-new/components/aboutprofiling/Range.js")
+);
+const DirectoryPicker = createFactory(
+ require("resource://devtools/client/performance-new/components/aboutprofiling/DirectoryPicker.js")
+);
+const {
+ makeLinear10Scale,
+ makePowerOf2Scale,
+ formatFileSize,
+ featureDescriptions,
+} = require("resource://devtools/client/performance-new/shared/utils.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 {
+ openFilePickerForObjdir,
+} = require("resource://devtools/client/performance-new/shared/browser.js");
+const Localized = createFactory(
+ require("resource://devtools/client/shared/vendor/fluent-react.js").Localized
+);
+
+// The Gecko Profiler interprets the "entries" setting as 8 bytes per entry.
+const PROFILE_ENTRY_SIZE = 8;
+
+/**
+ * @typedef {{ name: string, id: string, l10nId: string }} ThreadColumn
+ */
+
+/** @type {Array<ThreadColumn[]>} */
+const threadColumns = [
+ [
+ {
+ name: "GeckoMain",
+ id: "gecko-main",
+ // The l10nId take the form `perf-thread-${id}`, but isn't done programmatically
+ // so that it is easy to search in the codebase.
+ l10nId: "perftools-thread-gecko-main",
+ },
+ {
+ name: "Compositor",
+ id: "compositor",
+ l10nId: "perftools-thread-compositor",
+ },
+ {
+ name: "DOM Worker",
+ id: "dom-worker",
+ l10nId: "perftools-thread-dom-worker",
+ },
+ {
+ name: "Renderer",
+ id: "renderer",
+ l10nId: "perftools-thread-renderer",
+ },
+ ],
+ [
+ {
+ name: "RenderBackend",
+ id: "render-backend",
+ l10nId: "perftools-thread-render-backend",
+ },
+ {
+ name: "Timer",
+ id: "timer",
+ l10nId: "perftools-thread-timer",
+ },
+ {
+ name: "StyleThread",
+ id: "style-thread",
+ l10nId: "perftools-thread-style-thread",
+ },
+ {
+ name: "Socket Thread",
+ id: "socket-thread",
+ l10nId: "perftools-thread-socket-thread",
+ },
+ ],
+ [
+ {
+ name: "StreamTrans",
+ id: "stream-trans",
+ l10nId: "pref-thread-stream-trans",
+ },
+ {
+ name: "ImgDecoder",
+ id: "img-decoder",
+ l10nId: "perftools-thread-img-decoder",
+ },
+ {
+ name: "DNS Resolver",
+ id: "dns-resolver",
+ l10nId: "perftools-thread-dns-resolver",
+ },
+ {
+ // Threads that are part of XPCOM's TaskController thread pool.
+ name: "TaskController",
+ id: "task-controller",
+ l10nId: "perftools-thread-task-controller",
+ },
+ ],
+];
+
+/** @type {Array<ThreadColumn[]>} */
+const jvmThreadColumns = [
+ [
+ {
+ name: "Gecko",
+ id: "gecko",
+ l10nId: "perftools-thread-jvm-gecko",
+ },
+ {
+ name: "Nimbus",
+ id: "nimbus",
+ l10nId: "perftools-thread-jvm-nimbus",
+ },
+ ],
+ [
+ {
+ name: "DefaultDispatcher",
+ id: "default-dispatcher",
+ l10nId: "perftools-thread-jvm-default-dispatcher",
+ },
+ {
+ name: "Glean",
+ id: "glean",
+ l10nId: "perftools-thread-jvm-glean",
+ },
+ ],
+ [
+ {
+ name: "arch_disk_io",
+ id: "arch-disk-io",
+ l10nId: "perftools-thread-jvm-arch-disk-io",
+ },
+ {
+ name: "pool-",
+ id: "pool",
+ l10nId: "perftools-thread-jvm-pool",
+ },
+ ],
+];
+
+/**
+ * This component manages the settings for recording a performance profile.
+ * @extends {React.PureComponent<Props, State>}
+ */
+class Settings extends PureComponent {
+ /**
+ * @param {Props} props
+ */
+ constructor(props) {
+ super(props);
+ /** @type {State} */
+ this.state = {
+ // Allow the textbox to have a temporary tracked value.
+ temporaryThreadText: null,
+ };
+
+ this._intervalExponentialScale = makeLinear10Scale(0.01, 100);
+ this._entriesExponentialScale = makePowerOf2Scale(
+ 128 * 1024,
+ 256 * 1024 * 1024
+ );
+ }
+
+ /**
+ * Handle the checkbox change.
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ _handleThreadCheckboxChange = event => {
+ const { threads, changeThreads } = this.props;
+ const { checked, value } = event.target;
+
+ if (checked) {
+ if (!threads.includes(value)) {
+ changeThreads([...threads, value]);
+ }
+ } else {
+ changeThreads(threads.filter(thread => thread !== value));
+ }
+ };
+
+ /**
+ * Handle the checkbox change.
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ _handleFeaturesCheckboxChange = event => {
+ const { features, changeFeatures } = this.props;
+ const { checked, value } = event.target;
+
+ if (checked) {
+ if (!features.includes(value)) {
+ changeFeatures([value, ...features]);
+ }
+ } else {
+ changeFeatures(features.filter(feature => feature !== value));
+ }
+ };
+
+ _handleAddObjdir = () => {
+ const { objdirs, changeObjdirs } = this.props;
+ openFilePickerForObjdir(window, objdirs, changeObjdirs);
+ };
+
+ /**
+ * @param {number} index
+ * @return {void}
+ */
+ _handleRemoveObjdir = index => {
+ const { objdirs, changeObjdirs } = this.props;
+ const newObjdirs = [...objdirs];
+ newObjdirs.splice(index, 1);
+ changeObjdirs(newObjdirs);
+ };
+
+ /**
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ _setThreadTextFromInput = event => {
+ this.setState({ temporaryThreadText: event.target.value });
+ };
+
+ /**
+ * @param {React.ChangeEvent<HTMLInputElement>} event
+ */
+ _handleThreadTextCleanup = event => {
+ this.setState({ temporaryThreadText: null });
+ this.props.changeThreads(_threadTextToList(event.target.value));
+ };
+
+ /**
+ * @param {ThreadColumn[]} threadDisplay
+ * @param {number} index
+ * @return {React.ReactNode}
+ */
+ _renderThreadsColumns(threadDisplay, index) {
+ const { threads } = this.props;
+ const areAllThreadsIncluded = threads.includes("*");
+ return div(
+ { className: "perf-settings-thread-column", key: index },
+ threadDisplay.map(({ name, id, l10nId }) =>
+ Localized(
+ // The title is localized with a description of the thread.
+ { id: l10nId, attrs: { title: true }, key: name },
+ label(
+ {
+ className: `perf-settings-checkbox-label perf-settings-thread-label toggle-container-with-text ${
+ areAllThreadsIncluded
+ ? "perf-settings-checkbox-label-disabled"
+ : ""
+ }`,
+ },
+ input({
+ className: "perf-settings-checkbox",
+ id: `perf-settings-thread-checkbox-${id}`,
+ type: "checkbox",
+ // Do not localize the value, this is used internally by the profiler.
+ value: name,
+ checked: threads.includes(name),
+ disabled: areAllThreadsIncluded,
+ onChange: this._handleThreadCheckboxChange,
+ }),
+ span(null, name)
+ )
+ )
+ )
+ );
+ }
+ _renderThreads() {
+ const { temporaryThreadText } = this.state;
+ const { threads } = this.props;
+
+ return renderSection(
+ "perf-settings-threads-summary",
+ Localized({ id: "perftools-heading-threads" }, "Threads"),
+ div(
+ null,
+ div(
+ { className: "perf-settings-thread-columns" },
+ threadColumns.map((threadDisplay, index) =>
+ this._renderThreadsColumns(threadDisplay, index)
+ )
+ ),
+ this._renderJvmThreads(),
+ div(
+ {
+ className: "perf-settings-checkbox-label perf-settings-all-threads",
+ },
+ label(
+ {
+ className: "toggle-container-with-text",
+ },
+ input({
+ id: "perf-settings-thread-checkbox-all-threads",
+ type: "checkbox",
+ value: "*",
+ checked: threads.includes("*"),
+ onChange: this._handleThreadCheckboxChange,
+ }),
+ Localized({ id: "perftools-record-all-registered-threads" })
+ )
+ ),
+ div(
+ { className: "perf-settings-row" },
+ Localized(
+ { id: "perftools-tools-threads-input-label" },
+ label(
+ { className: "perf-settings-text-label" },
+ div(
+ null,
+ Localized(
+ { id: "perftools-custom-threads-label" },
+ "Add custom threads by name:"
+ )
+ ),
+ input({
+ className: "perf-settings-text-input",
+ id: "perftools-settings-thread-text",
+ type: "text",
+ value:
+ temporaryThreadText === null
+ ? threads.join(",")
+ : temporaryThreadText,
+ onBlur: this._handleThreadTextCleanup,
+ onFocus: this._setThreadTextFromInput,
+ onChange: this._setThreadTextFromInput,
+ })
+ )
+ )
+ )
+ )
+ );
+ }
+
+ _renderJvmThreads() {
+ if (!this.props.supportedFeatures.includes("java")) {
+ return null;
+ }
+
+ return [
+ h2(
+ null,
+ Localized({ id: "perftools-heading-threads-jvm" }, "JVM Threads")
+ ),
+ div(
+ { className: "perf-settings-thread-columns" },
+ jvmThreadColumns.map((threadDisplay, index) =>
+ this._renderThreadsColumns(threadDisplay, index)
+ )
+ ),
+ ];
+ }
+
+ /**
+ * @param {React.ReactNode} sectionTitle
+ * @param {FeatureDescription[]} features
+ * @param {boolean} isSupported
+ */
+ _renderFeatureSection(sectionTitle, features, isSupported) {
+ if (features.length === 0) {
+ return null;
+ }
+
+ // Note: This area is not localized. This area is pretty deep in the UI, and is mostly
+ // geared towards Firefox engineers. It may not be worth localizing. This decision
+ // can be tracked in Bug 1682333.
+
+ return div(
+ null,
+ h3(null, sectionTitle),
+ features.map(featureDescription => {
+ const { name, value, title, disabledReason } = featureDescription;
+ const extraClassName = isSupported
+ ? ""
+ : "perf-settings-checkbox-label-disabled";
+ return label(
+ {
+ className: `perf-settings-checkbox-label perf-toggle-label ${extraClassName}`,
+ key: value,
+ },
+ input({
+ id: `perf-settings-feature-checkbox-${value}`,
+ type: "checkbox",
+ value,
+ checked: isSupported && this.props.features.includes(value),
+ onChange: this._handleFeaturesCheckboxChange,
+ disabled: !isSupported,
+ }),
+ div(
+ { className: "perf-toggle-text-label" },
+ !isSupported && featureDescription.experimental
+ ? // Note when unsupported features are experimental.
+ `${name} (Experimental)`
+ : name
+ ),
+ div(
+ { className: "perf-toggle-description" },
+ title,
+ !isSupported && disabledReason
+ ? div(
+ { className: "perf-settings-feature-disabled-reason" },
+ disabledReason
+ )
+ : null
+ )
+ );
+ })
+ );
+ }
+
+ _renderFeatures() {
+ const { supportedFeatures } = this.props;
+
+ // Divvy up the features into their respective groups.
+ const recommended = [];
+ const supported = [];
+ const unsupported = [];
+ const experimental = [];
+
+ for (const feature of featureDescriptions) {
+ if (supportedFeatures.includes(feature.value)) {
+ if (feature.experimental) {
+ experimental.push(feature);
+ } else if (feature.recommended) {
+ recommended.push(feature);
+ } else {
+ supported.push(feature);
+ }
+ } else {
+ unsupported.push(feature);
+ }
+ }
+
+ return div(
+ { className: "perf-settings-sections" },
+ div(
+ null,
+ this._renderFeatureSection(
+ Localized(
+ { id: "perftools-heading-features-default" },
+ "Features (Recommended on by default)"
+ ),
+ recommended,
+ true
+ ),
+ this._renderFeatureSection(
+ Localized({ id: "perftools-heading-features" }, "Features"),
+ supported,
+ true
+ ),
+ this._renderFeatureSection(
+ Localized(
+ { id: "perftools-heading-features-experimental" },
+ "Experimental"
+ ),
+ experimental,
+ true
+ ),
+ this._renderFeatureSection(
+ Localized(
+ { id: "perftools-heading-features-disabled" },
+ "Disabled Features"
+ ),
+ unsupported,
+ false
+ )
+ )
+ );
+ }
+
+ _renderLocalBuildSection() {
+ const { objdirs } = this.props;
+ return renderSection(
+ "perf-settings-local-build-summary",
+ Localized({ id: "perftools-heading-local-build" }),
+ div(
+ null,
+ p(null, Localized({ id: "perftools-description-local-build" })),
+ DirectoryPicker({
+ dirs: objdirs,
+ onAdd: this._handleAddObjdir,
+ onRemove: this._handleRemoveObjdir,
+ })
+ )
+ );
+ }
+
+ render() {
+ return section(
+ { className: "perf-settings" },
+ h1(null, Localized({ id: "perftools-heading-settings" })),
+ h2(
+ { className: "perf-settings-title" },
+ Localized({ id: "perftools-heading-buffer" })
+ ),
+ Range({
+ label: Localized({ id: "perftools-range-interval-label" }),
+ value: this.props.interval,
+ id: "perf-range-interval",
+ scale: this._intervalExponentialScale,
+ display: _intervalTextDisplay,
+ onChange: this.props.changeInterval,
+ }),
+ Range({
+ label: Localized({ id: "perftools-range-entries-label" }),
+ value: this.props.entries,
+ id: "perf-range-entries",
+ scale: this._entriesExponentialScale,
+ display: _entriesTextDisplay,
+ onChange: this.props.changeEntries,
+ }),
+ this._renderThreads(),
+ this._renderFeatures(),
+ this._renderLocalBuildSection()
+ );
+ }
+}
+
+/**
+ * Clean up the thread list string into a list of values.
+ * @param {string} threads - Comma separated values.
+ * @return {string[]}
+ */
+function _threadTextToList(threads) {
+ return (
+ threads
+ // Split on commas
+ .split(",")
+ // Clean up any extraneous whitespace
+ .map(string => string.trim())
+ // Filter out any blank strings
+ .filter(string => string)
+ );
+}
+
+/**
+ * Format the interval number for display.
+ * @param {number} value
+ * @return {React.ReactNode}
+ */
+function _intervalTextDisplay(value) {
+ return Localized({
+ id: "perftools-range-interval-milliseconds",
+ $interval: value,
+ });
+}
+
+/**
+ * Format the entries number for display.
+ * @param {number} value
+ * @return {string}
+ */
+function _entriesTextDisplay(value) {
+ return formatFileSize(value * PROFILE_ENTRY_SIZE);
+}
+
+/**
+ * Renders a section for about:profiling.
+ *
+ * @param {string} id Unused.
+ * @param {React.ReactNode} title
+ * @param {React.ReactNode} children
+ * @returns React.ReactNode
+ */
+function renderSection(id, title, children) {
+ return div(
+ { className: "perf-settings-sections" },
+ div(null, h2(null, title), children)
+ );
+}
+
+/**
+ * @param {StoreState} state
+ * @returns {StateProps}
+ */
+function mapStateToProps(state) {
+ return {
+ interval: selectors.getInterval(state),
+ entries: selectors.getEntries(state),
+ features: selectors.getFeatures(state),
+ threads: selectors.getThreads(state),
+ threadsString: selectors.getThreadsString(state),
+ objdirs: selectors.getObjdirs(state),
+ supportedFeatures: selectors.getSupportedFeatures(state),
+ };
+}
+
+/** @type {ThunkDispatchProps} */
+const mapDispatchToProps = {
+ changeInterval: actions.changeInterval,
+ changeEntries: actions.changeEntries,
+ changeFeatures: actions.changeFeatures,
+ changeThreads: actions.changeThreads,
+ changeObjdirs: actions.changeObjdirs,
+};
+
+const SettingsConnected = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Settings);
+
+module.exports = SettingsConnected;
diff --git a/devtools/client/performance-new/components/aboutprofiling/moz.build b/devtools/client/performance-new/components/aboutprofiling/moz.build
new file mode 100644
index 0000000000..792041682e
--- /dev/null
+++ b/devtools/client/performance-new/components/aboutprofiling/moz.build
@@ -0,0 +1,12 @@
+# 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(
+ "AboutProfiling.js",
+ "DirectoryPicker.js",
+ "Presets.js",
+ "Range.js",
+ "Settings.js",
+)