From 086d2b57253f11dd66bf4403ad37455a322ff47c Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Wed, 4 Dec 2019 22:22:42 +0000 Subject: [PATCH] Bug 1598320 - Add types to the performance-new components; r=julienw Differential Revision: https://phabricator.services.mozilla.com/D54771 --HG-- extra : moz-landing-system : lando --- devtools/client/performance-new/.eslintrc.js | 8 +++ .../client/performance-new/@types/perf.d.ts | 17 +++++ .../performance-new/components/Description.js | 25 +++++-- .../components/DirectoryPicker.js | 52 ++++++++------ .../client/performance-new/components/Perf.js | 66 +++++++++++++----- .../performance-new/components/Range.js | 38 ++++++---- .../components/RecordingButton.js | 69 ++++++++++++++----- .../performance-new/components/Settings.js | 2 - devtools/client/performance-new/utils.js | 18 ++--- .../gecko-profiler-interface.js | 18 +++++ 10 files changed, 220 insertions(+), 93 deletions(-) create mode 100644 devtools/client/performance-new/.eslintrc.js diff --git a/devtools/client/performance-new/.eslintrc.js b/devtools/client/performance-new/.eslintrc.js new file mode 100644 index 000000000000..ed1285752944 --- /dev/null +++ b/devtools/client/performance-new/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + rules: { + // Props are checked by TypeScript, so we don't need dynamic type checking here. + "react/prop-types": "off" + } +}; diff --git a/devtools/client/performance-new/@types/perf.d.ts b/devtools/client/performance-new/@types/perf.d.ts index 39727d214cda..4ca19dc0ffb4 100644 --- a/devtools/client/performance-new/@types/perf.d.ts +++ b/devtools/client/performance-new/@types/perf.d.ts @@ -16,6 +16,7 @@ export interface PanelWindow { gTarget?: any; gInit(perfFront: any, preferenceFront: any): void; gDestroy(): void; + gReportReady?(): void } /** @@ -55,6 +56,8 @@ export interface PerfFront { isActive: () => MaybePromise; isSupportedPlatform: () => MaybePromise; isLockedForPrivateBrowsing: () => MaybePromise; + on: (type: string, listener: () => void) => void; + off: (type: string, listener: () => void) => void; /** * This method was was added in Firefox 72. */ @@ -356,3 +359,17 @@ export interface PerformancePref { export interface PopupWindow extends Window { gResizePopup?: (height: number) => void; } + +/** + * Scale a number value. + */ +export type NumberScaler = (value: number) => number; + +/** + * A collection of functions to scale numbers. + */ +export interface ScaleFunctions { + fromFractionToValue: NumberScaler, + fromValueToFraction: NumberScaler, + fromFractionToSingleDigitValue: NumberScaler, +} diff --git a/devtools/client/performance-new/components/Description.js b/devtools/client/performance-new/components/Description.js index 463ab4d5fb4c..da4efe8bce4e 100644 --- a/devtools/client/performance-new/components/Description.js +++ b/devtools/client/performance-new/components/Description.js @@ -1,6 +1,12 @@ /* 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 {{}} Props - This is an empty object. + */ + "use strict"; const { PureComponent } = require("devtools/client/shared/vendor/react"); @@ -13,25 +19,34 @@ const { /** * This component provides a helpful description for what is going on in the component * and provides some external links. + * @extends {React.PureComponent} */ class Description extends PureComponent { - static get propTypes() { - return {}; - } - + /** + * @param {Props} props + */ constructor(props) { super(props); this.handleLinkClick = this.handleLinkClick.bind(this); } + /** + * @param {React.MouseEvent} event + */ handleLinkClick(event) { const { openDocLink } = require("devtools/client/shared/link"); - openDocLink(event.target.value); + + /** @type HTMLButtonElement */ + const target = /** @type {any} */ (event.target); + + openDocLink(target.value, {}); } /** * Implement links as buttons to avoid any risk of loading the link in the * the panel. + * @param {string} href + * @param {string} text */ renderLink(href, text) { return button( diff --git a/devtools/client/performance-new/components/DirectoryPicker.js b/devtools/client/performance-new/components/DirectoryPicker.js index 310a6f62f80d..b37b0724429b 100644 --- a/devtools/client/performance-new/components/DirectoryPicker.js +++ b/devtools/client/performance-new/components/DirectoryPicker.js @@ -1,7 +1,17 @@ /* 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 } = require("devtools/client/shared/vendor/react"); const { div, @@ -12,29 +22,24 @@ const { const { withCommonPathPrefixRemoved, } = require("devtools/client/performance-new/utils"); -const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); - -// 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 | -// | | -// +---------------------------------------------+ -// -// [+] [-] +/** + * 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} + */ class DirectoryPicker extends PureComponent { - static get propTypes() { - return { - dirs: PropTypes.array.isRequired, - onAdd: PropTypes.func.isRequired, - onRemove: PropTypes.func.isRequired, - }; - } - + /** @param {Props} props */ constructor(props) { super(props); this._listBox = null; @@ -43,6 +48,9 @@ class DirectoryPicker extends PureComponent { this._handleRemoveButtonClick = this._handleRemoveButtonClick.bind(this); } + /** + * @param {HTMLSelectElement} element + */ _takeListBoxRef(element) { this._listBox = element; } @@ -64,7 +72,7 @@ class DirectoryPicker extends PureComponent { select( { className: "perf-settings-dir-list", - size: "4", + size: 4, ref: this._takeListBoxRef, }, dirs.map((fullPath, i) => diff --git a/devtools/client/performance-new/components/Perf.js b/devtools/client/performance-new/components/Perf.js index 9209f2a9647d..a0b727c70332 100644 --- a/devtools/client/performance-new/components/Perf.js +++ b/devtools/client/performance-new/components/Perf.js @@ -1,6 +1,40 @@ /* 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 {PerfFront} perfFront + * @property {RecordingState} recordingState + * @property {boolean?} isSupportedPlatform + * @property {boolean?} isPopup + * @property {string | null} promptEnvRestart + */ + +/** + * @typedef {Object} ThunkDispatchProps + * @property {typeof actions.changeRecordingState} changeRecordingState + * @property {typeof actions.reportProfilerReady} reportProfilerReady + */ + +/** + * @typedef {ResolveThunks} DispatchProps + * @typedef {StateProps & DispatchProps} Props + * @typedef {import("../@types/perf").PerfFront} PerfFront + * @typedef {import("../@types/perf").RecordingState} RecordingState + * @typedef {import("../@types/perf").State} StoreState + */ + +/** + * @typedef {import("../@types/perf").PanelWindow} PanelWindow + */ + "use strict"; const { @@ -12,7 +46,6 @@ const { div, button, } = require("devtools/client/shared/vendor/react-dom-factories"); -const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const RecordingButton = createFactory( require("devtools/client/performance-new/components/RecordingButton.js") ); @@ -38,23 +71,11 @@ const { * * 2) It mounts all of the sub components, but is itself very light on actual * markup for presentation. + * + * @extends {React.PureComponent} */ class Perf extends PureComponent { - static get propTypes() { - return { - // StateProps: - perfFront: PropTypes.object.isRequired, - recordingState: PropTypes.string.isRequired, - isSupportedPlatform: PropTypes.bool, - isPopup: PropTypes.bool, - promptEnvRestart: PropTypes.string, - - // DispatchProps: - changeRecordingState: PropTypes.func.isRequired, - reportProfilerReady: PropTypes.func.isRequired, - }; - } - + /** @param {Props} props */ constructor(props) { super(props); this.handleProfilerStarting = this.handleProfilerStarting.bind(this); @@ -103,8 +124,12 @@ class Perf extends PureComponent { // it will show. This defers the initial visibility of the popup until the // React components have fully rendered, and thus there is no annoying "blip" // to the screen when the page goes from fully blank, to showing the content. - if (window.gReportReady) { - window.gReportReady(); + /** @type {any} */ + const anyWindow = window; + /** @type {PanelWindow} - Coerce the window into the PanelWindow. */ + const { gReportReady } = anyWindow; + if (gReportReady) { + gReportReady(); } }); @@ -302,6 +327,10 @@ class Perf extends PureComponent { } } +/** + * @param {StoreState} state + * @returns {StateProps} + */ function mapStateToProps(state) { return { perfFront: selectors.getPerfFront(state), @@ -312,6 +341,7 @@ function mapStateToProps(state) { }; } +/** @type {ThunkDispatchProps} */ const mapDispatchToProps = { changeRecordingState: actions.changeRecordingState, reportProfilerReady: actions.reportProfilerReady, diff --git a/devtools/client/performance-new/components/Range.js b/devtools/client/performance-new/components/Range.js index a88a500d743b..e8c7ab3f656c 100644 --- a/devtools/client/performance-new/components/Range.js +++ b/devtools/client/performance-new/components/Range.js @@ -1,9 +1,23 @@ /* 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 {string} label + * @property {string} id + * @property {ScaleFunctions} scale + * @property {(value: number) => unknown} onChange + * @property {(value: number) => React.ReactNode} display + */ "use strict"; const { PureComponent } = require("devtools/client/shared/vendor/react"); -const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const { div, input, @@ -12,28 +26,22 @@ const { /** * Provide a numeric range slider UI that works off of custom numeric scales. + * @extends React.PureComponent */ class Range extends PureComponent { - static get propTypes() { - return { - value: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - id: PropTypes.string.isRequired, - scale: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - display: PropTypes.func.isRequired, - }; - } - + /** @param {Props} props */ constructor(props) { super(props); this.handleInput = this.handleInput.bind(this); } - handleInput(e) { - e.preventDefault(); + /** + * @param {React.ChangeEvent} event + */ + handleInput(event) { + event.preventDefault(); const { scale, onChange } = this.props; - const frac = e.target.value / 100; + const frac = Number(event.target.value) / 100; onChange(scale.fromFractionToSingleDigitValue(frac)); } diff --git a/devtools/client/performance-new/components/RecordingButton.js b/devtools/client/performance-new/components/RecordingButton.js index 70e64575e1ba..09f0cdd1b3a8 100644 --- a/devtools/client/performance-new/components/RecordingButton.js +++ b/devtools/client/performance-new/components/RecordingButton.js @@ -1,6 +1,36 @@ /* 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} isSupportedPlatform + * @property {boolean} recordingUnexpectedlyStopped + * @property {boolean} isPopup + */ + +/** + * @typedef {Object} ThunkDispatchProps + * @property {typeof actions.startRecording} startRecording + * @property {typeof actions.getProfileAndStopProfiler} getProfileAndStopProfiler + * @property {typeof actions.stopProfilerAndDiscardProfile} stopProfilerAndDiscardProfile + + */ + +/** + * @typedef {ResolveThunks} DispatchProps + * @typedef {StateProps & DispatchProps} Props + * @typedef {import("../@types/perf").RecordingState} RecordingState + * @typedef {import("../@types/perf").State} StoreState + */ + "use strict"; const { PureComponent } = require("devtools/client/shared/vendor/react"); @@ -10,7 +40,6 @@ const { span, img, } = require("devtools/client/shared/vendor/react-dom-factories"); -const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const { connect } = require("devtools/client/shared/vendor/react-redux"); const actions = require("devtools/client/performance-new/store/actions"); const selectors = require("devtools/client/performance-new/store/selectors"); @@ -19,29 +48,30 @@ const selectors = require("devtools/client/performance-new/store/selectors"); * 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 { - static get propTypes() { - return { - // StateProps - recordingState: PropTypes.string.isRequired, - isSupportedPlatform: PropTypes.bool, - recordingUnexpectedlyStopped: PropTypes.bool.isRequired, - isPopup: PropTypes.bool.isRequired, - - // DispatchProps - startRecording: PropTypes.func.isRequired, - getProfileAndStopProfiler: PropTypes.func.isRequired, - stopProfilerAndDiscardProfile: PropTypes.func.isRequired, - }; - } - + /** @param {Props} props */ constructor(props) { super(props); this._getProfileAndStopProfiler = () => this.props.getProfileAndStopProfiler(window); } + /** @param {{ + * disabled?: boolean, + * label?: React.ReactNode, + * onClick?: any, + * additionalMessage?: React.ReactNode, + * isPrimary?: boolean, + * isPopup?: boolean, + * additionalButton?: { + * label: string, + * onClick: any, + * }, + * }} buttonSettings + */ renderButton(buttonSettings) { const { disabled, @@ -70,7 +100,6 @@ class RecordingButton extends PureComponent { button( { className: `perf-photon-button perf-photon-button-${buttonClass} perf-button`, - "data-standalone": true, disabled, onClick, }, @@ -80,7 +109,6 @@ class RecordingButton extends PureComponent { ? button( { className: `perf-photon-button perf-photon-button-default perf-button`, - "data-standalone": true, onClick: additionalButton.onClick, disabled, }, @@ -184,6 +212,10 @@ class RecordingButton extends PureComponent { } } +/** + * @param {StoreState} state + * @returns {StateProps} + */ function mapStateToProps(state) { return { recordingState: selectors.getRecordingState(state), @@ -195,6 +227,7 @@ function mapStateToProps(state) { }; } +/** @type {ThunkDispatchProps} */ const mapDispatchToProps = { startRecording: actions.startRecording, stopProfilerAndDiscardProfile: actions.stopProfilerAndDiscardProfile, diff --git a/devtools/client/performance-new/components/Settings.js b/devtools/client/performance-new/components/Settings.js index a234129fe46e..3f7073da6c42 100644 --- a/devtools/client/performance-new/components/Settings.js +++ b/devtools/client/performance-new/components/Settings.js @@ -2,7 +2,6 @@ * 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 -/* eslint-disable react/prop-types */ /** * @typedef {Object} StateProps @@ -34,7 +33,6 @@ */ /** - * @typedef {import("react")} React * @typedef {import("../@types/perf").PopupWindow} PopupWindow * @typedef {import("../@types/perf").State} StoreState * @typedef {StateProps & DispatchProps} Props diff --git a/devtools/client/performance-new/utils.js b/devtools/client/performance-new/utils.js index b48737495791..00a6ed84d44c 100644 --- a/devtools/client/performance-new/utils.js +++ b/devtools/client/performance-new/utils.js @@ -2,6 +2,10 @@ * 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").NumberScaler} NumberScaler + * @typedef {import("./@types/perf").ScaleFunctions} ScaleFunctions + */ "use strict"; // @ts-ignore @@ -64,25 +68,13 @@ function formatFileSize(num) { return (neg ? "-" : "") + numStr + " " + unit; } -/** - * Scale a number value. - * - * @callback NumberScaler - * @param {number} value - * @returns {number} - */ - /** * Creates numbers that scale exponentially. * * @param {number} rangeStart * @param {number} rangeEnd * - * @returns {{ - * fromFractionToValue: NumberScaler, - * fromValueToFraction: NumberScaler, - * fromFractionToSingleDigitValue: NumberScaler, - * }} + * @returns {ScaleFunctions} */ function makeExponentialScale(rangeStart, rangeEnd) { const startExp = Math.log(rangeStart); diff --git a/devtools/shared/performance-new/gecko-profiler-interface.js b/devtools/shared/performance-new/gecko-profiler-interface.js index c9aaefea32f4..dd3f5e8fec0f 100644 --- a/devtools/shared/performance-new/gecko-profiler-interface.js +++ b/devtools/shared/performance-new/gecko-profiler-interface.js @@ -244,6 +244,24 @@ class ActorReadyGeckoProfilerInterface { } return Services.profiler.GetFeatures(); } + + /** + * @param {string} type + * @param {() => void} listener + */ + on(type, listener) { + // This is a stub for TypeScript. This function is assigned by the EventEmitter + // decorator. + } + + /** + * @param {string} type + * @param {() => void} listener + */ + off(type, listener) { + // This is a stub for TypeScript. This function is assigned by the EventEmitter + // decorator. + } } exports.ActorReadyGeckoProfilerInterface = ActorReadyGeckoProfilerInterface;