From 9ffc475983363755169115cc0bb063b1dca2e3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Wed, 30 Jul 2025 15:49:15 +0200 Subject: [PATCH 001/124] Change the 'JavaScript' radio button label to 'Script' so it makes sense when profiling other scripting languages like Python. --- locales/en-US/app.ftl | 4 +- .../shared/StackImplementationSetting.js | 2 +- src/test/components/StackSettings.test.js | 4 +- .../__snapshots__/FlameGraph.test.js.snap | 6 +- .../__snapshots__/MarkerChart.test.js.snap | 8 +- .../__snapshots__/MarkerTable.test.js.snap | 8 +- .../ProfileCallTreeView.test.js.snap | 104 +++++++++--------- .../__snapshots__/StackChart.test.js.snap | 30 ++--- .../__snapshots__/StackSettings.test.js.snap | 8 +- 9 files changed, 87 insertions(+), 87 deletions(-) diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl index 0b1ebdaed5..748963e31f 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -786,8 +786,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = All frames .title = Do not filter the stack frames -StackSettings--implementation-javascript2 = JavaScript - .title = Show only the stack frames related to JavaScript execution +StackSettings--implementation-script = Script + .title = Show only the stack frames related to script execution StackSettings--implementation-native2 = Native .title = Show only the stack frames for native code diff --git a/src/components/shared/StackImplementationSetting.js b/src/components/shared/StackImplementationSetting.js index b55d9d2b19..5f428f62c8 100644 --- a/src/components/shared/StackImplementationSetting.js +++ b/src/components/shared/StackImplementationSetting.js @@ -85,7 +85,7 @@ class StackImplementationSettingImpl extends PureComponent { 'combined' )} {this._renderImplementationRadioButton( - 'StackSettings--implementation-javascript2', + 'StackSettings--implementation-script', 'js' )} {this._renderImplementationRadioButton( diff --git a/src/test/components/StackSettings.test.js b/src/test/components/StackSettings.test.js index 069dd2a83d..c5d68d1a02 100644 --- a/src/test/components/StackSettings.test.js +++ b/src/test/components/StackSettings.test.js @@ -44,10 +44,10 @@ describe('StackSettings', function () { return (element: any).checked; } - it('can change the implementation filter to JavaScript', async function () { + it('can change the implementation filter to script', async function () { const { getByLabelText, getState } = setup(); expect(getImplementationFilter(getState())).toEqual('combined'); - const radioButton = getByLabelText(/JavaScript/); + const radioButton = getByLabelText(/Script/); fireFullClick(radioButton); diff --git a/src/test/components/__snapshots__/FlameGraph.test.js.snap b/src/test/components/__snapshots__/FlameGraph.test.js.snap index 661d12aac9..7070e5dfa6 100644 --- a/src/test/components/__snapshots__/FlameGraph.test.js.snap +++ b/src/test/components/__snapshots__/FlameGraph.test.js.snap @@ -433,16 +433,16 @@ exports[`FlameGraph matches the snapshot 1`] = ` @@ -29,16 +29,16 @@ exports[`StackSettings matches the snapshot 1`] = ` Date: Thu, 31 Jul 2025 16:54:09 +0200 Subject: [PATCH 002/124] Update all Yarn dependencies (2025-07-30) (#5529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Co-authored-by: Nazım Can Altınova --- package.json | 6 +++--- yarn.lock | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7e0841452a..cf9efcbc2e 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@codemirror/lang-rust": "^6.0.2", "@codemirror/language": "^6.11.2", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.38.0", + "@codemirror/view": "^6.38.1", "@firefox-devtools/react-contextmenu": "^5.2.2", "@fluent/bundle": "^0.19.1", "@fluent/langneg": "^0.7.0", @@ -128,7 +128,7 @@ "babel-loader": "^10.0.0", "babel-plugin-module-resolver": "^5.0.2", "browserslist": "^4.25.1", - "caniuse-lite": "^1.0.30001726", + "caniuse-lite": "^1.0.30001727", "circular-dependency-plugin": "^5.2.1", "codecov": "^3.8.3", "copy-webpack-plugin": "^13.0.0", @@ -137,7 +137,7 @@ "cssnano": "^7.1.0", "devtools-license-check": "^0.9.0", "eslint": "^8.57.1", - "eslint-config-prettier": "^10.1.5", + "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.32.0", diff --git a/yarn.lock b/yarn.lock index c6e7644a03..793270eeca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1130,10 +1130,10 @@ dependencies: "@marijn/find-cluster-break" "^1.0.0" -"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.38.0": - version "6.38.0" - resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.38.0.tgz#4486062b791a4247793e0953e05ae71a9e172217" - integrity sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg== +"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.38.1": + version "6.38.1" + resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.38.1.tgz#74214434351719ec0710431363a85f7a01e80a73" + integrity sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ== dependencies: "@codemirror/state" "^6.5.0" crelt "^1.0.6" @@ -3511,10 +3511,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726: - version "1.0.30001726" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz#a15bd87d5a4bf01f6b6f70ae7c97fdfd28b5ae47" - integrity sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726, caniuse-lite@^1.0.30001727: + version "1.0.30001727" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" + integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== ccount@^2.0.0: version "2.0.1" @@ -4803,10 +4803,10 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -eslint-config-prettier@^10.1.5: - version "10.1.5" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz#00c18d7225043b6fbce6a665697377998d453782" - integrity sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw== +eslint-config-prettier@^10.1.8: + version "10.1.8" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" + integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== eslint-import-resolver-alias@^1.1.2: version "1.1.2" From db6c334b242c34ce5c71ef58d4f1d1f832d2b858 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 31 Jul 2025 17:31:59 +0000 Subject: [PATCH 003/124] Pontoon/Firefox Profiler: Update Portuguese (Brazil) (pt-BR) Co-authored-by: Marcelo Ghelman (pt-BR) --- locales/pt-BR/app.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl index 60a2ba4b2d..dee9eee23c 100644 --- a/locales/pt-BR/app.ftl +++ b/locales/pt-BR/app.ftl @@ -678,6 +678,7 @@ StackSettings--call-tree-strategy-native-deallocations-sites = Locais de desaloc StackSettings--invert-call-stack = Inverter pilha de chamadas .title = Ordenar pelo tempo gasto em um node de chamadas, ignorando seus filhos. StackSettings--show-user-timing = Mostrar tempo do usuário +StackSettings--use-stack-chart-same-widths = Usar a mesma largura em cada pilha StackSettings--panel-search = .label = Filtrar pilhas: .title = Só exibir pilhas que contêm uma função cujo nome corresponde a esta substring From afc9d6a16d17f05ac1f0043c16fafefd145d80bc Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:49:58 +0200 Subject: [PATCH 004/124] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20cross-env?= =?UTF-8?q?=20to=20version=2010.0.0=20(#5536)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index cf9efcbc2e..a5ee7cc5da 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "circular-dependency-plugin": "^5.2.1", "codecov": "^3.8.3", "copy-webpack-plugin": "^13.0.0", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "css-loader": "^7.1.2", "cssnano": "^7.1.0", "devtools-license-check": "^0.9.0", diff --git a/yarn.lock b/yarn.lock index 793270eeca..992659e855 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1210,6 +1210,11 @@ dependencies: tslib "^2.4.0" +"@epic-web/invariant@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" + integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" @@ -3974,14 +3979,15 @@ crelt@^1.0.5, crelt@^1.0.6: resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== +cross-env@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.0.0.tgz#ba25823cfa1ed6af293dcded8796fa16cd162456" + integrity sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q== dependencies: - cross-spawn "^7.0.1" + "@epic-web/invariant" "^1.0.0" + cross-spawn "^7.0.6" -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== From 461794a5bd762dc094efc4bff790e1c6492474d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 4 Aug 2025 12:53:48 +0200 Subject: [PATCH 005/124] Display the marker description at the top inside the marker tooltips (#5534) --- src/components/tooltip/Marker.js | 22 +++++------ .../__snapshots__/MarkerChart.test.js.snap | 38 +++++++++---------- .../__snapshots__/StackChart.test.js.snap | 36 +++++++++--------- .../__snapshots__/TooltipMarker.test.js.snap | 18 ++++----- 4 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/components/tooltip/Marker.js b/src/components/tooltip/Marker.js index 561b92d80c..f6a4d7a1f7 100644 --- a/src/components/tooltip/Marker.js +++ b/src/components/tooltip/Marker.js @@ -241,6 +241,17 @@ class MarkerTooltipContents extends React.PureComponent { // Add the details for the markers based on their Marker schema. const schema = getSchemaFromMarker(markerSchemaByName, marker.data); if (schema) { + if (schema.description) { + const key = schema.name + '-description'; + details.push( + +
+ {schema.description} +
+
+ ); + } + for (const field of schema.fields) { if (field.hidden) { // Do not include hidden fields. @@ -268,17 +279,6 @@ class MarkerTooltipContents extends React.PureComponent { ); } - - if (schema.description) { - const key = schema.name + '-description'; - details.push( - -
- {schema.description} -
-
- ); - } } switch (data.type) { diff --git a/src/test/components/__snapshots__/MarkerChart.test.js.snap b/src/test/components/__snapshots__/MarkerChart.test.js.snap index 7e9cfd7000..e53e60fa3e 100644 --- a/src/test/components/__snapshots__/MarkerChart.test.js.snap +++ b/src/test/components/__snapshots__/MarkerChart.test.js.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`MarkerChart EmptyReasons shows a reason when a profile has no markers 1`] = `
- Name + Description :
- Marker B
- Entry Type - : + UserTiming is created using the DOM APIs performance.mark() and performance.measure().
- measure
- Description + Name :
+ Marker B
- UserTiming is created using the DOM APIs performance.mark() and performance.measure(). + Entry Type + :
+ measure
@@ -374,28 +374,28 @@ exports[`MarkerChart renders the hoveredItem markers properly 2`] = `
- Name + Description :
- Marker B
- Entry Type - : + UserTiming is created using the DOM APIs performance.mark() and performance.measure().
- measure
- Description + Name :
+ Marker B
- UserTiming is created using the DOM APIs performance.mark() and performance.measure(). + Entry Type + :
+ measure
diff --git a/src/test/components/__snapshots__/StackChart.test.js.snap b/src/test/components/__snapshots__/StackChart.test.js.snap index cb4747d4e4..bd0e3dca1d 100644 --- a/src/test/components/__snapshots__/StackChart.test.js.snap +++ b/src/test/components/__snapshots__/StackChart.test.js.snap @@ -869,28 +869,28 @@ exports[`MarkerChart shows a tooltip when hovering 1`] = `
- Name + Description :
- componentB
- Entry Type - : + UserTiming is created using the DOM APIs performance.mark() and performance.measure().
- measure
- Description + Name :
+ componentB
- UserTiming is created using the DOM APIs performance.mark() and performance.measure(). + Entry Type + :
+ measure
@@ -936,28 +936,28 @@ exports[`MarkerChart shows a tooltip when hovering 2`] = `
- Name + Description :
- componentA
- Entry Type - : + UserTiming is created using the DOM APIs performance.mark() and performance.measure().
- measure
- Description + Name :
+ componentA
- UserTiming is created using the DOM APIs performance.mark() and performance.measure(). + Entry Type + :
+ measure
diff --git a/src/test/components/__snapshots__/TooltipMarker.test.js.snap b/src/test/components/__snapshots__/TooltipMarker.test.js.snap index 7d58b48927..18acd2e838 100644 --- a/src/test/components/__snapshots__/TooltipMarker.test.js.snap +++ b/src/test/components/__snapshots__/TooltipMarker.test.js.snap @@ -4684,28 +4684,28 @@ exports[`TooltipMarker renders tooltips for various markers: UserTiming-12.5 1`]
- Name + Description :
- foobar
- Entry Type - : + UserTiming is created using the DOM APIs performance.mark() and performance.measure().
- mark
- Description + Name :
+ foobar
- UserTiming is created using the DOM APIs performance.mark() and performance.measure(). + Entry Type + :
+ mark
From e554500de657a84e5ca60e516fb1bad1c420ecf9 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 10:49:11 -0400 Subject: [PATCH 006/124] Null-initialize fields that can't be undefined. --- src/components/app/Home.js | 2 +- src/components/app/MenuButtons/Permalink.js | 2 +- src/components/shared/WithSize.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/app/Home.js b/src/components/app/Home.js index 08864beb0f..c11b8de722 100644 --- a/src/components/app/Home.js +++ b/src/components/app/Home.js @@ -54,7 +54,7 @@ class ActionButtons extends React.PureComponent< ActionButtonsProps, ActionButtonsState, > { - _fileInput: HTMLInputElement | null; + _fileInput: HTMLInputElement | null = null; state = { isLoadFromUrlPressed: false, diff --git a/src/components/app/MenuButtons/Permalink.js b/src/components/app/MenuButtons/Permalink.js index 76c0ace3de..b3f19db089 100644 --- a/src/components/app/MenuButtons/Permalink.js +++ b/src/components/app/MenuButtons/Permalink.js @@ -27,7 +27,7 @@ type State = {| |}; export class MenuButtonsPermalink extends React.PureComponent { - _permalinkTextField: HTMLInputElement | null; + _permalinkTextField: HTMLInputElement | null = null; _takePermalinkTextFieldRef = (elem: HTMLInputElement | null) => { this._permalinkTextField = elem; }; diff --git a/src/components/shared/WithSize.js b/src/components/shared/WithSize.js index a858194b6b..bbbec43043 100644 --- a/src/components/shared/WithSize.js +++ b/src/components/shared/WithSize.js @@ -45,7 +45,7 @@ export function withSize< // eslint-disable-next-line flowtype/no-existential-type return class WithSizeWrapper extends React.PureComponent<*, State> { state = { width: 0, height: 0 }; - _container: HTMLElement | null; + _container: HTMLElement | null = null; componentDidMount() { const container = findDOMNode(this); // eslint-disable-line react/no-find-dom-node From 39b08d59f0b6f678c1c2915de3f1386c6e259d9e Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 11:00:53 -0400 Subject: [PATCH 007/124] Remove unused member. --- src/profile-logic/call-tree.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 32ad1e7e75..71d52b15c0 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -195,7 +195,6 @@ class CallTreeInternalInverted implements CallTreeInternal { _nonInvertedCallNodeTable: CallNodeTable; _callNodeSelf: Float64Array; _rootNodes: IndexIntoCallNodeTable[]; - _funcCount: number; _totalPerRootFunc: Float64Array; _hasChildrenPerRootFunc: Uint8Array; _totalAndHasChildrenPerNonRootNode: Map< From 9e32dabc671739f56bf172818372e37474a5b330 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 13:36:20 -0400 Subject: [PATCH 008/124] Don't calculate null + 2. --- src/components/network-chart/NetworkChartRow.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/network-chart/NetworkChartRow.js b/src/components/network-chart/NetworkChartRow.js index ac448d90b3..6b70035219 100644 --- a/src/components/network-chart/NetworkChartRow.js +++ b/src/components/network-chart/NetworkChartRow.js @@ -395,6 +395,10 @@ export class NetworkChartRow extends React.PureComponent< // Remove `Load 123: ` from markers.name _cropNameToUrl(name: string): string { + const colonPos = this._findIndexOfLoadid(name); + if (colonPos === null) { + return ''; + } const url = name.slice(this._findIndexOfLoadid(name) + 2); return url; } From d4747567c99dddf31a203c4e75603a531d57ffa0 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 11:05:32 -0400 Subject: [PATCH 009/124] Change withSize to accept PropsWithoutSize as its type parameter. This simplifies porting it to TypeScript. Also move some withSize calls to be within the explicitConnect call because that allows Flow to infer the props argument in more cases. I'm actually not sure why I have to manually pass $Diff in many cases still - TypeScript infers the parameter correctly and we can leave it out everywhere. --- src/components/network-chart/index.js | 64 ++++++++++--------- src/components/shared/WithSize.js | 28 +++----- src/components/shared/thread/ActivityGraph.js | 4 +- src/components/shared/thread/SampleGraph.js | 4 +- .../timeline/EmptyThreadIndicator.js | 4 +- src/components/timeline/FullTimeline.js | 2 +- src/components/timeline/Markers.js | 20 ++---- .../timeline/TrackBandwidthGraph.js | 2 +- .../timeline/TrackCustomMarkerGraph.js | 2 +- .../timeline/TrackEventDelayGraph.js | 2 +- src/components/timeline/TrackMemoryGraph.js | 2 +- src/components/timeline/TrackNetwork.js | 2 +- src/components/timeline/TrackPowerGraph.js | 2 +- .../timeline/TrackProcessCPUGraph.js | 2 +- src/components/timeline/TrackScreenshots.js | 2 +- src/components/timeline/TrackThread.js | 2 +- .../timeline/TrackVisualProgressGraph.js | 2 +- src/test/types/with-size.js | 11 +++- 18 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/components/network-chart/index.js b/src/components/network-chart/index.js index f9f3e4860c..132bfd065e 100644 --- a/src/components/network-chart/index.js +++ b/src/components/network-chart/index.js @@ -43,7 +43,8 @@ import './index.css'; const ROW_HEIGHT = 16; -// The SizeProps are injected by the WithSize higher order component. +type OwnProps = {||}; + type DispatchProps = {| +changeSelectedNetworkMarker: typeof changeSelectedNetworkMarker, +changeRightClickedMarker: typeof changeRightClickedMarker, @@ -62,9 +63,10 @@ type StateProps = {| +scrollToSelectionGeneration: number, |}; -type OwnProps = {| ...SizeProps |}; - -type Props = ConnectedProps; +type Props = {| + ...SizeProps, + ...ConnectedProps, +|}; class NetworkChartImpl extends React.PureComponent { _virtualListRef = React.createRef>(); @@ -368,33 +370,33 @@ class NetworkChartImpl extends React.PureComponent { * Wrap the component in the WithSize higher order component, as well as the redux * connected component. */ -const ConnectedComponent = explicitConnect( - { - mapStateToProps: (state) => ({ - markerIndexes: - selectedThreadSelectors.getSearchFilteredNetworkMarkerIndexes(state), - scrollToSelectionGeneration: getScrollToSelectionGeneration(state), - getMarker: selectedThreadSelectors.getMarkerGetter(state), - selectedNetworkMarkerIndex: - selectedThreadSelectors.getSelectedNetworkMarkerIndex(state), - rightClickedMarkerIndex: - selectedThreadSelectors.getRightClickedMarkerIndex(state), - hoveredMarkerIndexFromState: - selectedThreadSelectors.getHoveredMarkerIndex(state), - timeRange: getPreviewSelectionRange(state), - disableOverscan: getPreviewSelection(state).isModifying, - threadsKey: getSelectedThreadsKey(state), - }), - mapDispatchToProps: { - changeSelectedNetworkMarker, - changeRightClickedMarker, - changeHoveredMarker, - }, - component: NetworkChartImpl, - } -); - -export const NetworkChart = withSize(ConnectedComponent); +export const NetworkChart = explicitConnect< + OwnProps, + StateProps, + DispatchProps, +>({ + mapStateToProps: (state) => ({ + markerIndexes: + selectedThreadSelectors.getSearchFilteredNetworkMarkerIndexes(state), + scrollToSelectionGeneration: getScrollToSelectionGeneration(state), + getMarker: selectedThreadSelectors.getMarkerGetter(state), + selectedNetworkMarkerIndex: + selectedThreadSelectors.getSelectedNetworkMarkerIndex(state), + rightClickedMarkerIndex: + selectedThreadSelectors.getRightClickedMarkerIndex(state), + hoveredMarkerIndexFromState: + selectedThreadSelectors.getHoveredMarkerIndex(state), + timeRange: getPreviewSelectionRange(state), + disableOverscan: getPreviewSelection(state).isModifying, + threadsKey: getSelectedThreadsKey(state), + }), + mapDispatchToProps: { + changeSelectedNetworkMarker, + changeRightClickedMarker, + changeHoveredMarker, + }, + component: withSize(NetworkChartImpl), +}); /** * Our definition of markers does not currently have the ability to refine diff --git a/src/components/shared/WithSize.js b/src/components/shared/WithSize.js index a858194b6b..7240279266 100644 --- a/src/components/shared/WithSize.js +++ b/src/components/shared/WithSize.js @@ -15,6 +15,8 @@ type State = {| export type SizeProps = $ReadOnly; +export type PropsWithSize = {| ...Props, ...SizeProps |}; + /** * Wraps a React component and makes 'width' and 'height' available in the * wrapped component's props. These props start out at zero and are updated to @@ -24,26 +26,14 @@ export type SizeProps = $ReadOnly; * * Note that the props are *not* updated if the size of the element changes * for reasons other than a window resize. - * - * Usage: withSize must be used with explicit type arguments. - * - * Correct: withSize(ComponentClass) - * Incorrect: withSize(ComponentClass) */ -export function withSize< - // The SizeProps act as a bounds on the generic props. This ensures that the props - // that passed in take into account they are being given the width and height. - Props: $ReadOnly<{ ...SizeProps }>, ->(Wrapped: React.ComponentType): React.ComponentType< - // The component that is returned does not accept width and height parameters, as - // they are injected by this higher order component. - $ReadOnly<$Diff>, -> { - // An existential type in a generic is a bit tricky to remove. Perhaps this can - // use a hook instead. - // See: https://github.com/firefox-devtools/profiler/issues/3062 - // eslint-disable-next-line flowtype/no-existential-type - return class WithSizeWrapper extends React.PureComponent<*, State> { +export function withSize( + Wrapped: React.ComponentType> +): React.ComponentType { + return class WithSizeWrapper extends React.PureComponent< + Props, + State, + > { state = { width: 0, height: 0 }; _container: HTMLElement | null; diff --git a/src/components/shared/thread/ActivityGraph.js b/src/components/shared/thread/ActivityGraph.js index b541f1df29..465d00839b 100644 --- a/src/components/shared/thread/ActivityGraph.js +++ b/src/components/shared/thread/ActivityGraph.js @@ -199,4 +199,6 @@ class ThreadActivityGraphImpl extends React.PureComponent { } } -export const ThreadActivityGraph = withSize(ThreadActivityGraphImpl); +export const ThreadActivityGraph = withSize<$Diff>( + ThreadActivityGraphImpl +); diff --git a/src/components/shared/thread/SampleGraph.js b/src/components/shared/thread/SampleGraph.js index 533448256b..da7f530c7f 100644 --- a/src/components/shared/thread/SampleGraph.js +++ b/src/components/shared/thread/SampleGraph.js @@ -393,4 +393,6 @@ export class ThreadSampleGraphImpl extends PureComponent { } } -export const ThreadSampleGraph = withSize(ThreadSampleGraphImpl); +export const ThreadSampleGraph = withSize<$Diff>( + ThreadSampleGraphImpl +); diff --git a/src/components/timeline/EmptyThreadIndicator.js b/src/components/timeline/EmptyThreadIndicator.js index 733632f589..b26d31ea75 100644 --- a/src/components/timeline/EmptyThreadIndicator.js +++ b/src/components/timeline/EmptyThreadIndicator.js @@ -143,4 +143,6 @@ export function getIndicatorPositions(props: Props): {| return { startup, shutdown, emptyBufferStart }; } -export const EmptyThreadIndicator = withSize(EmptyThreadIndicatorImpl); +export const EmptyThreadIndicator = withSize<$Diff>( + EmptyThreadIndicatorImpl +); diff --git a/src/components/timeline/FullTimeline.js b/src/components/timeline/FullTimeline.js index 024162d14d..b50508dc16 100644 --- a/src/components/timeline/FullTimeline.js +++ b/src/components/timeline/FullTimeline.js @@ -222,5 +222,5 @@ export const FullTimeline = explicitConnect< changeGlobalTrackOrder, changeRightClickedTrack, }, - component: withSize(FullTimelineImpl), + component: withSize(FullTimelineImpl), }); diff --git a/src/components/timeline/Markers.js b/src/components/timeline/Markers.js index dda437aa9f..02ce6e5736 100644 --- a/src/components/timeline/Markers.js +++ b/src/components/timeline/Markers.js @@ -219,7 +219,7 @@ class TimelineMarkersCanvas extends React.PureComponent { this._requestedAnimationFrame = false; const c = this._canvas.current; if (c) { - timeCode('TimelineMarkersImplementation render', () => { + timeCode('TimelineMarkers render', () => { this.drawCanvas(c); }); } @@ -316,7 +316,7 @@ type State = { mouseY: CssPixels, }; -class TimelineMarkersImplementation extends React.PureComponent { +class TimelineMarkers extends React.PureComponent { state = { hoveredMarkerIndex: null, mouseDownMarker: null, @@ -505,12 +505,6 @@ class TimelineMarkersImplementation extends React.PureComponent { } } -/** - * Combine the base implementation of the TimelineMarkers with the - * WithSize component. - */ -export const TimelineMarkers = withSize(TimelineMarkersImplementation); - /** * Memoize the isSelected result of the markers since this is user multiple times. */ @@ -543,7 +537,7 @@ export const TimelineMarkersJank = explicitConnect< }; }, mapDispatchToProps: { changeRightClickedMarker }, - component: TimelineMarkers, + component: withSize(TimelineMarkers), }); /** @@ -572,7 +566,7 @@ export const TimelineMarkersOverview = explicitConnect< }; }, mapDispatchToProps: { changeRightClickedMarker }, - component: TimelineMarkers, + component: withSize(TimelineMarkers), }); /** @@ -598,7 +592,7 @@ export const TimelineMarkersFileIo = explicitConnect< }; }, mapDispatchToProps: { changeRightClickedMarker }, - component: TimelineMarkers, + component: withSize(TimelineMarkers), }); /** @@ -625,7 +619,7 @@ export const TimelineMarkersMemory = explicitConnect< }; }, mapDispatchToProps: { changeRightClickedMarker }, - component: TimelineMarkers, + component: withSize(TimelineMarkers), }); /** @@ -652,5 +646,5 @@ export const TimelineMarkersIPC = explicitConnect< }; }, mapDispatchToProps: { changeRightClickedMarker }, - component: TimelineMarkers, + component: withSize(TimelineMarkers), }); diff --git a/src/components/timeline/TrackBandwidthGraph.js b/src/components/timeline/TrackBandwidthGraph.js index 360b3daa7b..daf6ead991 100644 --- a/src/components/timeline/TrackBandwidthGraph.js +++ b/src/components/timeline/TrackBandwidthGraph.js @@ -711,5 +711,5 @@ export const TrackBandwidthGraph = explicitConnect< previewSelection: getPreviewSelection(state), }; }, - component: withSize(TrackBandwidthGraphImpl), + component: withSize(TrackBandwidthGraphImpl), }); diff --git a/src/components/timeline/TrackCustomMarkerGraph.js b/src/components/timeline/TrackCustomMarkerGraph.js index fb1d643989..501902dafb 100644 --- a/src/components/timeline/TrackCustomMarkerGraph.js +++ b/src/components/timeline/TrackCustomMarkerGraph.js @@ -631,5 +631,5 @@ export const TrackCustomMarkerGraph = explicitConnect< getMarker: selectors.getMarkerGetter(state), }; }, - component: withSize(TrackCustomMarkerGraphImpl), + component: withSize(TrackCustomMarkerGraphImpl), }); diff --git a/src/components/timeline/TrackEventDelayGraph.js b/src/components/timeline/TrackEventDelayGraph.js index f4c1610473..063f825400 100644 --- a/src/components/timeline/TrackEventDelayGraph.js +++ b/src/components/timeline/TrackEventDelayGraph.js @@ -380,5 +380,5 @@ export const TrackEventDelayGraph = explicitConnect< eventDelays: selectors.getProcessedEventDelays(state), }; }, - component: withSize(TrackEventDelayGraphImpl), + component: withSize(TrackEventDelayGraphImpl), }); diff --git a/src/components/timeline/TrackMemoryGraph.js b/src/components/timeline/TrackMemoryGraph.js index 9e02fa9697..257d28ff66 100644 --- a/src/components/timeline/TrackMemoryGraph.js +++ b/src/components/timeline/TrackMemoryGraph.js @@ -550,5 +550,5 @@ export const TrackMemoryGraph = explicitConnect< unfilteredSamplesRange: selectors.unfilteredSamplesRange(state), }; }, - component: withSize(TrackMemoryGraphImpl), + component: withSize(TrackMemoryGraphImpl), }); diff --git a/src/components/timeline/TrackNetwork.js b/src/components/timeline/TrackNetwork.js index a3319ef28a..85fe39f7b5 100644 --- a/src/components/timeline/TrackNetwork.js +++ b/src/components/timeline/TrackNetwork.js @@ -445,5 +445,5 @@ export const TrackNetwork = explicitConnect< changeSelectedNetworkMarker, changeHoveredMarker, }, - component: withSize(Network), + component: withSize(Network), }); diff --git a/src/components/timeline/TrackPowerGraph.js b/src/components/timeline/TrackPowerGraph.js index 1e52625862..3d99271882 100644 --- a/src/components/timeline/TrackPowerGraph.js +++ b/src/components/timeline/TrackPowerGraph.js @@ -575,5 +575,5 @@ export const TrackPowerGraph = explicitConnect< unfilteredSamplesRange: selectors.unfilteredSamplesRange(state), }; }, - component: withSize(TrackPowerGraphImpl), + component: withSize(TrackPowerGraphImpl), }); diff --git a/src/components/timeline/TrackProcessCPUGraph.js b/src/components/timeline/TrackProcessCPUGraph.js index 7ececacb0e..97c0f21d73 100644 --- a/src/components/timeline/TrackProcessCPUGraph.js +++ b/src/components/timeline/TrackProcessCPUGraph.js @@ -478,5 +478,5 @@ export const TrackProcessCPUGraph = explicitConnect< unfilteredSamplesRange: selectors.unfilteredSamplesRange(state), }; }, - component: withSize(TrackProcessCPUGraphImpl), + component: withSize(TrackProcessCPUGraphImpl), }); diff --git a/src/components/timeline/TrackScreenshots.js b/src/components/timeline/TrackScreenshots.js index ca69bd6757..8889528dee 100644 --- a/src/components/timeline/TrackScreenshots.js +++ b/src/components/timeline/TrackScreenshots.js @@ -211,7 +211,7 @@ export const TimelineTrackScreenshots = explicitConnect< mapDispatchToProps: { updatePreviewSelection, }, - component: withSize(Screenshots), + component: withSize(Screenshots), }); type HoverPreviewProps = {| diff --git a/src/components/timeline/TrackThread.js b/src/components/timeline/TrackThread.js index 85b2d6b43e..df81326dd1 100644 --- a/src/components/timeline/TrackThread.js +++ b/src/components/timeline/TrackThread.js @@ -384,5 +384,5 @@ export const TimelineTrackThread = explicitConnect< selectSelfCallNode, reportTrackThreadHeight, }, - component: withSize(TimelineTrackThreadImpl), + component: withSize(TimelineTrackThreadImpl), }); diff --git a/src/components/timeline/TrackVisualProgressGraph.js b/src/components/timeline/TrackVisualProgressGraph.js index ae9f23019d..3faea2481c 100644 --- a/src/components/timeline/TrackVisualProgressGraph.js +++ b/src/components/timeline/TrackVisualProgressGraph.js @@ -348,5 +348,5 @@ export const TrackVisualProgressGraph = explicitConnect< interval: getProfileInterval(state), }; }, - component: withSize(TrackVisualProgressGraphImpl), + component: withSize(TrackVisualProgressGraphImpl), }); diff --git a/src/test/types/with-size.js b/src/test/types/with-size.js index 06a39dd9b4..b8bf6173c9 100644 --- a/src/test/types/with-size.js +++ b/src/test/types/with-size.js @@ -48,7 +48,8 @@ const example2 = ( /> ); -const ExampleComponentWithSize = withSize(ExampleComponent); +const ExampleComponentWithSize = + withSize<$Diff>(ExampleComponent); // This it the correct use const exampleWithSize1 = ; @@ -79,5 +80,9 @@ class NoSizing extends React.PureComponent { } } -// $FlowExpectError - The component does not have sizing props. -const exampleNoSizing = withSize(NoSizing); +// This test no longer works. +// Not sure why Flow accepts NoSizing as a React.ComponentType> +// TypeScript catches this particular error, so this test will be re-enabled once +// we migrate. +// /*$*/FlowExpectError - The component does not have sizing props. +const exampleNoSizing = withSize(NoSizing); From bf7da5f71b779756bff028ff0267bac7b1fb1f69 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 13:23:11 -0400 Subject: [PATCH 010/124] Simplify return type of the callback we pass to setState. The TypeScript types for setState are a bit restrictive because they require that you pick a single subset of properties that is returned by all execution paths of the callback. Just returning the full state is an easy way of achieving that. --- src/components/app/KeyboardShortcut.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/app/KeyboardShortcut.js b/src/components/app/KeyboardShortcut.js index 27c575de7e..0b49de2b26 100644 --- a/src/components/app/KeyboardShortcut.js +++ b/src/components/app/KeyboardShortcut.js @@ -54,10 +54,10 @@ export class KeyboardShortcut extends React.PureComponent { }); } - _open = (state: State): $Shape => { + _open = (state: State): State => { if (state.isOpen) { // Do nothing. - return {}; + return state; } const focusAfterClosed = document.activeElement; this._trapFocus(); @@ -65,12 +65,12 @@ export class KeyboardShortcut extends React.PureComponent { return { isOpen: true, focusAfterClosed }; }; - _close = (state: State): $Shape => { + _close = (state: State): State => { const { focusAfterClosed, isOpen } = state; if (!isOpen) { // Do nothing. - return {}; + return state; } this._untrapFocus(); if (focusAfterClosed) { From 64b09c2025bc79a7cf269735e3a0875b09a87507 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 13:41:50 -0400 Subject: [PATCH 011/124] Improve type coverage involving network phases. --- .../network-chart/NetworkChartRow.js | 70 +++---- src/components/tooltip/NetworkMarker.js | 192 ++++-------------- src/profile-logic/network.js | 138 +++++++++++++ src/types/index.js | 1 + src/types/markers.js | 13 +- src/types/network.js | 32 +++ 6 files changed, 241 insertions(+), 205 deletions(-) create mode 100644 src/profile-logic/network.js create mode 100644 src/types/network.js diff --git a/src/components/network-chart/NetworkChartRow.js b/src/components/network-chart/NetworkChartRow.js index ac448d90b3..e6f80cfe5b 100644 --- a/src/components/network-chart/NetworkChartRow.js +++ b/src/components/network-chart/NetworkChartRow.js @@ -13,6 +13,10 @@ import { guessMimeTypeFromNetworkMarker, getColorClassNameForMimeType, } from '../../profile-logic/marker-data'; +import { + getLatestPreconnectPhaseAndValue, + getMatchingPhaseValues, +} from '../../profile-logic/network'; import { formatNumber } from '../../utils/format-numbers'; import { TIMELINE_MARGIN_LEFT, @@ -28,6 +32,7 @@ import type { Marker, MarkerIndex, NetworkPayload, + NetworkPhaseName, MixedObject, } from 'firefox-profiler/types'; @@ -65,14 +70,14 @@ const PATH_SPLIT_RE = /(.*)(\/[^/]+\/?)$/; // `endTime` is the timestamp when the response is delivered to the caller. It's // not present in this array as it's implicit in the component logic. // They may be all missing in a specific marker, that's fine. -const PROPERTIES_IN_ORDER = [ +const PHASE_NAMES_IN_ORDER: NetworkPhaseName[] = [ 'domainLookupStart', 'requestStart', 'responseStart', 'responseEnd', ]; -const PHASE_OPACITIES = PROPERTIES_IN_ORDER.reduce( +const PHASE_OPACITIES = PHASE_NAMES_IN_ORDER.reduce( (result, property, i, { length }) => { result[property] = length > 1 ? i / (length - 1) : 0; return result; @@ -81,8 +86,8 @@ const PHASE_OPACITIES = PROPERTIES_IN_ORDER.reduce( ); type NetworkPhaseProps = {| - +name: string, - +previousName: string, + +name: NetworkPhaseName, + +previousName: NetworkPhaseName, +value: number | string, +duration: Milliseconds, +style: MixedObject, @@ -140,25 +145,6 @@ class NetworkChartRowBar extends React.PureComponent { return markerPosition; } - /** - * Properties `connectEnd` and `domainLookupEnd` aren't always present. This - * function returns the latest one so that we can determine if these phases - * happen in a preconnect session. - */ - _getLatestPreconnectEndProperty(): 'connectEnd' | 'domainLookupEnd' | null { - const { networkPayload } = this.props; - - if (typeof networkPayload.connectEnd === 'number') { - return 'connectEnd'; - } - - if (typeof networkPayload.domainLookupEnd === 'number') { - return 'domainLookupEnd'; - } - - return null; - } - /** * This returns the preconnect component, or null if there's no preconnect * operation for this marker. @@ -175,15 +161,14 @@ class NetworkChartRowBar extends React.PureComponent { // The preconnect bar goes from the start to the end of the whole preconnect // operation, that includes both the domain lookup and the connection // process. Therefore we want the property that represents the latest phase. - const latestPreconnectEndProperty = this._getLatestPreconnectEndProperty(); + const latestPreconnectEndProperty = getLatestPreconnectPhaseAndValue( + this.props.networkPayload + ); if (!latestPreconnectEndProperty) { return null; } - // We force-coerce the value into a number just to appease Flow. Indeed - // the previous find operation ensures that all values are numbers but - // Flow can't know that. - const preconnectEnd = +networkPayload[latestPreconnectEndProperty]; + const preconnectEnd = latestPreconnectEndProperty.value; // If the latest phase ends before the start of the marker, we'll display a // separate preconnect bar. @@ -201,7 +186,7 @@ class NetworkChartRowBar extends React.PureComponent { const preconnectWidth = preconnectEndPosition - preconnectStartPosition; const preconnectPhase = { - name: latestPreconnectEndProperty, + name: latestPreconnectEndProperty.phase, previousName: 'domainLookupStart', value: preconnectEnd, duration: preconnectDuration, @@ -243,16 +228,11 @@ class NetworkChartRowBar extends React.PureComponent { const preconnectComponent = this._preconnectComponent(); // Compute the phases for this marker. - - // If there's a preconnect phase, we remove `domainLookupStart` from the - // main bar, but we'll draw a separate bar to represent it. - const mainBarProperties = preconnectComponent - ? PROPERTIES_IN_ORDER.slice(1) - : PROPERTIES_IN_ORDER; - - // Not all properties are always present. - const availableProperties = mainBarProperties.filter( - (property) => typeof networkPayload[property] === 'number' + const availablePhases = getMatchingPhaseValues( + networkPayload, + // If there's a preconnect phase, we remove `domainLookupStart` from the + // main bar, but we'll draw a separate bar to represent it. + preconnectComponent ? PHASE_NAMES_IN_ORDER.slice(1) : PHASE_NAMES_IN_ORDER ); const mainBarPhases = []; @@ -260,13 +240,9 @@ class NetworkChartRowBar extends React.PureComponent { let previousName = 'startTime'; // In this loop we add the various phases to the array. - availableProperties.forEach((property, i) => { - // We force-coerce the value into a number just to appease Flow. Indeed the - // previous filter ensures that all values are numbers but Flow can't know - // that. - const value = +networkPayload[property]; + availablePhases.forEach(({ phase, value }, i) => { mainBarPhases.push({ - name: property, + name: phase, previousName, value, duration: value - previousValue, @@ -274,11 +250,11 @@ class NetworkChartRowBar extends React.PureComponent { left: ((previousValue - start) / dur) * markerWidth, width: Math.max(((value - previousValue) / dur) * markerWidth, 1), // The first phase is always transparent because this represents the wait time. - opacity: i === 0 ? 0 : PHASE_OPACITIES[property], + opacity: i === 0 ? 0 : PHASE_OPACITIES[phase], }, }); previousValue = value; - previousName = property; + previousName = phase; }); // The last part isn't generally colored (opacity is 0) unless it's the only diff --git a/src/components/tooltip/NetworkMarker.js b/src/components/tooltip/NetworkMarker.js index deaac52baa..01a44d6f0b 100644 --- a/src/components/tooltip/NetworkMarker.js +++ b/src/components/tooltip/NetworkMarker.js @@ -17,108 +17,28 @@ import { formatNumber, formatMilliseconds, } from 'firefox-profiler/utils/format-numbers'; -import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; +import { + getLatestPreconnectPhaseAndValue, + getMatchingPhaseValues, + getHumanReadableDataStatus, + getHumanReadablePriority, + getHumanReadableHttpVersion, + PRECONNECT_PHASES_IN_ORDER, + REQUEST_PHASES_IN_ORDER, + ALL_NETWORK_PHASES_IN_ORDER, +} from 'firefox-profiler/profile-logic/network.js'; import type { - NetworkHttpVersion, NetworkPayload, - NetworkStatus, + NetworkPhaseName, + NetworkPhaseAndValue, Milliseconds, } from 'firefox-profiler/types'; import './NetworkMarker.css'; -function _getHumanReadablePriority(priority: number): string | null { - if (typeof priority !== 'number') { - return null; - } - - let prioLabel = null; - - // https://searchfox.org/mozilla-central/source/xpcom/threads/nsISupportsPriority.idl#24-28 - if (priority < -10) { - prioLabel = 'Highest'; - } else if (priority >= -10 && priority < 0) { - prioLabel = 'High'; - } else if (priority === 0) { - prioLabel = 'Normal'; - } else if (priority <= 10 && priority > 0) { - prioLabel = 'Low'; - } else if (priority > 10) { - prioLabel = 'Lowest'; - } - - if (!prioLabel) { - return null; - } - - return prioLabel + '(' + priority + ')'; -} - -function _getHumanReadableDataStatus(status: NetworkStatus): string { - switch (status) { - case 'STATUS_START': - return 'Waiting for response'; - case 'STATUS_STOP': - return 'Response received'; - case 'STATUS_REDIRECT': - return 'Redirecting request'; - case 'STATUS_CANCEL': - return 'Request was canceled'; - default: - throw assertExhaustiveCheck(status); - } -} - -function _getHumanReadableHttpVersion(httpVersion: NetworkHttpVersion): string { - switch (httpVersion) { - case 'h3': - return '3'; - case 'h2': - return '2'; - case 'http/1.0': - return '1.0'; - case 'http/1.1': - return '1.1'; - default: - throw assertExhaustiveCheck( - httpVersion, - `Unknown received HTTP version ${httpVersion}` - ); - } -} - -/* The preconnect phase may only contain these properties. */ -const PRECONNECT_PROPERTIES_IN_ORDER = [ - 'domainLookupStart', - 'domainLookupEnd', - 'connectStart', - 'tcpConnectEnd', - 'secureConnectionStart', - 'connectEnd', -]; - -/* A marker without a preconnect phase may contain all these properties. */ -const ALL_NETWORK_PROPERTIES_IN_ORDER = [ - 'startTime', - ...PRECONNECT_PROPERTIES_IN_ORDER, - 'requestStart', - 'responseStart', - 'responseEnd', - 'endTime', -]; - -/* For a marker with a preconnect phase, the second displayed diagram may only - * contain these properties. - * We use `splice` to generate this list out of the previous arrays, taking - * ALL_NETWORK_PROPERTIES_IN_ORDER as source, then removing all the properties - * of PRECONNECT_PROPERTIES_IN_ORDER. - */ -const REQUEST_PROPERTIES_IN_ORDER = ALL_NETWORK_PROPERTIES_IN_ORDER.slice(); -REQUEST_PROPERTIES_IN_ORDER.splice(1, PRECONNECT_PROPERTIES_IN_ORDER.length); - /* The labels are for the duration between _this_ label and the next label. */ -const PROPERTIES_HUMAN_LABELS = { +const HUMAN_LABEL_FOR_PHASE: { [NetworkPhaseName]: string } = { startTime: 'Waiting for socket thread', domainLookupStart: 'DNS request', domainLookupEnd: 'After DNS request', @@ -132,7 +52,7 @@ const PROPERTIES_HUMAN_LABELS = { endTime: 'End', }; -const NETWORK_PROPERTY_OPACITIES = { +const OPACITY_FOR_PHASE: { [NetworkPhaseName]: number } = { startTime: 0, domainLookupStart: 0.5, domainLookupEnd: 0.5, @@ -147,7 +67,7 @@ const NETWORK_PROPERTY_OPACITIES = { }; type NetworkPhaseProps = {| - +propertyName: string, + +propertyName: NetworkPhaseName, +dur: Milliseconds, +startPosition: Milliseconds, +phaseDuration: Milliseconds, @@ -158,12 +78,12 @@ class NetworkPhase extends React.PureComponent { const { startPosition, dur, propertyName, phaseDuration } = this.props; const startPositionPercent = (startPosition / dur) * 100; const durationPercent = Math.max(0.3, (phaseDuration / dur) * 100); - const opacity = NETWORK_PROPERTY_OPACITIES[propertyName]; + const opacity = OPACITY_FOR_PHASE[propertyName]; return (
- {PROPERTIES_HUMAN_LABELS[propertyName]}: + {HUMAN_LABEL_FOR_PHASE[propertyName]}:
{ - _getPhasesForProperties( - properties: string[], + _renderPhases( + properties: NetworkPhaseAndValue[], sectionDuration: Milliseconds, startTime: Milliseconds ): Array> | null { @@ -208,17 +128,11 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { return null; } - const { payload } = this.props; const phases = []; for (let i = 1; i < properties.length; i++) { - const thisProperty = properties[i]; - const previousProperty = properties[i - 1]; - // We force-coerce the values into numbers just to appease Flow. Indeed the - // previous filter ensures that all values are numbers but Flow can't know - // that. - const startValue = +payload[previousProperty]; - const endValue = +payload[thisProperty]; + const { phase: previousProperty, value: startValue } = properties[i - 1]; + const { value: endValue } = properties[i]; const phaseDuration = endValue - startValue; const startPosition = startValue - startTime; @@ -236,25 +150,6 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { return phases; } - /** - * Properties `connectEnd` and `domainLookupEnd` aren't always present. This - * function returns the value for the latest one so that we can determine if - * these phases happen in a preconnect session. - */ - _getLatestPreconnectValue(): number | null { - const { payload } = this.props; - - if (typeof payload.connectEnd === 'number') { - return payload.connectEnd; - } - - if (typeof payload.domainLookupEnd === 'number') { - return payload.domainLookupEnd; - } - - return null; - } - _renderPreconnectPhases(): React.Node { const { payload, zeroAt } = this.props; const preconnectStart = payload.domainLookupStart; @@ -266,10 +161,13 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { // The preconnect bar goes from the start to the end of the whole preconnect // operation, that includes both the domain lookup and the connection // process. Therefore we want the value that represents the latest phase. - const preconnectEnd = this._getLatestPreconnectValue(); - if (preconnectEnd === null) { + const preconnectEndPhase = getLatestPreconnectPhaseAndValue( + this.props.payload + ); + if (preconnectEndPhase === null) { return null; } + const preconnectEnd = preconnectEndPhase.value; // If the latest phase ends before the start of the marker, we'll display a // separate preconnect section. @@ -281,16 +179,13 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { return null; } - const availableProperties = PRECONNECT_PROPERTIES_IN_ORDER.filter( - (property) => typeof payload[property] === 'number' + const preconnectValues = getMatchingPhaseValues( + payload, + PRECONNECT_PHASES_IN_ORDER ); const dur = preconnectEnd - preconnectStart; - const phases = this._getPhasesForProperties( - availableProperties, - dur, - preconnectStart - ); + const phases = this._renderPhases(preconnectValues, dur, preconnectStart); return ( <> @@ -314,21 +209,18 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { } const preconnectPhases = this._renderPreconnectPhases(); - const networkProperties = preconnectPhases - ? REQUEST_PROPERTIES_IN_ORDER - : ALL_NETWORK_PROPERTIES_IN_ORDER; - - const availableProperties = networkProperties.filter( - (property) => typeof payload[property] === 'number' + const availablePhases = getMatchingPhaseValues( + payload, + preconnectPhases ? REQUEST_PHASES_IN_ORDER : ALL_NETWORK_PHASES_IN_ORDER ); - if (availableProperties.length === 0 || availableProperties.length === 1) { + if (availablePhases.length === 0 || availablePhases.length === 1) { // This shouldn't happen as we should always have both startTime and endTime. return null; } const dur = payload.endTime - payload.startTime; - if (availableProperties.length === 2) { + if (availablePhases.length === 2) { // We only have startTime and endTime. return (
@@ -342,9 +234,9 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { ); } - // Looks like availableProperties.length >= 3. - const phases = this._getPhasesForProperties( - availableProperties, + // Looks like availablePhases.length >= 3. + const renderedPhases = this._renderPhases( + availablePhases, dur, payload.startTime ); @@ -359,7 +251,7 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent {

Actual request

) : null} - {phases} + {renderedPhases}
); } @@ -383,7 +275,7 @@ export function getNetworkMarkerDetails( details.push( - {_getHumanReadableDataStatus(payload.status)} + {getHumanReadableDataStatus(payload.status)} ); if (payload.redirectType !== undefined) { @@ -414,7 +306,7 @@ export function getNetworkMarkerDetails( details.push( - {_getHumanReadablePriority(payload.pri)} + {getHumanReadablePriority(payload.pri)} ); @@ -451,7 +343,7 @@ export function getNetworkMarkerDetails( if (payload.httpVersion) { details.push( - {_getHumanReadableHttpVersion(payload.httpVersion)} + {getHumanReadableHttpVersion(payload.httpVersion)} ); } diff --git a/src/profile-logic/network.js b/src/profile-logic/network.js new file mode 100644 index 0000000000..274a4e3b7d --- /dev/null +++ b/src/profile-logic/network.js @@ -0,0 +1,138 @@ +/* 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/. */ +// @flow + +import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; + +import type { + NetworkPhaseName, + NetworkPhaseAndValue, + NetworkPayload, + NetworkStatus, + NetworkHttpVersion, +} from 'firefox-profiler/types'; + +/* The preconnect phase may only contain these properties. */ +export const PRECONNECT_PHASES_IN_ORDER: NetworkPhaseName[] = [ + 'domainLookupStart', + 'domainLookupEnd', + 'connectStart', + 'tcpConnectEnd', + 'secureConnectionStart', + 'connectEnd', +]; + +/* A marker without a preconnect phase may contain all these properties. */ +export const ALL_NETWORK_PHASES_IN_ORDER: NetworkPhaseName[] = [ + 'startTime', + ...PRECONNECT_PHASES_IN_ORDER, + 'requestStart', + 'responseStart', + 'responseEnd', + 'endTime', +]; + +/* For a marker with a preconnect phase, the second displayed diagram may only + * contain these properties. + * We use `splice` to generate this list out of the previous arrays, taking + * ALL_NETWORK_PHASES_IN_ORDER as source, then removing all the properties + * of PRECONNECT_PHASES_IN_ORDER starting at index 1 (after 'startTime'). + */ +export const REQUEST_PHASES_IN_ORDER: NetworkPhaseName[] = + ALL_NETWORK_PHASES_IN_ORDER.slice(); +REQUEST_PHASES_IN_ORDER.splice(1, PRECONNECT_PHASES_IN_ORDER.length); + +export function getHumanReadablePriority(priority: number): string | null { + if (typeof priority !== 'number') { + return null; + } + + let prioLabel = null; + + // https://searchfox.org/mozilla-central/source/xpcom/threads/nsISupportsPriority.idl#24-28 + if (priority < -10) { + prioLabel = 'Highest'; + } else if (priority >= -10 && priority < 0) { + prioLabel = 'High'; + } else if (priority === 0) { + prioLabel = 'Normal'; + } else if (priority <= 10 && priority > 0) { + prioLabel = 'Low'; + } else if (priority > 10) { + prioLabel = 'Lowest'; + } + + if (!prioLabel) { + return null; + } + + return prioLabel + '(' + priority + ')'; +} + +export function getHumanReadableDataStatus(status: NetworkStatus): string { + switch (status) { + case 'STATUS_START': + return 'Waiting for response'; + case 'STATUS_STOP': + return 'Response received'; + case 'STATUS_REDIRECT': + return 'Redirecting request'; + case 'STATUS_CANCEL': + return 'Request was canceled'; + default: + throw assertExhaustiveCheck(status); + } +} + +export function getHumanReadableHttpVersion( + httpVersion: NetworkHttpVersion +): string { + switch (httpVersion) { + case 'h3': + return '3'; + case 'h2': + return '2'; + case 'http/1.0': + return '1.0'; + case 'http/1.1': + return '1.1'; + default: + throw assertExhaustiveCheck( + httpVersion, + `Unknown received HTTP version ${httpVersion}` + ); + } +} + +/** + * Properties `connectEnd` and `domainLookupEnd` aren't always present. This + * function returns the latest one so that we can determine if these phases + * happen in a preconnect session. + */ +export function getLatestPreconnectPhaseAndValue( + networkPayload: NetworkPayload +): NetworkPhaseAndValue | null { + if (typeof networkPayload.connectEnd === 'number') { + return { phase: 'connectEnd', value: networkPayload.connectEnd }; + } + + if (typeof networkPayload.domainLookupEnd === 'number') { + return { phase: 'domainLookupEnd', value: networkPayload.domainLookupEnd }; + } + + return null; +} + +export function getMatchingPhaseValues( + networkPayload: NetworkPayload, + phasesInOrder: NetworkPhaseName[] +): NetworkPhaseAndValue[] { + const values: NetworkPhaseAndValue[] = []; + for (const phase of phasesInOrder) { + if (typeof networkPayload[phase] === 'number') { + values.push({ phase, value: networkPayload[phase] }); + } + } + return values; +} diff --git a/src/types/index.js b/src/types/index.js index 5b11d0b7c8..c22520d151 100644 --- a/src/types/index.js +++ b/src/types/index.js @@ -9,6 +9,7 @@ export * from './indexeddb'; export * from './markers'; export * from './profile-derived'; export * from './profile'; +export * from './network'; export * from './state'; export * from './store'; export * from './symbolication'; diff --git a/src/types/markers.js b/src/types/markers.js index 0de8d79b50..a753d68007 100644 --- a/src/types/markers.js +++ b/src/types/markers.js @@ -12,6 +12,11 @@ import type { Pid, GraphColor, } from './profile'; +import type { + NetworkHttpVersion, + NetworkStatus, + NetworkRedirectType, +} from './network'; import type { ObjectMap } from './utils'; // Provide different formatting options for strings. @@ -494,14 +499,6 @@ export type GCSliceMarkerPayload_Gecko = {| * be included depending on what states happen during the load. Also note * that redirects are logged as well. */ - -export type NetworkHttpVersion = 'h3' | 'h2' | 'http/1.1' | 'http/1.0'; -export type NetworkStatus = - | 'STATUS_START' - | 'STATUS_STOP' - | 'STATUS_REDIRECT' - | 'STATUS_CANCEL'; -export type NetworkRedirectType = 'Permanent' | 'Temporary' | 'Internal'; export type NetworkPayload = {| type: 'Network', innerWindowID?: number, diff --git a/src/types/network.js b/src/types/network.js new file mode 100644 index 0000000000..b147d2003e --- /dev/null +++ b/src/types/network.js @@ -0,0 +1,32 @@ +/* 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/. */ +// @flow + +export type NetworkHttpVersion = 'h3' | 'h2' | 'http/1.1' | 'http/1.0'; + +export type NetworkStatus = + | 'STATUS_START' + | 'STATUS_STOP' + | 'STATUS_REDIRECT' + | 'STATUS_CANCEL'; + +export type NetworkRedirectType = 'Permanent' | 'Temporary' | 'Internal'; + +export type NetworkPhaseName = + | 'startTime' + | 'domainLookupStart' + | 'domainLookupEnd' + | 'connectStart' + | 'tcpConnectEnd' + | 'secureConnectionStart' + | 'connectEnd' + | 'requestStart' + | 'responseStart' + | 'responseEnd' + | 'endTime'; + +export type NetworkPhaseAndValue = {| + phase: NetworkPhaseName, + value: number, +|}; From 9a3afd4fa28171a9e13b6ce6c74621602318089d Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 12:22:00 -0400 Subject: [PATCH 012/124] Change implementation of withChartViewport. --- src/components/flame-graph/Canvas.js | 12 +- src/components/js-tracer/Canvas.js | 6 +- src/components/marker-chart/Canvas.js | 8 +- src/components/shared/chart/Viewport.js | 197 ++++++++++++------------ src/components/stack-chart/Canvas.js | 12 +- 5 files changed, 110 insertions(+), 125 deletions(-) diff --git a/src/components/flame-graph/Canvas.js b/src/components/flame-graph/Canvas.js index 8284dc3e68..a762ab1248 100644 --- a/src/components/flame-graph/Canvas.js +++ b/src/components/flame-graph/Canvas.js @@ -5,11 +5,7 @@ // @flow import * as React from 'react'; import memoize from 'memoize-immutable'; -import { - withChartViewport, - type WithChartViewport, - type Viewport, -} from '../shared/chart/Viewport'; +import { withChartViewport, type Viewport } from '../shared/chart/Viewport'; import { ChartCanvas } from '../shared/chart/Canvas'; import { FastFillStyle } from '../../utils'; import TextMeasurement from '../../utils/text-measurement'; @@ -525,7 +521,5 @@ class FlameGraphCanvasImpl extends React.PureComponent { } } -export const FlameGraphCanvas = (withChartViewport: WithChartViewport< - OwnProps, - Props, ->)(FlameGraphCanvasImpl); +export const FlameGraphCanvas = + withChartViewport(FlameGraphCanvasImpl); diff --git a/src/components/js-tracer/Canvas.js b/src/components/js-tracer/Canvas.js index d7ad58afd6..1f3c63f8ef 100644 --- a/src/components/js-tracer/Canvas.js +++ b/src/components/js-tracer/Canvas.js @@ -12,7 +12,6 @@ import { } from 'firefox-profiler/app-logic/constants'; import { withChartViewport, - type WithChartViewport, type Viewport, } from 'firefox-profiler/components/shared/chart/Viewport'; import { ChartCanvas } from 'firefox-profiler/components/shared/chart/Canvas'; @@ -657,7 +656,4 @@ class JsTracerCanvasImpl extends React.PureComponent { } } -export const JsTracerCanvas = (withChartViewport: WithChartViewport< - OwnProps, - Props, ->)(JsTracerCanvasImpl); +export const JsTracerCanvas = withChartViewport(JsTracerCanvasImpl); diff --git a/src/components/marker-chart/Canvas.js b/src/components/marker-chart/Canvas.js index f6291ef9f5..2205f6f9b5 100644 --- a/src/components/marker-chart/Canvas.js +++ b/src/components/marker-chart/Canvas.js @@ -7,7 +7,6 @@ import { GREY_20, GREY_30, BLUE_60, BLUE_80 } from 'photon-colors'; import * as React from 'react'; import { withChartViewport, - type WithChartViewport, type Viewport, } from 'firefox-profiler/components/shared/chart/Viewport'; import { ChartCanvas } from 'firefox-profiler/components/shared/chart/Canvas'; @@ -826,7 +825,6 @@ class MarkerChartCanvasImpl extends React.PureComponent { } } -export const MarkerChartCanvas = (withChartViewport: WithChartViewport< - OwnProps, - Props, ->)(MarkerChartCanvasImpl); +export const MarkerChartCanvas = withChartViewport( + MarkerChartCanvasImpl +); diff --git a/src/components/shared/chart/Viewport.js b/src/components/shared/chart/Viewport.js index b4269bf92e..e764223aea 100644 --- a/src/components/shared/chart/Viewport.js +++ b/src/components/shared/chart/Viewport.js @@ -3,10 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // @flow -// This is using the existential types in the generics, which would be harder to -// remove. It might be possible to switch this took use hooks. -/* eslint-disable flowtype/no-existential-type */ - import * as React from 'react'; import classNames from 'classnames'; import explicitConnect from 'firefox-profiler/utils/connect'; @@ -136,50 +132,50 @@ export type Viewport = {| +isSizeSet: boolean, |}; -type ViewportStateProps = {| +type ChartViewportImplStateProps = {| +panelLayoutGeneration: number, +hasZoomedViaMousewheel?: boolean, |}; -type ViewportDispatchProps = {| +type ChartViewportImplDispatchProps = {| +updatePreviewSelection: typeof updatePreviewSelection, +setHasZoomedViaMousewheel?: typeof setHasZoomedViaMousewheel, |}; -// These are the props consumed by this Higher-Order Component (HOC), but can be -// optionally used by the wrapped component. -type ViewportOwnProps = {| - +viewportProps: {| - // The "committed range", whose endpoints correspond to 0 and 1. - +timeRange: StartEndRange, - // The preview selection, whose endpoints correspond to viewportLeft and viewportRight. - +previewSelection: PreviewSelection, - // The left margin. Margins are outside the viewport but inside containerWidth. - +marginLeft: CssPixels, - // The right margin. Margins are outside the viewport but inside containerWidth. - +marginRight: CssPixels, - - +maxViewportHeight: number, - +startsAtBottom?: boolean, - +maximumZoom: UnitIntervalOfProfileRange, - +disableHorizontalMovement?: boolean, - +className?: string, - +containerRef?: (HTMLDivElement | null) => void, - // These props are defined by the generic variables passed into to the type - // WithChartViewport when calling withChartViewport. This is how the relationship - // is guaranteed. e.g. here with OwnProps: - // - // (withChartViewport: WithChartViewport)( - // MarkerChartCanvas - // ) - +viewportNeedsUpdate: ( - prevProps: ChartProps, - nextProps: ChartProps - ) => boolean, - |}, +type ViewportProps = {| + // The "committed range", whose endpoints correspond to 0 and 1. + +timeRange: StartEndRange, + // The preview selection, whose endpoints correspond to viewportLeft and viewportRight. + +previewSelection: PreviewSelection, + // The left margin. Margins are outside the viewport but inside containerWidth. + +marginLeft: CssPixels, + // The right margin. Margins are outside the viewport but inside containerWidth. + +marginRight: CssPixels, + + +maxViewportHeight: number, + +startsAtBottom?: boolean, + +maximumZoom: UnitIntervalOfProfileRange, + +disableHorizontalMovement?: boolean, + +className?: string, + +containerRef?: (HTMLDivElement | null) => void, + +viewportNeedsUpdate: ( + prevProps: ChartProps, + nextProps: ChartProps + ) => boolean, +|}; + +// These are the props consumed by the ViewportImpl component. +type ChartViewportImplOwnProps = {| + +viewportProps: ViewportProps, + +chart: React.ComponentType>, +chartProps: ChartProps, |}; +export type ChartPropsPlusViewport = {| + ...ChartProps, + +viewport: Viewport, +|}; + type HorizontalViewport = {| // The position of the profile range that should be drawn at the left edge of // the chart's "inner box", i.e. after the marginLeft. @@ -208,50 +204,22 @@ const ZOOM_SPEED = 1.003; // This value makes the pinch zooming faster than shift+scroll zooming. const PINCH_ZOOM_FACTOR = 3; -/** - * This is the type signature for the higher order component. It's easier to use generics - * by separating out the type definition. - */ -export type WithChartViewport< - // False positive generic trait bounds. - // eslint-disable-next-line flowtype/no-weak-types - ChartOwnProps: Object, - // The chart component's props are given the viewport object, as well as the original - // ChartOwnProps. - ChartProps: $ReadOnly<{| - ...ChartOwnProps, - viewport: Viewport, - |}>, -> = ( - // Take as input a React component whose props accept the { +viewport: Viewport }. - ChartComponent: React.ComponentType -) => React.ComponentType< - // Finally the returned component takes as input the InternalViewportProps, and - // the ChartProps, but NOT { +viewport: Viewport }. - ViewportOwnProps, +type ChartViewportImplProps = ConnectedProps< + ChartViewportImplOwnProps, + ChartViewportImplStateProps, + ChartViewportImplDispatchProps, >; -// Create the implementation of the WithChartViewport type, but let flow infer the -// generic parameters. -export const withChartViewport: WithChartViewport<*, *> = - // ChartOwnProps is the only generic actually used in the implementation. Infer - // the type signature of the arguments as the WithChartViewport will apply them. - ( - ChartComponent: React.ComponentType<$Subtype<{ +viewport: Viewport }>> - ): * => { - type ViewportProps = ConnectedProps< - ViewportOwnProps, - ViewportStateProps, - ViewportDispatchProps, - >; - - class ChartViewport extends React.PureComponent { + export class ChartViewportImpl extends React.PureComponent< + ChartViewportImplProps, + State, + > { zoomScrollId: number = 0; _pendingPreviewSelectionUpdates: Array< (HorizontalViewport) => PreviewSelection, > = []; - _container: HTMLElement | null = null; - _takeContainerRef = (container) => { + _container: HTMLDivElement | null = null; + _takeContainerRef = (container: HTMLDivElement | null) => { if (this.props.viewportProps.containerRef) { this.props.viewportProps.containerRef(container); } @@ -259,11 +227,11 @@ export const withChartViewport: WithChartViewport<*, *> = }; _lastKeyboardNavigationFrame: number = 0; _keysDown: Set = new Set(); - _deltaToZoomFactor = (delta) => Math.pow(ZOOM_SPEED, delta); + _deltaToZoomFactor = (delta: number) => Math.pow(ZOOM_SPEED, delta); _dragX: number = 0; _dragY: number = 0; - constructor(props: ViewportProps) { + constructor(props: ChartViewportImplProps) { super(props); this.state = this.getDefaultState(props); } @@ -286,7 +254,7 @@ export const withChartViewport: WithChartViewport<*, *> = }; } - getDefaultState(props: ViewportProps) { + getDefaultState(props: ChartViewportImplProps) { const { previewSelection, timeRange } = props.viewportProps; const horizontalViewport = this.getHorizontalViewport( previewSelection, @@ -326,7 +294,9 @@ export const withChartViewport: WithChartViewport<*, *> = }, 1000); } - UNSAFE_componentWillReceiveProps(newProps: ViewportProps) { + UNSAFE_componentWillReceiveProps( + newProps: ChartViewportImplProps + ) { if ( this.props.viewportProps.viewportNeedsUpdate( this.props.chartProps, @@ -534,9 +504,9 @@ export const withChartViewport: WithChartViewport<*, *> = zoomRangeSelection = ( // A number between 0 and 1 indicating the horizontal position of the // zoom center. - center, + center: number, // The factor to zoom by. Factors smaller than 1 zoom in, larger than 1 zoom out. - zoomFactor + zoomFactor: number ) => { const { disableHorizontalMovement, maximumZoom } = this.props.viewportProps; @@ -666,7 +636,7 @@ export const withChartViewport: WithChartViewport<*, *> = this._keysDown.clear(); }; - _keyboardNavigation = (timestamp) => { + _keyboardNavigation = (timestamp: number) => { if (this._keysDown.size === 0) { // No keys are down, nothing to do. Don't request a new // animation frame. @@ -838,6 +808,7 @@ export const withChartViewport: WithChartViewport<*, *> = render() { const { + chart, chartProps, hasZoomedViaMousewheel, viewportProps: { className }, @@ -879,6 +850,11 @@ export const withChartViewport: WithChartViewport<*, *> = isSizeSet, }; + const Chart = chart; + const chartPropsWithViewport = { + ...chartProps, + viewport, + }; return (
= ref={this._takeContainerRef} tabIndex={0} > - +
Zoom Chart: Ctrl + Scrollor @@ -900,21 +876,48 @@ export const withChartViewport: WithChartViewport<*, *> = } } - // Connect this component so that it knows whether or not to nag the user to use ctrl - // for zooming on range selections. - return explicitConnect< - ViewportOwnProps, - ViewportStateProps, - ViewportDispatchProps, - >({ - mapStateToProps: (state) => ({ - panelLayoutGeneration: getPanelLayoutGeneration(state), - hasZoomedViaMousewheel: getHasZoomedViaMousewheel(state), - }), - mapDispatchToProps: { setHasZoomedViaMousewheel, updatePreviewSelection }, - component: ChartViewport, - }); +export type ChartViewportProps = {| + +viewportProps: ViewportProps, + +chartProps: ChartProps, +|}; + +// const MyChartOuter = withChartViewport(MyChartInner); +// +// The outer component has props { chartProps, viewportProps }. +// +// The inner component must have props { ...chartProps, viewport }. +// +export function withChartViewport( + chart: React.ComponentType> +): React.ComponentType> { + // Connect this component so that it knows whether or not to nag the user to use ctrl + // for zooming on range selections. + const ConnectedChartViewport = explicitConnect< + ChartViewportImplOwnProps, + ChartViewportImplStateProps, + ChartViewportImplDispatchProps, + >({ + mapStateToProps: (state) => ({ + panelLayoutGeneration: getPanelLayoutGeneration(state), + hasZoomedViaMousewheel: getHasZoomedViaMousewheel(state), + }), + mapDispatchToProps: { setHasZoomedViaMousewheel, updatePreviewSelection }, + component: ChartViewportImpl, + }); + + return function ChartViewportFunctionComponent({ + chartProps, + viewportProps, + }: ChartViewportProps) { + return ( + + ); }; +} function clamp(min, max, value) { return Math.max(min, Math.min(max, value)); diff --git a/src/components/stack-chart/Canvas.js b/src/components/stack-chart/Canvas.js index 3dbb09d717..d3ad9df0a1 100644 --- a/src/components/stack-chart/Canvas.js +++ b/src/components/stack-chart/Canvas.js @@ -6,11 +6,7 @@ import { GREY_30 } from 'photon-colors'; import * as React from 'react'; import { TIMELINE_MARGIN_RIGHT } from '../../app-logic/constants'; -import { - withChartViewport, - type WithChartViewport, - type Viewport, -} from '../shared/chart/Viewport'; +import { withChartViewport, type Viewport } from '../shared/chart/Viewport'; import { ChartCanvas } from '../shared/chart/Canvas'; import { FastFillStyle } from '../../utils'; import TextMeasurement from '../../utils/text-measurement'; @@ -827,7 +823,5 @@ class StackChartCanvasImpl extends React.PureComponent { } } -export const StackChartCanvas = (withChartViewport: WithChartViewport< - OwnProps, - Props, ->)(StackChartCanvasImpl); +export const StackChartCanvas = + withChartViewport(StackChartCanvasImpl); From b9d9eb69096b9d59ae384e5217b62c02ea829012 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 14:35:43 -0400 Subject: [PATCH 013/124] yarn lint-fix --- src/components/shared/chart/Viewport.js | 1219 +++++++++++------------ 1 file changed, 602 insertions(+), 617 deletions(-) diff --git a/src/components/shared/chart/Viewport.js b/src/components/shared/chart/Viewport.js index e764223aea..0b8856c056 100644 --- a/src/components/shared/chart/Viewport.js +++ b/src/components/shared/chart/Viewport.js @@ -210,671 +210,656 @@ type ChartViewportImplProps = ConnectedProps< ChartViewportImplDispatchProps, >; - export class ChartViewportImpl extends React.PureComponent< - ChartViewportImplProps, - State, - > { - zoomScrollId: number = 0; - _pendingPreviewSelectionUpdates: Array< - (HorizontalViewport) => PreviewSelection, - > = []; - _container: HTMLDivElement | null = null; - _takeContainerRef = (container: HTMLDivElement | null) => { - if (this.props.viewportProps.containerRef) { - this.props.viewportProps.containerRef(container); - } - this._container = container; - }; - _lastKeyboardNavigationFrame: number = 0; - _keysDown: Set = new Set(); - _deltaToZoomFactor = (delta: number) => Math.pow(ZOOM_SPEED, delta); - _dragX: number = 0; - _dragY: number = 0; - - constructor(props: ChartViewportImplProps) { - super(props); - this.state = this.getDefaultState(props); - } +export class ChartViewportImpl extends React.PureComponent< + ChartViewportImplProps, + State, +> { + zoomScrollId: number = 0; + _pendingPreviewSelectionUpdates: Array< + (HorizontalViewport) => PreviewSelection, + > = []; + _container: HTMLDivElement | null = null; + _takeContainerRef = (container: HTMLDivElement | null) => { + if (this.props.viewportProps.containerRef) { + this.props.viewportProps.containerRef(container); + } + this._container = container; + }; + _lastKeyboardNavigationFrame: number = 0; + _keysDown: Set = new Set(); + _deltaToZoomFactor = (delta: number) => Math.pow(ZOOM_SPEED, delta); + _dragX: number = 0; + _dragY: number = 0; + + constructor(props: ChartViewportImplProps) { + super(props); + this.state = this.getDefaultState(props); + } - getHorizontalViewport( - previewSelection: PreviewSelection, - timeRange: StartEndRange - ) { - if (previewSelection.hasSelection) { - const { selectionStart, selectionEnd } = previewSelection; - const timeRangeLength = timeRange.end - timeRange.start; - return { - viewportLeft: (selectionStart - timeRange.start) / timeRangeLength, - viewportRight: (selectionEnd - timeRange.start) / timeRangeLength, - }; - } - return { - viewportLeft: 0, - viewportRight: 1, - }; - } + getHorizontalViewport( + previewSelection: PreviewSelection, + timeRange: StartEndRange + ) { + if (previewSelection.hasSelection) { + const { selectionStart, selectionEnd } = previewSelection; + const timeRangeLength = timeRange.end - timeRange.start; + return { + viewportLeft: (selectionStart - timeRange.start) / timeRangeLength, + viewportRight: (selectionEnd - timeRange.start) / timeRangeLength, + }; + } + return { + viewportLeft: 0, + viewportRight: 1, + }; + } - getDefaultState(props: ChartViewportImplProps) { - const { previewSelection, timeRange } = props.viewportProps; - const horizontalViewport = this.getHorizontalViewport( - previewSelection, - timeRange - ); - const { startsAtBottom, maxViewportHeight } = props.viewportProps; - return { - containerWidth: 0, - containerHeight: 0, - containerLeft: 0, - viewportTop: 0, - viewportBottom: startsAtBottom ? maxViewportHeight : 0, - horizontalViewport, - isDragging: false, - isScrollHintVisible: false, - isSizeSet: false, - }; - } + getDefaultState(props: ChartViewportImplProps) { + const { previewSelection, timeRange } = props.viewportProps; + const horizontalViewport = this.getHorizontalViewport( + previewSelection, + timeRange + ); + const { startsAtBottom, maxViewportHeight } = props.viewportProps; + return { + containerWidth: 0, + containerHeight: 0, + containerLeft: 0, + viewportTop: 0, + viewportBottom: startsAtBottom ? maxViewportHeight : 0, + horizontalViewport, + isDragging: false, + isScrollHintVisible: false, + isSizeSet: false, + }; + } - /** - * Let the viewport know when we are actively scrolling. - */ - showScrollingHint() { - // Only show this message if we haven't ctrl/shift zoomed yet. - if (this.props.hasZoomedViaMousewheel) { - return; - } + /** + * Let the viewport know when we are actively scrolling. + */ + showScrollingHint() { + // Only show this message if we haven't ctrl/shift zoomed yet. + if (this.props.hasZoomedViaMousewheel) { + return; + } - const scrollId = ++this.zoomScrollId; - if (!this.state.isScrollHintVisible) { - this.setState({ isScrollHintVisible: true }); - } - setTimeout(() => { - if (scrollId === this.zoomScrollId) { - this.setState({ isScrollHintVisible: false }); - } - }, 1000); + const scrollId = ++this.zoomScrollId; + if (!this.state.isScrollHintVisible) { + this.setState({ isScrollHintVisible: true }); + } + setTimeout(() => { + if (scrollId === this.zoomScrollId) { + this.setState({ isScrollHintVisible: false }); } + }, 1000); + } + + UNSAFE_componentWillReceiveProps( + newProps: ChartViewportImplProps + ) { + if ( + this.props.viewportProps.viewportNeedsUpdate( + this.props.chartProps, + newProps.chartProps + ) + ) { + this.setState(this.getDefaultState(newProps)); + this._setSizeNextFrame(); + } else if ( + this.props.viewportProps.previewSelection !== + newProps.viewportProps.previewSelection || + this.props.viewportProps.timeRange !== newProps.viewportProps.timeRange + ) { + const { previewSelection, timeRange } = newProps.viewportProps; + const horizontalViewport = this.getHorizontalViewport( + previewSelection, + timeRange + ); + this.setState({ + horizontalViewport, + }); + } else if ( + this.props.panelLayoutGeneration !== newProps.panelLayoutGeneration + ) { + this._setSizeNextFrame(); + } + } - UNSAFE_componentWillReceiveProps( - newProps: ChartViewportImplProps + _setSize = () => { + if (this._container) { + const rect = this._container.getBoundingClientRect(); + const { startsAtBottom } = this.props.viewportProps; + if ( + this.state.containerWidth !== rect.width || + this.state.containerHeight !== rect.height ) { - if ( - this.props.viewportProps.viewportNeedsUpdate( - this.props.chartProps, - newProps.chartProps - ) - ) { - this.setState(this.getDefaultState(newProps)); - this._setSizeNextFrame(); - } else if ( - this.props.viewportProps.previewSelection !== - newProps.viewportProps.previewSelection || - this.props.viewportProps.timeRange !== - newProps.viewportProps.timeRange - ) { - const { previewSelection, timeRange } = newProps.viewportProps; - const horizontalViewport = this.getHorizontalViewport( - previewSelection, - timeRange - ); - this.setState({ - horizontalViewport, - }); - } else if ( - this.props.panelLayoutGeneration !== newProps.panelLayoutGeneration - ) { - this._setSizeNextFrame(); - } + this.setState((prevState) => ({ + containerWidth: rect.width, + containerHeight: rect.height, + containerLeft: rect.left, + viewportBottom: startsAtBottom + ? prevState.viewportBottom + : prevState.viewportTop + rect.height, + viewportTop: startsAtBottom + ? prevState.viewportBottom - rect.height + : prevState.viewportTop, + isSizeSet: true, + })); } + } + }; - _setSize = () => { - if (this._container) { - const rect = this._container.getBoundingClientRect(); - const { startsAtBottom } = this.props.viewportProps; - if ( - this.state.containerWidth !== rect.width || - this.state.containerHeight !== rect.height - ) { - this.setState((prevState) => ({ - containerWidth: rect.width, - containerHeight: rect.height, - containerLeft: rect.left, - viewportBottom: startsAtBottom - ? prevState.viewportBottom - : prevState.viewportTop + rect.height, - viewportTop: startsAtBottom - ? prevState.viewportBottom - rect.height - : prevState.viewportTop, - isSizeSet: true, - })); - } - } - }; - - _setSizeNextFrame = () => { - requestAnimationFrame(this._setSize); - }; + _setSizeNextFrame = () => { + requestAnimationFrame(this._setSize); + }; - // To scroll and zoom the chart, we need to install a wheel event listener. - // This listener needs to call `preventDefault()` in order to be able to - // consume wheel events, so that the browser does not trigger additional - // scrolling, zooming, or back/forward swiping for events that the Viewport - // already handles. In other words, this listener cannot be a "passive" - // event listener. - // In the past, we were using ReactDOM's onWheel attribute to install the - // event listener. However, this has two drawbacks: - // - // 1. It does not let us control which DOM element the listener is - // installed on - ReactDOM will use event delegation and install the - // actual listener on an ancester DOM node. More specifically, on - // React versions before v17, the listener will be installed on the - // document, and starting with v17 the listener will be installed on - // the React root. - // 2. It does not let us control the event listener options ("passive"). - // - // As a general rule, non-passive wheel event listeners should be attached - // to an element that only covers the area of the page that actually needs - // to consume wheel events - the listener should be scoped as "tightly" as - // possible. That's because these listeners require additional roundtrips - // to the main thread for asynchronous scrolling, and browsers have added - // optimizations to ensure that this extra roundtrip only affects the area - // of the page covered by the DOM subtree that the listener is attached to. - // So we really don't want React to put our wheel event listener on the - // document or on the React root; we want it to be on the DOM element for - // our Viewport component so that there is no scrolling performance impact - // on elements outside the Viewport component. - // Another problem with React setting the listener on the document is the - // fact that, as of 2019 [1][2], `preventDefault()` no longer has any effect - // in wheel event listeners that are set on the document, unless that - // listener is explicitly marked with `{passive: false}` (which React - // doesn't let us do). - // - // So, instead of using a ReactDOM onWheel listener, we use a native DOM - // wheel event listener. We set/unset it when the Viewport component - // mounts/unmounts. - // This solves both problems: It makes `preventDefault()` work, and it - // limits the performance impact from the non-passiveness to the Viewport - // component itself, so that scrolling outside of the Viewport can proceed - // in a fully accelerated and asynchronous fashion. - // - // [1] https://developer.chrome.com/blog/scrolling-intervention-2/ - // [2] https://bugzilla.mozilla.org/show_bug.cgi?id=1526725 - _mouseWheelListener = (event: WheelEvent) => { - // We handle the wheel event, so disable the browser's handling, such - // as back/forward swiping or scrolling. - event.preventDefault(); - - const { disableHorizontalMovement } = this.props.viewportProps; - if (event.ctrlKey || event.shiftKey) { - if (!disableHorizontalMovement) { - // Pinch and zoom needs to be faster to feel nice, which happens on the - // ctrlKey modifier. - const zoomModifier = event.ctrlKey ? PINCH_ZOOM_FACTOR : 1; - this.zoomWithMouseWheel(event, zoomModifier); - } - return; - } + // To scroll and zoom the chart, we need to install a wheel event listener. + // This listener needs to call `preventDefault()` in order to be able to + // consume wheel events, so that the browser does not trigger additional + // scrolling, zooming, or back/forward swiping for events that the Viewport + // already handles. In other words, this listener cannot be a "passive" + // event listener. + // In the past, we were using ReactDOM's onWheel attribute to install the + // event listener. However, this has two drawbacks: + // + // 1. It does not let us control which DOM element the listener is + // installed on - ReactDOM will use event delegation and install the + // actual listener on an ancester DOM node. More specifically, on + // React versions before v17, the listener will be installed on the + // document, and starting with v17 the listener will be installed on + // the React root. + // 2. It does not let us control the event listener options ("passive"). + // + // As a general rule, non-passive wheel event listeners should be attached + // to an element that only covers the area of the page that actually needs + // to consume wheel events - the listener should be scoped as "tightly" as + // possible. That's because these listeners require additional roundtrips + // to the main thread for asynchronous scrolling, and browsers have added + // optimizations to ensure that this extra roundtrip only affects the area + // of the page covered by the DOM subtree that the listener is attached to. + // So we really don't want React to put our wheel event listener on the + // document or on the React root; we want it to be on the DOM element for + // our Viewport component so that there is no scrolling performance impact + // on elements outside the Viewport component. + // Another problem with React setting the listener on the document is the + // fact that, as of 2019 [1][2], `preventDefault()` no longer has any effect + // in wheel event listeners that are set on the document, unless that + // listener is explicitly marked with `{passive: false}` (which React + // doesn't let us do). + // + // So, instead of using a ReactDOM onWheel listener, we use a native DOM + // wheel event listener. We set/unset it when the Viewport component + // mounts/unmounts. + // This solves both problems: It makes `preventDefault()` work, and it + // limits the performance impact from the non-passiveness to the Viewport + // component itself, so that scrolling outside of the Viewport can proceed + // in a fully accelerated and asynchronous fashion. + // + // [1] https://developer.chrome.com/blog/scrolling-intervention-2/ + // [2] https://bugzilla.mozilla.org/show_bug.cgi?id=1526725 + _mouseWheelListener = (event: WheelEvent) => { + // We handle the wheel event, so disable the browser's handling, such + // as back/forward swiping or scrolling. + event.preventDefault(); + + const { disableHorizontalMovement } = this.props.viewportProps; + if (event.ctrlKey || event.shiftKey) { + if (!disableHorizontalMovement) { + // Pinch and zoom needs to be faster to feel nice, which happens on the + // ctrlKey modifier. + const zoomModifier = event.ctrlKey ? PINCH_ZOOM_FACTOR : 1; + this.zoomWithMouseWheel(event, zoomModifier); + } + return; + } - if (!disableHorizontalMovement) { - this.showScrollingHint(); - } + if (!disableHorizontalMovement) { + this.showScrollingHint(); + } - // Do the work to move the viewport. - const { containerHeight } = this.state; + // Do the work to move the viewport. + const { containerHeight } = this.state; - this.moveViewport( - -getNormalizedScrollDelta(event, containerHeight, 'deltaX'), - -getNormalizedScrollDelta(event, containerHeight, 'deltaY') - ); - }; + this.moveViewport( + -getNormalizedScrollDelta(event, containerHeight, 'deltaX'), + -getNormalizedScrollDelta(event, containerHeight, 'deltaY') + ); + }; - /** - * Add a batched update to the preview selection. - * - * This method works asynchronously in order to avoid spamming the redux store - * with updates (which cause synchronous react renders) in response to mouse events. - * The actual redux update happens in _flushPendingPreviewSelectionUpdates(), which - * processes all queued updates from a requestAnimationFrame callback. - */ - _addBatchedPreviewSelectionUpdate( - callback: (HorizontalViewport) => PreviewSelection - ) { - if (this._pendingPreviewSelectionUpdates.length === 0) { - requestAnimationFrame(() => - this._flushPendingPreviewSelectionUpdates() - ); - } - this._pendingPreviewSelectionUpdates.push(callback); - } + /** + * Add a batched update to the preview selection. + * + * This method works asynchronously in order to avoid spamming the redux store + * with updates (which cause synchronous react renders) in response to mouse events. + * The actual redux update happens in _flushPendingPreviewSelectionUpdates(), which + * processes all queued updates from a requestAnimationFrame callback. + */ + _addBatchedPreviewSelectionUpdate( + callback: (HorizontalViewport) => PreviewSelection + ) { + if (this._pendingPreviewSelectionUpdates.length === 0) { + requestAnimationFrame(() => this._flushPendingPreviewSelectionUpdates()); + } + this._pendingPreviewSelectionUpdates.push(callback); + } - /** - * Flush all batched preview selection updates at once, with only a single - * call to update the Redux store. - */ - _flushPendingPreviewSelectionUpdates() { - if (this._pendingPreviewSelectionUpdates.length !== 0) { - const pendingUpdates = this._pendingPreviewSelectionUpdates; - this._pendingPreviewSelectionUpdates = []; - const { - updatePreviewSelection, - viewportProps: { previewSelection, timeRange }, - } = this.props; - let nextSelection = previewSelection; - for (const callback of pendingUpdates) { - const horizontalViewport = this.getHorizontalViewport( - nextSelection, - timeRange - ); - nextSelection = callback(horizontalViewport); - } - updatePreviewSelection(nextSelection); - } + /** + * Flush all batched preview selection updates at once, with only a single + * call to update the Redux store. + */ + _flushPendingPreviewSelectionUpdates() { + if (this._pendingPreviewSelectionUpdates.length !== 0) { + const pendingUpdates = this._pendingPreviewSelectionUpdates; + this._pendingPreviewSelectionUpdates = []; + const { + updatePreviewSelection, + viewportProps: { previewSelection, timeRange }, + } = this.props; + let nextSelection = previewSelection; + for (const callback of pendingUpdates) { + const horizontalViewport = this.getHorizontalViewport( + nextSelection, + timeRange + ); + nextSelection = callback(horizontalViewport); } + updatePreviewSelection(nextSelection); + } + } - zoomWithMouseWheel( - event: WheelEvent, - // Allow different handlers to make the zoom faster or slower. - zoomModifier: number = 1 - ) { - const { - hasZoomedViaMousewheel, - setHasZoomedViaMousewheel, - viewportProps: { marginLeft, marginRight }, - } = this.props; - if (!hasZoomedViaMousewheel && setHasZoomedViaMousewheel) { - setHasZoomedViaMousewheel(); - } + zoomWithMouseWheel( + event: WheelEvent, + // Allow different handlers to make the zoom faster or slower. + zoomModifier: number = 1 + ) { + const { + hasZoomedViaMousewheel, + setHasZoomedViaMousewheel, + viewportProps: { marginLeft, marginRight }, + } = this.props; + if (!hasZoomedViaMousewheel && setHasZoomedViaMousewheel) { + setHasZoomedViaMousewheel(); + } - const deltaY = getNormalizedScrollDelta( - event, - this.state.containerHeight, - // Shift is a modifier that will change some mice to scroll horizontally, check - // for that here. - event.deltaY === 0 ? 'deltaX' : 'deltaY' - ); - const zoomFactor = this._deltaToZoomFactor(deltaY * zoomModifier); - - const mouseX = event.clientX; - const { containerLeft, containerWidth } = this.state; - const viewportPixelWidth = containerWidth - marginLeft - marginRight; - const mouseCenter = - (mouseX - containerLeft - marginLeft) / viewportPixelWidth; - this.zoomRangeSelection(mouseCenter, zoomFactor); - } + const deltaY = getNormalizedScrollDelta( + event, + this.state.containerHeight, + // Shift is a modifier that will change some mice to scroll horizontally, check + // for that here. + event.deltaY === 0 ? 'deltaX' : 'deltaY' + ); + const zoomFactor = this._deltaToZoomFactor(deltaY * zoomModifier); + + const mouseX = event.clientX; + const { containerLeft, containerWidth } = this.state; + const viewportPixelWidth = containerWidth - marginLeft - marginRight; + const mouseCenter = + (mouseX - containerLeft - marginLeft) / viewportPixelWidth; + this.zoomRangeSelection(mouseCenter, zoomFactor); + } - zoomRangeSelection = ( - // A number between 0 and 1 indicating the horizontal position of the - // zoom center. - center: number, - // The factor to zoom by. Factors smaller than 1 zoom in, larger than 1 zoom out. - zoomFactor: number - ) => { - const { disableHorizontalMovement, maximumZoom } = - this.props.viewportProps; - if (disableHorizontalMovement) { - return; - } + zoomRangeSelection = ( + // A number between 0 and 1 indicating the horizontal position of the + // zoom center. + center: number, + // The factor to zoom by. Factors smaller than 1 zoom in, larger than 1 zoom out. + zoomFactor: number + ) => { + const { disableHorizontalMovement, maximumZoom } = this.props.viewportProps; + if (disableHorizontalMovement) { + return; + } - this._addBatchedPreviewSelectionUpdate( - ({ viewportLeft, viewportRight }) => { - const viewportLength = viewportRight - viewportLeft; - const newViewportLength = clamp( - maximumZoom, - 1, - viewportLength * zoomFactor - ); - const deltaViewportLength = newViewportLength - viewportLength; - const newViewportLeft = clamp( - 0, - 1 - newViewportLength, - viewportLeft - deltaViewportLength * center - ); - const newViewportRight = clamp( - newViewportLength, - 1, - viewportRight + deltaViewportLength * (1 - center) - ); - - const { - viewportProps: { timeRange }, - } = this.props; - if (newViewportLeft === 0 && newViewportRight === 1) { - return { - hasSelection: false, - isModifying: false, - }; - } - const timeRangeLength = timeRange.end - timeRange.start; - return { - hasSelection: true, - isModifying: false, - selectionStart: - timeRange.start + timeRangeLength * newViewportLeft, - selectionEnd: - timeRange.start + timeRangeLength * newViewportRight, - }; - } + this._addBatchedPreviewSelectionUpdate( + ({ viewportLeft, viewportRight }) => { + const viewportLength = viewportRight - viewportLeft; + const newViewportLength = clamp( + maximumZoom, + 1, + viewportLength * zoomFactor + ); + const deltaViewportLength = newViewportLength - viewportLength; + const newViewportLeft = clamp( + 0, + 1 - newViewportLength, + viewportLeft - deltaViewportLength * center + ); + const newViewportRight = clamp( + newViewportLength, + 1, + viewportRight + deltaViewportLength * (1 - center) ); - }; - _mouseDownListener = (event: SyntheticMouseEvent<>) => { - event.preventDefault(); - if (this._container) { - this._container.focus(); + const { + viewportProps: { timeRange }, + } = this.props; + if (newViewportLeft === 0 && newViewportRight === 1) { + return { + hasSelection: false, + isModifying: false, + }; } + const timeRangeLength = timeRange.end - timeRange.start; + return { + hasSelection: true, + isModifying: false, + selectionStart: timeRange.start + timeRangeLength * newViewportLeft, + selectionEnd: timeRange.start + timeRangeLength * newViewportRight, + }; + } + ); + }; - window.addEventListener('mousemove', this._mouseMoveListener, true); - window.addEventListener('mouseup', this._mouseUpListener, true); - }; + _mouseDownListener = (event: SyntheticMouseEvent<>) => { + event.preventDefault(); + if (this._container) { + this._container.focus(); + } - _mouseMoveListener = (event: MouseEvent) => { - event.preventDefault(); + window.addEventListener('mousemove', this._mouseMoveListener, true); + window.addEventListener('mouseup', this._mouseUpListener, true); + }; - let { _dragX: dragX, _dragY: dragY } = this; - if (!this.state.isDragging) { - dragX = event.clientX; - dragY = event.clientY; - } + _mouseMoveListener = (event: MouseEvent) => { + event.preventDefault(); - const offsetX = event.clientX - dragX; - const offsetY = event.clientY - dragY; + let { _dragX: dragX, _dragY: dragY } = this; + if (!this.state.isDragging) { + dragX = event.clientX; + dragY = event.clientY; + } - this._dragX = event.clientX; - this._dragY = event.clientY; - this.setState({ isDragging: true }); + const offsetX = event.clientX - dragX; + const offsetY = event.clientY - dragY; - this.moveViewport(offsetX, offsetY); - }; + this._dragX = event.clientX; + this._dragY = event.clientY; + this.setState({ isDragging: true }); - _keyDownListener = ( - event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> - ) => { - let navigationKey; - if ( - !event.ctrlKey && - !event.shiftKey && - !event.altKey && - !event.metaKey - ) { - navigationKey = BARE_KEYMAP[event.nativeEvent.code]; - } else if (event.ctrlKey) { - /* Having the ctrl key down changes the meaning of the key - * it's modifying, so pick the navigation key from another keymap. */ - navigationKey = CTRL_KEYMAP[event.nativeEvent.code]; - } + this.moveViewport(offsetX, offsetY); + }; - if (navigationKey !== undefined) { - // Start requesting frames if there were no keys down before - // this event triggered. - if (this._keysDown.size === 0) { - requestAnimationFrame(this._keyboardNavigation); - this._lastKeyboardNavigationFrame = performance.now(); - } + _keyDownListener = ( + event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> + ) => { + let navigationKey; + if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { + navigationKey = BARE_KEYMAP[event.nativeEvent.code]; + } else if (event.ctrlKey) { + /* Having the ctrl key down changes the meaning of the key + * it's modifying, so pick the navigation key from another keymap. */ + navigationKey = CTRL_KEYMAP[event.nativeEvent.code]; + } - this._keysDown.add(navigationKey); - event.preventDefault(); - } - }; + if (navigationKey !== undefined) { + // Start requesting frames if there were no keys down before + // this event triggered. + if (this._keysDown.size === 0) { + requestAnimationFrame(this._keyboardNavigation); + this._lastKeyboardNavigationFrame = performance.now(); + } - _keyUpListener = ( - event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> - ) => { - if (!event.ctrlKey) { - // The ctrl modifier might have been released here. Try to - // delete all keys associated with the modifier. Since the - // navigation is aliased with non-ctrl-modified keys also, - // this will affect (stop) the operation even if it was - // introduced without a ctrl-modified key. - for (const code of getObjectValuesAsUnion(CTRL_KEYMAP)) { - this._keysDown.delete(code); - } - } - this._keysDown.delete(CTRL_KEYMAP[event.nativeEvent.code]); - this._keysDown.delete(BARE_KEYMAP[event.nativeEvent.code]); - }; + this._keysDown.add(navigationKey); + event.preventDefault(); + } + }; - _onBlur = () => { - this._keysDown.clear(); - }; + _keyUpListener = ( + event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> + ) => { + if (!event.ctrlKey) { + // The ctrl modifier might have been released here. Try to + // delete all keys associated with the modifier. Since the + // navigation is aliased with non-ctrl-modified keys also, + // this will affect (stop) the operation even if it was + // introduced without a ctrl-modified key. + for (const code of getObjectValuesAsUnion(CTRL_KEYMAP)) { + this._keysDown.delete(code); + } + } + this._keysDown.delete(CTRL_KEYMAP[event.nativeEvent.code]); + this._keysDown.delete(BARE_KEYMAP[event.nativeEvent.code]); + }; - _keyboardNavigation = (timestamp: number) => { - if (this._keysDown.size === 0) { - // No keys are down, nothing to do. Don't request a new - // animation frame. - return; - } + _onBlur = () => { + this._keysDown.clear(); + }; - const delta = Math.min( - KEYBOARD_NAVIGATION_SPEED * - (timestamp - this._lastKeyboardNavigationFrame) * - 0.001, - MAX_KEYBOARD_DELTA // Don't jump like crazy if we experience long janks - ); - this._lastKeyboardNavigationFrame = timestamp; - - for (const navigationKey of this._keysDown.values()) { - switch (navigationKey) { - case 'zoomIn': - this.zoomRangeSelection(0.5, this._deltaToZoomFactor(-delta)); - break; - case 'zoomOut': - this.zoomRangeSelection(0.5, this._deltaToZoomFactor(delta)); - break; - case 'up': - this.moveViewport(0, delta); - break; - case 'down': - this.moveViewport(0, -delta); - break; - case 'left': - this.moveViewport(delta, 0); - break; - case 'right': - this.moveViewport(-delta, 0); - break; - default: - throw assertExhaustiveCheck(navigationKey); - } - } - requestAnimationFrame(this._keyboardNavigation); - }; + _keyboardNavigation = (timestamp: number) => { + if (this._keysDown.size === 0) { + // No keys are down, nothing to do. Don't request a new + // animation frame. + return; + } - moveViewport = (offsetX: CssPixels, offsetY: CssPixels): void => { - const { - viewportProps: { - maxViewportHeight, - timeRange, - startsAtBottom, - disableHorizontalMovement, - marginLeft, - marginRight, - }, - } = this.props; - const { containerWidth, containerHeight, viewportTop, isSizeSet } = - this.state; - - if (!isSizeSet) { - // Moving the viewport and calculating its dimensions - // assumes its size is actually set. Just ignore the move - // request if it's not. - return; - } + const delta = Math.min( + KEYBOARD_NAVIGATION_SPEED * + (timestamp - this._lastKeyboardNavigationFrame) * + 0.001, + MAX_KEYBOARD_DELTA // Don't jump like crazy if we experience long janks + ); + this._lastKeyboardNavigationFrame = timestamp; + + for (const navigationKey of this._keysDown.values()) { + switch (navigationKey) { + case 'zoomIn': + this.zoomRangeSelection(0.5, this._deltaToZoomFactor(-delta)); + break; + case 'zoomOut': + this.zoomRangeSelection(0.5, this._deltaToZoomFactor(delta)); + break; + case 'up': + this.moveViewport(0, delta); + break; + case 'down': + this.moveViewport(0, -delta); + break; + case 'left': + this.moveViewport(delta, 0); + break; + case 'right': + this.moveViewport(-delta, 0); + break; + default: + throw assertExhaustiveCheck(navigationKey); + } + } + requestAnimationFrame(this._keyboardNavigation); + }; - // Calculate top and bottom in terms of pixels. - let newViewportTop: CssPixels = viewportTop - offsetY; - let newViewportBottom: CssPixels = newViewportTop + containerHeight; - - if (maxViewportHeight < containerHeight) { - // If the view is extra small, anchor content to the top or bottom. - if (startsAtBottom) { - newViewportTop = maxViewportHeight - containerHeight; - newViewportBottom = maxViewportHeight; - } else { - newViewportTop = 0; - newViewportBottom = containerHeight; - } - } else if (newViewportBottom > maxViewportHeight) { - // Constrain the viewport to the bottom. - newViewportTop = maxViewportHeight - containerHeight; - newViewportBottom = maxViewportHeight; - } else if (newViewportTop < 0) { - // Constrain the viewport to the top. - newViewportTop = 0; - newViewportBottom = containerHeight; - } + moveViewport = (offsetX: CssPixels, offsetY: CssPixels): void => { + const { + viewportProps: { + maxViewportHeight, + timeRange, + startsAtBottom, + disableHorizontalMovement, + marginLeft, + marginRight, + }, + } = this.props; + const { containerWidth, containerHeight, viewportTop, isSizeSet } = + this.state; + + if (!isSizeSet) { + // Moving the viewport and calculating its dimensions + // assumes its size is actually set. Just ignore the move + // request if it's not. + return; + } - if (newViewportTop !== viewportTop) { - this.setState({ - viewportTop: newViewportTop, - viewportBottom: newViewportBottom, - }); - } + // Calculate top and bottom in terms of pixels. + let newViewportTop: CssPixels = viewportTop - offsetY; + let newViewportBottom: CssPixels = newViewportTop + containerHeight; + + if (maxViewportHeight < containerHeight) { + // If the view is extra small, anchor content to the top or bottom. + if (startsAtBottom) { + newViewportTop = maxViewportHeight - containerHeight; + newViewportBottom = maxViewportHeight; + } else { + newViewportTop = 0; + newViewportBottom = containerHeight; + } + } else if (newViewportBottom > maxViewportHeight) { + // Constrain the viewport to the bottom. + newViewportTop = maxViewportHeight - containerHeight; + newViewportBottom = maxViewportHeight; + } else if (newViewportTop < 0) { + // Constrain the viewport to the top. + newViewportTop = 0; + newViewportBottom = containerHeight; + } - if (!disableHorizontalMovement) { - this._addBatchedPreviewSelectionUpdate( - ({ viewportLeft, viewportRight }) => { - // Calculate left and right in terms of the unit interval of the profile range. - const viewportLength = viewportRight - viewportLeft; - if (viewportLength >= 1) { - return { - hasSelection: false, - isModifying: false, - }; - } - const viewportPixelWidth = - containerWidth - marginLeft - marginRight; - const unitOffsetX = - offsetX * (viewportLength / viewportPixelWidth); - let newViewportLeft = viewportLeft - unitOffsetX; - let newViewportRight = viewportRight - unitOffsetX; - if (newViewportLeft < 0) { - newViewportLeft = 0; - newViewportRight = viewportLength; - } - if (newViewportRight > 1) { - newViewportLeft = 1 - viewportLength; - newViewportRight = 1; - } - - const timeRangeLength = timeRange.end - timeRange.start; - return { - hasSelection: true, - isModifying: false, - selectionStart: - timeRange.start + timeRangeLength * newViewportLeft, - selectionEnd: - timeRange.start + timeRangeLength * newViewportRight, - }; - } - ); - } - }; + if (newViewportTop !== viewportTop) { + this.setState({ + viewportTop: newViewportTop, + viewportBottom: newViewportBottom, + }); + } - _mouseUpListener = (event: MouseEvent) => { - event.preventDefault(); - window.removeEventListener('mousemove', this._mouseMoveListener, true); - window.removeEventListener('mouseup', this._mouseUpListener, true); - this.setState({ - isDragging: false, - }); - }; + if (!disableHorizontalMovement) { + this._addBatchedPreviewSelectionUpdate( + ({ viewportLeft, viewportRight }) => { + // Calculate left and right in terms of the unit interval of the profile range. + const viewportLength = viewportRight - viewportLeft; + if (viewportLength >= 1) { + return { + hasSelection: false, + isModifying: false, + }; + } + const viewportPixelWidth = containerWidth - marginLeft - marginRight; + const unitOffsetX = offsetX * (viewportLength / viewportPixelWidth); + let newViewportLeft = viewportLeft - unitOffsetX; + let newViewportRight = viewportRight - unitOffsetX; + if (newViewportLeft < 0) { + newViewportLeft = 0; + newViewportRight = viewportLength; + } + if (newViewportRight > 1) { + newViewportLeft = 1 - viewportLength; + newViewportRight = 1; + } - componentDidMount() { - // The first _setSize ensures that the screen does not blip when mounting - // the component, while the second ensures that it lays out correctly if the DOM - // is not fully layed out correctly yet. - this._setSize(); - this._setSizeNextFrame(); - if (this._container) { - const container = this._container; - getResizeObserverWrapper().subscribe(container, this._setSize); - container.addEventListener('wheel', this._mouseWheelListener, { - passive: false, - }); + const timeRangeLength = timeRange.end - timeRange.start; + return { + hasSelection: true, + isModifying: false, + selectionStart: timeRange.start + timeRangeLength * newViewportLeft, + selectionEnd: timeRange.start + timeRangeLength * newViewportRight, + }; } - } + ); + } + }; - componentWillUnmount() { - window.removeEventListener('resize', this._setSizeNextFrame, false); - window.removeEventListener('mousemove', this._mouseMoveListener, true); - window.removeEventListener('mouseup', this._mouseUpListener, true); - const container = this._container; - if (container) { - getResizeObserverWrapper().unsubscribe(container, this._setSize); - container.removeEventListener('wheel', this._mouseWheelListener, { - passive: false, - }); - } - } + _mouseUpListener = (event: MouseEvent) => { + event.preventDefault(); + window.removeEventListener('mousemove', this._mouseMoveListener, true); + window.removeEventListener('mouseup', this._mouseUpListener, true); + this.setState({ + isDragging: false, + }); + }; - render() { - const { - chart, - chartProps, - hasZoomedViaMousewheel, - viewportProps: { className }, - } = this.props; + componentDidMount() { + // The first _setSize ensures that the screen does not blip when mounting + // the component, while the second ensures that it lays out correctly if the DOM + // is not fully layed out correctly yet. + this._setSize(); + this._setSizeNextFrame(); + if (this._container) { + const container = this._container; + getResizeObserverWrapper().subscribe(container, this._setSize); + container.addEventListener('wheel', this._mouseWheelListener, { + passive: false, + }); + } + } - const { - containerWidth, - containerHeight, - viewportTop, - viewportBottom, - horizontalViewport: { viewportLeft, viewportRight }, - isDragging, - isScrollHintVisible, - isSizeSet, - } = this.state; - - const viewportClassName = classNames( - { - chartViewport: true, - dragging: isDragging, - }, - className - ); + componentWillUnmount() { + window.removeEventListener('resize', this._setSizeNextFrame, false); + window.removeEventListener('mousemove', this._mouseMoveListener, true); + window.removeEventListener('mouseup', this._mouseUpListener, true); + const container = this._container; + if (container) { + getResizeObserverWrapper().unsubscribe(container, this._setSize); + container.removeEventListener('wheel', this._mouseWheelListener, { + passive: false, + }); + } + } - const scrollClassName = classNames({ - chartViewportScroll: true, - hidden: hasZoomedViaMousewheel || !isScrollHintVisible, - }); - - const viewport: Viewport = { - containerWidth, - containerHeight, - viewportLeft, - viewportRight, - viewportTop, - viewportBottom, - isDragging, - moveViewport: this.moveViewport, - isSizeSet, - }; + render() { + const { + chart, + chartProps, + hasZoomedViaMousewheel, + viewportProps: { className }, + } = this.props; + + const { + containerWidth, + containerHeight, + viewportTop, + viewportBottom, + horizontalViewport: { viewportLeft, viewportRight }, + isDragging, + isScrollHintVisible, + isSizeSet, + } = this.state; + + const viewportClassName = classNames( + { + chartViewport: true, + dragging: isDragging, + }, + className + ); - const Chart = chart; - const chartPropsWithViewport = { - ...chartProps, - viewport, - }; - return ( -
- -
- Zoom Chart: - Ctrl + Scrollor - Shift + Scroll -
-
- ); - } - } + const scrollClassName = classNames({ + chartViewportScroll: true, + hidden: hasZoomedViaMousewheel || !isScrollHintVisible, + }); + + const viewport: Viewport = { + containerWidth, + containerHeight, + viewportLeft, + viewportRight, + viewportTop, + viewportBottom, + isDragging, + moveViewport: this.moveViewport, + isSizeSet, + }; + + const Chart = chart; + const chartPropsWithViewport = { + ...chartProps, + viewport, + }; + return ( +
+ +
+ Zoom Chart: + Ctrl + Scrollor + Shift + Scroll +
+
+ ); + } +} export type ChartViewportProps = {| +viewportProps: ViewportProps, From 200b0ff6b554529071a366c75cd44878cba9fade Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 19:31:10 -0400 Subject: [PATCH 014/124] Pass the correct value to the reducer's action argument. Not sure why Flow didn't catch this. TypeScript caught it. --- src/reducers/url-state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reducers/url-state.js b/src/reducers/url-state.js index 816d4264a6..86d65c2a5c 100644 --- a/src/reducers/url-state.js +++ b/src/reducers/url-state.js @@ -707,7 +707,7 @@ const wrapReducerInResetter = ( // Invalidate all information that would be specific to an individual profile. return { ...regularUrlStateReducer(state, action), - profileSpecific: profileSpecific(undefined, state), + profileSpecific: profileSpecific(undefined, action), selectedTab: selectedTab(undefined, action), }; default: From 0aede865caca3df4d489cff05cc21a92abb18883 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 27 Nov 2024 13:20:00 -0500 Subject: [PATCH 015/124] Make these variables start with a lowercase letter. --- src/profile-logic/call-tree.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 32ad1e7e75..b13258cf44 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -764,7 +764,7 @@ export function computeCallTreeTimingsInverted( export function computeCallTreeTimings( callNodeInfo: CallNodeInfo, - CallNodeSelfAndSummary: CallNodeSelfAndSummary + callNodeSelfAndSummary: CallNodeSelfAndSummary ): CallTreeTimings { const callNodeInfoInverted = callNodeInfo.asInverted(); if (callNodeInfoInverted !== null) { @@ -772,7 +772,7 @@ export function computeCallTreeTimings( type: 'INVERTED', timings: computeCallTreeTimingsInverted( callNodeInfoInverted, - CallNodeSelfAndSummary + callNodeSelfAndSummary ), }; } @@ -780,7 +780,7 @@ export function computeCallTreeTimings( type: 'NON_INVERTED', timings: computeCallTreeTimingsNonInverted( callNodeInfo, - CallNodeSelfAndSummary + callNodeSelfAndSummary ), }; } @@ -791,10 +791,10 @@ export function computeCallTreeTimings( */ export function computeCallTreeTimingsNonInverted( callNodeInfo: CallNodeInfo, - CallNodeSelfAndSummary: CallNodeSelfAndSummary + callNodeSelfAndSummary: CallNodeSelfAndSummary ): CallTreeTimingsNonInverted { const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); - const { callNodeSelf, rootTotalSummary } = CallNodeSelfAndSummary; + const { callNodeSelf, rootTotalSummary } = callNodeSelfAndSummary; // Compute the following variables: const callNodeTotalSummary = new Float64Array(callNodeTable.length); From ed53a8db58ca4e5b878f59c9db8725b2ca80b376 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 27 Nov 2024 13:20:00 -0500 Subject: [PATCH 016/124] Simplify the arguments of getCallNodeInfoInverted. --- src/profile-logic/profile-data.js | 7 +++---- src/selectors/per-thread/stack-sample.js | 9 +-------- src/test/unit/address-timings.test.js | 3 +-- src/test/unit/line-timings.test.js | 3 +-- src/test/unit/profile-data.test.js | 13 ++++--------- src/test/unit/profile-tree.test.js | 3 +-- 6 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index 4ae6f0a4d9..a4366d58da 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -574,14 +574,13 @@ function _computeCallNodeTableExtraColumns( * Generate the inverted CallNodeInfo for a thread. */ export function getInvertedCallNodeInfo( - nonInvertedCallNodeTable: CallNodeTable, - stackIndexToNonInvertedCallNodeIndex: Int32Array, + callNodeInfo: CallNodeInfo, defaultCategory: IndexIntoCategoryList, funcCount: number ): CallNodeInfoInverted { return new CallNodeInfoInverted( - nonInvertedCallNodeTable, - stackIndexToNonInvertedCallNodeIndex, + callNodeInfo.getNonInvertedCallNodeTable(), + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), defaultCategory, funcCount ); diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index cb64859145..84ad54b7e3 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -118,14 +118,7 @@ export function getStackAndSampleSelectorsPerThread( _getNonInvertedCallNodeInfo, ProfileSelectors.getDefaultCategory, (state) => threadSelectors.getFilteredThread(state).funcTable.length, - (nonInvertedCallNodeInfo, defaultCategory, funcCount) => { - return ProfileData.getInvertedCallNodeInfo( - nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), - nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), - defaultCategory, - funcCount - ); - } + ProfileData.getInvertedCallNodeInfo ); const getCallNodeInfo: Selector = (state) => { diff --git a/src/test/unit/address-timings.test.js b/src/test/unit/address-timings.test.js index cbf31d10a5..fc110ac415 100644 --- a/src/test/unit/address-timings.test.js +++ b/src/test/unit/address-timings.test.js @@ -169,8 +169,7 @@ describe('getAddressTimings for getStackAddressInfoForCallNode', function () { ); const callNodeInfo = isInverted ? getInvertedCallNodeInfo( - nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), - nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + nonInvertedCallNodeInfo, defaultCategory, funcTable.length ) diff --git a/src/test/unit/line-timings.test.js b/src/test/unit/line-timings.test.js index 3c868a68af..23f17a5d68 100644 --- a/src/test/unit/line-timings.test.js +++ b/src/test/unit/line-timings.test.js @@ -128,8 +128,7 @@ describe('getLineTimings for getStackLineInfoForCallNode', function () { ); const callNodeInfo = isInverted ? getInvertedCallNodeInfo( - nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), - nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + nonInvertedCallNodeInfo, defaultCategory, funcTable.length ) diff --git a/src/test/unit/profile-data.test.js b/src/test/unit/profile-data.test.js index 50f64a2a99..a1553c6441 100644 --- a/src/test/unit/profile-data.test.js +++ b/src/test/unit/profile-data.test.js @@ -586,8 +586,7 @@ describe('getInvertedCallNodeInfo', function () { ); const invertedCallNodeInfo = getInvertedCallNodeInfo( - nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), - nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + nonInvertedCallNodeInfo, defaultCategory, thread.funcTable.length ); @@ -962,16 +961,13 @@ describe('getSamplesSelectedStates', function () { thread.frameTable, 0 ); - const stackIndexToCallNodeIndex = - callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const sampleCallNodes = getSampleIndexToCallNodeIndex( thread.samples.stack, - stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ); const callNodeInfoInverted = getInvertedCallNodeInfo( - callNodeInfo.getNonInvertedCallNodeTable(), - stackIndexToCallNodeIndex, + callNodeInfo, defaultCategory, thread.funcTable.length ); @@ -1479,8 +1475,7 @@ describe('getNativeSymbolsForCallNode', function () { defaultCategory ); const callNodeInfo = getInvertedCallNodeInfo( - nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), - nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + nonInvertedCallNodeInfo, defaultCategory, thread.funcTable.length ); diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index 1d5a3e6773..d9cdffabe8 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -471,8 +471,7 @@ describe('inverted call tree', function () { // Now compute the inverted tree and check it. const invertedCallNodeInfo = getInvertedCallNodeInfo( - callNodeInfo.getNonInvertedCallNodeTable(), - callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + callNodeInfo, defaultCategory, thread.funcTable.length ); From 0e29fe10a346d161df07f7c7137ae63ae045537d Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Fri, 25 Jul 2025 14:28:59 -0400 Subject: [PATCH 017/124] Rename nonInvertedCallNodeTable to callNodeTable everywhere. There is no inverted call node table. The call node table is always non-inverted. (Well, the inverted call node info internally stores two tables, for the inverted root nodes and the inverted non-root nodes, but those tables aren't exposed to the outside and should be considered an implementation detail of the inverted call node info. I actually forgot about this part when I first wrote this patch. I'm less sure about the rename now, but I still feel like it's worth doing.) --- src/actions/profile-view.js | 8 ++++---- src/components/flame-graph/Canvas.js | 4 ++-- src/components/flame-graph/FlameGraph.js | 8 ++++---- src/components/shared/CallNodeContextMenu.js | 2 +- src/components/shared/thread/StackGraph.js | 12 ++++++------ src/components/stack-chart/Canvas.js | 2 +- src/profile-logic/call-node-info.js | 20 ++++++++++---------- src/profile-logic/call-tree.js | 17 ++++++++--------- src/profile-logic/profile-data.js | 20 ++++++++++---------- src/profile-logic/stack-timing.js | 2 +- src/selectors/per-thread/stack-sample.js | 8 ++++---- src/test/fixtures/utils.js | 2 +- src/test/store/actions.test.js | 4 ++-- src/test/unit/profile-data.test.js | 8 ++++---- src/test/unit/profile-tree.test.js | 10 +++++----- src/types/profile-derived.js | 9 ++++++++- 16 files changed, 71 insertions(+), 65 deletions(-) diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js index 4be1b0320c..bc69596aa9 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.js @@ -182,7 +182,7 @@ export function selectSelfCallNode( // We're not calling callNodeInfo.getCallNodePathFromIndex here because we // only have a non-inverted call node index, which wouldn't be accepted by // the inverted call node info. - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const callNodePath = []; let cni = nonInvertedSelfCallNode; while (cni !== -1) { @@ -1951,7 +1951,7 @@ export function handleCallNodeTransformShortcut( const funcIndex = callNodeInfo.funcForNode(callNodeIndex); const category = callNodeInfo.categoryForNode(callNodeIndex); - const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); switch (event.key) { case 'F': @@ -2010,7 +2010,7 @@ export function handleCallNodeTransformShortcut( break; } case 'r': { - if (funcHasRecursiveCall(nonInvertedCallNodeTable, funcIndex)) { + if (funcHasRecursiveCall(callNodeTable, funcIndex)) { dispatch( addTransformToStack(threadsKey, { type: 'collapse-recursion', @@ -2021,7 +2021,7 @@ export function handleCallNodeTransformShortcut( break; } case 'R': { - if (funcHasDirectRecursiveCall(nonInvertedCallNodeTable, funcIndex)) { + if (funcHasDirectRecursiveCall(callNodeTable, funcIndex)) { dispatch( addTransformToStack(threadsKey, { type: 'collapse-direct-recursion', diff --git a/src/components/flame-graph/Canvas.js b/src/components/flame-graph/Canvas.js index 8284dc3e68..6d2f08564c 100644 --- a/src/components/flame-graph/Canvas.js +++ b/src/components/flame-graph/Canvas.js @@ -162,7 +162,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { return; } - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[selectedCallNodeIndex]; const y = (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT; @@ -236,7 +236,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { fastFillStyle.set('#ffffff'); ctx.fillRect(0, 0, deviceContainerWidth, deviceContainerHeight); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const startDepth = Math.floor( maxStackDepthPlusOne - viewportBottom / stackFrameHeight diff --git a/src/components/flame-graph/FlameGraph.js b/src/components/flame-graph/FlameGraph.js index 6823987456..cadbfa7d99 100644 --- a/src/components/flame-graph/FlameGraph.js +++ b/src/components/flame-graph/FlameGraph.js @@ -163,7 +163,7 @@ class FlameGraphImpl extends React.PureComponent { _wideEnough = (callNodeIndex: IndexIntoCallNodeTable): boolean => { const { flameGraphTiming, callNodeInfo } = this.props; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; const columnIndex = row.callNode.indexOf(callNodeIndex); @@ -188,7 +188,7 @@ class FlameGraphImpl extends React.PureComponent { let callNodeIndex = startingCallNodeIndex; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; let columnIndex = row.callNode.indexOf(callNodeIndex); @@ -219,7 +219,7 @@ class FlameGraphImpl extends React.PureComponent { changeSelectedCallNode, handleCallNodeTransformShortcut, } = this.props; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); if ( // Please do not forget to update the switch/case below if changing the array to allow more keys. @@ -305,7 +305,7 @@ class FlameGraphImpl extends React.PureComponent { if (document.activeElement === this._viewport) { event.preventDefault(); const { callNodeInfo, selectedCallNodeIndex, thread } = this.props; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); if (selectedCallNodeIndex !== null) { const funcIndex = callNodeTable.func[selectedCallNodeIndex]; const funcName = thread.stringTable.getString( diff --git a/src/components/shared/CallNodeContextMenu.js b/src/components/shared/CallNodeContextMenu.js index 01f7ed46f1..498f29e7da 100644 --- a/src/components/shared/CallNodeContextMenu.js +++ b/src/components/shared/CallNodeContextMenu.js @@ -594,7 +594,7 @@ class CallNodeContextMenuImpl extends React.PureComponent { filePath && parseFileNameFromSymbolication(filePath).path.match(/[^\\/]+$/)?.[0]; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const showOpenDebuggerItem = isJS && diff --git a/src/components/shared/thread/StackGraph.js b/src/components/shared/thread/StackGraph.js index 64e8c8638c..9d0df41da4 100644 --- a/src/components/shared/thread/StackGraph.js +++ b/src/components/shared/thread/StackGraph.js @@ -44,8 +44,8 @@ export class ThreadStackGraph extends PureComponent { return null; } - const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); - return nonInvertedCallNodeTable.depth[nonInvertedCallNodeIndex]; + const callNodeTable = callNodeInfo.getCallNodeTable(); + return callNodeTable.depth[nonInvertedCallNodeIndex]; }; render() { @@ -61,12 +61,12 @@ export class ThreadStackGraph extends PureComponent { trackName, onSampleClick, } = this.props; - const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); let maxDepth = 0; - for (let i = 0; i < nonInvertedCallNodeTable.depth.length; i++) { - if (nonInvertedCallNodeTable.depth[i] > maxDepth) { - maxDepth = nonInvertedCallNodeTable.depth[i]; + for (let i = 0; i < callNodeTable.depth.length; i++) { + if (callNodeTable.depth[i] > maxDepth) { + maxDepth = callNodeTable.depth[i]; } } diff --git a/src/components/stack-chart/Canvas.js b/src/components/stack-chart/Canvas.js index 3dbb09d717..d824275511 100644 --- a/src/components/stack-chart/Canvas.js +++ b/src/components/stack-chart/Canvas.js @@ -358,7 +358,7 @@ class StackChartCanvasImpl extends React.PureComponent { categoryForUserTiming = 0; } - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); // Only draw the stack frames that are vertically within view. for (let depth = startDepth; depth < endDepth; depth++) { diff --git a/src/profile-logic/call-node-info.js b/src/profile-logic/call-node-info.js index f5203318fc..d5a1943d16 100644 --- a/src/profile-logic/call-node-info.js +++ b/src/profile-logic/call-node-info.js @@ -37,7 +37,7 @@ export interface CallNodeInfo { // Returns the non-inverted call node table. // This is always the non-inverted call node table, regardless of isInverted(). - getNonInvertedCallNodeTable(): CallNodeTable; + getCallNodeTable(): CallNodeTable; // Returns a mapping from the stack table to the non-inverted call node table. // The Int32Array should be used as if it were a @@ -122,7 +122,7 @@ export class CallNodeInfoNonInverted implements CallNodeInfo { return null; } - getNonInvertedCallNodeTable(): CallNodeTable { + getCallNodeTable(): CallNodeTable { return this._callNodeTable; } @@ -507,7 +507,7 @@ type SuffixOrderForInvertedRoots = {| * nodes can be very high, e.g. ~3 million for https://share.firefox.dev/3N56qMu */ function _computeSuffixOrderForInvertedRoots( - nonInvertedCallNodeTable: CallNodeTable, + callNodeTable: CallNodeTable, funcCount: number ): SuffixOrderForInvertedRoots { // Rather than using Array.prototype.sort, this function uses the technique @@ -524,8 +524,8 @@ function _computeSuffixOrderForInvertedRoots( // Pass 1: Compute, per func, how many non-inverted call nodes end in this func. const nodeCountPerFunc = new Uint32Array(funcCount); - const callNodeCount = nonInvertedCallNodeTable.length; - const callNodeTableFuncCol = nonInvertedCallNodeTable.func; + const callNodeCount = callNodeTable.length; + const callNodeTableFuncCol = callNodeTable.func; for (let i = 0; i < callNodeCount; i++) { const func = callNodeTableFuncCol[i]; nodeCountPerFunc[func]++; @@ -931,13 +931,13 @@ export class CallNodeInfoInverted implements CallNodeInfo { // from sample counts. _rootCount: number; - // This is a Map. + // This is a Map. // It lists the non-inverted call nodes in "suffix order", i.e. ordered by // comparing their call paths from back to front. _suffixOrderedCallNodes: Uint32Array; // This is the inverse of _suffixOrderedCallNodes; i.e. it is a - // Map. + // Map. _suffixOrderIndexes: Uint32Array; // The default category (usually "Other"), used when creating new inverted @@ -993,7 +993,7 @@ export class CallNodeInfoInverted implements CallNodeInfo { return this; } - getNonInvertedCallNodeTable(): CallNodeTable { + getCallNodeTable(): CallNodeTable { return this._callNodeTable; } @@ -1001,7 +1001,7 @@ export class CallNodeInfoInverted implements CallNodeInfo { return this._stackIndexToNonInvertedCallNodeIndex; } - // Get a mapping SuffixOrderIndex -> IndexIntoNonInvertedCallNodeTable. + // Get a mapping SuffixOrderIndex -> IndexIntoCallNodeTable. // This array contains all non-inverted call node indexes, ordered by // call path suffix. See "suffix order" in the documentation above. // Note that the contents of this array will be mutated by CallNodeInfoInverted @@ -1013,7 +1013,7 @@ export class CallNodeInfoInverted implements CallNodeInfo { } // Returns the inverse of getSuffixOrderedCallNodes(), i.e. a mapping - // IndexIntoNonInvertedCallNodeTable -> SuffixOrderIndex. + // IndexIntoCallNodeTable -> SuffixOrderIndex. // Note that the contents of this array will be mutated by CallNodeInfoInverted // when new inverted nodes are created on demand (e.g. during a call to // getChildren or to getCallNodeIndexFromPath). So callers should not hold on diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index b13258cf44..39f5758b7f 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -115,7 +115,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { callTreeTimings: CallTreeTimingsNonInverted ) { this._callNodeInfo = callNodeInfo; - this._callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + this._callNodeTable = callNodeInfo.getCallNodeTable(); this._callTreeTimings = callTreeTimings; this._callNodeHasChildren = callTreeTimings.callNodeHasChildren; } @@ -192,7 +192,7 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { class CallTreeInternalInverted implements CallTreeInternal { _callNodeInfo: CallNodeInfoInverted; - _nonInvertedCallNodeTable: CallNodeTable; + _callNodeTable: CallNodeTable; _callNodeSelf: Float64Array; _rootNodes: IndexIntoCallNodeTable[]; _funcCount: number; @@ -208,7 +208,7 @@ class CallTreeInternalInverted implements CallTreeInternal { callTreeTimingsInverted: CallTreeTimingsInverted ) { this._callNodeInfo = callNodeInfo; - this._nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + this._callNodeTable = callNodeInfo.getCallNodeTable(); this._callNodeSelf = callTreeTimingsInverted.callNodeSelf; const { sortedRoots, totalPerRootFunc, hasChildrenPerRootFunc } = callTreeTimingsInverted; @@ -303,9 +303,9 @@ class CallTreeInternalInverted implements CallTreeInternal { for ( let currentNode = maxNode; currentNode !== -1; - currentNode = this._nonInvertedCallNodeTable.prefix[currentNode] + currentNode = this._callNodeTable.prefix[currentNode] ) { - callPath.push(this._nonInvertedCallNodeTable.func[currentNode]); + callPath.push(this._callNodeTable.func[currentNode]); } return callPath; } @@ -680,8 +680,7 @@ function _getInvertedTreeNodeTotalAndHasChildren( const [rangeStart, rangeEnd] = callNodeInfo.getSuffixOrderIndexRangeForCallNode(callNodeIndex); const suffixOrderedCallNodes = callNodeInfo.getSuffixOrderedCallNodes(); - const callNodeTableDepthCol = - callNodeInfo.getNonInvertedCallNodeTable().depth; + const callNodeTableDepthCol = callNodeInfo.getCallNodeTable().depth; // Warning: This function can be quite confusing. That's because we are dealing // with both inverted call nodes and non-inverted call nodes. @@ -723,7 +722,7 @@ export function computeCallTreeTimingsInverted( { callNodeSelf, rootTotalSummary }: CallNodeSelfAndSummary ): CallTreeTimingsInverted { const funcCount = callNodeInfo.getFuncCount(); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const callNodeTableFuncCol = callNodeTable.func; const callNodeTableDepthCol = callNodeTable.depth; const totalPerRootFunc = new Float64Array(funcCount); @@ -793,7 +792,7 @@ export function computeCallTreeTimingsNonInverted( callNodeInfo: CallNodeInfo, callNodeSelfAndSummary: CallNodeSelfAndSummary ): CallTreeTimingsNonInverted { - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const { callNodeSelf, rootTotalSummary } = callNodeSelfAndSummary; // Compute the following variables: diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index a4366d58da..34ddac0e1b 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -579,7 +579,7 @@ export function getInvertedCallNodeInfo( funcCount: number ): CallNodeInfoInverted { return new CallNodeInfoInverted( - callNodeInfo.getNonInvertedCallNodeTable(), + callNodeInfo.getCallNodeTable(), callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), defaultCategory, funcCount @@ -600,19 +600,19 @@ export function getInvertedCallNodeInfo( function _compareNonInvertedCallNodesInSuffixOrder( callNodeA: IndexIntoCallNodeTable, callNodeB: IndexIntoCallNodeTable, - nonInvertedCallNodeTable: CallNodeTable + callNodeTable: CallNodeTable ): number { // Walk up both and stop at the first non-matching function. // Walking up the non-inverted tree is equivalent to walking down the // inverted tree. while (true) { - const funcA = nonInvertedCallNodeTable.func[callNodeA]; - const funcB = nonInvertedCallNodeTable.func[callNodeB]; + const funcA = callNodeTable.func[callNodeA]; + const funcB = callNodeTable.func[callNodeB]; if (funcA !== funcB) { return funcA - funcB; } - callNodeA = nonInvertedCallNodeTable.prefix[callNodeA]; - callNodeB = nonInvertedCallNodeTable.prefix[callNodeB]; + callNodeA = callNodeTable.prefix[callNodeA]; + callNodeB = callNodeTable.prefix[callNodeB]; if (callNodeA === callNodeB) { break; } @@ -822,7 +822,7 @@ function _getSamplesSelectedStatesNonInverted( selectedCallNodeIndex: IndexIntoCallNodeTable, callNodeInfo: CallNodeInfo ): SelectedState[] { - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const selectedCallNodeDescendantsEndIndex = callNodeTable.subtreeRangeEnd[selectedCallNodeIndex]; const sampleCount = sampleCallNodes.length; @@ -1068,7 +1068,7 @@ export function getTimingsForCallNodeIndex( return { forPath: pathTimings, rootTime }; } - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const callNodeInfoInverted = callNodeInfo.asInverted(); @@ -2165,7 +2165,7 @@ export function computeCallNodeMaxDepthPlusOne( // computed for the filtered thread, but a samples-like table can use the preview // filtered thread, which involves a subset of the total call nodes. let maxDepth = -1; - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); // TODO: Use sampleCallNodes instead const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); @@ -2954,7 +2954,7 @@ function _getTreeOrderComparatorInverted( sampleNonInvertedCallNodes: Array, callNodeInfo: CallNodeInfoInverted ): (IndexIntoSamplesTable, IndexIntoSamplesTable) => number { - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); return function treeOrderComparator( sampleA: IndexIntoSamplesTable, sampleB: IndexIntoSamplesTable diff --git a/src/profile-logic/stack-timing.js b/src/profile-logic/stack-timing.js index 00824c7dbd..75b59f814d 100644 --- a/src/profile-logic/stack-timing.js +++ b/src/profile-logic/stack-timing.js @@ -108,7 +108,7 @@ export function getStackTimingByDepth( maxDepthPlusOne: number, interval: Milliseconds ): StackTimingByDepthWithMap { - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const { prefix: callNodeTablePrefixColumn, subtreeRangeEnd: callNodeTableSubtreeRangeEndColumn, diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index 84ad54b7e3..1bd65fa4eb 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -297,7 +297,7 @@ export function getStackAndSampleSelectorsPerThread( return CallTree.computeCallNodeSelfAndSummary( samples, sampleIndexToCallNodeIndex, - callNodeInfo.getNonInvertedCallNodeTable().length + callNodeInfo.getCallNodeTable().length ); } ); @@ -348,7 +348,7 @@ export function getStackAndSampleSelectorsPerThread( CallTree.computeCallNodeTracedSelfAndSummary( samples, sampleIndexToCallNodeIndex, - callNodeInfo.getNonInvertedCallNodeTable().length, + callNodeInfo.getCallNodeTable().length, interval ); if (CallNodeSelfAndSummary === null) { @@ -396,7 +396,7 @@ export function getStackAndSampleSelectorsPerThread( _getStackTimingByDepthWithMap(state).sameWidthsIndexToTimestampMap; const getFlameGraphRows: Selector = createSelector( - (state) => getCallNodeInfo(state).getNonInvertedCallNodeTable(), + (state) => getCallNodeInfo(state).getCallNodeTable(), (state) => threadSelectors.getFilteredThread(state).funcTable, (state) => threadSelectors.getFilteredThread(state).stringTable, FlameGraph.computeFlameGraphRows @@ -405,7 +405,7 @@ export function getStackAndSampleSelectorsPerThread( const getFlameGraphTiming: Selector = createSelector( getFlameGraphRows, - (state) => getCallNodeInfo(state).getNonInvertedCallNodeTable(), + (state) => getCallNodeInfo(state).getCallNodeTable(), getCallTreeTimingsNonInverted, FlameGraph.getFlameGraphTiming ); diff --git a/src/test/fixtures/utils.js b/src/test/fixtures/utils.js index 520417989f..68dc010a1c 100644 --- a/src/test/fixtures/utils.js +++ b/src/test/fixtures/utils.js @@ -183,7 +183,7 @@ export function callTreeFromProfile( thread.samples.stack, callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ), - callNodeInfo.getNonInvertedCallNodeTable().length + callNodeInfo.getCallNodeTable().length ) ); return getCallTree( diff --git a/src/test/store/actions.test.js b/src/test/store/actions.test.js index 0fe4af60c0..d287369414 100644 --- a/src/test/store/actions.test.js +++ b/src/test/store/actions.test.js @@ -202,7 +202,7 @@ describe('selectors/getFlameGraphTiming', function () { const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); @@ -233,7 +233,7 @@ describe('selectors/getFlameGraphTiming', function () { const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); diff --git a/src/test/unit/profile-data.test.js b/src/test/unit/profile-data.test.js index a1553c6441..74973f611c 100644 --- a/src/test/unit/profile-data.test.js +++ b/src/test/unit/profile-data.test.js @@ -453,7 +453,7 @@ describe('profile-data', function () { thread.frameTable, defaultCategory ); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); it('should create one callNode per original stack', function () { // After nudgeReturnAddresses, the stack table now has 8 entries. @@ -502,7 +502,7 @@ describe('profile-data', function () { thread.frameTable, defaultCategory ); - const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodeTable = callNodeInfo.getCallNodeTable(); const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const stack0 = thread.samples.stack[0]; @@ -899,13 +899,13 @@ describe('funcHasDirectRecursiveCall and funcHasRecursiveCall', function () { thread.stackTable, thread.frameTable, defaultCategory - ).getNonInvertedCallNodeTable(); + ).getCallNodeTable(); const jsOnlyThread = filterThreadByImplementation(thread, 'js'); const jsOnlyCallNodeTable = getCallNodeInfo( jsOnlyThread.stackTable, jsOnlyThread.frameTable, defaultCategory - ).getNonInvertedCallNodeTable(); + ).getCallNodeTable(); return { callNodeTable, jsOnlyCallNodeTable, funcNames }; } diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index d9cdffabe8..f2e9b586ce 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -73,7 +73,7 @@ describe('unfiltered call tree', function () { thread.samples.stack, callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ), - callNodeInfo.getNonInvertedCallNodeTable().length + callNodeInfo.getCallNodeTable().length ) ); expect(callTreeTimings).toEqual({ @@ -120,7 +120,7 @@ describe('unfiltered call tree', function () { const cnKN = callNodeInfo.getCallNodeIndexFromPath([K, N]); const rows = computeFlameGraphRows( - callNodeInfo.getNonInvertedCallNodeTable(), + callNodeInfo.getCallNodeTable(), thread.funcTable, thread.stringTable ); @@ -442,7 +442,7 @@ describe('inverted call tree', function () { thread.samples.stack, callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ), - callNodeInfo.getNonInvertedCallNodeTable().length + callNodeInfo.getCallNodeTable().length ) ); const callTree = getCallTree( @@ -483,7 +483,7 @@ describe('inverted call tree', function () { thread.samples.stack, invertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ), - invertedCallNodeInfo.getNonInvertedCallNodeTable().length + invertedCallNodeInfo.getCallNodeTable().length ) ); const invertedCallTree = getCallTree( @@ -632,7 +632,7 @@ describe('diffing trees', function () { thread.samples.stack, callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() ), - callNodeInfo.getNonInvertedCallNodeTable().length + callNodeInfo.getCallNodeTable().length ) ); expect(callTreeTimings.timings.rootTotalSummary).toBe(12); diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index 16875cb875..da9dcab43a 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -208,7 +208,14 @@ export type StackTable = {| |}; /** - * Contains a table of function call information that represents the stacks of what + * Similar to the StackTable, but based on functions rather than on frames. + * + * The CallNodeTable, like the StackTable, always describes the *non-inverted* tree. + * Indexes into its columns are the non-inverted call node indexes. + * + * # Detailed description + * + * The CallNodeTable is a table of function call information and represents the stacks of what * functions were called, as opposed to stacks based on frames. There can be multiple * frames for a single function call. Using stacks as opposed to a computed tree of * CallNodes can cause duplicated functions in the call tree. From 7490ae9940649fad60f9768080dfbaed0a2c0b93 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 27 Nov 2024 13:20:00 -0500 Subject: [PATCH 018/124] Minor refactoring in computeCallTreeTimingsNonInverted. No functional changes. --- src/profile-logic/call-tree.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 39f5758b7f..3f878fa902 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -796,7 +796,7 @@ export function computeCallTreeTimingsNonInverted( const { callNodeSelf, rootTotalSummary } = callNodeSelfAndSummary; // Compute the following variables: - const callNodeTotalSummary = new Float64Array(callNodeTable.length); + const callNodeTotal = new Float64Array(callNodeTable.length); const callNodeHasChildren = new Uint8Array(callNodeTable.length); // We loop the call node table in reverse, so that we find the children @@ -807,9 +807,13 @@ export function computeCallTreeTimingsNonInverted( callNodeIndex >= 0; callNodeIndex-- ) { - callNodeTotalSummary[callNodeIndex] += callNodeSelf[callNodeIndex]; + // callNodeTotal[callNodeIndex] is the sum of our children's totals. + // Compute this node's total by adding this node's self. + const total = callNodeTotal[callNodeIndex] + callNodeSelf[callNodeIndex]; const hasChildren = callNodeHasChildren[callNodeIndex] !== 0; - const hasTotalValue = callNodeTotalSummary[callNodeIndex] !== 0; + const hasTotalValue = total !== 0; + + callNodeTotal[callNodeIndex] = total; if (!hasChildren && !hasTotalValue) { continue; @@ -817,15 +821,14 @@ export function computeCallTreeTimingsNonInverted( const prefixCallNode = callNodeTable.prefix[callNodeIndex]; if (prefixCallNode !== -1) { - callNodeTotalSummary[prefixCallNode] += - callNodeTotalSummary[callNodeIndex]; + callNodeTotal[prefixCallNode] += total; callNodeHasChildren[prefixCallNode] = 1; } } return { self: callNodeSelf, - total: callNodeTotalSummary, + total: callNodeTotal, callNodeHasChildren, rootTotalSummary, }; From 38cdaebcaf93b346e18205a9135babb2b195a934 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Wed, 27 Nov 2024 13:20:00 -0500 Subject: [PATCH 019/124] Implement some profile logic for the function list. This adds one very simple test to show the general idea. --- src/profile-logic/call-tree.js | 220 +++++++++++++++++++++++ src/profile-logic/profile-data.js | 88 +++++++++ src/selectors/per-thread/stack-sample.js | 50 +++++- src/test/fixtures/utils.js | 51 ++++++ src/test/unit/bitset.test.js | 64 +++++++ src/test/unit/profile-tree.test.js | 23 +++ src/types/profile-derived.js | 5 + src/utils/bitset.js | 39 ++++ 8 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 src/test/unit/bitset.test.js create mode 100644 src/utils/bitset.js diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 3f878fa902..7a8181396f 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -19,6 +19,7 @@ import type { SamplesLikeTable, WeightType, CallNodeTable, + CallNodeTableBitSet, CallNodePath, IndexIntoCallNodeTable, CallNodeData, @@ -33,6 +34,7 @@ import type { import ExtensionIcon from '../../res/img/svg/extension.svg'; import { formatCallNodeNumber, formatPercent } from '../utils/format-numbers'; import { assertExhaustiveCheck, ensureExists } from '../utils/flow'; +import { checkBit } from '../utils/bitset'; import * as ProfileData from './profile-data'; import type { CallTreeSummaryStrategy } from '../types/actions'; import type { CallNodeInfo, CallNodeInfoInverted } from './call-node-info'; @@ -61,8 +63,16 @@ export type CallTreeTimingsInverted = {| hasChildrenPerRootFunc: Uint8Array, |}; +export type CallTreeTimingsFunctionList = {| + funcSelf: Float64Array, + funcTotal: Float64Array, + sortedFuncs: IndexIntoFuncTable[], + rootTotalSummary: number, +|}; + export type CallTreeTimings = | {| type: 'NON_INVERTED', timings: CallTreeTimingsNonInverted |} + | {| type: 'FUNCTION_LIST', timings: CallTreeTimingsFunctionList |} | {| type: 'INVERTED', timings: CallTreeTimingsInverted |}; /** @@ -190,6 +200,42 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { } } +export class CallTreeInternalFunctionList implements CallTreeInternal { + _timings: CallTreeTimingsFunctionList; + _roots: IndexIntoCallNodeTable[]; + + constructor(timings: CallTreeTimingsFunctionList) { + this._timings = timings; + } + + hasChildren(_callNodeIndex: IndexIntoCallNodeTable): boolean { + return false; + } + + createChildren(_nodeIndex: IndexIntoCallNodeTable): CallNodeChildren { + return []; + } + + createRoots(): CallNodeChildren { + return this._timings.sortedFuncs; + } + + getSelfAndTotal(nodeIndex: IndexIntoCallNodeTable): SelfAndTotal { + return { + self: this._timings.funcSelf[nodeIndex], + total: this._timings.funcTotal[nodeIndex], + }; + } + + findHeaviestPathInSubtree( + _callNodeIndex: IndexIntoCallNodeTable + ): CallNodePath { + throw new Error( + 'unexpected call to findHeaviestPathInSubtree in CallTreeInternalFunctionList' + ); + } +} + class CallTreeInternalInverted implements CallTreeInternal { _callNodeInfo: CallNodeInfoInverted; _callNodeTable: CallNodeTable; @@ -651,6 +697,17 @@ export function getSelfAndTotalForCallNode( const total = timings.total[callNodeIndex]; return { self, total }; } + case 'FUNCTION_LIST': { + const callNodeInfoInverted = ensureExists(callNodeInfo.asInverted()); + const { timings } = callTreeTimings; + const { funcSelf, funcTotal } = timings; + if (!callNodeInfoInverted.isRoot(callNodeIndex)) { + throw new Error( + 'When using function list timings, callNodeIndex must always refer to a function (an inverted root)' + ); + } + return { self: funcSelf[callNodeIndex], total: funcTotal[callNodeIndex] }; + } case 'INVERTED': { const callNodeInfoInverted = ensureExists(callNodeInfo.asInverted()); const { timings } = callTreeTimings; @@ -834,6 +891,157 @@ export function computeCallTreeTimingsNonInverted( }; } +/** + * For each function in the func table, compute the sum of the sample weights + * of the samples which have that function somewhere in their stack. + * + * Example: + * + * 1 A -> B -> A + * 1 A -> B + * 1 B -> B + * + * funcTotal[A]: 2 (only the first two samples have A in them) + * funcTotal[B]: 3 (all three samples have B in them) + * + * We compute these totals by propagating totals upwards in the non-inverted + * call tree, and then on each node, also sum that node's total to the funcTotal + * for its function, but *only if this function is not present in the node's + * ancestors*. + * + * For the ancestor check we use the pre-computed `callNodeFuncIsDuplicate` bitset. + * + * - A (total: 2, self: 0, duplicate: no) + * - B (total: 2, self: 1, duplicate: no) + * - A (total: 1, self: 1, duplicate: yes) + * - B (total: 1, self: 0, duplicate: no) + * - B (total: 1, self: 1, duplicate: yes) + */ +function _computeFuncTotal( + callNodeTable: CallNodeTable, + callNodeFuncIsDuplicate: CallNodeTableBitSet, + callNodeSelf: Float64Array, + funcCount: number +): { funcTotal: Float64Array, sortedFuncs: IndexIntoFuncTable[] } { + const callNodeTableFuncCol = callNodeTable.func; + const callNodeTablePrefixCol = callNodeTable.prefix; + const callNodeCount = callNodeTable.length; + + const callNodeChildrenTotal = new Float64Array(callNodeCount); + const funcTotal = new Float64Array(funcCount); + + // The set of "functions with potentially non-zero totals", stored as an array. + const seenFuncs = []; + // seenPerFunc[func] stores whether seenFuncs.includes(func). + const seenPerFunc = new Uint8Array(funcCount); + + // We loop the call node table in reverse, so that we find the children + // before their parents, and the total is known at the time we reach a + // node. + for ( + let callNodeIndex = callNodeCount - 1; + callNodeIndex >= 0; + callNodeIndex-- + ) { + // callNodeChildrenTotal[callNodeIndex] is the sum of our children's totals. + // Compute this node's total by adding this node's self. + const total = + callNodeChildrenTotal[callNodeIndex] + callNodeSelf[callNodeIndex]; + + if (total === 0) { + continue; + } + + const prefix = callNodeTablePrefixCol[callNodeIndex]; + if (prefix !== -1) { + callNodeChildrenTotal[prefix] += total; + } + + // Accumulate this node's total to the funcTotal for this node's func. + // But don't do it if this function is present in our ancestors! We don't + // want to count the sample twice if the function is present in the stack + // multiple times. + // We've already propagated this node's total to our parent node (via + // callNodeChildrenTotal[prefix]), so this node's total will keep propagating + // upwards. At some point it will hit the shallowest ancestor node + // with our function, where callNodeFuncIsDuplicate will be false, and we + // will accumulate our total into funcTotal there. + if (!checkBit(callNodeFuncIsDuplicate, callNodeIndex)) { + // We now know that func is not present in any of this node's ancestors. + const func = callNodeTableFuncCol[callNodeIndex]; + funcTotal[func] += total; + + // Add this func to the set of funcs with potentially non-zero totals. + if (seenPerFunc[func] === 0) { + seenPerFunc[func] = 1; + seenFuncs.push(func); + } + } + } + + const abs = Math.abs; + seenFuncs.sort((a, b) => { + const absDiff = abs(funcTotal[b]) - abs(funcTotal[a]); + if (absDiff !== 0) { + return absDiff; + } + return a - b; + }); + + return { + funcTotal, + sortedFuncs: seenFuncs, + }; +} + +/** + * For each function in the funcTable, computes the self time spent in that function, + * i.e. the sum of sample weights of the samples whose call node's "self function" is that function. + */ +function _computeFuncSelf( + callNodeTable: CallNodeTable, + callNodeSelf: Float64Array, + funcCount: number +): Float64Array { + const callNodeTableFuncCol = callNodeTable.func; + const callNodeCount = callNodeSelf.length; + + const funcSelf = new Float64Array(funcCount); + for (let i = 0; i < callNodeCount; i++) { + const self = callNodeSelf[i]; + if (self !== 0) { + const func = callNodeTableFuncCol[i]; + funcSelf[func] += self; + } + } + + return funcSelf; +} + +/** + * Compute the timings for the function list. + */ +export function computeFunctionListTimings( + callNodeTable: CallNodeTable, + callNodeFuncIsDuplicate: CallNodeTableBitSet, + { callNodeSelf, rootTotalSummary }: CallNodeSelfAndSummary, + funcCount: number +): CallTreeTimingsFunctionList { + const funcSelf = _computeFuncSelf(callNodeTable, callNodeSelf, funcCount); + const { funcTotal, sortedFuncs } = _computeFuncTotal( + callNodeTable, + callNodeFuncIsDuplicate, + callNodeSelf, + funcCount + ); + return { + funcSelf, + funcTotal, + sortedFuncs, + rootTotalSummary, + }; +} + /** * An exported interface to get an instance of the CallTree class. */ @@ -858,6 +1066,18 @@ export function getCallTree( weightType ); } + case 'FUNCTION_LIST': { + const { timings } = callTreeTimings; + return new CallTree( + thread, + categories, + callNodeInfo, + new CallTreeInternalFunctionList(timings), + timings.rootTotalSummary, + Boolean(thread.isJsTracer), + weightType + ); + } case 'INVERTED': { const { timings } = callTreeTimings; return new CallTree( diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index 34ddac0e1b..2a95d63837 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -27,6 +27,7 @@ import { } from 'firefox-profiler/app-logic/constants'; import { timeCode } from 'firefox-profiler/utils/time-code'; import { bisectionRight, bisectionLeft } from 'firefox-profiler/utils/bisect'; +import { makeBitSet } from 'firefox-profiler/utils/bitset'; import { parseFileNameFromSymbolication } from 'firefox-profiler/utils/special-paths'; import { StringTable } from 'firefox-profiler/utils/string-table'; import { ensureExists, getFirstItemFromSet } from 'firefox-profiler/utils/flow'; @@ -58,6 +59,7 @@ import type { IndexIntoStackTable, IndexIntoResourceTable, IndexIntoNativeSymbolTable, + CallNodeTableBitSet, ThreadIndex, Category, RawCounter, @@ -1147,6 +1149,92 @@ export function getTimingsForCallNodeIndex( return { forPath: pathTimings, rootTime }; } +/** + * For every call node in CallNodeTable, compute whether the node's function is + * already present in one of the node's ancestors. + * + * This is used in the function list timings, so that we don't double-count + * samples for recursive functions which are present in a sample's stack multiple + * times. + * + * Example: + * + * - A (no) + * - B (no) + * - A (yes) + * - C (no) + * - C (yes) + * - C (yes) + * - C (no) + * - D (no) + */ +export function computeCallNodeFuncIsDuplicate( + callNodeTable: CallNodeTable +): CallNodeTableBitSet { + const callNodeCount = callNodeTable.length; + const maxDepth = callNodeTable.maxDepth; + const depthCol = callNodeTable.depth; + const funcCol = callNodeTable.func; + + const nodeFuncIsDuplicateBitSet = makeBitSet(callNodeCount); + + // We take advantage of the fact that the callNodeTable is laid out in DFS order, + // specifically of the property that, if the current node's depth is d, for any + // lower depth ld < d, the last seen node at ld is an ancestor of the current node, + // specifically it's the ancestor at depth ld. + + // funcsOnStack stores the deduplicated ancestors of the current call node. + // More precisely, the first `dd` items of funcsOnStack store the ancestor + // functions, with `dd` being the "depth of the deduplicated path". The rest + // of the array is meaningless. We use a typed array instead of a regular JS + // array for performance reasons. + const funcsOnStack = new Int32Array(maxDepth + 1); + + // depthToDedupDepth stores, for each original depth `d` up to the current depth, + // the depth `dd` of the deduplicated path. + // + // depthToDedupDepth[0] == 0 + // depthToDedupDepth[d] == depthToDedupDepth[d - 1] if there's a duplicate at depth d in the current ancestor path + // depthToDedupDepth[d] == depthToDedupDepth[d - 1] + 1 otherwise + const depthToDedupDepth = new Int32Array(maxDepth + 1); + + outer: for ( + let callNodeIndex = 0; + callNodeIndex < callNodeCount; + callNodeIndex++ + ) { + const depth = depthCol[callNodeIndex]; + const func = funcCol[callNodeIndex]; + + if (depth === 0) { + funcsOnStack[0] = func; + continue; + } + + // Check if `func` is already on the stack. + const dedupPrefixDepth = depthToDedupDepth[depth - 1]; + for (let ancDepth = dedupPrefixDepth; ancDepth >= 0; ancDepth--) { + if (funcsOnStack[ancDepth] === func) { + depthToDedupDepth[depth] = dedupPrefixDepth; + + // Mark this call node as having a duplicate func. + // Equivalent to setBit(nodeFuncIsDuplicateBitSet, callNodeIndex); + // Inlined manually for a 1.55x perf improvement in Firefox. + const q = callNodeIndex >> 5; + const r = callNodeIndex & 0b11111; + nodeFuncIsDuplicateBitSet[q] |= 1 << r; + continue outer; + } + } + + const dedupDepth = dedupPrefixDepth + 1; + funcsOnStack[dedupDepth] = func; + depthToDedupDepth[depth] = dedupDepth; + } + + return nodeFuncIsDuplicateBitSet; +} + // This function computes the time range for a thread, using both its samples // and markers data. It's memoized and exported below, because it's called both // here in getTimeRangeIncludingAllThreads, and in selectors when dealing with diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index 1bd65fa4eb..4bf55eda12 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -43,9 +43,14 @@ import type { $ReturnType, ThreadsKey, SelfAndTotal, + CallNodeTable, CallNodeSelfAndSummary, + CallNodeTableBitSet, } from 'firefox-profiler/types'; -import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; +import type { + CallNodeInfo, + CallNodeInfoInverted, +} from 'firefox-profiler/profile-logic/call-node-info'; import type { ThreadSelectorsPerThread } from './thread'; import type { MarkerSelectorsPerThread } from './markers'; @@ -113,7 +118,7 @@ export function getStackAndSampleSelectorsPerThread( ProfileData.getCallNodeInfo ); - const _getInvertedCallNodeInfo: Selector = + const _getInvertedCallNodeInfo: Selector = createSelectorWithTwoCacheSlots( _getNonInvertedCallNodeInfo, ProfileSelectors.getDefaultCategory, @@ -128,6 +133,15 @@ export function getStackAndSampleSelectorsPerThread( return _getNonInvertedCallNodeInfo(state); }; + const _getCallNodeTable: Selector = (state) => + _getNonInvertedCallNodeInfo(state).getCallNodeTable(); + + const _getCallNodeFuncIsDuplicate: Selector = + createSelector( + _getCallNodeTable, + ProfileData.computeCallNodeFuncIsDuplicate + ); + const getSourceViewStackLineInfo: Selector = createSelector( threadSelectors.getFilteredThread, @@ -315,6 +329,15 @@ export function getStackAndSampleSelectorsPerThread( CallTree.computeCallTreeTimingsNonInverted ); + const getFunctionListTimings: Selector = + createSelector( + _getCallNodeTable, + _getCallNodeFuncIsDuplicate, + getCallNodeSelfAndSummary, + (state) => threadSelectors.getFilteredThread(state).funcTable.length, + CallTree.computeFunctionListTimings + ); + const getCallTree: Selector = createSelector( threadSelectors.getFilteredThread, getCallNodeInfo, @@ -324,6 +347,28 @@ export function getStackAndSampleSelectorsPerThread( CallTree.getCallTree ); + const getFunctionListTree: Selector = createSelector( + threadSelectors.getFilteredThread, + _getInvertedCallNodeInfo, + ProfileSelectors.getCategories, + getFunctionListTimings, + getWeightTypeForCallTree, + ( + thread, + callNodeInfoInverted, + categories, + functionListTimings, + weightType + ) => + CallTree.getCallTree( + thread, + callNodeInfoInverted, + categories, + { type: 'FUNCTION_LIST', timings: functionListTimings }, + weightType + ) + ); + const getSourceViewLineTimings: Selector = createSelector( getSourceViewStackLineInfo, threadSelectors.getPreviewFilteredCtssSamples, @@ -443,6 +488,7 @@ export function getStackAndSampleSelectorsPerThread( getSamplesSelectedStatesInFilteredThread, getTreeOrderComparatorInFilteredThread, getCallTree, + getFunctionListTree, getSourceViewLineTimings, getAssemblyViewAddressTimings, getTracedTiming, diff --git a/src/test/fixtures/utils.js b/src/test/fixtures/utils.js index 68dc010a1c..870d23f519 100644 --- a/src/test/fixtures/utils.js +++ b/src/test/fixtures/utils.js @@ -6,11 +6,14 @@ import { getCallTree, computeCallNodeSelfAndSummary, computeCallTreeTimings, + computeFunctionListTimings, type CallTree, } from 'firefox-profiler/profile-logic/call-tree'; import { getEmptyThread } from 'firefox-profiler/profile-logic/data-structures'; import { + computeCallNodeFuncIsDuplicate, getCallNodeInfo, + getInvertedCallNodeInfo, getSampleIndexToCallNodeIndex, getOriginAnnotationForFunc, createThreadFromDerivedTables, @@ -195,6 +198,54 @@ export function callTreeFromProfile( ); } +/** + * This function creates the "function list" CallTree object for a profile. + * It's convenient to use it with formatTree below. + */ +export function functionListTreeFromProfile( + profile: Profile, + threadIndex: number = 0 +): CallTree { + if (!profile.threads[threadIndex]) { + profile.threads[threadIndex] = getEmptyThread(); + } + const { derivedThreads, defaultCategory } = getProfileWithDicts(profile); + const thread = derivedThreads[threadIndex]; + const callNodeInfo = getCallNodeInfo( + thread.stackTable, + thread.frameTable, + defaultCategory + ); + const funcCount = thread.funcTable.length; + const invertedCallNodeInfo = getInvertedCallNodeInfo( + callNodeInfo, + defaultCategory, + funcCount + ); + const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeFuncIsDuplicate = computeCallNodeFuncIsDuplicate(callNodeTable); + const functionListTimings = computeFunctionListTimings( + callNodeTable, + callNodeFuncIsDuplicate, + computeCallNodeSelfAndSummary( + thread.samples, + getSampleIndexToCallNodeIndex( + thread.samples.stack, + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex() + ), + callNodeTable.length + ), + funcCount + ); + return getCallTree( + thread, + invertedCallNodeInfo, + ensureExists(profile.meta.categories), + { type: 'FUNCTION_LIST', timings: functionListTimings }, + 'samples' + ); +} + /** * This function formats a call tree into a human readable form, to make it easy * to assert certain relationships about the data structure in a really terse diff --git a/src/test/unit/bitset.test.js b/src/test/unit/bitset.test.js new file mode 100644 index 0000000000..559ffae6e5 --- /dev/null +++ b/src/test/unit/bitset.test.js @@ -0,0 +1,64 @@ +/* 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/. */ + +// @flow + +import { makeBitSet, setBit, clearBit, checkBit } from '../../utils/bitset'; + +describe('BitSet', function () { + it('uses an empty typed array when empty', function () { + expect(makeBitSet(0).length).toBe(0); + }); + + it('allocates the right amount of slots', function () { + expect(makeBitSet(31).length).toBe(1); + expect(makeBitSet(32).length).toBe(1); + expect(makeBitSet(33).length).toBe(2); + expect(makeBitSet(63).length).toBe(2); + expect(makeBitSet(64).length).toBe(2); + expect(makeBitSet(65).length).toBe(3); + }); + + it('works in simple cases', function () { + const bitset = makeBitSet(7); + setBit(bitset, 3); + expect(checkBit(bitset, 0)).toBeFalse(); + expect(checkBit(bitset, 3)).toBeTrue(); + expect(checkBit(bitset, 4)).toBeFalse(); + setBit(bitset, 5); + expect(checkBit(bitset, 3)).toBeTrue(); + expect(checkBit(bitset, 5)).toBeTrue(); + setBit(bitset, 3); + expect(checkBit(bitset, 3)).toBeTrue(); + expect(checkBit(bitset, 5)).toBeTrue(); + clearBit(bitset, 5); + expect(checkBit(bitset, 3)).toBeTrue(); + expect(checkBit(bitset, 5)).toBeFalse(); + clearBit(bitset, 3); + expect(checkBit(bitset, 3)).toBeFalse(); + expect(checkBit(bitset, 5)).toBeFalse(); + }); + + it('works when it has to touch the sign bit', function () { + const bitset = makeBitSet(65); + setBit(bitset, 30); + expect(checkBit(bitset, 30)).toBeTrue(); + expect(checkBit(bitset, 31)).toBeFalse(); + setBit(bitset, 31); + expect(checkBit(bitset, 30)).toBeTrue(); + expect(checkBit(bitset, 31)).toBeTrue(); + expect(checkBit(bitset, 32)).toBeFalse(); + setBit(bitset, 32); + setBit(bitset, 63); + expect(checkBit(bitset, 32)).toBeTrue(); + expect(checkBit(bitset, 62)).toBeFalse(); + expect(checkBit(bitset, 63)).toBeTrue(); + expect(checkBit(bitset, 64)).toBeFalse(); + clearBit(bitset, 31); + expect(checkBit(bitset, 30)).toBeTrue(); + expect(checkBit(bitset, 31)).toBeFalse(); + expect(checkBit(bitset, 32)).toBeTrue(); + expect(checkBit(bitset, 63)).toBeTrue(); + }); +}); diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index f2e9b586ce..f69ea2639d 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -22,6 +22,7 @@ import { import { resourceTypes } from '../../profile-logic/data-structures'; import { callTreeFromProfile, + functionListTreeFromProfile, formatTree, formatTreeIncludeCategories, } from '../fixtures/utils'; @@ -546,6 +547,28 @@ describe('inverted call tree', function () { }); }); +describe('function list', function () { + it('computes an unfiltered function list', function () { + const { profile } = getProfileFromTextSamples(` + A A A + B E B + C C A + B F G + D E + `); + const callTree = functionListTreeFromProfile(profile); + expect(formatTree(callTree)).toEqual([ + '- A (total: 3, self: —)', + '- B (total: 2, self: —)', + '- C (total: 2, self: —)', + '- D (total: 1, self: 1)', + '- E (total: 1, self: 1)', + '- F (total: 1, self: —)', + '- G (total: 1, self: 1)', + ]); + }); +}); + describe('diffing trees', function () { function getProfile() { return getMergedProfileFromTextSamples([ diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index da9dcab43a..10b635e831 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -35,6 +35,7 @@ import type { IndexIntoSubcategoryListForCategory, } from './profile'; import type { IndexedArray } from './utils'; +import type { BitSet } from '../utils/bitset'; import type { StackTiming } from '../profile-logic/stack-timing'; import type { StringTable } from '../utils/string-table'; export type IndexIntoCallNodeTable = number; @@ -308,6 +309,10 @@ export type CallNodeTable = { length: number, }; +// A bitset which indicates something per call node. Use `checkBit` from +// utils/bitset to check whether a call node is part of the set. +export type CallNodeTableBitSet = BitSet; + export type LineNumber = number; // Stores the line numbers which are hit by each stack, for one specific source diff --git a/src/utils/bitset.js b/src/utils/bitset.js new file mode 100644 index 0000000000..4af277fc18 --- /dev/null +++ b/src/utils/bitset.js @@ -0,0 +1,39 @@ +/* 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/. */ + +// @flow + +// A packed alternative to Array. +// Created with makeBitSet. +// All 32 bits in each array element are utilized, though of course the last +// element is only partially used if the "bit length" is not a multiple of 32. +// The original "bit length" is not remembered. +export type BitSet = Int32Array; + +// 2^5 == 32. + +export function makeBitSet(length: number): BitSet { + const lastIndex = length - 1; + const lastSlot = lastIndex >> 5; + const slotCount = lastSlot + 1; + return new Int32Array(slotCount); +} + +export function setBit(bitSet: BitSet, bitIndex: number) { + const q = bitIndex >> 5; + const r = bitIndex & 0b11111; + bitSet[q] |= 1 << r; +} + +export function clearBit(bitSet: BitSet, bitIndex: number) { + const q = bitIndex >> 5; + const r = bitIndex & 0b11111; + bitSet[q] &= ~(1 << r); +} + +export function checkBit(bitSet: BitSet, bitIndex: number): boolean { + const q = bitIndex >> 5; + const r = bitIndex & 0b11111; + return (bitSet[q] & (1 << r)) !== 0; +} From 312ca69f5a42e8ec99df9011fd61a52846241008 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Mon, 4 Aug 2025 20:19:36 -0400 Subject: [PATCH 020/124] Update outdated comment. --- src/profile-logic/call-tree.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index 7a8181396f..2ec25db3be 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -842,8 +842,8 @@ export function computeCallTreeTimings( } /** - * This computes all of the count and timing information displayed in the calltree. - * It takes into account both the normal tree, and the inverted tree. + * This computes all of the count and timing information displayed in the + * regular (non-inverted) calltree. */ export function computeCallTreeTimingsNonInverted( callNodeInfo: CallNodeInfo, From d9798a05c923f5e08d55b785d965f11ca47cc0d6 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 08:12:04 +0000 Subject: [PATCH 021/124] Pontoon/Firefox Profiler: Update 20 localizations --- locales/be/app.ftl | 2 -- locales/de/app.ftl | 2 -- locales/el/app.ftl | 2 -- locales/en-CA/app.ftl | 2 -- locales/en-GB/app.ftl | 2 -- locales/es-CL/app.ftl | 2 -- locales/fr/app.ftl | 2 -- locales/fur/app.ftl | 2 -- locales/fy-NL/app.ftl | 2 -- locales/ia/app.ftl | 2 -- locales/it/app.ftl | 2 -- locales/nl/app.ftl | 2 -- locales/pt-BR/app.ftl | 2 -- locales/ru/app.ftl | 2 -- locales/sv-SE/app.ftl | 2 -- locales/tr/app.ftl | 2 -- locales/uk/app.ftl | 2 -- locales/zh-CN/app.ftl | 2 -- locales/zh-TW/app.ftl | 2 -- 19 files changed, 38 deletions(-) diff --git a/locales/be/app.ftl b/locales/be/app.ftl index 84d6e25f36..db2eb44ef5 100644 --- a/locales/be/app.ftl +++ b/locales/be/app.ftl @@ -731,8 +731,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Усе кадры .title = Не фільтраваць кадры стэка -StackSettings--implementation-javascript2 = JavaScript - .title = Паказваць толькі кадры стэка, звязаныя з выкананнем JavaScript StackSettings--implementation-native2 = Убудаваны .title = Паказваць толькі кадры стэка для платформна-залежнага кода # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/de/app.ftl b/locales/de/app.ftl index 215f7b6a6c..462621d234 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -709,8 +709,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle Frames .title = Die Stack-Frames nicht filtern -StackSettings--implementation-javascript2 = JavaScript - .title = Nur die Stack-Frames anzeigen, die sich auf JavaScript-Ausführung beziehen StackSettings--implementation-native2 = Native .title = Nur die Stack-Frames für nativen Quelltext anzeigen # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/el/app.ftl b/locales/el/app.ftl index b827c21a64..2dd5a94838 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -728,8 +728,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Όλα τα καρέ .title = Να μην γίνεται φιλτράρισμα των καρέ στοίβας -StackSettings--implementation-javascript2 = JavaScript - .title = Εμφάνιση μόνο των καρέ στοίβας που σχετίζονται με την εκτέλεση της JavaScript StackSettings--implementation-native2 = Εγγενές .title = Εμφάνιση μόνο των καρέ στοίβας για εγγενή κώδικα # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/en-CA/app.ftl b/locales/en-CA/app.ftl index 1fb5e93e3a..09e8bc00d9 100644 --- a/locales/en-CA/app.ftl +++ b/locales/en-CA/app.ftl @@ -733,8 +733,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = All frames .title = Do not filter the stack frames -StackSettings--implementation-javascript2 = JavaScript - .title = Show only the stack frames related to JavaScript execution StackSettings--implementation-native2 = Native .title = Show only the stack frames for native code # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl index 64c7806a31..d30794c999 100644 --- a/locales/en-GB/app.ftl +++ b/locales/en-GB/app.ftl @@ -733,8 +733,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = All frames .title = Do not filter the stack frames -StackSettings--implementation-javascript2 = JavaScript - .title = Show only the stack frames related to JavaScript execution StackSettings--implementation-native2 = Native .title = Show only the stack frames for native code # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/es-CL/app.ftl b/locales/es-CL/app.ftl index 5a74855679..599b63081c 100644 --- a/locales/es-CL/app.ftl +++ b/locales/es-CL/app.ftl @@ -660,8 +660,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Todos los cuadros .title = No filtrar las pilas de cuadros -StackSettings--implementation-javascript2 = JavaScript - .title = Mostrar solo las pilas de cuadros relacionadas a la ejecución de JavaScript StackSettings--implementation-native2 = Nativo .title = Mostrar solo las pilas de cuadros para el código nativo # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/fr/app.ftl b/locales/fr/app.ftl index 119d7ce6e3..e48427119d 100644 --- a/locales/fr/app.ftl +++ b/locales/fr/app.ftl @@ -654,8 +654,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Toutes les trames .title = Ne pas filtrer les trames de pile -StackSettings--implementation-javascript2 = JavaScript - .title = Afficher uniquement les trames de pile liées à l’exécution JavaScript StackSettings--implementation-native2 = Natif .title = Afficher uniquement les trames de pile pour le code natif # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/fur/app.ftl b/locales/fur/app.ftl index 28b1169d16..a2ff3403ce 100644 --- a/locales/fur/app.ftl +++ b/locales/fur/app.ftl @@ -693,8 +693,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Ducj i ricuadris .title = No sta filtrâ i ricuadris dal stack -StackSettings--implementation-javascript2 = JavaScript - .title = Mostre dome i ricuadris dal stack corelâts ae esecuzion di JavaScript StackSettings--implementation-native2 = Natîf .title = Mostre dome i ricuadris dal stack pal codiç natîf # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/fy-NL/app.ftl b/locales/fy-NL/app.ftl index 480fe1c55d..5821dd906d 100644 --- a/locales/fy-NL/app.ftl +++ b/locales/fy-NL/app.ftl @@ -733,8 +733,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle frames .title = De stackframes net filterje -StackSettings--implementation-javascript2 = JavaScript - .title = Allinnich de stackframes relatearre oan útfiering fan JavaScript toane StackSettings--implementation-native2 = Ynboud .title = Allinnich de stackframes foar ynboude koade toane # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl index 863bc9be53..eab326c59f 100644 --- a/locales/ia/app.ftl +++ b/locales/ia/app.ftl @@ -716,8 +716,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Tote le structuras .title = Non filtrar le structuras de pila -StackSettings--implementation-javascript2 = JavaScript - .title = Monstrar solo le structuras de pila correlate a execution JavaScript StackSettings--implementation-native2 = Native .title = Monstrar solo le structuras de pila pro codice native # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/it/app.ftl b/locales/it/app.ftl index 8d29b4cd1d..a2833711be 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -645,8 +645,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Tutti i frame .title = Non filtrare gli stack frame -StackSettings--implementation-javascript2 = JavaScript - .title = Mostra solo gli stack frame relativi all’esecuzione di JavaScript StackSettings--implementation-native2 = Nativo .title = Mostra solo gli stack frame per il codice nativo # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/nl/app.ftl b/locales/nl/app.ftl index 535badb32a..d37c983b2d 100644 --- a/locales/nl/app.ftl +++ b/locales/nl/app.ftl @@ -733,8 +733,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle frames .title = De stackframes niet filteren -StackSettings--implementation-javascript2 = JavaScript - .title = Alleen de stackframes gerelateerd aan uitvoering van JavaScript tonen StackSettings--implementation-native2 = Ingebouwd .title = Alleen de stackframes voor ingebouwde code tonen # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl index dee9eee23c..10093f8561 100644 --- a/locales/pt-BR/app.ftl +++ b/locales/pt-BR/app.ftl @@ -656,8 +656,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Todos os frames .title = Não filtrar frames de pilha -StackSettings--implementation-javascript2 = JavaScript - .title = Mostrar apenas os frames de pilha relacionados à execução JavaScript StackSettings--implementation-native2 = Nativo .title = Mostrar apenas os frames de pilha de código nativo # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/ru/app.ftl b/locales/ru/app.ftl index 6c04d4bafe..d7b998e762 100644 --- a/locales/ru/app.ftl +++ b/locales/ru/app.ftl @@ -742,8 +742,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Все фреймы .title = Не фильтровать стек фреймов -StackSettings--implementation-javascript2 = JavaScript - .title = Отображать только стек фреймов, относящихся к выполнению JavaScript StackSettings--implementation-native2 = Собственные .title = Отображать только стек фреймов для собственного кода # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/sv-SE/app.ftl b/locales/sv-SE/app.ftl index a2d31ccd05..644cf450fa 100644 --- a/locales/sv-SE/app.ftl +++ b/locales/sv-SE/app.ftl @@ -728,8 +728,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alla ramar .title = Filtrera inte stackramar -StackSettings--implementation-javascript2 = JavaScript - .title = Visa endast stackramar relaterade till JavaScript-körning StackSettings--implementation-native2 = Intern .title = Visa bara stackramar för intern kod # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/tr/app.ftl b/locales/tr/app.ftl index b04f973ddb..2cd05ee659 100644 --- a/locales/tr/app.ftl +++ b/locales/tr/app.ftl @@ -562,8 +562,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Tüm çerçeveler .title = Yığın çerçevelerini filtreleme -StackSettings--implementation-javascript2 = JavaScript - .title = Yalnızca JavaScript yürütmesiyle ilgili yığın çerçevelerini göster # This label is displayed in the marker chart and marker table panels only. StackSettings--stack-implementation-label = Yığın filtresi: StackSettings--use-data-source-label = Veri kaynağı: diff --git a/locales/uk/app.ftl b/locales/uk/app.ftl index 43c4b99a64..422119fc15 100644 --- a/locales/uk/app.ftl +++ b/locales/uk/app.ftl @@ -740,8 +740,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Усі фрейми .title = Не фільтрувати фрейми стека -StackSettings--implementation-javascript2 = JavaScript - .title = Показувати лише фрейми стека, пов'язані з виконанням JavaScript StackSettings--implementation-native2 = Вбудовані .title = Показувати лише фрейми стека для власного коду # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/zh-CN/app.ftl b/locales/zh-CN/app.ftl index 5ac9df3bcd..83fb97fadb 100644 --- a/locales/zh-CN/app.ftl +++ b/locales/zh-CN/app.ftl @@ -636,8 +636,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = 所有帧 .title = 不过滤栈上的帧 -StackSettings--implementation-javascript2 = JavaScript - .title = 仅显示栈上需要执行的 JavaScript 帧 StackSettings--implementation-native2 = 原生 .title = 仅显示栈上的原生代码帧 # This label is displayed in the marker chart and marker table panels only. diff --git a/locales/zh-TW/app.ftl b/locales/zh-TW/app.ftl index d81a9bf7f9..39ad7ecf4d 100644 --- a/locales/zh-TW/app.ftl +++ b/locales/zh-TW/app.ftl @@ -635,8 +635,6 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = 所有堆疊框 .title = 不過濾堆疊框 -StackSettings--implementation-javascript2 = JavaScript - .title = 僅顯示與執行 JavaScript 有關的堆疊框 StackSettings--implementation-native2 = 原生 .title = 僅顯示原生程式碼相關的堆疊框 # This label is displayed in the marker chart and marker table panels only. From 03d2a3af549d54a7634968844f187aa6533a7911 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 08:21:47 +0000 Subject: [PATCH 022/124] Pontoon/Firefox Profiler: Update Chinese (Taiwan) (zh-TW) Co-authored-by: Pin-guang Chen (zh-TW) --- locales/zh-TW/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/zh-TW/app.ftl b/locales/zh-TW/app.ftl index 39ad7ecf4d..43d6d10df1 100644 --- a/locales/zh-TW/app.ftl +++ b/locales/zh-TW/app.ftl @@ -635,6 +635,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = 所有堆疊框 .title = 不過濾堆疊框 +StackSettings--implementation-script = 指令碼 + .title = 僅顯示指令碼執行相關的堆疊框 StackSettings--implementation-native2 = 原生 .title = 僅顯示原生程式碼相關的堆疊框 # This label is displayed in the marker chart and marker table panels only. From 7cd227edcb987ae9911480e3bd15d955f00e4365 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 08:31:46 +0000 Subject: [PATCH 023/124] Pontoon/Firefox Profiler: Update Italian (it) Co-authored-by: Francesco Lodolo [:flod] (it) --- locales/it/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/it/app.ftl b/locales/it/app.ftl index a2833711be..058d1f73ba 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -645,6 +645,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Tutti i frame .title = Non filtrare gli stack frame +StackSettings--implementation-script = Script + .title = Mostra solo gli stack frame relativi all’esecuzione di script StackSettings--implementation-native2 = Nativo .title = Mostra solo gli stack frame per il codice nativo # This label is displayed in the marker chart and marker table panels only. From 62dec453423042031764d609e411a709ed2b52df Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 11:02:28 +0000 Subject: [PATCH 024/124] Pontoon/Firefox Profiler: Update Dutch (nl) Co-authored-by: Mark Heijl (nl) --- locales/nl/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/nl/app.ftl b/locales/nl/app.ftl index d37c983b2d..a11541fb9b 100644 --- a/locales/nl/app.ftl +++ b/locales/nl/app.ftl @@ -733,6 +733,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle frames .title = De stackframes niet filteren +StackSettings--implementation-script = Script + .title = Alleen de stackframes gerelateerd aan scriptuitvoering tonen StackSettings--implementation-native2 = Ingebouwd .title = Alleen de stackframes voor ingebouwde code tonen # This label is displayed in the marker chart and marker table panels only. From 79a38d7db1a1679013172aebe803aa5c0ea5c70d Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 14:41:52 +0000 Subject: [PATCH 025/124] Pontoon/Firefox Profiler: Update German (de) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Köhler (de) --- locales/de/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/de/app.ftl b/locales/de/app.ftl index 462621d234..7232a155ff 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -709,6 +709,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle Frames .title = Die Stack-Frames nicht filtern +StackSettings--implementation-script = Skript + .title = Nur die Stapelrahmen anzeigen, die mit der Skriptausführung zusammenhängen. StackSettings--implementation-native2 = Native .title = Nur die Stack-Frames für nativen Quelltext anzeigen # This label is displayed in the marker chart and marker table panels only. From 1dd862667ab5e003a3a91614e7b7a8f042ed5e49 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 15:11:58 +0000 Subject: [PATCH 026/124] Pontoon/Firefox Profiler: Update Greek (el) Co-authored-by: Jim Spentzos (el) --- locales/el/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/el/app.ftl b/locales/el/app.ftl index 2dd5a94838..3f27e38828 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -728,6 +728,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Όλα τα καρέ .title = Να μην γίνεται φιλτράρισμα των καρέ στοίβας +StackSettings--implementation-script = Δέσμη ενεργειών + .title = Εμφάνιση μόνο των καρέ στοίβας που σχετίζονται με την εκτέλεση της δέσμης ενεργειών StackSettings--implementation-native2 = Εγγενές .title = Εμφάνιση μόνο των καρέ στοίβας για εγγενή κώδικα # This label is displayed in the marker chart and marker table panels only. From f7b340daebf7b0e1e57f68b40befc8f125ab8a18 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 16:31:57 +0000 Subject: [PATCH 027/124] Pontoon/Firefox Profiler: Update Interlingua (ia) Co-authored-by: Melo46 (ia) --- locales/ia/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl index eab326c59f..f42cd0c066 100644 --- a/locales/ia/app.ftl +++ b/locales/ia/app.ftl @@ -716,6 +716,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Tote le structuras .title = Non filtrar le structuras de pila +StackSettings--implementation-script = Script + .title = Solo monstrar le pannellos de pila relative a execution de script StackSettings--implementation-native2 = Native .title = Monstrar solo le structuras de pila pro codice native # This label is displayed in the marker chart and marker table panels only. From 859578eecb04fab3e19ce932306832b8f198fbb8 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 19:11:39 +0000 Subject: [PATCH 028/124] Pontoon/Firefox Profiler: Update Russian (ru) Co-authored-by: Valery Ledovskoy (ru) --- locales/ru/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/ru/app.ftl b/locales/ru/app.ftl index d7b998e762..013354911e 100644 --- a/locales/ru/app.ftl +++ b/locales/ru/app.ftl @@ -742,6 +742,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Все фреймы .title = Не фильтровать стек фреймов +StackSettings--implementation-script = Скрипт + .title = Показывать только фреймы стека, связанные с выполнением скрипта StackSettings--implementation-native2 = Собственные .title = Отображать только стек фреймов для собственного кода # This label is displayed in the marker chart and marker table panels only. From 1cc4e2a4a22d89711f2e02a68d59fc1760b319f2 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Tue, 5 Aug 2025 20:32:01 +0000 Subject: [PATCH 029/124] Pontoon/Firefox Profiler: Update Swedish (sv-SE) Co-authored-by: Luna Jernberg (sv-SE) --- locales/sv-SE/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/sv-SE/app.ftl b/locales/sv-SE/app.ftl index 644cf450fa..540787b6a1 100644 --- a/locales/sv-SE/app.ftl +++ b/locales/sv-SE/app.ftl @@ -728,6 +728,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alla ramar .title = Filtrera inte stackramar +StackSettings--implementation-script = Skript + .title = Visa endast stackramar relaterade till skriptkörning StackSettings--implementation-native2 = Intern .title = Visa bara stackramar för intern kod # This label is displayed in the marker chart and marker table panels only. From afc692a29ab870c7b8ce59385f069e99aa28cfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Wed, 6 Aug 2025 11:16:45 +0200 Subject: [PATCH 030/124] Make sure that the test-debug command runs the tests properly (#5545) We should set these environment variables to make sure that all the tests behave the same way they would with `yarn test`. Also, we added this check so we make sure people run the tests through `yarn test`: https://github.com/firefox-devtools/profiler/blob/082631b9ab95733b5e1e36b5edfb73cbcedcd866/src/test/setup.js#L19-L21 But it was preventing us to run `yarn test-debug` all together too. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5ee7cc5da..1d0ae145c4 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "test-coverage": "run-s test-build-coverage test-serve-coverage", "test-alex": "alex ./docs-* *.md", "test-lockfile": "lockfile-lint --path yarn.lock --allowed-hosts yarn --validate-https", - "test-debug": "node --inspect-brk node_modules/.bin/jest --runInBand", + "test-debug": "cross-env LC_ALL=C TZ=UTC NODE_ENV=test node --inspect-brk node_modules/.bin/jest --runInBand", "postinstall": "patch-package" }, "license": "MPL-2.0", From c8f1bcc0b66ea511ec56eff88de9c8f273cfcd40 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Wed, 6 Aug 2025 19:41:54 +0000 Subject: [PATCH 031/124] Pontoon/Firefox Profiler: Update Frisian (fy-NL) Co-authored-by: Fjoerfoks (fy-NL) --- locales/fy-NL/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/fy-NL/app.ftl b/locales/fy-NL/app.ftl index 5821dd906d..892e57251f 100644 --- a/locales/fy-NL/app.ftl +++ b/locales/fy-NL/app.ftl @@ -733,6 +733,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Alle frames .title = De stackframes net filterje +StackSettings--implementation-script = Script + .title = Allinnich de stackframes relatearre oan scriptútfiering toane StackSettings--implementation-native2 = Ynboud .title = Allinnich de stackframes foar ynboude koade toane # This label is displayed in the marker chart and marker table panels only. From d7a2091ceea214b244cdefd5c8638282d8d55b92 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 7 Aug 2025 06:21:55 +0000 Subject: [PATCH 032/124] Pontoon/Firefox Profiler: Update English (Great Britain) (en-GB) Co-authored-by: Paul (en-GB) --- locales/en-GB/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl index d30794c999..6e874d2ea3 100644 --- a/locales/en-GB/app.ftl +++ b/locales/en-GB/app.ftl @@ -733,6 +733,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = All frames .title = Do not filter the stack frames +StackSettings--implementation-script = Script + .title = Show only the stack frames related to script execution StackSettings--implementation-native2 = Native .title = Show only the stack frames for native code # This label is displayed in the marker chart and marker table panels only. From ecdc81f73c059dceabf09d6e0fc16eb9de28434a Mon Sep 17 00:00:00 2001 From: Pontoon Date: Fri, 8 Aug 2025 20:12:05 +0000 Subject: [PATCH 033/124] Pontoon/Firefox Profiler: Update English (Canada) (en-CA) Co-authored-by: chutten (en-CA) --- locales/en-CA/app.ftl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locales/en-CA/app.ftl b/locales/en-CA/app.ftl index 09e8bc00d9..0be66bf4c2 100644 --- a/locales/en-CA/app.ftl +++ b/locales/en-CA/app.ftl @@ -733,6 +733,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = All frames .title = Do not filter the stack frames +StackSettings--implementation-script = Script + .title = Show only the stack frames related to script execution StackSettings--implementation-native2 = Native .title = Show only the stack frames for native code # This label is displayed in the marker chart and marker table panels only. @@ -753,6 +755,7 @@ StackSettings--call-tree-strategy-native-deallocations-sites = Deallocation Site StackSettings--invert-call-stack = Invert call stack .title = Sort by the time spent in a call node, ignoring its children. StackSettings--show-user-timing = Show user timing +StackSettings--use-stack-chart-same-widths = Use the same width for each stack StackSettings--panel-search = .label = Filter stacks: .title = Only display stacks which contain a function whose name matches this substring From 614c5828ef2926a77fb5ee7e4b772920557aa38d Mon Sep 17 00:00:00 2001 From: Pontoon Date: Sun, 10 Aug 2025 18:21:54 +0000 Subject: [PATCH 034/124] Pontoon/Firefox Profiler: Update Belarusian (be) Co-authored-by: Mikalai Udodau (be) --- locales/be/app.ftl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locales/be/app.ftl b/locales/be/app.ftl index db2eb44ef5..4b47948d6c 100644 --- a/locales/be/app.ftl +++ b/locales/be/app.ftl @@ -731,6 +731,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Усе кадры .title = Не фільтраваць кадры стэка +StackSettings--implementation-script = Скрыпт + .title = Паказваць толькі фрэймы стэку, датычныя выканання скрыпта StackSettings--implementation-native2 = Убудаваны .title = Паказваць толькі кадры стэка для платформна-залежнага кода # This label is displayed in the marker chart and marker table panels only. @@ -751,6 +753,7 @@ StackSettings--call-tree-strategy-native-deallocations-sites = Вызвален StackSettings--invert-call-stack = Інвертаваць стэк выклікаў .title = Сартаваць па часе, праведзенаму ў вузле выкліку, ігнаруючы яго даччыныя вузлы. StackSettings--show-user-timing = Паказаць таймінгі карыстальніка +StackSettings--use-stack-chart-same-widths = Выкарыстоўваць аднолькавую шырыню для кожнага стэка StackSettings--panel-search = .label = Фільтр стэкаў: .title = Паказаць толькі стэкі, якія змяшчаюць функцыю, назва якой адпавядае гэтаму падрадку From 9957a08af3acb708c2c361987d1b34974d9ef1b9 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Mon, 11 Aug 2025 02:13:15 +0000 Subject: [PATCH 035/124] Pontoon/Firefox Profiler: Update Portuguese (Brazil) (pt-BR) Co-authored-by: Marcelo Ghelman (pt-BR) --- locales/pt-BR/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl index 10093f8561..bb51d92ba3 100644 --- a/locales/pt-BR/app.ftl +++ b/locales/pt-BR/app.ftl @@ -656,6 +656,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Todos os frames .title = Não filtrar frames de pilha +StackSettings--implementation-script = Script + .title = Mostrar somente os frames de pilhas relacionados a execução de scripts StackSettings--implementation-native2 = Nativo .title = Mostrar apenas os frames de pilha de código nativo # This label is displayed in the marker chart and marker table panels only. From 333baf87a7776721ba7246207090bcdb4cce7cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Mon, 11 Aug 2025 11:16:03 +0200 Subject: [PATCH 036/124] A new permalink should be generated and shown after using the re-upload feature. (#5547) --- src/components/app/MenuButtons/Permalink.js | 2 +- .../shared/ButtonWithPanel/index.js | 15 ++++++++--- src/test/components/ButtonWithPanel.test.js | 4 +-- .../components/MenuButtonsPermalinks.test.js | 27 +++++++++++++++++++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/app/MenuButtons/Permalink.js b/src/components/app/MenuButtons/Permalink.js index b3f19db089..21dd3ebc68 100644 --- a/src/components/app/MenuButtons/Permalink.js +++ b/src/components/app/MenuButtons/Permalink.js @@ -78,7 +78,7 @@ export class MenuButtonsPermalink extends React.PureComponent { mixed, @@ -57,7 +56,7 @@ export class ButtonWithPanel extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { open: !!props.initialOpen }; + this.state = { open: !!props.open }; } componentDidMount() { @@ -70,6 +69,14 @@ export class ButtonWithPanel extends React.PureComponent { } } + componentDidUpdate(prevProps: Props) { + // Open the panel when the open prop becomes true. + if (!prevProps.open && this.props.open) { + this.setState({ open: true }); + this.openPanel(); + } + } + componentWillUnmount() { window.removeEventListener('keydown', this._onKeyDown); window.removeEventListener('click', this._onWindowClick); diff --git a/src/test/components/ButtonWithPanel.test.js b/src/test/components/ButtonWithPanel.test.js index 1407817a07..bbd6d09f81 100644 --- a/src/test/components/ButtonWithPanel.test.js +++ b/src/test/components/ButtonWithPanel.test.js @@ -44,7 +44,7 @@ describe('shared/ButtonWithPanel', () => { Panel content
} /> @@ -73,7 +73,7 @@ describe('shared/ButtonWithPanel', () => { Panel content
} /> ); diff --git a/src/test/components/MenuButtonsPermalinks.test.js b/src/test/components/MenuButtonsPermalinks.test.js index 3681303ae7..70bbabfb68 100644 --- a/src/test/components/MenuButtonsPermalinks.test.js +++ b/src/test/components/MenuButtonsPermalinks.test.js @@ -88,4 +88,31 @@ describe('', function () { ); expect(input).toHaveValue(shortUrl); }); + + it('opens the permalink panel when isNewlyPublished changes to true', async function () { + const { dispatch, queryInput, shortUrl, shortUrlPromise } = setup(); + + // Initially the panel should not be open + expect(queryInput()).toBeFalsy(); + + // Simulate a profile being published/re-uploaded by dispatching PROFILE_PUBLISHED + act(() => { + dispatch({ + type: 'PROFILE_PUBLISHED', + hash: 'newhash', + profileName: 'test', + prePublishedState: null, + }); + }); + + // Wait for the async operations to complete + await act(() => shortUrlPromise); + + // The input should now be visible, indicating the panel opened automatically + const input = ensureExists( + queryInput(), + 'Expected the permalink panel to open automatically after publishing' + ); + expect(input).toHaveValue(shortUrl); + }); }); From 927870f7a4c5f4e0e98646c263ce7c01f74aff03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Qu=C3=A8ze?= Date: Tue, 12 Aug 2025 15:18:04 +0200 Subject: [PATCH 037/124] Show the vertical ruler in the timeline when hovering the network chart (#5548) When hovering over the network chart, display a vertical ruler in the timeline to match the behavior of marker chart and stack chart. This makes it easier to correlate slow network request handling with high CPU use for other reasons in the timeline. --- src/components/network-chart/index.js | 40 ++++++++++++++++++++++++ src/test/components/NetworkChart.test.js | 34 ++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/components/network-chart/index.js b/src/components/network-chart/index.js index 132bfd065e..2d0b476cdc 100644 --- a/src/components/network-chart/index.js +++ b/src/components/network-chart/index.js @@ -14,6 +14,10 @@ import { withSize } from '../shared/WithSize'; import { NetworkChartEmptyReasons } from './NetworkChartEmptyReasons'; import { NetworkChartRow } from './NetworkChartRow'; import { ContextMenuTrigger } from '../shared/ContextMenuTrigger'; +import { + TIMELINE_MARGIN_LEFT, + TIMELINE_MARGIN_RIGHT, +} from '../../app-logic/constants'; import { getScrollToSelectionGeneration, @@ -26,6 +30,7 @@ import { changeSelectedNetworkMarker, changeRightClickedMarker, changeHoveredMarker, + changeMouseTimePosition, } from '../../actions/profile-view'; import type { SizeProps } from '../shared/WithSize'; import type { @@ -49,6 +54,7 @@ type DispatchProps = {| +changeSelectedNetworkMarker: typeof changeSelectedNetworkMarker, +changeRightClickedMarker: typeof changeRightClickedMarker, +changeHoveredMarker: typeof changeHoveredMarker, + +changeMouseTimePosition: typeof changeMouseTimePosition, |}; type StateProps = {| @@ -259,6 +265,37 @@ class NetworkChartImpl extends React.PureComponent { changeHoveredMarker(threadsKey, null); }; + _onMouseMove = (event: SyntheticMouseEvent) => { + const { timeRange, width, changeMouseTimePosition } = this.props; + + // Calculate the mouse position relative to the chart area + if (!event.currentTarget) { + return; + } + const rect = event.currentTarget.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + + // Account for timeline margins (similar to marker chart logic) + const chartWidth = width - TIMELINE_MARGIN_LEFT - TIMELINE_MARGIN_RIGHT; + const adjustedMouseX = mouseX - TIMELINE_MARGIN_LEFT; + + // Calculate the time position + const { start: rangeStart, end: rangeEnd } = timeRange; + const rangeLength = rangeEnd - rangeStart; + const xInUnitInterval = adjustedMouseX / chartWidth; + + if (xInUnitInterval < 0 || xInUnitInterval > 1) { + changeMouseTimePosition(null); + } else { + const xInTime = rangeStart + xInUnitInterval * rangeLength; + changeMouseTimePosition(xInTime); + } + }; + + _onMouseLeave = () => { + this.props.changeMouseTimePosition(null); + }; + _shouldDisplayTooltips = () => this.props.rightClickedMarkerIndex === null; _renderRow = (markerIndex: MarkerIndex, index: number): React.Node => { @@ -327,6 +364,8 @@ class NetworkChartImpl extends React.PureComponent { id="network-chart-tab" role="tabpanel" aria-labelledby="network-chart-tab-button" + onMouseMove={this._onMouseMove} + onMouseLeave={this._onMouseLeave} > {markerIndexes.length === 0 ? ( @@ -394,6 +433,7 @@ export const NetworkChart = explicitConnect< changeSelectedNetworkMarker, changeRightClickedMarker, changeHoveredMarker, + changeMouseTimePosition, }, component: withSize(NetworkChartImpl), }); diff --git a/src/test/components/NetworkChart.test.js b/src/test/components/NetworkChart.test.js index 533ebba7bb..cc048eb813 100644 --- a/src/test/components/NetworkChart.test.js +++ b/src/test/components/NetworkChart.test.js @@ -777,4 +777,38 @@ describe('calltree/ProfileCallTreeView navigation keys', () => { initialScrollGeneration ); }); + + it('changes the mouse time position when the mouse moves', function () { + const { getState, container } = setupWithPayload(getNetworkMarkers()); + + // Expect the mouseTimePosition to not be set at the beginning of the test. + expect(getState().profileView.viewOptions.mouseTimePosition).toBeNull(); + + const networkChart = ensureExists( + container.querySelector('.networkChart'), + 'Could not find the network chart element' + ); + + // Move the mouse over the network chart, ensure mouseTimePosition is set. + fireEvent.mouseMove(networkChart, { + clientX: TIMELINE_MARGIN_LEFT + 100, // Position within the chart area + clientY: 100, + }); + const mouseTimePosition = + getState().profileView.viewOptions.mouseTimePosition; + expect(typeof mouseTimePosition).toEqual('number'); + + // Move the mouse to a different position, ensure mouseTimePosition changed. + fireEvent.mouseMove(networkChart, { + clientX: TIMELINE_MARGIN_LEFT + 150, // Different position within chart area + clientY: 100, + }); + expect(getState().profileView.viewOptions.mouseTimePosition).not.toEqual( + mouseTimePosition + ); + + // Move the mouse out of the network chart, ensure mouseTimePosition is no longer set. + fireEvent.mouseLeave(networkChart); + expect(getState().profileView.viewOptions.mouseTimePosition).toBeNull(); + }); }); From 5a1a0e93ba66a10aee7aad5efb51c1032179cee3 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Wed, 13 Aug 2025 22:51:55 +0000 Subject: [PATCH 038/124] Pontoon/Firefox Profiler: Update Spanish (Chile) (es-CL) Co-authored-by: ravmn (es-CL) --- locales/es-CL/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/es-CL/app.ftl b/locales/es-CL/app.ftl index 599b63081c..e35a4738b4 100644 --- a/locales/es-CL/app.ftl +++ b/locales/es-CL/app.ftl @@ -660,6 +660,8 @@ ServiceWorkerManager--hide-notice-button = StackSettings--implementation-all-frames = Todos los cuadros .title = No filtrar las pilas de cuadros +StackSettings--implementation-script = Script + .title = Mostrar solo los cuadros apilados relacionados a la ejecución del script StackSettings--implementation-native2 = Nativo .title = Mostrar solo las pilas de cuadros para el código nativo # This label is displayed in the marker chart and marker table panels only. From 7a111d93230d8b14d833e721990890a65eda9933 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 20:29:33 -0400 Subject: [PATCH 039/124] Set up TypeScript support basics. I had to add two "resolutions" to the package.json: - "@types/react-splitter-layout/@types/react": "^18.3.1" so that we don't end up with two different typings for React. - "@types/trusted-types": "^2.0.7" because workbox depends on "^2.0.2" which has conflicts with the "official" TypeScript DOM types, and this conflict was fixed in v2.0.3. v2.0.7 is just the most recent version, so I picked that. --- .circleci/config.yml | 6 +- .gitignore | 1 + babel.config.json | 90 ++++++++++++------- package.json | 28 +++--- src/global.d.ts | 23 +++++ tsconfig.json | 47 ++++++++++ webpack.config.js | 6 ++ yarn.lock | 203 ++++++++++++++++++++++++++++++++++++++----- 8 files changed, 339 insertions(+), 65 deletions(-) create mode 100644 src/global.d.ts create mode 100644 tsconfig.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 539401cd94..7113f2723c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,12 +62,12 @@ jobs: - checkout-and-dependencies - run: yarn license-check - flow: + typecheck: executor: node resource_class: large steps: - checkout-and-dependencies - - run: yarn flow:ci + - run: yarn typecheck alex: executor: node @@ -110,7 +110,7 @@ workflows: - tests - lint - build-prod - - flow + - typecheck - licence-check - alex - yarn_lock diff --git a/.gitignore b/.gitignore index ea39538b0b..14f36789cc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ flow-coverage coverage .eslintcache .prettiercache +.tsbuildinfo webpack.local-config.js *.orig *.rej diff --git a/babel.config.json b/babel.config.json index 7650ef5a89..b4a1d82932 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,33 +1,65 @@ { - "presets": [ - [ - "@babel/preset-env", - { - // Allow `@babel/preset-env` to import polyfills from core-js as needed. - "useBuiltIns": "usage", - // Help `@babel/preset-env` make use of the correct core-js polyfills. - "corejs": "3.9", - // Perform transforms closest to targets defined in `.browserslistrc`. - "bugfixes": true - } - ], - [ - "@babel/preset-react", - { - // When spreading props, use inline object with spread elements - // directly instead of Babel's `extend` helper or `Object.assign`. - "useSpread": true - } - ], - // The "all" property is needed for Flow to properly parse the syntax. - // It has trouble understanding when the flow comment is the second comment. - // See: https://github.com/babel/babel/issues/9845 - [ - "@babel/preset-flow", - { - "all": true - } - ] + "overrides": [ + { + "test": "**/*.js", + "presets": [ + [ + "@babel/preset-env", + { + // Allow `@babel/preset-env` to import polyfills from core-js as needed. + "useBuiltIns": "usage", + // Help `@babel/preset-env` make use of the correct core-js polyfills. + "corejs": "3.9", + // Perform transforms closest to targets defined in `.browserslistrc`. + "bugfixes": true + } + ], + [ + "@babel/preset-react", + { + // When spreading props, use inline object with spread elements + // directly instead of Babel's `extend` helper or `Object.assign`. + "useSpread": true + } + ], + // The "all" property is needed for Flow to properly parse the syntax. + // It has trouble understanding when the flow comment is the second comment. + // See: https://github.com/babel/babel/issues/9845 + [ + "@babel/preset-flow", + { + "all": true + } + ] + ] + }, + { + "test": ["**/*.ts", "**/*.tsx"], + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "usage", + "corejs": "3.9", + "bugfixes": true + } + ], + [ + "@babel/preset-react", + { + "useSpread": true, + "runtime": "automatic" + } + ], + [ + "@babel/preset-typescript", + { + "isTSX": true, + "allExtensions": true + } + ] + ] + } ], "plugins": [ // Though `@babel/plugin-transform-class-properties` is already included diff --git a/package.json b/package.json index 1d0ae145c4..f24627783d 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,7 @@ "lint-fix-css": "yarn lint-css --fix", "prettier-run": "node bin/output-fixing-commands.js prettier --check . --cache --cache-strategy content --cache-location .prettiercache", "prettier-fix": "prettier --write . --cache --cache-strategy content --cache-location .prettiercache", - "flow": "flow --max-warnings 0", - "flow:ci": "flow check --max-warnings 0", - "flow-stop": "flow stop", - "flow-coverage": "npx flow-coverage-report -i 'src/**/*.js' -t html -t text", - "flow-generate-libdefs": "npx flow-typed install --libdefDir src/types/libdef", + "typecheck": "tsc --noEmit", "protoc": "npx -p protobufjs-cli pbjs -t static-module -w commonjs -o ./src/profile-logic/import/proto/simpleperf_report.js ./src/profile-logic/import/proto/simpleperf_report.proto", "license-check": "devtools-license-check", "preinstall": "node bin/pre-install.js", @@ -47,12 +43,12 @@ "start-docs": "ws -d docs-user/ -p 3000", "start-photon": "node res/photon/server", "test": "node bin/output-fixing-commands.js cross-env LC_ALL=C TZ=UTC NODE_ENV=test jest", - "test-all": "run-p --max-parallel 4 flow license-check lint test test-alex test-lockfile", - "test-all:ci": "run-p --max-parallel 4 flow:ci license-check lint test test-alex test-lockfile", + "test-all": "run-p --max-parallel 4 typecheck license-check lint test test-alex test-lockfile", + "test-all:ci": "run-p --max-parallel 4 typecheck license-check lint test test-alex test-lockfile", "test-build-coverage": "yarn test --coverage --coverageReporters=html", "test-serve-coverage": "ws -d coverage/ -p 4343", "test-coverage": "run-s test-build-coverage test-serve-coverage", - "test-alex": "alex ./docs-* *.md", + "test-alex": "alex ./docs-* CODE_OF_CONDUCT.md CONTRIBUTING.md README.md", "test-lockfile": "lockfile-lint --path yarn.lock --allowed-hosts yarn --validate-https", "test-debug": "cross-env LC_ALL=C TZ=UTC NODE_ENV=test node --inspect-brk node_modules/.bin/jest --runInBand", "postinstall": "patch-package" @@ -118,10 +114,13 @@ "@babel/preset-env": "^7.28.0", "@babel/preset-flow": "^7.27.1", "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", "@fetch-mock/jest": "^0.2.16", "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.3", + "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", + "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/parser": "^8.38.0", "alex": "^11.0.1", "autoprefixer": "^10.4.21", "babel-jest": "^30.0.4", @@ -173,21 +172,28 @@ "stylelint": "^16.21.1", "stylelint-config-idiomatic-order": "^10.0.0", "stylelint-config-standard": "^38.0.0", + "typescript": "^5.8.3", "webpack": "^5.100.2", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.2", "workbox-webpack-plugin": "^7.3.0", "yargs": "^18.0.0" }, + "resolutions": { + "@types/react-splitter-layout/@types/react": "^18.3.1", + "@types/trusted-types": "^2.0.7" + }, "jest": { "collectCoverageFrom": [ - "src/**/*.{js,jsx}", + "src/**/*.{js,jsx,ts,tsx}", "!**/node_modules/**", "!src/types/libdef/**" ], "moduleFileExtensions": [ "js", - "jsx" + "jsx", + "ts", + "tsx" ], "transformIgnorePatterns": [ "/node_modules/(?!(query-string|decode-uri-component|split-on-first|filter-obj|@fetch-mock/jest|fetch-mock)/)" diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000000..ff8ff56374 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +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/. */ + +// Added by webpack's DefinePlugin +declare const AVAILABLE_STAGING_LOCALES: string[] | null; + +declare module '*.css' {} + +declare module '*.svg' { + const content: string; + export default content; +} + +declare module '*.jpg' { + const content: string; + export default content; +} + +declare module '*.png' { + const content: string; + export default content; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..0f41202180 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "es2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "es2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "verbatimModuleSyntax": false, + "noUncheckedSideEffectImports": true, + "forceConsistentCasingInFileNames": true, + + "noFallthroughCasesInSwitch": false, // Already checked by eslint, with opt-out + "noUnusedLocals": true, + "noImplicitOverride": true, + "noUnusedParameters": false, // Should enable in the future + + // Default to strict, with the exceptions below + "strict": true, + + // Exceptions to strictness, consider enabling in the future + "useUnknownInCatchVariables": false, // Sounds fine, enforces `} catch (e) { if (e instanceof Error) {` etc + "alwaysStrict": false, // Affects runtime behavior, I think - need to check what we had with Flow + "exactOptionalPropertyTypes": false, // Benefit unclear + + // Build & Performance + "isolatedModules": true, // May want to change this once we start using const enum + "listFiles": false, + "noEmit": true, + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo", + + // Module Resolution + "resolveJsonModule": true, + "baseUrl": ".", + "typeRoots": [], + "paths": { + "firefox-profiler/*": ["./src/*"], + "firefox-profiler-res/*": ["./res/*"] + }, + + // React & JSX + "jsx": "react-jsx" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "dist", "src/types/libdef"] +} diff --git a/webpack.config.js b/webpack.config.js index da6c8c0037..1810af9757 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,6 +27,7 @@ const config = { }, mode: process.env.NODE_ENV, resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], alias: { // Note: the alias for firefox-profiler is defined at the Babel level, so // that Jest can profit from it too. @@ -41,6 +42,11 @@ const config = { use: ['babel-loader'], include: includes.concat(es6modulePaths), }, + { + test: /\.(ts|tsx)$/, + use: ['babel-loader'], + include: includes, + }, { test: /\.json$/, use: ['json-loader'], diff --git a/yarn.lock b/yarn.lock index 992659e855..51a59d7d32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -884,6 +884,17 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" +"@babel/plugin-transform-typescript@^7.27.1": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz#796cbd249ab56c18168b49e3e1d341b72af04a6b" + integrity sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/plugin-transform-unicode-escapes@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806" @@ -1021,6 +1032,17 @@ "@babel/plugin-transform-react-jsx-development" "^7.27.1" "@babel/plugin-transform-react-pure-annotations" "^7.27.1" +"@babel/preset-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz#190742a6428d282306648a55b0529b561484f912" + integrity sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-typescript" "^7.27.1" + "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" @@ -1222,6 +1244,11 @@ dependencies: eslint-visitor-keys "^3.4.3" +"@eslint-community/regexpp@^4.10.0": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + "@eslint-community/regexpp@^4.6.1": version "4.8.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.0.tgz#11195513186f68d42fbf449f9a7136b2c0c92005" @@ -1966,17 +1993,17 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^6.6.3": - version "6.6.3" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" - integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== +"@testing-library/jest-dom@^6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.4.tgz#577a1761768bda5458c42241add3b1570c34d39c" + integrity sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ== dependencies: "@adobe/css-tools" "^4.4.0" aria-query "^5.0.0" - chalk "^3.0.0" css.escape "^1.5.1" dom-accessibility-api "^0.6.3" lodash "^4.17.21" + picocolors "^1.1.1" redent "^3.0.0" "@testing-library/react@^16.3.0": @@ -2307,6 +2334,11 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== +"@types/prop-types@*": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -2317,6 +2349,14 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react@^18.3.1": + version "18.3.23" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.23.tgz#86ae6f6b95a48c418fecdaccc8069e0fbb63696a" + integrity sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" @@ -2373,10 +2413,10 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== -"@types/trusted-types@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" - integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== +"@types/trusted-types@^2.0.2", "@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.6": version "2.0.6" @@ -2407,6 +2447,32 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^8.38.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz#c9afec1866ee1a6ea3d768b5f8e92201efbbba06" + integrity sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.39.0" + "@typescript-eslint/type-utils" "8.39.0" + "@typescript-eslint/utils" "8.39.0" + "@typescript-eslint/visitor-keys" "8.39.0" + graphemer "^1.4.0" + ignore "^7.0.0" + natural-compare "^1.4.0" + ts-api-utils "^2.1.0" + +"@typescript-eslint/parser@^8.38.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.39.0.tgz#c4b895d7a47f4cd5ee6ee77ea30e61d58b802008" + integrity sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg== + dependencies: + "@typescript-eslint/scope-manager" "8.39.0" + "@typescript-eslint/types" "8.39.0" + "@typescript-eslint/typescript-estree" "8.39.0" + "@typescript-eslint/visitor-keys" "8.39.0" + debug "^4.3.4" + "@typescript-eslint/project-service@8.35.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.35.0.tgz#00bd77e6845fbdb5684c6ab2d8a400a58dcfb07b" @@ -2416,6 +2482,15 @@ "@typescript-eslint/types" "^8.35.0" debug "^4.3.4" +"@typescript-eslint/project-service@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.39.0.tgz#71cb29c3f8139f99a905b8705127bffc2ae84759" + integrity sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.39.0" + "@typescript-eslint/types" "^8.39.0" + debug "^4.3.4" + "@typescript-eslint/scope-manager@8.35.0", "@typescript-eslint/scope-manager@^8.15.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz#8ccb2ab63383544fab98fc4b542d8d141259ff4f" @@ -2424,16 +2499,45 @@ "@typescript-eslint/types" "8.35.0" "@typescript-eslint/visitor-keys" "8.35.0" +"@typescript-eslint/scope-manager@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz#ba4bf6d8257bbc172c298febf16bc22df4856570" + integrity sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A== + dependencies: + "@typescript-eslint/types" "8.39.0" + "@typescript-eslint/visitor-keys" "8.39.0" + "@typescript-eslint/tsconfig-utils@8.35.0", "@typescript-eslint/tsconfig-utils@^8.35.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz#6e05aeb999999e31d562ceb4fe144f3cbfbd670e" integrity sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA== +"@typescript-eslint/tsconfig-utils@8.39.0", "@typescript-eslint/tsconfig-utils@^8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz#b2e87fef41a3067c570533b722f6af47be213f13" + integrity sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ== + +"@typescript-eslint/type-utils@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz#310ec781ae5e7bb0f5940bfd652573587f22786b" + integrity sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q== + dependencies: + "@typescript-eslint/types" "8.39.0" + "@typescript-eslint/typescript-estree" "8.39.0" + "@typescript-eslint/utils" "8.39.0" + debug "^4.3.4" + ts-api-utils "^2.1.0" + "@typescript-eslint/types@8.35.0", "@typescript-eslint/types@^8.35.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.35.0.tgz#e60d062907930e30008d796de5c4170f02618a93" integrity sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ== +"@typescript-eslint/types@8.39.0", "@typescript-eslint/types@^8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.39.0.tgz#80f010b7169d434a91cd0529d70a528dbc9c99c6" + integrity sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg== + "@typescript-eslint/typescript-estree@8.35.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz#86141e6c55b75bc1eaecc0781bd39704de14e52a" @@ -2450,6 +2554,32 @@ semver "^7.6.0" ts-api-utils "^2.1.0" +"@typescript-eslint/typescript-estree@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz#b9477a5c47a0feceffe91adf553ad9a3cd4cb3d6" + integrity sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw== + dependencies: + "@typescript-eslint/project-service" "8.39.0" + "@typescript-eslint/tsconfig-utils" "8.39.0" + "@typescript-eslint/types" "8.39.0" + "@typescript-eslint/visitor-keys" "8.39.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.1.0" + +"@typescript-eslint/utils@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.39.0.tgz#dfea42f3c7ec85f9f3e994ff0bba8f3b2f09e220" + integrity sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ== + dependencies: + "@eslint-community/eslint-utils" "^4.7.0" + "@typescript-eslint/scope-manager" "8.39.0" + "@typescript-eslint/types" "8.39.0" + "@typescript-eslint/typescript-estree" "8.39.0" + "@typescript-eslint/utils@^8.0.0", "@typescript-eslint/utils@^8.15.0": version "8.35.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.35.0.tgz#aaf0afab5ab51ea2f1897002907eacd9834606d5" @@ -2468,6 +2598,14 @@ "@typescript-eslint/types" "8.35.0" eslint-visitor-keys "^4.2.1" +"@typescript-eslint/visitor-keys@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz#5d619a6e810cdd3fd1913632719cbccab08bf875" + integrity sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA== + dependencies: + "@typescript-eslint/types" "8.39.0" + eslint-visitor-keys "^4.2.1" + "@ungap/structured-clone@^1.2.0", "@ungap/structured-clone@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" @@ -3533,14 +3671,6 @@ chalk-template@^0.4.0: dependencies: chalk "^4.1.2" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -6234,7 +6364,7 @@ ignore@^5.0.0, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -ignore@^7.0.5: +ignore@^7.0.0, ignore@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== @@ -11245,7 +11375,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11367,7 +11506,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11381,6 +11520,13 @@ strip-ansi@^0.3.0: dependencies: ansi-regex "^0.2.1" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -11962,6 +12108,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== +typescript@^5.8.3: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + typical@^5.0.0, typical@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" @@ -12901,8 +13052,16 @@ workbox-window@7.3.0, workbox-window@^7.3.0: "@types/trusted-types" "^2.0.2" workbox-core "7.3.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 971a6a383b9458cf5fb886b1a73e636dc06e77fe Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 20:29:33 -0400 Subject: [PATCH 040/124] Update eslintrc --- .eslintrc.js | 367 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 251 insertions(+), 116 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f42e958731..6d0df942d6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,124 +1,259 @@ // @flow module.exports = { - env: { - browser: true, - es6: true, - es2020: true, - node: true, - }, - parser: '@babel/eslint-parser', - plugins: ['@babel', 'react', 'flowtype', 'import'], - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:flowtype/recommended', - 'prettier', - ], - parserOptions: { - ecmaVersion: '2017', - ecmaFeatures: { - experimentalObjectRestSpread: true, - jsx: true, + overrides: [ + { + // TypeScript files + files: ['**/*.ts', '**/*.tsx'], + env: { + browser: true, + es6: true, + es2020: true, + node: true, + }, + parser: '@typescript-eslint/parser', + plugins: ['@babel', '@typescript-eslint', 'react', 'import'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'prettier', + ], + parserOptions: { + ecmaVersion: '2022', + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true, + }, + sourceType: 'module', + }, + rules: { + // Plugin rules: + 'import/no-duplicates': 'error', + 'import/no-unresolved': 'error', + 'import/named': 'error', + 'react/button-has-type': 'error', + 'react/no-access-state-in-setstate': 'error', + 'react/no-danger': 'error', + 'react/no-did-mount-set-state': 'error', + 'react/no-did-update-set-state': 'error', + 'react/no-will-update-set-state': 'error', + 'react/no-redundant-should-component-update': 'error', + 'react/no-unused-class-component-methods': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-typos': 'error', + // TypeScript provides enough coverage over the prop types. + 'react/prop-types': 'off', + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never' }, + ], + // `no-unused-prop-types` is buggy when we use destructuring parameters in + // functions as it misunderstands them as functional components. + // See https://github.com/yannickcr/eslint-plugin-react/issues/1561 + // 'react/no-unused-prop-types': 'error', + 'react/no-unused-state': 'error', + 'react/jsx-no-bind': 'error', + 'react/jsx-no-leaked-render': 'error', + // no-dupe-keys crashes with recent eslint. See + // https://github.com/gajus/eslint-plugin-flowtype/pull/266 and + // https://github.com/gajus/eslint-plugin-flowtype/pull/302 + // 'flowtype/no-dupe-keys': 'error', + + // overriding recommended rules + 'no-constant-condition': ['error', { checkLoops: false }], + 'no-console': ['error', { allow: ['log', 'warn', 'error'] }], + + // possible errors + 'array-callback-return': 'error', + 'consistent-return': 'error', + curly: 'error', + 'default-case': 'error', + 'dot-notation': 'error', + eqeqeq: 'error', + 'for-direction': 'error', + 'no-alert': 'error', + 'no-caller': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-implied-eval': 'error', + // We use the version from the babel plugin so that `this` in a function + // class property doesn't give a false positive. + '@babel/no-invalid-this': 'error', + 'no-return-await': 'error', + 'no-self-compare': 'error', + 'no-throw-literal': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-useless-call': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-concat': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'no-void': 'error', + 'no-with': 'error', + 'prefer-const': 'error', + 'prefer-promise-reject-errors': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'no-else-return': 'error', + 'no-nested-ternary': 'error', + + // Use `import type` everywhere we can. + '@typescript-eslint/consistent-type-imports': 'error', + // Allow `as any` escape hatches + '@typescript-eslint/no-explicit-any': 'off', + // Disable a rule that the TypeScript FAQ disapproves of + '@typescript-eslint/no-empty-object-type': 'off', + // Should enable this soon, mostly finds `catch (e)` with unused e + '@typescript-eslint/no-unused-vars': 'off', + // TypeScript imports react-jsx into .tsx files for us + 'react/react-in-jsx-scope': 'off', + // Allow @ts-expect-error annotations with descriptions. + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + // Allow @ts-expect-error annotations with descriptions. + 'ts-expect-error': 'allow-with-description', + // Don't allow @ts-ignore or @ts-nocheck because we want to be notified + // when the error goes away so we can remove the annotation - use + // @ts-expect-error instead + 'ts-ignore': true, + 'ts-nocheck': true, + 'ts-check': false, // allow even without description + }, + ], + // TODO: Re-enable for src when we update to eslint and switch to the + // flat config format + '@typescript-eslint/no-require-imports': 'off', + }, }, - sourceType: 'module', - }, - rules: { - // Plugin rules: - 'import/no-duplicates': 'error', - 'import/no-unresolved': 'error', - 'import/named': 'error', - 'react/button-has-type': 'error', - 'react/no-access-state-in-setstate': 'error', - 'react/no-danger': 'error', - 'react/no-did-mount-set-state': 'error', - 'react/no-did-update-set-state': 'error', - 'react/no-will-update-set-state': 'error', - 'react/no-redundant-should-component-update': 'error', - 'react/no-unused-class-component-methods': 'error', - 'react/no-this-in-sfc': 'error', - 'react/no-typos': 'error', - // Flow provides enough coverage over the prop types, and there can be errors - // with some of the more complicated Flow types. - 'react/prop-types': 'off', - 'react/jsx-curly-brace-presence': [ - 'error', - { props: 'never', children: 'never' }, - ], - // `no-unused-prop-types` is buggy when we use destructuring parameters in - // functions as it misunderstands them as functional components. - // See https://github.com/yannickcr/eslint-plugin-react/issues/1561 - // 'react/no-unused-prop-types': 'error', - 'react/no-unused-state': 'error', - 'react/jsx-no-bind': 'error', - 'react/jsx-no-leaked-render': 'error', - 'flowtype/require-valid-file-annotation': [ - 'error', - 'always', - { annotationStyle: 'line' }, - ], - // no-dupe-keys crashes with recent eslint. See - // https://github.com/gajus/eslint-plugin-flowtype/pull/266 and - // https://github.com/gajus/eslint-plugin-flowtype/pull/302 - // 'flowtype/no-dupe-keys': 'error', + { + // Flow JS files + files: ['**/*.js'], + env: { + browser: true, + es6: true, + es2020: true, + node: true, + }, + parser: '@babel/eslint-parser', + plugins: ['@babel', 'react', 'flowtype', 'import'], + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:flowtype/recommended', + 'prettier', + ], + parserOptions: { + ecmaVersion: '2017', + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true, + }, + sourceType: 'module', + }, + rules: { + // Plugin rules: + 'import/no-duplicates': 'error', + 'import/no-unresolved': 'error', + 'import/named': 'error', + 'react/button-has-type': 'error', + 'react/no-access-state-in-setstate': 'error', + 'react/no-danger': 'error', + 'react/no-did-mount-set-state': 'error', + 'react/no-did-update-set-state': 'error', + 'react/no-will-update-set-state': 'error', + 'react/no-redundant-should-component-update': 'error', + 'react/no-unused-class-component-methods': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-typos': 'error', + // Flow provides enough coverage over the prop types, and there can be errors + // with some of the more complicated Flow types. + 'react/prop-types': 'off', + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never' }, + ], + // `no-unused-prop-types` is buggy when we use destructuring parameters in + // functions as it misunderstands them as functional components. + // See https://github.com/yannickcr/eslint-plugin-react/issues/1561 + // 'react/no-unused-prop-types': 'error', + 'react/no-unused-state': 'error', + 'react/jsx-no-bind': 'error', + 'react/jsx-no-leaked-render': 'error', + 'flowtype/require-valid-file-annotation': [ + 'error', + 'always', + { annotationStyle: 'line' }, + ], + // no-dupe-keys crashes with recent eslint. See + // https://github.com/gajus/eslint-plugin-flowtype/pull/266 and + // https://github.com/gajus/eslint-plugin-flowtype/pull/302 + // 'flowtype/no-dupe-keys': 'error', - // overriding recommended rules - 'no-constant-condition': ['error', { checkLoops: false }], - 'no-console': ['error', { allow: ['log', 'warn', 'error'] }], - 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + // overriding recommended rules + 'no-constant-condition': ['error', { checkLoops: false }], + 'no-console': ['error', { allow: ['log', 'warn', 'error'] }], + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - // possible errors - 'array-callback-return': 'error', - 'consistent-return': 'error', - curly: 'error', - 'default-case': 'error', - 'dot-notation': 'error', - eqeqeq: 'error', - 'for-direction': 'error', - 'no-alert': 'error', - 'no-caller': 'error', - 'no-eval': 'error', - 'no-extend-native': 'error', - 'no-extra-bind': 'error', - 'no-extra-label': 'error', - 'no-implied-eval': 'error', - // We use the version from the babel plugin so that `this` in a function - // class property doesn't give a false positive. - '@babel/no-invalid-this': 'error', - 'no-return-await': 'error', - 'no-self-compare': 'error', - 'no-throw-literal': 'error', - 'no-unmodified-loop-condition': 'error', - // We use the version from the flowtype plugin so that flow assertions don't - // output an error. - 'flowtype/no-unused-expressions': 'error', - // The Object type and Function type aren't particularly useful, and usually hide - // type errors. It also blocks a migration to TypeScript. Disable this rule if - // using the Object or Function as generic type bounds. - 'flowtype/no-weak-types': [ - 'error', - { - any: false, - Object: true, - Function: true, + // possible errors + 'array-callback-return': 'error', + 'consistent-return': 'error', + curly: 'error', + 'default-case': 'error', + 'dot-notation': 'error', + eqeqeq: 'error', + 'for-direction': 'error', + 'no-alert': 'error', + 'no-caller': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-implied-eval': 'error', + // We use the version from the babel plugin so that `this` in a function + // class property doesn't give a false positive. + '@babel/no-invalid-this': 'error', + 'no-return-await': 'error', + 'no-self-compare': 'error', + 'no-throw-literal': 'error', + 'no-unmodified-loop-condition': 'error', + // We use the version from the flowtype plugin so that flow assertions don't + // output an error. + 'flowtype/no-unused-expressions': 'error', + // The Object type and Function type aren't particularly useful, and usually hide + // type errors. It also blocks a migration to TypeScript. Disable this rule if + // using the Object or Function as generic type bounds. + 'flowtype/no-weak-types': [ + 'error', + { + any: false, + Object: true, + Function: true, + }, + ], + 'flowtype/no-existential-type': 'error', + 'no-useless-call': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-concat': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'no-void': 'error', + 'no-with': 'error', + 'prefer-const': 'error', + 'prefer-promise-reject-errors': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'no-else-return': 'error', + 'no-nested-ternary': 'error', }, - ], - 'flowtype/no-existential-type': 'error', - 'no-useless-call': 'error', - 'no-useless-computed-key': 'error', - 'no-useless-concat': 'error', - 'no-useless-constructor': 'error', - 'no-useless-rename': 'error', - 'no-useless-return': 'error', - 'no-var': 'error', - 'no-void': 'error', - 'no-with': 'error', - 'prefer-const': 'error', - 'prefer-promise-reject-errors': 'error', - 'prefer-rest-params': 'error', - 'prefer-spread': 'error', - 'no-else-return': 'error', - 'no-nested-ternary': 'error', - }, + }, + ], // This property is specified both here in addition to the command line in // package.json. // The reason is that the property only warns but the command line option @@ -137,7 +272,7 @@ module.exports = { ['firefox-profiler', './src'], ['firefox-profiler-res', './res'], ], - extensions: ['.js', '.jpg'], + extensions: ['.js', '.ts', '.tsx', '.jpg'], }, }, }, From 9a09f31569cfa5189cbafd4d8c4c4f14e792304d Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:35:19 -0400 Subject: [PATCH 041/124] Remove .js extension from imports. --- src/components/tooltip/NetworkMarker.js | 2 +- src/selectors/app.js | 2 +- src/test/components/BottomBox.test.js | 2 +- src/test/components/Timeline.test.js | 2 +- src/test/components/TrackScreenshots.test.js | 2 +- src/test/store/symbolication.test.js | 2 +- src/test/unit/format-numbers.test.js | 2 +- src/types/state.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/tooltip/NetworkMarker.js b/src/components/tooltip/NetworkMarker.js index 01a44d6f0b..e470b9494d 100644 --- a/src/components/tooltip/NetworkMarker.js +++ b/src/components/tooltip/NetworkMarker.js @@ -26,7 +26,7 @@ import { PRECONNECT_PHASES_IN_ORDER, REQUEST_PHASES_IN_ORDER, ALL_NETWORK_PHASES_IN_ORDER, -} from 'firefox-profiler/profile-logic/network.js'; +} from 'firefox-profiler/profile-logic/network'; import type { NetworkPayload, diff --git a/src/selectors/app.js b/src/selectors/app.js index 8cf7f63406..6b92cf1f2f 100644 --- a/src/selectors/app.js +++ b/src/selectors/app.js @@ -12,7 +12,7 @@ import { getHiddenLocalTracksByPid, } from './url-state'; import { getGlobalTracks, getLocalTracksByPid } from './profile'; -import { getZipFileState } from './zipped-profiles.js'; +import { getZipFileState } from './zipped-profiles'; import { assertExhaustiveCheck, ensureExists } from '../utils/flow'; import { FULL_TRACK_SCREENSHOT_HEIGHT, diff --git a/src/test/components/BottomBox.test.js b/src/test/components/BottomBox.test.js index 40c0bf3d5d..216eaa3a64 100644 --- a/src/test/components/BottomBox.test.js +++ b/src/test/components/BottomBox.test.js @@ -22,7 +22,7 @@ import { import { blankStore } from 'firefox-profiler/test/fixtures/stores'; import { getProfileFromTextSamples } from 'firefox-profiler/test/fixtures/profiles/processed-profile'; import { fireFullClick } from 'firefox-profiler/test/fixtures/utils'; -import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect.js'; +import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect'; // We're not interested in the timeline in this test jest.mock('../../components/timeline', () => ({ diff --git a/src/test/components/Timeline.test.js b/src/test/components/Timeline.test.js index 392d7f8172..983e6ad11c 100644 --- a/src/test/components/Timeline.test.js +++ b/src/test/components/Timeline.test.js @@ -30,7 +30,7 @@ import { addIPCMarkerPairToThreads, } from '../fixtures/profiles/processed-profile'; import { autoMockCanvasContext } from '../fixtures/mocks/canvas-context'; -import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect.js'; +import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect'; import { mockRaf } from '../fixtures/mocks/request-animation-frame'; import { autoMockElementSize, diff --git a/src/test/components/TrackScreenshots.test.js b/src/test/components/TrackScreenshots.test.js index 9f95ead8d4..465c433b74 100644 --- a/src/test/components/TrackScreenshots.test.js +++ b/src/test/components/TrackScreenshots.test.js @@ -38,7 +38,7 @@ import { import { getScreenshotTrackProfile } from '../fixtures/profiles/processed-profile'; import { getProfileWithNiceTracks } from '../fixtures/profiles/tracks'; import { getPreviewSelection } from '../../selectors/profile'; -import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect.js'; +import { autoMockDomRect } from 'firefox-profiler/test/fixtures/mocks/domrect'; import { autoMockElementSize, setMockedElementSize, diff --git a/src/test/store/symbolication.test.js b/src/test/store/symbolication.test.js index c5493e2054..ed32e768e0 100644 --- a/src/test/store/symbolication.test.js +++ b/src/test/store/symbolication.test.js @@ -10,7 +10,7 @@ import { } from '../fixtures/example-symbol-table'; import type { ExampleSymbolTable } from '../fixtures/example-symbol-table'; import type { MarkerPayload } from 'firefox-profiler/types'; -import { SymbolStore } from '../../profile-logic/symbol-store.js'; +import { SymbolStore } from '../../profile-logic/symbol-store'; import * as ProfileViewSelectors from '../../selectors/profile'; import { selectedThreadSelectors } from '../../selectors/per-thread'; import { INTERVAL } from 'firefox-profiler/app-logic/constants'; diff --git a/src/test/unit/format-numbers.test.js b/src/test/unit/format-numbers.test.js index 1141009822..f22b2ece61 100644 --- a/src/test/unit/format-numbers.test.js +++ b/src/test/unit/format-numbers.test.js @@ -12,7 +12,7 @@ import { formatBytes, findRoundBytesValueGreaterOrEqualTo, findRoundMillisecondsValueGreaterOrEqualTo, -} from 'firefox-profiler/utils/format-numbers.js'; +} from 'firefox-profiler/utils/format-numbers'; describe('formatNumber', () => { it('return 0 without digits when called with 0', () => { diff --git a/src/types/state.js b/src/types/state.js index a30be9180d..a2a1248633 100644 --- a/src/types/state.js +++ b/src/types/state.js @@ -40,7 +40,7 @@ import type { Attempt } from '../utils/errors'; import type { TransformStacksPerThread } from './transforms'; import type JSZip from 'jszip'; import type { IndexIntoZipFileTable } from '../profile-logic/zip-files'; -import type { PathSet } from '../utils/path.js'; +import type { PathSet } from '../utils/path'; import type { UploadedProfileInformation as ImportedUploadedProfileInformation } from 'firefox-profiler/app-logic/uploaded-profiles-db'; import type { BrowserConnectionStatus } from 'firefox-profiler/app-logic/browser-connection'; From c323b6288adef32d70a5c2796539807591d217c9 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 20:32:23 -0400 Subject: [PATCH 042/124] Add TypeScript types for various dependencies. --- package.json | 11 ++++ tsconfig.json | 2 +- yarn.lock | 169 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 178 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f24627783d..3fb9aa86c2 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,17 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", + "@types/clamp": "^1.0.3", + "@types/common-tags": "^1.8.4", + "@types/jest": "^30.0.0", + "@types/minimist": "^1.2.5", + "@types/query-string": "^6.3.0", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@types/react-splitter-layout": "^4.0.0", + "@types/react-transition-group": "^4.4.5", + "@types/redux-logger": "^3.0.6", + "@types/tgwf__co2": "^0.14.2", "@typescript-eslint/eslint-plugin": "^8.38.0", "@typescript-eslint/parser": "^8.38.0", "alex": "^11.0.1", diff --git a/tsconfig.json b/tsconfig.json index 0f41202180..01b5411945 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,7 @@ // Module Resolution "resolveJsonModule": true, "baseUrl": ".", - "typeRoots": [], + "typeRoots": ["node_modules/@types"], "paths": { "firefox-profiler/*": ["./src/*"], "firefox-profiler-res/*": ["./res/*"] diff --git a/yarn.lock b/yarn.lock index 51a59d7d32..dd765406ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1451,6 +1451,13 @@ dependencies: "@jest/get-type" "30.0.1" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect@30.0.4": version "30.0.4" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.0.4.tgz#de25549873ccc0302faeef96044acae464f50997" @@ -1530,6 +1537,13 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -1610,6 +1624,19 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -2095,6 +2122,16 @@ dependencies: "@types/node" "*" +"@types/clamp@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/clamp/-/clamp-1.0.3.tgz#4b0976f1a272bca801f603c02724f9b9fcb1d366" + integrity sha512-xFcJ5qIQGxEjYjwSo5f6wyiPQx30CEfxMzh2PYYyoSM8Yvts54/pg7HU0g1bA5R6sklRAkVJpY1ejES/uyLOVw== + +"@types/common-tags@^1.8.4": + version "1.8.4" + resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.8.4.tgz#3b31fcb5952cd326a55cabe9dbe6c5be3c1671a0" + integrity sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg== + "@types/concat-stream@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-2.0.0.tgz#a716f0ba9015014e643addb351da05a73bef425c" @@ -2247,6 +2284,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^21.1.7": version "21.1.7" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.7.tgz#9edcb09e0b07ce876e7833922d3274149c898cfa" @@ -2288,6 +2333,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/minimist@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" @@ -2344,12 +2394,36 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/query-string@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-6.3.0.tgz#b6fa172a01405abcaedac681118e78429d62ea39" + integrity sha512-yuIv/WRffRzL7cBW+sla4HwBZrEXRNf1MKQ5SklPEadth+BKbDxiVG8A3iISN5B3yC4EeSCzMZP8llHTcUhOzQ== + dependencies: + query-string "*" + "@types/range-parser@*": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react@^18.3.1": +"@types/react-dom@^18.3.1": + version "18.3.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.7.tgz#b89ddf2cd83b4feafcc4e2ea41afdfb95a0d194f" + integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== + +"@types/react-splitter-layout@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz#b24634815a28782b8284ea89d1e20b87f38554fd" + integrity sha512-spwh1mpRdOt7pyYTpa6tje82OB5P5UmTsq1qxf9m9Dw8VzP3jy52Qkicq4Rn04ANYsXGHdNn3xraJkKS1A/Z0A== + dependencies: + "@types/react" "*" + +"@types/react-transition-group@^4.4.5": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@^18.3.1": version "18.3.23" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.23.tgz#86ae6f6b95a48c418fecdaccc8069e0fbb63696a" integrity sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w== @@ -2357,6 +2431,13 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/redux-logger@^3.0.6": + version "3.0.13" + resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.13.tgz#473e98428cdcc6dc93c908de66732bf932e36bc8" + integrity sha512-jylqZXQfMxahkuPcO8J12AKSSCQngdEWQrw7UiLUJzMBcv1r4Qg77P6mjGLjM27e5gFQDPD8vwUMJ9AyVxFSsg== + dependencies: + redux "^5.0.0" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" @@ -2408,6 +2489,11 @@ resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.1.tgz#1b44b1b096479273adf7f93c75fc4ecc40a61ee4" integrity sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw== +"@types/tgwf__co2@^0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@types/tgwf__co2/-/tgwf__co2-0.14.2.tgz#fea70be29000e2308fa80319da90600773392c12" + integrity sha512-kgSh14wLbydB4TwHYVq/1pbOC4ww3EDNgLQ3kZSH6wZyDmBMTRyEaIBqXtCmuhKCotU71QAOpkIO3IHbHTtANA== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -5265,6 +5351,18 @@ expect@30.0.4: jest-mock "30.0.2" jest-util "30.0.2" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express@^4.21.2: version "4.21.2" resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" @@ -7048,6 +7146,16 @@ jest-diff@30.0.4: chalk "^4.1.2" pretty-format "30.0.2" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^29.0.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" @@ -7148,6 +7256,16 @@ jest-matcher-utils@30.0.4: jest-diff "30.0.4" pretty-format "30.0.2" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-message-util@30.0.2: version "30.0.2" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.2.tgz#9dfdc37570d172f0ffdc42a0318036ff4008837f" @@ -7163,6 +7281,21 @@ jest-message-util@30.0.2: slash "^3.0.0" stack-utils "^2.0.6" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-mock@30.0.2: version "30.0.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.2.tgz#5e4245f25f6f9532714906cab10a2b9e39eb2183" @@ -7172,6 +7305,15 @@ jest-mock@30.0.2: "@types/node" "*" jest-util "30.0.2" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-pnp-resolver@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" @@ -7299,6 +7441,18 @@ jest-util@30.0.2: graceful-fs "^4.2.11" picomatch "^4.0.2" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-validate@30.0.2: version "30.0.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.0.2.tgz#f62a2f0e014dac94747509ba8c2bcd5d48215b7f" @@ -10020,6 +10174,15 @@ pretty-format@30.0.2: ansi-styles "^5.2.0" react-is "^18.3.1" +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" @@ -10149,7 +10312,7 @@ qs@6.13.0, qs@^6.12.3, qs@^6.5.2: dependencies: side-channel "^1.0.6" -query-string@^9.2.2: +query-string@*, query-string@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.2.2.tgz#a0104824edfdd2c1db2f18af71cef7abf6a3b20f" integrity sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g== @@ -10438,7 +10601,7 @@ redux-thunk@^3.1.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== -redux@^5.0.1: +redux@^5.0.0, redux@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== From 6d93997f08d4cb27148ac3a5cd8d350ccbba5513 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 20:35:19 -0400 Subject: [PATCH 043/124] Add handwritten types for dependencies which don't have types in @types. --- src/types/@types/array-range/index.d.ts | 9 ++ src/types/@types/memoize-immutable/index.d.ts | 35 +++++++ src/types/@types/mixedtuplemap/index.d.ts | 16 ++++ src/types/@types/namedtuplemap/index.d.ts | 15 +++ src/types/@types/photon-colors/index.d.ts | 93 +++++++++++++++++++ tsconfig.json | 2 +- 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/types/@types/array-range/index.d.ts create mode 100644 src/types/@types/memoize-immutable/index.d.ts create mode 100644 src/types/@types/mixedtuplemap/index.d.ts create mode 100644 src/types/@types/namedtuplemap/index.d.ts create mode 100644 src/types/@types/photon-colors/index.d.ts diff --git a/src/types/@types/array-range/index.d.ts b/src/types/@types/array-range/index.d.ts new file mode 100644 index 0000000000..2302e871db --- /dev/null +++ b/src/types/@types/array-range/index.d.ts @@ -0,0 +1,9 @@ +/* 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/. */ + +declare module 'array-range' { + function range(end: number): number[]; + function range(start: number, end: number): number[]; + export = range; +} diff --git a/src/types/@types/memoize-immutable/index.d.ts b/src/types/@types/memoize-immutable/index.d.ts new file mode 100644 index 0000000000..92249bc25e --- /dev/null +++ b/src/types/@types/memoize-immutable/index.d.ts @@ -0,0 +1,35 @@ +/* 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/. */ + +declare module 'memoize-immutable' { + export interface CacheInstance { + has(key: K): boolean; + get(key: K): V | undefined; + set(key: K, value: V): CacheInstance; + } + + // Extract the argument types as a tuple + type ExtractArgType = F extends (...args: infer A) => any ? A : never; + + // Extract the return type + type ExtractReturnType = F extends (...args: any[]) => infer R ? R : never; + + // Config with custom cache instance + export type CacheConfig any> = { + cache: CacheInstance, ExtractReturnType>; + }; + + // Simple limit config + export type LimitConfig = { + limit: number; + }; + + // Main export: memoized version of the function + const memoizeImmutable: any>( + fn: F, + config?: CacheConfig | LimitConfig + ) => F; + + export default memoizeImmutable; +} diff --git a/src/types/@types/mixedtuplemap/index.d.ts b/src/types/@types/mixedtuplemap/index.d.ts new file mode 100644 index 0000000000..f66fe1067f --- /dev/null +++ b/src/types/@types/mixedtuplemap/index.d.ts @@ -0,0 +1,16 @@ +/* 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/. */ + +declare module 'mixedtuplemap' { + export class MixedTupleMap { + constructor(); + toString(): string; + has(tuple: K): boolean; + set(tuple: K, value: V): MixedTupleMap; + get(tuple: K): V; + clear(): void; + } + + export default MixedTupleMap; +} diff --git a/src/types/@types/namedtuplemap/index.d.ts b/src/types/@types/namedtuplemap/index.d.ts new file mode 100644 index 0000000000..95ff321d4a --- /dev/null +++ b/src/types/@types/namedtuplemap/index.d.ts @@ -0,0 +1,15 @@ +/* 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/. */ + +declare module 'namedtuplemap' { + class NamedTupleMap { + constructor(options?: { limit?: number }); + has(key: K): boolean; + get(key: K): V | undefined; + set(key: K, value: V): NamedTupleMap; + clear(): void; + } + + export default NamedTupleMap; +} diff --git a/src/types/@types/photon-colors/index.d.ts b/src/types/@types/photon-colors/index.d.ts new file mode 100644 index 0000000000..ecbb100af4 --- /dev/null +++ b/src/types/@types/photon-colors/index.d.ts @@ -0,0 +1,93 @@ +/* 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/. */ + +declare module 'photon-colors' { + export const MAGENTA_50: string; + export const MAGENTA_60: string; + export const MAGENTA_70: string; + export const MAGENTA_80: string; + export const MAGENTA_90: string; + + export const PURPLE_30: string; + export const PURPLE_40: string; + export const PURPLE_50: string; + export const PURPLE_60: string; + export const PURPLE_70: string; + export const PURPLE_80: string; + export const PURPLE_90: string; + + export const BLUE_40: string; + export const BLUE_50: string; + export const BLUE_50_A30: string; + export const BLUE_60: string; + export const BLUE_70: string; + export const BLUE_80: string; + export const BLUE_90: string; + + export const TEAL_50: string; + export const TEAL_60: string; + export const TEAL_70: string; + export const TEAL_80: string; + export const TEAL_90: string; + + export const GREEN_50: string; + export const GREEN_60: string; + export const GREEN_70: string; + export const GREEN_80: string; + export const GREEN_90: string; + + export const YELLOW_50: string; + export const YELLOW_60: string; + export const YELLOW_60_A30: string; + export const YELLOW_70: string; + export const YELLOW_80: string; + export const YELLOW_90: string; + + export const RED_50: string; + export const RED_60: string; + export const RED_60_A30: string; + export const RED_70: string; + export const RED_80: string; + export const RED_90: string; + + export const ORANGE_50: string; + export const ORANGE_60: string; + export const ORANGE_70: string; + export const ORANGE_80: string; + export const ORANGE_90: string; + + export const GREY_10: string; + export const GREY_10_A10: string; + export const GREY_10_A20: string; + export const GREY_10_A40: string; + export const GREY_10_A60: string; + export const GREY_10_A80: string; + export const GREY_20: string; + export const GREY_30: string; + export const GREY_40: string; + export const GREY_50: string; + export const GREY_60: string; + export const GREY_70: string; + export const GREY_80: string; + export const GREY_90: string; + export const GREY_90_A05: string; + export const GREY_90_A10: string; + export const GREY_90_A20: string; + export const GREY_90_A30: string; + export const GREY_90_A40: string; + export const GREY_90_A50: string; + export const GREY_90_A60: string; + export const GREY_90_A70: string; + export const GREY_90_A80: string; + export const GREY_90_A90: string; + + export const INK_40: string; + export const INK_50: string; + export const INK_60: string; + export const INK_70: string; + export const INK_80: string; + export const INK_90: string; + + export const WHITE_100: string; +} diff --git a/tsconfig.json b/tsconfig.json index 01b5411945..eb9b109dc6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,7 @@ // Module Resolution "resolveJsonModule": true, "baseUrl": ".", - "typeRoots": ["node_modules/@types"], + "typeRoots": ["node_modules/@types", "src/types/@types"], "paths": { "firefox-profiler/*": ["./src/*"], "firefox-profiler-res/*": ["./res/*"] From 806318da588900a4fcf04a580483d81f1e58588c Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 21:27:26 -0400 Subject: [PATCH 044/124] Convert some files under src/types/. --- src/types/indexeddb.js | 177 ------------------ src/types/{network.js => network.ts} | 9 +- .../{symbolication.js => symbolication.ts} | 8 +- src/types/{units.js => units.ts} | 9 +- src/types/utils.js | 35 ---- src/types/utils.ts | 13 ++ 6 files changed, 27 insertions(+), 224 deletions(-) delete mode 100644 src/types/indexeddb.js rename src/types/{network.js => network.ts} (88%) rename src/types/{symbolication.js => symbolication.ts} (92%) rename src/types/{units.js => units.ts} (91%) delete mode 100644 src/types/utils.js create mode 100644 src/types/utils.ts diff --git a/src/types/indexeddb.js b/src/types/indexeddb.js deleted file mode 100644 index f97861d421..0000000000 --- a/src/types/indexeddb.js +++ /dev/null @@ -1,177 +0,0 @@ -// Coming from flow's source code: -// https://github.com/facebook/flow/blob/c8b17be6770568bb0ab4f7d865adbd6b38d5aa0e/lib/indexeddb.js -// See issue https://github.com/facebook/flow/issues/4143 - -// Fixed the interfaces, especially added some genericity, -// and changed so that it can be simply `import`ed. - -// @flow - -// Implemented by window & worker -export interface IDBEnvironment { - indexedDB: IDBFactory; -} - -export type IDBDirection = 'next' | 'nextunique' | 'prev' | 'prevunique'; - -export interface IDBVersionChangeEvent extends Event { - oldVersion: number; - newVersion: number | null; -} - -// Implemented by window.indexedDB & worker.indexedDB -export interface IDBFactory { - open(name: string, version?: number): IDBOpenDBRequest; - deleteDatabase(name: string): IDBOpenDBRequest; - cmp(a: K, b: K): -1 | 0 | 1; -} - -export interface IDBRequest extends EventTarget { - result: V; - error: Error; - source: ?( - | IDBIndex - | IDBObjectStore - | IDBCursor - ); - transaction: IDBTransaction; - readyState: 'pending' | 'done'; - onerror: (e: Event & { target: IDBRequest }) => mixed; - onsuccess: (e: Event & { target: IDBRequest }) => mixed; -} - -export interface IDBOpenDBRequest extends IDBRequest { - onblocked: (e: IDBVersionChangeEvent & { target: IDBDatabase }) => mixed; - onupgradeneeded: ( - e: IDBVersionChangeEvent & { target: IDBDatabase } - ) => mixed; -} - -export interface IDBDatabase extends EventTarget { - close(): void; - createObjectStore( - name: string, - options?: { - keyPath?: ?(string | string[]), - autoIncrement?: boolean, - } - ): IDBObjectStore; - deleteObjectStore(name: string): void; - transaction( - storeNames: string | string[], - mode?: 'readonly' | 'readwrite' | 'versionchange' - ): IDBTransaction; - name: string; - version: number; - objectStoreNames: string[]; - onabort: (e: Event) => mixed; - onerror: (e: Event) => mixed; - onversionchange: (e: Event) => mixed; -} - -export interface IDBTransaction extends EventTarget { - abort(): void; - db: IDBDatabase; - error: Error; - mode: 'readonly' | 'readwrite' | 'versionchange'; - name: string; - objectStore(name: string): IDBObjectStore; - onabort: (e: Event) => mixed; - oncomplete: (e: Event) => mixed; - onerror: (e: Event) => mixed; -} - -export interface IDBObjectStore { - add(value: V, key?: K | null): IDBRequest; - autoIncrement: boolean; - clear(): IDBRequest; - createIndex( - indexName: string, - keyPath: string | string[], - optionalParameter?: { - unique?: boolean, - multiEntry?: boolean, - } - ): IDBIndex; - count(keyRange?: K | IDBKeyRange): IDBRequest; - delete(key: K): IDBRequest; - deleteIndex(indexName: string): void; - get(key: K): IDBRequest; - getAll(query?: K | IDBKeyRange | null, count?: number): IDBRequest; - getKey(key: K | IDBKeyRange): IDBRequest; - getAllKeys( - query?: K | IDBKeyRange | null, - count?: number - ): IDBRequest; - index(indexName: string): IDBIndex; - indexNames: string[]; - name: string; - keyPath: string | string[] | null; - openCursor( - range?: K | IDBKeyRange, - direction?: IDBDirection - ): IDBRequest | null>; - openKeyCursor( - range?: K | IDBKeyRange, - direction?: IDBDirection - ): IDBRequest | null>; - put(value: V, key?: K): IDBRequest; - transaction: IDBTransaction; -} - -export interface IDBIndex extends EventTarget { - count(key?: L | IDBKeyRange): IDBRequest; - get(key: L | IDBKeyRange): IDBRequest; - getAll(query?: L | IDBKeyRange | null, count?: number): IDBRequest; - getKey(key: L | IDBKeyRange): IDBRequest; - getAllKeys( - query?: L | IDBKeyRange | null, - count?: number - ): IDBRequest; - openCursor( - range?: L | IDBKeyRange, - direction?: IDBDirection - ): IDBRequest | null>; - openKeyCursor( - range?: L | IDBKeyRange, - direction?: IDBDirection - ): IDBRequest | null>; - name: string; - objectStore: IDBObjectStore; - keyPath: string | string[] | null; - multiEntry: boolean; - unique: boolean; -} - -// TODO - Investigate for correctness, see: -// https://github.com/firefox-devtools/profiler/issues/718 -export interface IDBKeyRange { - bound( - lower: J, - upper: J, - lowerOpen?: boolean, - upperOpen?: boolean - ): IDBKeyRange; - only(value: J): IDBKeyRange; - lowerBound(bound: J, open?: boolean): IDBKeyRange; - upperBound(bound: J, open?: boolean): IDBKeyRange; - lower: K; - upper: K; - lowerOpen: boolean; - upperOpen: boolean; -} - -export interface IDBCursor { - advance(count: number): void; - continue(key?: L): void; - continuePrimaryKey(key: L, primaryKey: K): void; - delete(): IDBRequest; - update(newValue: V): IDBRequest; - source: IDBObjectStore | IDBIndex; - direction: IDBDirection; - key: L; - primaryKey: K; -} -export interface IDBCursorWithValue extends IDBCursor { - value: V; -} diff --git a/src/types/network.js b/src/types/network.ts similarity index 88% rename from src/types/network.js rename to src/types/network.ts index b147d2003e..c95d0c9035 100644 --- a/src/types/network.js +++ b/src/types/network.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow export type NetworkHttpVersion = 'h3' | 'h2' | 'http/1.1' | 'http/1.0'; @@ -26,7 +25,7 @@ export type NetworkPhaseName = | 'responseEnd' | 'endTime'; -export type NetworkPhaseAndValue = {| - phase: NetworkPhaseName, - value: number, -|}; +export type NetworkPhaseAndValue = { + phase: NetworkPhaseName; + value: number; +}; diff --git a/src/types/symbolication.js b/src/types/symbolication.ts similarity index 92% rename from src/types/symbolication.js rename to src/types/symbolication.ts index 15166cbf67..5935020119 100644 --- a/src/types/symbolication.js +++ b/src/types/symbolication.ts @@ -2,8 +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/. */ -// @flow - export interface ISymbolStoreDB { /** * Store the symbol table for a given library. @@ -34,3 +32,9 @@ export interface ISymbolStoreDB { close(): Promise; } + +export type SymbolTableAsTuple = [ + Uint32Array, // addrs + Uint32Array, // index + Uint8Array, // buffer +]; diff --git a/src/types/units.js b/src/types/units.ts similarity index 91% rename from src/types/units.js rename to src/types/units.ts index effea99c42..82643bb99d 100644 --- a/src/types/units.js +++ b/src/types/units.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow export type Nanoseconds = number; export type Microseconds = number; @@ -32,12 +31,12 @@ export type UnitIntervalOfProfileRange = number; * For the a viewport into the profile range. */ export type HorizontalViewport = { - left: UnitIntervalOfProfileRange, - right: UnitIntervalOfProfileRange, - length: UnitIntervalOfProfileRange, + left: UnitIntervalOfProfileRange; + right: UnitIntervalOfProfileRange; + length: UnitIntervalOfProfileRange; }; -export type StartEndRange = {| start: Milliseconds, end: Milliseconds |}; +export type StartEndRange = { start: Milliseconds; end: Milliseconds }; // An absolute address that was valid in the (virtual memory) address space of // the profiled process, in bytes. diff --git a/src/types/utils.js b/src/types/utils.js deleted file mode 100644 index 7a1d614262..0000000000 --- a/src/types/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -/* 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/. */ - -// @flow - -export type ExtractReturnType = ((...args: any[]) => V) => V; - -/** - * This type serves as documentation for how an array is meant to be used, but does - * not support type checking. We often use an Array instead of a Map to translate - * one type of index into another type of index. This is similar to how we use the - * Map type, but with the Array. - */ -// eslint-disable-next-line no-unused-vars -export type IndexedArray<_IndexType, Value> = Array; - -/** - * This is a utility type that extracts the return type of a function. - */ -export type $ReturnType = $Call; - -/** - * This type is equivalent to {[string]: T} for an object created without a prototype, - * e.g. Object.create(null). - * - * See: https://github.com/facebook/flow/issues/4967#issuecomment-402355640 - */ -export type ObjectMap = { - [string]: T, - // No prototype was created: - __proto__: null, -}; - -export type MixedObject = { [key: string]: mixed }; diff --git a/src/types/utils.ts b/src/types/utils.ts new file mode 100644 index 0000000000..1cb9ed6dae --- /dev/null +++ b/src/types/utils.ts @@ -0,0 +1,13 @@ +/* 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/. */ + +/** + * This type serves as documentation for how an array is meant to be used, but does + * not support type checking. We often use an Array instead of a Map to translate + * one type of index into another type of index. This is similar to how we use the + * Map type, but with the Array. + */ +export type IndexedArray<_IndexType, Value> = Array; + +export type MixedObject = { [key: string]: unknown }; From 8ccd2d25a5b846c6cde671ad36212ba496d820bf Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 21:35:37 -0400 Subject: [PATCH 045/124] Convert some files under src/utils. --- .../{worker-factory.js => worker-factory.ts} | 42 +++++----- src/utils/{analytics.js => analytics.ts} | 68 ++++++++------- src/utils/{base64.js => base64.ts} | 7 +- src/utils/{bisect.js => bisect.ts} | 49 ++++++----- src/utils/{bitset.js => bitset.ts} | 2 - src/utils/{colors.js => colors.ts} | 15 ++-- ...eometry-tools.js => css-geometry-tools.ts} | 11 +-- ...ata-table-utils.js => data-table-utils.ts} | 13 ++- src/utils/{errors.js => errors.ts} | 8 +- src/utils/{jwt.js => jwt.ts} | 1 - ...ftl-functions.js => l10n-ftl-functions.ts} | 26 +++--- src/utils/{l10n-pseudo.js => l10n-pseudo.ts} | 13 +-- src/utils/{magic.js => magic.ts} | 7 +- .../{number-series.js => number-series.ts} | 2 - .../{pretty-bytes.js => pretty-bytes.ts} | 2 - src/utils/{react.js => react.ts} | 2 - ...-wrapper.js => resize-observer-wrapper.ts} | 17 ++-- src/utils/{set.js => set.ts} | 6 +- src/utils/{sha1.js => sha1.ts} | 2 +- src/utils/{string.js => string.ts} | 2 - ...ext-measurement.js => text-measurement.ts} | 6 +- ...rray-encoding.js => uintarray-encoding.ts} | 21 +++-- src/utils/{untar.js => untar.ts} | 84 +++++++++---------- src/utils/{url.js => url.ts} | 4 +- .../{worker-factory.js => worker-factory.ts} | 2 - 25 files changed, 194 insertions(+), 218 deletions(-) rename src/utils/__mocks__/{worker-factory.js => worker-factory.ts} (66%) rename src/utils/{analytics.js => analytics.ts} (53%) rename src/utils/{base64.js => base64.ts} (81%) rename src/utils/{bisect.js => bisect.ts} (92%) rename src/utils/{bitset.js => bitset.ts} (99%) rename src/utils/{colors.js => colors.ts} (96%) rename src/utils/{css-geometry-tools.js => css-geometry-tools.ts} (86%) rename src/utils/{data-table-utils.js => data-table-utils.ts} (94%) rename src/utils/{errors.js => errors.ts} (84%) rename src/utils/{jwt.js => jwt.ts} (99%) rename src/utils/{l10n-ftl-functions.js => l10n-ftl-functions.ts} (79%) rename src/utils/{l10n-pseudo.js => l10n-pseudo.ts} (95%) rename src/utils/{magic.js => magic.ts} (60%) rename src/utils/{number-series.js => number-series.ts} (98%) rename src/utils/{pretty-bytes.js => pretty-bytes.ts} (98%) rename src/utils/{react.js => react.ts} (99%) rename src/utils/{resize-observer-wrapper.js => resize-observer-wrapper.ts} (88%) rename src/utils/{set.js => set.ts} (81%) rename src/utils/{sha1.js => sha1.ts} (94%) rename src/utils/{string.js => string.ts} (99%) rename src/utils/{text-measurement.js => text-measurement.ts} (96%) rename src/utils/{uintarray-encoding.js => uintarray-encoding.ts} (96%) rename src/utils/{untar.js => untar.ts} (86%) rename src/utils/{url.js => url.ts} (91%) rename src/utils/{worker-factory.js => worker-factory.ts} (91%) diff --git a/src/utils/__mocks__/worker-factory.js b/src/utils/__mocks__/worker-factory.ts similarity index 66% rename from src/utils/__mocks__/worker-factory.js rename to src/utils/__mocks__/worker-factory.ts index d1462cff83..b0c8bdedbb 100644 --- a/src/utils/__mocks__/worker-factory.js +++ b/src/utils/__mocks__/worker-factory.ts @@ -1,14 +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/. */ -// @flow -// $FlowExpectError Flow doesn't know about this util import { Worker } from 'worker_threads'; class NodeWorker { - _instance: Worker; - onmessage: (MessageEvent) => mixed; + _instance: Worker | null; + onmessage: ((event: MessageEvent) => unknown) | null; constructor(file: string) { const worker = new Worker(__dirname + '/node-worker-contents.js', { @@ -17,28 +15,26 @@ class NodeWorker { worker.on('message', this.onMessage); worker.on('error', this.onError); this._instance = worker; + this.onmessage = null; } - postMessage( - message: mixed, - transfer?: Array - ) { + postMessage(message: unknown, transfer?: any[]) { let payload = message; // Starting with node v11.12, postMessage sends the payload using the same // semantics than Web Workers. This code adds the support for older node // versions. We can remove this thin compatibility layer when we stop // supporting these node versions. - const nodeVersion = process.versions.node; + const nodeVersion = (process as any).versions.node; const [major, minor] = nodeVersion.split('.'); if (+major < 11 || (+major === 11 && +minor < 12)) { payload = { data: message }; } - this._instance.postMessage(payload, transfer); + this._instance?.postMessage(payload, transfer); } - onMessage = (message: mixed) => { + onMessage = (message: unknown) => { if (this.onmessage) { this.onmessage(new MessageEvent('message', { data: message })); } @@ -49,25 +45,25 @@ class NodeWorker { }; terminate() { - this._instance.terminate(); - this._instance.unref(); - this._instance = null; + if (this._instance) { + this._instance.terminate(); + this._instance.unref(); + this._instance = null; + } } } -const workerConfigs = { +const workerConfigs: { [key: string]: string } = { 'zee-worker': './res/zee-worker.js', }; -const workerInstances = []; +const workerInstances: NodeWorker[] = []; -export default class { - constructor(file: string) { - const path = workerConfigs[file]; - const worker = new NodeWorker(path); - workerInstances.push(worker); - return worker; - } +export default function (file: string): NodeWorker { + const path = workerConfigs[file]; + const worker = new NodeWorker(path); + workerInstances.push(worker); + return worker; } /** diff --git a/src/utils/analytics.js b/src/utils/analytics.ts similarity index 53% rename from src/utils/analytics.js rename to src/utils/analytics.ts index 6ed7ddbb4d..f08254578c 100644 --- a/src/utils/analytics.js +++ b/src/utils/analytics.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow /** * Document Google Analytics API that is used in the project. These definitions @@ -9,50 +8,61 @@ * * https://developers.google.com/analytics/devguides/collection/analyticsjs/pages */ -type GAEvent = {| - hitType: 'event', +type GAEvent = { + hitType: 'event'; // Specifies the event category. Must not be empty - eventCategory: string, - eventAction: string, - eventLabel?: string, - eventValue?: number, -|}; - -type GAPageView = {| - hitType: 'pageview', - page: string, -|}; - -type GATiming = {| - hitType: 'timing', - timingCategory: string, - timingVar: string, - timingValue: number, - timingLabel?: string, -|}; + eventCategory: string; + eventAction: string; + eventLabel?: string; + eventValue?: number; +}; + +type GAPageView = { + hitType: 'pageview'; + page: string; +}; + +type GATiming = { + hitType: 'timing'; + timingCategory: string; + timingVar: string; + timingValue: number; + timingLabel?: string; +}; export type GAPayload = GAEvent | GAPageView | GATiming; -export type GAErrorPayload = {| - +exDescription: string, - +exFatal: boolean, -|}; +export type GAErrorPayload = { + readonly exDescription: string; + readonly exFatal: boolean; +}; + +declare global { + interface Window { + // Google Analytics + ga?: GoogleAnalytics; + } +} // Prettier breaks with multiple arrow functions and intersections, so name the arrow // functions. -type _Send = ('send', GAPayload) => void; -type _Exception = ('send', 'exception', GAErrorPayload) => void; +type _Send = (command: 'send', payload: GAPayload) => void; +type _Exception = ( + command: 'send', + type: 'exception', + payload: GAErrorPayload +) => void; export type GoogleAnalytics = _Send & _Exception; export function sendAnalytics(payload: GAPayload) { - const ga: ?GoogleAnalytics = self.ga; + const ga: GoogleAnalytics | undefined = self.ga; if (ga) { ga('send', payload); } } export function reportError(errorPayload: GAErrorPayload) { - const ga: ?GoogleAnalytics = self.ga; + const ga: GoogleAnalytics | undefined = self.ga; if (ga) { ga('send', 'exception', errorPayload); } diff --git a/src/utils/base64.js b/src/utils/base64.ts similarity index 81% rename from src/utils/base64.js rename to src/utils/base64.ts index a72bda23e1..31571311ec 100644 --- a/src/utils/base64.js +++ b/src/utils/base64.ts @@ -1,18 +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/. */ -// @flow /** - * Encode the ArrayBuffer{,View} bytes into a base64 data url. + * Encode the ArrayBuffer bytes into a base64 data url. */ export async function bytesToBase64DataUrl( - bytes: $ArrayBufferView | ArrayBuffer, + bytes: ArrayBuffer, type: string = 'application/octet-stream' ): Promise { return new Promise((resolve, reject) => { const reader = Object.assign(new FileReader(), { - onload: () => resolve((reader.result: any)), + onload: () => resolve(reader.result as string), onerror: () => reject(reader.error), }); reader.readAsDataURL(new Blob([bytes], { type })); diff --git a/src/utils/bisect.js b/src/utils/bisect.ts similarity index 92% rename from src/utils/bisect.js rename to src/utils/bisect.ts index 16dcc4e724..a2a1168af8 100644 --- a/src/utils/bisect.js +++ b/src/utils/bisect.ts @@ -91,17 +91,25 @@ * ``` */ -// @flow +type TypedArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | Float32Array + | Float64Array + | BigInt64Array + | BigUint64Array; export function bisectionRight( - array: number[] | $TypedArray, + array: number[] | TypedArray, x: number, - low?: number, - high?: number + low: number = 0, + high: number = array.length ): number { - low = low || 0; - high = high || array.length; - if (low < 0 || low > array.length || high < 0 || high > array.length) { throw new TypeError("low and high must lie within the array's range"); } @@ -127,13 +135,10 @@ export function bisectionRight( export function bisectionRightByKey( array: T[], x: number, - toKey: (T) => number, - low?: number, - high?: number + toKey: (arg: T) => number, + low: number = 0, + high: number = array.length ): number { - low = low || 0; - high = high || array.length; - if (low < 0 || low > array.length || high < 0 || high > array.length) { throw new TypeError("low and high must lie within the array's range"); } @@ -159,13 +164,10 @@ export function bisectionRightByKey( export function bisectionRightByStrKey( array: T[], x: string, - toKey: (T) => string, - low?: number, - high?: number + toKey: (arg: T) => string, + low: number = 0, + high: number = array.length ): number { - low = low || 0; - high = high || array.length; - if (low < 0 || low > array.length || high < 0 || high > array.length) { throw new TypeError("low and high must lie within the array's range"); } @@ -184,14 +186,11 @@ export function bisectionRightByStrKey( } export function bisectionLeft( - array: number[] | $TypedArray, + array: number[] | TypedArray, x: number, - low?: number, - high?: number + low: number = 0, + high: number = array.length ): number { - low = low || 0; - high = high || array.length; - if (low < 0 || low > array.length || high < 0 || high > array.length) { throw new TypeError("low and high must lie within the array's range"); } diff --git a/src/utils/bitset.js b/src/utils/bitset.ts similarity index 99% rename from src/utils/bitset.js rename to src/utils/bitset.ts index 4af277fc18..87043985fa 100644 --- a/src/utils/bitset.js +++ b/src/utils/bitset.ts @@ -2,8 +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/. */ -// @flow - // A packed alternative to Array. // Created with makeBitSet. // All 32 bits in each array element are utilized, though of course the last diff --git a/src/utils/colors.js b/src/utils/colors.ts similarity index 96% rename from src/utils/colors.js rename to src/utils/colors.ts index c3b2db5eb8..845e33409a 100644 --- a/src/utils/colors.js +++ b/src/utils/colors.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow /** * These are the colors from Photon. They are inlined to provide easy access. If updating @@ -74,12 +73,12 @@ export const INK_70 = '#363959'; export const INK_80 = '#202340'; export const INK_90 = '#0f1126'; -type ColorStyles = {| - +selectedFillStyle: string, - +unselectedFillStyle: string, - +selectedTextColor: string, - +gravity: number, -|}; +type ColorStyles = { + readonly selectedFillStyle: string; + readonly unselectedFillStyle: string; + readonly selectedTextColor: string; + readonly gravity: number; +}; const GRAY_STYLE = { selectedFillStyle: GREY_40, @@ -93,7 +92,7 @@ const DARK_GRAY_STYLE = { selectedTextColor: '#fff', gravity: 11, }; -const STYLE_MAP: { [string]: ColorStyles } = { +const STYLE_MAP: { [key: string]: ColorStyles } = { transparent: { selectedFillStyle: 'transparent', unselectedFillStyle: 'transparent', diff --git a/src/utils/css-geometry-tools.js b/src/utils/css-geometry-tools.ts similarity index 86% rename from src/utils/css-geometry-tools.js rename to src/utils/css-geometry-tools.ts index fb755a7866..d1a60610f3 100644 --- a/src/utils/css-geometry-tools.js +++ b/src/utils/css-geometry-tools.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow /** * Return a float number for the number of CSS pixels from the computed style @@ -21,7 +20,6 @@ function subtractBorder(element: HTMLElement, rect: DOMRect): DOMRect { const borderBottom = getFloatStyle(element, 'border-bottom-width'); const borderLeft = getFloatStyle(element, 'border-left-width'); - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 return new DOMRect( rect.left + borderLeft, rect.top + borderTop, @@ -35,7 +33,6 @@ function subtractPadding(element: HTMLElement, rect: DOMRect): DOMRect { const paddingRight = getFloatStyle(element, 'padding-right'); const paddingBottom = getFloatStyle(element, 'padding-bottom'); const paddingLeft = getFloatStyle(element, 'padding-left'); - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 return new DOMRect( rect.left + paddingLeft, rect.top + paddingTop, @@ -49,7 +46,6 @@ function addMargin(element: HTMLElement, rect: DOMRect): DOMRect { const marginRight = getFloatStyle(element, 'margin-right'); const marginBottom = getFloatStyle(element, 'margin-bottom'); const marginLeft = getFloatStyle(element, 'margin-left'); - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 return new DOMRect( rect.left - marginLeft, rect.top - marginTop, @@ -65,7 +61,6 @@ function addMargin(element: HTMLElement, rect: DOMRect): DOMRect { export function getContentRect(element: HTMLElement): DOMRect { const clientRects = element.getClientRects(); if (clientRects.length !== 1) { - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 return new DOMRect(0, 0, 0, 0); } @@ -76,8 +71,7 @@ export function getContentRect(element: HTMLElement): DOMRect { ); } -function clientRectToDomRect(clientRect: ClientRect): DOMRect { - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 +function clientRectToDomRect(clientRect: DOMRectReadOnly): DOMRect { return new DOMRect( clientRect.left, clientRect.top, @@ -93,7 +87,6 @@ function clientRectToDomRect(clientRect: ClientRect): DOMRect { export function getMarginRect(element: HTMLElement): DOMRect { const clientRects = element.getClientRects(); if (clientRects.length !== 1) { - // $FlowFixMe Error introduced by upgrading to v0.96.0. See issue #1935 return new DOMRect(0, 0, 0, 0); } @@ -109,5 +102,5 @@ export function extractDomRectValue( rect: DOMRect, key: 'top' | 'left' | 'right' | 'bottom' ): number { - return (rect: any)[key]; + return rect[key]; } diff --git a/src/utils/data-table-utils.js b/src/utils/data-table-utils.ts similarity index 94% rename from src/utils/data-table-utils.js rename to src/utils/data-table-utils.ts index bbb60c1a20..bec56331ef 100644 --- a/src/utils/data-table-utils.js +++ b/src/utils/data-table-utils.ts @@ -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/. */ -// @flow /** * A "data table" is a JS object of the form: * { @@ -13,8 +12,8 @@ */ type DataTable = { - [key: string]: mixed[], - length: number, + length: number; + [key: string]: unknown[] | number; }; type compareFn = { (a: T, b: T): number }; @@ -41,11 +40,11 @@ export function sortDataTable( keyColumn: KeyColumnElementType[], comparator: compareFn ): DataTable { - function swap(i, j) { + function swap(i: number, j: number) { if (i !== j) { for (const columnName in table) { if (columnName !== 'length') { - const column = table[columnName]; + const column = table[columnName] as unknown[]; const temp = column[i]; column[i] = column[j]; column[j] = temp; @@ -59,7 +58,7 @@ export function sortDataTable( // are < pivotValue, the elements at k for partitionIndex < k <= right // are >= pivotValue, and the element at partitionIndex is == pivotValue. // If the range is already sorted, no swaps are performed. - function partition(pivot, left, right) { + function partition(pivot: number, left: number, right: number) { const pivotValue = keyColumn[pivot]; // At the end of each iteration, the following is true: @@ -87,7 +86,7 @@ export function sortDataTable( return partitionIndex; } - function quickSort(left, right) { + function quickSort(left: number, right: number) { if (left < right) { // QuickSort's effectiveness depends on its ability to partition the // sequence being sorted into two subsequences of roughly equal length: diff --git a/src/utils/errors.js b/src/utils/errors.ts similarity index 84% rename from src/utils/errors.js rename to src/utils/errors.ts index 33045d96fd..b4a4741092 100644 --- a/src/utils/errors.js +++ b/src/utils/errors.ts @@ -2,11 +2,9 @@ * 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/. */ -// @flow - export type Attempt = { - count: number, - total: number, + count: number; + total: number; }; export class TemporaryError extends Error { @@ -15,7 +13,7 @@ export class TemporaryError extends Error { constructor(message: string, attempt: Attempt | null = null) { super(message); // Workaround for a babel issue when extending Errors - (this: any).__proto__ = TemporaryError.prototype; + (this as any).__proto__ = TemporaryError.prototype; this.name = 'TemporaryError'; this.attempt = attempt; } diff --git a/src/utils/jwt.js b/src/utils/jwt.ts similarity index 99% rename from src/utils/jwt.js rename to src/utils/jwt.ts index a606a05878..2cca2652f5 100644 --- a/src/utils/jwt.js +++ b/src/utils/jwt.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow // This file provides simple utils to deal with JWT tokens. diff --git a/src/utils/l10n-ftl-functions.js b/src/utils/l10n-ftl-functions.ts similarity index 79% rename from src/utils/l10n-ftl-functions.js rename to src/utils/l10n-ftl-functions.ts index 411e44f249..1fddf060c4 100644 --- a/src/utils/l10n-ftl-functions.js +++ b/src/utils/l10n-ftl-functions.ts @@ -2,24 +2,21 @@ * 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/. */ -// @flow - // This file implements functions that we can use in fluent translation files. +import type { FluentValue } from '@fluent/bundle'; import { FluentDateTime } from '@fluent/bundle'; // These types come from Fluent's typescript types. We'll be able to remove them // and directly import Fluent's types when we switch to Typescript. interface Scope { - reportError(error: mixed): void; + reportError(error: unknown): void; } -export type FluentValue = FluentType | string; - export type FluentFunction = ( positional: Array, - named: { [string]: FluentValue } + named: { [key: string]: FluentValue } ) => FluentValue; /** @@ -77,23 +74,28 @@ const DATE_FORMATS = { month: 'short', day: 'numeric', }, -}; +} as const; /** * This function takes a timestamp as a parameter. It's similar to the builtin * DATE but it changes the date format depending on the proximity of the date * from the current date. */ -export const SHORTDATE: FluentFunction = (args, _named) => { +export const SHORTDATE: FluentFunction = ( + args: Array, + _named: { [key: string]: FluentValue } +): FluentValue => { const date = args[0]; const nowTimestamp = Date.now(); - const timeDifference = nowTimestamp - +date; + // Convert FluentValue to number for calculations + const dateValue = +date; + const timeDifference = nowTimestamp - dateValue; if (timeDifference < 0 || timeDifference > ONE_YEAR_IN_MS) { - return new FluentDateTime(date, DATE_FORMATS.ancient); + return new FluentDateTime(dateValue, DATE_FORMATS.ancient); } if (timeDifference > ONE_DAY_IN_MS) { - return new FluentDateTime(date, DATE_FORMATS.thisYear); + return new FluentDateTime(dateValue, DATE_FORMATS.thisYear); } - return new FluentDateTime(date, DATE_FORMATS.thisDay); + return new FluentDateTime(dateValue, DATE_FORMATS.thisDay); }; diff --git a/src/utils/l10n-pseudo.js b/src/utils/l10n-pseudo.ts similarity index 95% rename from src/utils/l10n-pseudo.js rename to src/utils/l10n-pseudo.ts index 9579dfe9e2..5f03354ce7 100644 --- a/src/utils/l10n-pseudo.js +++ b/src/utils/l10n-pseudo.ts @@ -2,8 +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/. */ -// @flow - // Stolen from https://hg.mozilla.org/mozilla-central/file/a1f74e8c8fb72390d22054d6b00c28b1a32f6c43/intl/l10n/L10nRegistry.jsm#l425 /** * Pseudolocalizations @@ -60,12 +58,12 @@ const FLIPPED_MAP = { }; function transformString( - map, + map: { caps: number[]; small: number[] }, elongate = false, prefix = '', postfix = '', msg: string -) { +): string { // Exclude access-keys and other single-char messages if (msg.length === 1) { return msg; @@ -109,7 +107,12 @@ export const PSEUDO_STRATEGIES = { bidi: transformString.bind(null, FLIPPED_MAP, false, '\u202e', '\u202c'), }; -export const PSEUDO_STRATEGIES_DIRECTION = { +export type Direction = 'ltr' | 'rtl'; + +export const PSEUDO_STRATEGIES_DIRECTION: { + readonly accented: Direction; + readonly bidi: Direction; +} = { accented: 'ltr', bidi: 'rtl', }; diff --git a/src/utils/magic.js b/src/utils/magic.ts similarity index 60% rename from src/utils/magic.js rename to src/utils/magic.ts index 2e8313840b..abbeb05e59 100644 --- a/src/utils/magic.js +++ b/src/utils/magic.ts @@ -1,8 +1,9 @@ -// @flow - export const SIMPLEPERF = 'SIMPLEPERF'; -export function verifyMagic(magic: string, traceBuffer: ArrayBuffer): boolean { +export function verifyMagic( + magic: string, + traceBuffer: ArrayBufferLike +): boolean { return ( new TextDecoder('utf8').decode(traceBuffer.slice(0, magic.length)) === magic ); diff --git a/src/utils/number-series.js b/src/utils/number-series.ts similarity index 98% rename from src/utils/number-series.js rename to src/utils/number-series.ts index 4a25bb5117..e676a9faeb 100644 --- a/src/utils/number-series.js +++ b/src/utils/number-series.ts @@ -2,8 +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/. */ -// @flow - export function numberSeriesFromDeltas(deltas: number[]): number[] { const values = new Array(deltas.length); let prev = 0; diff --git a/src/utils/pretty-bytes.js b/src/utils/pretty-bytes.ts similarity index 98% rename from src/utils/pretty-bytes.js rename to src/utils/pretty-bytes.ts index d73f3c0b95..ee5aebeda2 100644 --- a/src/utils/pretty-bytes.js +++ b/src/utils/pretty-bytes.ts @@ -3,8 +3,6 @@ // Copied here because we needed it in ES2015 module form. Also flow-typed. // Otherwise not modified. -// @flow - const UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; export default (num: number): string => { diff --git a/src/utils/react.js b/src/utils/react.ts similarity index 99% rename from src/utils/react.js rename to src/utils/react.ts index 54065c8c7e..a4966f1fd9 100644 --- a/src/utils/react.js +++ b/src/utils/react.ts @@ -2,8 +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/. */ -// @flow - // This file contains some functions that might be interesting when debugging // react code. diff --git a/src/utils/resize-observer-wrapper.js b/src/utils/resize-observer-wrapper.ts similarity index 88% rename from src/utils/resize-observer-wrapper.js rename to src/utils/resize-observer-wrapper.ts index 3adfb228c9..920ce90fa3 100644 --- a/src/utils/resize-observer-wrapper.js +++ b/src/utils/resize-observer-wrapper.ts @@ -1,26 +1,25 @@ /* 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/. */ -// @flow // This implements a wrapper to the ResizeObserver API, so that only one copy of // a ResizeObserver exists. This is much more performant than having one // ResizeObserver for each use. // This was inspired by the code in https://github.com/jaredLunde/react-hook/blob/master/packages/resize-observer/src/index.tsx -export type ResizeObserverCallback = (DOMRectReadOnly) => mixed; -export type ResizeObserverWrapper = {| - subscribe: (elt: HTMLElement, ResizeObserverCallback) => void, - unsubscribe: (elt: HTMLElement, ResizeObserverCallback) => void, -|}; +export type ResizeObserverCallback = (rect: DOMRectReadOnly) => unknown; +export type ResizeObserverWrapper = { + subscribe: (elt: HTMLElement, callback: ResizeObserverCallback) => void; + unsubscribe: (elt: HTMLElement, callback: ResizeObserverCallback) => void; +}; -function createResizeObserverWrapper() { +function createResizeObserverWrapper(): ResizeObserverWrapper { // This keeps the list of callbacks for each observed element. const callbacks: Map> = new Map(); // This keeps the list of changes while the tab is hidden. const dirtyChanges: Map = new Map(); - let _resizeObserver = null; + let _resizeObserver: ResizeObserver | null = null; function notifyListenersForElement(element: Element, rect: DOMRectReadOnly) { const callbacksForElement = callbacks.get(element); @@ -29,7 +28,7 @@ function createResizeObserverWrapper() { } } - function resizeObserverCallback(entries) { + function resizeObserverCallback(entries: ResizeObserverEntry[]) { for (const entry of entries) { if (document.hidden) { dirtyChanges.set(entry.target, entry.contentRect); diff --git a/src/utils/set.js b/src/utils/set.ts similarity index 81% rename from src/utils/set.js rename to src/utils/set.ts index 06ae211111..b1990a6e17 100644 --- a/src/utils/set.js +++ b/src/utils/set.ts @@ -2,16 +2,14 @@ * 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/. */ -// @flow - // Returns set1 ∩ set2, i.e. a new set which contains the elements which are // present in both set1 and set2. export function intersectSets(set1: Set, set2: Set): Set { - return new Set([...set1].filter((x) => set2.has(x))); + return new Set(Array.from(set1).filter((x) => set2.has(x))); } // Returns set1 ∖ set2, i.e. a new set which contains the elements which are // in set1 but not in set2. export function subtractSets(set1: Set, set2: Set): Set { - return new Set([...set1].filter((x) => !set2.has(x))); + return new Set(Array.from(set1).filter((x) => !set2.has(x))); } diff --git a/src/utils/sha1.js b/src/utils/sha1.ts similarity index 94% rename from src/utils/sha1.js rename to src/utils/sha1.ts index caa8536770..18a6159332 100644 --- a/src/utils/sha1.js +++ b/src/utils/sha1.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow // Copied and adapted from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest @@ -26,5 +25,6 @@ function hex(buffer: ArrayBuffer): string { export default function sha1(data: string | Uint8Array): Promise { const arrayData = typeof data === 'string' ? new TextEncoder().encode(data) : data; + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/60846 return window.crypto.subtle.digest('SHA-1', arrayData).then(hex); } diff --git a/src/utils/string.js b/src/utils/string.ts similarity index 99% rename from src/utils/string.js rename to src/utils/string.ts index d8d8772ff0..a80c76fcb4 100644 --- a/src/utils/string.js +++ b/src/utils/string.ts @@ -2,8 +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/. */ -// @flow - import escapeStringRegexp from 'escape-string-regexp'; // Initializing this RegExp outside of removeURLs because that function is in a diff --git a/src/utils/text-measurement.js b/src/utils/text-measurement.ts similarity index 96% rename from src/utils/text-measurement.js rename to src/utils/text-measurement.ts index 6a48f04ff8..71c6fde133 100644 --- a/src/utils/text-measurement.js +++ b/src/utils/text-measurement.ts @@ -2,8 +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/. */ -// @flow - /** * Measure the size of text for drawing within a 2d context. This will allow text * to be drawn in a constrained space. This class uses a variety of heuristics and @@ -76,10 +74,10 @@ class TextMeasurement { // Returns the actual width of a string composed of the n first characters // of the text variable. - const getWidth = (n) => this.getTextWidth(text.substring(0, n)); + const getWidth = (n: number) => this.getTextWidth(text.substring(0, n)); // Estimate how many characters can still be added after taking into account // the space used by the n first characters. The result can be negative. - const getRemainingCharacterCount = (n) => + const getRemainingCharacterCount = (n: number) => Math.round((availableWidth - getWidth(n)) / this._averageCharWidth); // Approximate the number of characters to truncate to, diff --git a/src/utils/uintarray-encoding.js b/src/utils/uintarray-encoding.ts similarity index 96% rename from src/utils/uintarray-encoding.js rename to src/utils/uintarray-encoding.ts index 93ed7a9d8d..96e604c389 100644 --- a/src/utils/uintarray-encoding.js +++ b/src/utils/uintarray-encoding.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow /** * Space-efficient url component compatible encoding for arrays of 32bit @@ -78,19 +77,19 @@ export function encodeUintArrayForUrlComponent(numbers: number[]): string { } export function decodeUintArrayFromUrlComponent(s: string): number[] { - const array = []; + const array: number[] = []; let i = 0; while (i < s.length) { const { value, hasLeadingZero, nextI } = decodeUint(s, i); if (hasLeadingZero && array.length >= 1) { - const startValue = array[array.length - 1]; - const endValue = value; + const startValue: number = array[array.length - 1]; + const endValue: number = value; if (endValue > startValue) { - for (let x = startValue + 1; x < endValue; x++) { + for (let x: number = startValue + 1; x < endValue; x++) { array.push(x); } } else { - for (let x = startValue - 1; x > endValue; x--) { + for (let x: number = startValue - 1; x > endValue; x--) { array.push(x); } } @@ -306,15 +305,15 @@ function bitsFromEncodingDigit(x: string): number { function decodeUint( s: string, start: number -): {| +): { // The decoded number. - value: number, + value: number; // Whether the encoding of this number started with a "leading zero" digit. // Our caller uses this as a "consecutive range" marker. - hasLeadingZero: boolean, + hasLeadingZero: boolean; // The end of the variable-length encoding; the next number starts at s[nextI]. - nextI: number, -|} { + nextI: number; +} { let i = start; let bits = bitsFromEncodingDigit(s[i]); let continuationBit = bits & 0b100000; diff --git a/src/utils/untar.js b/src/utils/untar.ts similarity index 86% rename from src/utils/untar.js rename to src/utils/untar.ts index 14593ea360..1578e472f6 100644 --- a/src/utils/untar.js +++ b/src/utils/untar.ts @@ -2,8 +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/. */ -// @flow - // This code was originally from https://github.com/InvokIT/js-untar/blob/master/src/untar-worker.js (MIT). // It was modernized, the Worker requirement was removed, and some Flow types were added. // Tar is an uncompressed format. If you have the tar bytes in a buffer, you can @@ -19,12 +17,12 @@ class ByteReader { _position = 0; _asciiDecoder = new TextDecoder('ascii', { fatal: true }); - constructor(arrayBuffer) { + constructor(arrayBuffer: ArrayBufferLike) { this._uint8Array = new Uint8Array(arrayBuffer); this._bufferView = new DataView(arrayBuffer); } - readString(byteCount) { + readString(byteCount: number): string { const bytes = this.readBuffer(byteCount); const nulBytePos = bytes.indexOf(0); return nulBytePos === -1 @@ -32,7 +30,7 @@ class ByteReader { : this._asciiDecoder.decode(bytes.subarray(0, nulBytePos)); } - readBuffer(byteCount) { + readBuffer(byteCount: number): Uint8Array { const buf = this._uint8Array.subarray( this.position(), this.position() + byteCount @@ -41,33 +39,33 @@ class ByteReader { return buf; } - seekBy(byteCount) { + seekBy(byteCount: number): void { this._position += byteCount; } - seekTo(newpos) { + seekTo(newpos: number): void { this._position = newpos; } - peekUint32() { + peekUint32(): number { return this._bufferView.getUint32(this.position(), true); } - position() { + position(): number { return this._position; } - size() { + size(): number { return this._bufferView.byteLength; } } -type FieldEntry = {| - name: string, - value: string | number | null, -|}; +type FieldEntry = { + name: string; + value: string | number | null; +}; -function _parseIntStrict(s, base): number { +function _parseIntStrict(s: string, base: number): number { const val = parseInt(s, base); if (isNaN(val)) { throw new Error('Parsing number failed'); @@ -75,7 +73,7 @@ function _parseIntStrict(s, base): number { return val; } -function _parseOptionalInt(s, base): number | null { +function _parseOptionalInt(s: string, base: number): number | null { if (s === '') { return null; } @@ -91,7 +89,7 @@ function _parseOptionalInt(s, base): number | null { class PaxHeader { _fields: FieldEntry[]; - static parse(buffer) { + static parse(buffer: Uint8Array): PaxHeader { // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_03 // An extended header shall consist of one or more records, each constructed as follows: // "%d %s=%s\n", , , @@ -124,11 +122,11 @@ class PaxHeader { } const fieldName = fieldMatch[1]; - let fieldValue = fieldMatch[2]; + let fieldValue: string | number | null = fieldMatch[2]; - if (fieldValue.length === 0) { + if (typeof fieldValue === 'string' && fieldValue.length === 0) { fieldValue = null; - } else if (/^\d+$/.test(fieldValue)) { + } else if (typeof fieldValue === 'string' && /^\d+$/.test(fieldValue)) { // If it's an integer field, parse it as int fieldValue = _parseIntStrict(fieldValue, 10); } @@ -151,7 +149,7 @@ class PaxHeader { this._fields = fields; } - applyHeader(file) { + applyHeader(file: any): void { // Apply fields to the file // If a field is of value null, it should be deleted from the file // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_03 @@ -175,41 +173,41 @@ class PaxHeader { if (fieldValue === null) { delete file[fieldName]; } else { - file[fieldName] = (fieldValue: any); + file[fieldName] = fieldValue; } } } } -export type TarFileEntry = {| - name: string, - type: string, - size: number, - buffer: Uint8Array | null, - mode: string, - uid: number | null, - gid: number | null, - mtime: number | null, - checksum: number | null, - linkname: string, - ustarFormat: string, - version: string | null, - uname: string | null, - gname: string | null, - devmajor: number | null, - devminor: number | null, - namePrefix: string | null, -|}; +export type TarFileEntry = { + name: string; + type: string; + size: number; + buffer: Uint8Array | null; + mode: string; + uid: number | null; + gid: number | null; + mtime: number | null; + checksum: number | null; + linkname: string; + ustarFormat: string; + version: string | null; + uname: string | null; + gname: string | null; + devmajor: number | null; + devminor: number | null; + namePrefix: string | null; +}; export class UntarFileStream { _reader: ByteReader; _globalPaxHeader: PaxHeader | null = null; - constructor(arrayBuffer: ArrayBuffer) { + constructor(arrayBuffer: ArrayBufferLike) { this._reader = new ByteReader(arrayBuffer); } - hasNext() { + hasNext(): boolean { // A tar file ends with 4 zero bytes return ( this._reader.position() + 4 < this._reader.size() && diff --git a/src/utils/url.js b/src/utils/url.ts similarity index 91% rename from src/utils/url.js rename to src/utils/url.ts index 61a01d8f2d..a8d19dc5ac 100644 --- a/src/utils/url.js +++ b/src/utils/url.ts @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -export const localhostHostnames: $ReadOnlyArray = [ +export const localhostHostnames: readonly string[] = [ 'localhost', '127.0.0.1', '::1', diff --git a/src/utils/worker-factory.js b/src/utils/worker-factory.ts similarity index 91% rename from src/utils/worker-factory.js rename to src/utils/worker-factory.ts index 97a2e8868f..32038507a6 100644 --- a/src/utils/worker-factory.js +++ b/src/utils/worker-factory.ts @@ -1,5 +1,3 @@ -// @flow - export default class { constructor(file: string) { return new window.Worker(`/${file}.js`); From 2c8992581ac9b5091437d987a67040256956df43 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:00:54 -0400 Subject: [PATCH 046/124] Convert more files under src/types. --- .../{gecko-profile.js => gecko-profile.ts} | 516 ++++++------ src/types/{markers.js => markers.ts} | 785 +++++++++--------- src/types/{profile.js => profile.ts} | 620 +++++++------- 3 files changed, 953 insertions(+), 968 deletions(-) rename src/types/{gecko-profile.js => gecko-profile.ts} (69%) rename src/types/{markers.js => markers.ts} (67%) rename src/types/{profile.js => profile.ts} (79%) diff --git a/src/types/gecko-profile.js b/src/types/gecko-profile.ts similarity index 69% rename from src/types/gecko-profile.js rename to src/types/gecko-profile.ts index eb5bdb03a7..98fbd8a845 100644 --- a/src/types/gecko-profile.js +++ b/src/types/gecko-profile.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import type { IndexIntoStringTable, @@ -51,18 +50,18 @@ export type GeckoMarkerTuple = [ MarkerPayload_Gecko | null, ]; -type GeckoMarkerSchema = { - name: 0, - startTime: 1, - endTime: 2, - phase: 3, - category: 4, - data: 5, +export type GeckoMarkerSchema = { + name: 0; + startTime: 1; + endTime: 2; + phase: 3; + category: 4; + data: 5; }; export type GeckoMarkers = { - schema: GeckoMarkerSchema, - data: Array, + schema: GeckoMarkerSchema; + data: Array; }; export type ExternalMarkerTuple = [ @@ -75,17 +74,17 @@ export type ExternalMarkerTuple = [ ]; export type ExternalMarkers = { - schema: GeckoMarkerSchema, - data: Array, + schema: GeckoMarkerSchema; + data: Array; }; export type ExternalMarkersData = - | {| - markerSchema: GeckoMetaMarkerSchema[], - categories: CategoryList, - markers: ExternalMarkers, - |} - | {||}; + | { + markerSchema: GeckoMetaMarkerSchema[]; + categories: CategoryList; + markers: ExternalMarkers; + } + | {}; /** * These structs aren't very DRY, but it is a simple and complete approach. @@ -93,40 +92,40 @@ export type ExternalMarkersData = * processed format. See `docs-developer/gecko-profile-format.md` for more * information. */ -export type GeckoMarkerStruct = {| - name: IndexIntoStringTable[], - startTime: Milliseconds[], - endTime: Milliseconds[], - phase: MarkerPhase[], - data: Array, - category: IndexIntoCategoryList[], - length: number, -|}; - -export type GeckoMarkerStack = {| - name: 'SyncProfile', - registerTime: null, - unregisterTime: null, - processType: string, - tid: number, - pid: number, - markers: GeckoMarkers, - samples: GeckoSamples, -|}; - -export type GeckoSamples = {| +export type GeckoMarkerStruct = { + name: IndexIntoStringTable[]; + startTime: Milliseconds[]; + endTime: Milliseconds[]; + phase: MarkerPhase[]; + data: Array; + category: IndexIntoCategoryList[]; + length: number; +}; + +export type GeckoMarkerStack = { + name: 'SyncProfile'; + registerTime: null; + unregisterTime: null; + processType: string; + tid: number; + pid: number; + markers: GeckoMarkers; + samples: GeckoSamples; +}; + +export type GeckoSamples = { schema: - | {| - stack: 0, - time: 1, - responsiveness: 2, - |} - | {| - stack: 0, - time: 1, - eventDelay: 2, - threadCPUDelta?: 3, - |}, + | { + stack: 0; + time: 1; + responsiveness: 2; + } + | { + stack: 0; + time: 1; + eventDelay: 2; + threadCPUDelta?: 3; + }; data: Array< | [ null | IndexIntoGeckoStackTable, @@ -144,53 +143,53 @@ export type GeckoSamples = {| // CPU usage value of the current thread. // It's present only when the CPU Utilization feature is enabled in Firefox. number | null, - ], - >, -|}; + ] + >; +}; // Older profiles have samples with `responsiveness` values. -export type GeckoSampleStructWithResponsiveness = {| - stack: Array, - time: Milliseconds[], - responsiveness: Array, +export type GeckoSampleStructWithResponsiveness = { + stack: Array; + time: Milliseconds[]; + responsiveness: Array; // CPU usage value of the current thread. Its values are null only if the back-end // fails to get the CPU usage from operating system. // It's landed in Firefox 86, and it is optional because older profile // versions may not have it or that feature could be disabled. No upgrader was // written for this change because it's a completely new data source. - threadCPUDelta?: Array, - length: number, -|}; + threadCPUDelta?: Array; + length: number; +}; // Newer profiles have the improved version of `responsiveness`, `eventDelay`. -export type GeckoSampleStructWithEventDelay = {| - stack: Array, - time: Milliseconds[], - eventDelay: Array, +export type GeckoSampleStructWithEventDelay = { + stack: Array; + time: Milliseconds[]; + eventDelay: Array; // CPU usage value of the current thread. Its values are null only if the back-end // fails to get the CPU usage from operating system. // It's landed in Firefox 86, and it is optional because older profile // versions may not have it or that feature could be disabled. No upgrader was // written for this change because it's a completely new data source. - threadCPUDelta?: Array, - length: number, -|}; + threadCPUDelta?: Array; + length: number; +}; export type GeckoSampleStruct = | GeckoSampleStructWithResponsiveness | GeckoSampleStructWithEventDelay; -export type GeckoFrameTable = {| - schema: {| - location: 0, - relevantForJS: 1, - innerWindowID: 2, - implementation: 3, - line: 4, - column: 5, - category: 6, - subcategory: 7, - |}, +export type GeckoFrameTable = { + schema: { + location: 0; + relevantForJS: 1; + innerWindowID: 2; + implementation: 3; + line: 4; + column: 5; + category: 6; + subcategory: 7; + }; data: Array< [ // index into stringTable, points to strings like: @@ -220,171 +219,171 @@ export type GeckoFrameTable = {| null | number, // index into profile.meta.categories[category].subcategories. Always non-null if category is non-null. null | number, - ], - >, -|}; + ] + >; +}; export type IndexIntoGeckoThreadStringTable = number; -export type GeckoFrameStruct = {| - location: IndexIntoGeckoThreadStringTable[], - relevantForJS: Array, - implementation: Array, - line: Array, - column: Array, - category: Array, - subcategory: Array, - innerWindowID: Array, - length: number, -|}; - -export type GeckoStackTable = {| - schema: {| - prefix: 0, - frame: 1, - |}, - data: Array<[IndexIntoGeckoStackTable | null, IndexIntoGeckoFrameTable]>, -|}; - -export type GeckoStackStruct = {| - frame: IndexIntoGeckoFrameTable[], - prefix: Array, - length: number, -|}; - -export type GeckoThread = {| - name: string, +export type GeckoFrameStruct = { + location: IndexIntoGeckoThreadStringTable[]; + relevantForJS: Array; + implementation: Array; + line: Array; + column: Array; + category: Array; + subcategory: Array; + innerWindowID: Array; + length: number; +}; + +export type GeckoStackTable = { + schema: { + prefix: 0; + frame: 1; + }; + data: Array<[IndexIntoGeckoStackTable | null, IndexIntoGeckoFrameTable]>; +}; + +export type GeckoStackStruct = { + frame: IndexIntoGeckoFrameTable[]; + prefix: Array; + length: number; +}; + +export type GeckoThread = { + name: string; // The eTLD+1 of the isolated content process if provided by the back-end. // It will be undefined if: // - Fission is not enabled. // - It's not an isolated content process. // - It's a profile from an older Firefox which doesn't include this field (introduced in Firefox 80). - 'eTLD+1'?: string, + 'eTLD+1'?: string; // If present and true, this thread was launched for a private browsing // session only. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // doesn't have any non-origin attribute (this happens in non-Fission // especially but also in Fission for normal threads). - isPrivateBrowsing?: boolean, + isPrivateBrowsing?: boolean; // If present, the number represents the container this thread was loaded in. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // doesn't have any non-origin attribute (this happens in non-Fission // especially but also in Fission for normal threads). - userContextId?: number, - registerTime: number, - processType: string, - processName?: string, - unregisterTime: number | null, - tid: number, - pid: number, - markers: GeckoMarkers, - samples: GeckoSamples, - frameTable: GeckoFrameTable, - stackTable: GeckoStackTable, - stringTable: string[], - jsTracerEvents?: JsTracerTable, -|}; - -export type GeckoExtensionMeta = {| - schema: {| - id: 0, - name: 1, - baseURL: 2, - |}, - data: Array<[string, string, string]>, -|}; - -export type GeckoCounter = {| - name: string, - category: string, - description: string, - samples: {| - schema: {| - time: 0, - count: 1, - number: 2, - |}, - data: $ReadOnlyArray<[number, number, number]>, - |}, -|}; - -export type GeckoCounterSamplesStruct = {| - time: Milliseconds[], - count: number[], - number?: number[], - length: number, -|}; - -export type GeckoProfilerOverhead = {| - samples: {| - schema: {| - time: 0, - locking: 1, - expiredMarkerCleaning: 2, - counters: 3, - threads: 4, - |}, + userContextId?: number; + registerTime: number; + processType: string; + processName?: string; + unregisterTime: number | null; + tid: number; + pid: number; + markers: GeckoMarkers; + samples: GeckoSamples; + frameTable: GeckoFrameTable; + stackTable: GeckoStackTable; + stringTable: string[]; + jsTracerEvents?: JsTracerTable; +}; + +export type GeckoExtensionMeta = { + schema: { + id: 0; + name: 1; + baseURL: 2; + }; + data: Array<[string, string, string]>; +}; + +export type GeckoCounter = { + name: string; + category: string; + description: string; + samples: { + schema: { + time: 0; + count: 1; + number: 2; + }; + data: Array<[number, number, number]>; + }; +}; + +export type GeckoCounterSamplesStruct = { + time: Milliseconds[]; + count: number[]; + number?: number[]; + length: number; +}; + +export type GeckoProfilerOverhead = { + samples: { + schema: { + time: 0; + locking: 1; + expiredMarkerCleaning: 2; + counters: 3; + threads: 4; + }; data: Array< - [Nanoseconds, Nanoseconds, Nanoseconds, Nanoseconds, Nanoseconds], - >, - |}, + [Nanoseconds, Nanoseconds, Nanoseconds, Nanoseconds, Nanoseconds] + >; + }; // There is no statistics object if there is no sample. - statistics?: ProfilerOverheadStats, -|}; + statistics?: ProfilerOverheadStats; +}; -export type GeckoDynamicFieldSchemaData = {| +export type GeckoDynamicFieldSchemaData = { // The property key of the marker data property that carries the field value. - key: string, + key: string; // An optional user-facing label. // If no label is provided, the key is displayed instead. - label?: string, + label?: string; // The format / type of this field. This affects how the field's value is // displayed and determines which types of values are accepted for this field. - format: MarkerFormatType, + format: MarkerFormatType; // If present and set to true, the marker search string will be matched // against the values of this field when determining which markers match the // search. - searchable?: boolean, + searchable?: boolean; // If present and set to true, this field will not be shown in the list // of fields in the tooltip or in the sidebar. Such fields can still be // used inside labels and they can be searchable. - hidden?: boolean, -|}; + hidden?: boolean; +}; -export type GeckoStaticFieldSchemaData = {| +export type GeckoStaticFieldSchemaData = { // This type is a static bit of text that will be displayed - label: string, - value: string, -|}; + label: string; + value: string; +}; -export type GeckoMetaMarkerSchema = {| +export type GeckoMetaMarkerSchema = { // The unique identifier for this marker. - name: string, // e.g. "CC" + name: string; // e.g. "CC" // The label of how this marker should be displayed in the UI. // If none is provided, then the name is used. - tooltipLabel?: string, // e.g. "Cycle Collect" + tooltipLabel?: string; // e.g. "Cycle Collect" // This is how the marker shows up in the Marker Table description. // If none is provided, then the name is used. - tableLabel?: string, // e.g. "{marker.data.eventType} – DOMEvent" + tableLabel?: string; // e.g. "{marker.data.eventType} – DOMEvent" // This is how the marker shows up in the Marker Chart, where it is drawn // on the screen as a bar. // If none is provided, then the name is used. - chartLabel?: string, + chartLabel?: string; // The locations to display - display: MarkerDisplayLocation[], + display: MarkerDisplayLocation[]; - data: Array, + data: Array; // if present, give the marker its own local track - graphs?: Array, + graphs?: Array; // If set to true, markers of this type are assumed to be well-nested with all // other stack-based markers on the same thread. Stack-based markers may @@ -395,47 +394,46 @@ export type GeckoMetaMarkerSchema = {| // well-nested means that, for all marker-defined timestamp intervals A and B, // A either fully encompasses B or is fully encompassed by B - there is no // partial overlap. - isStackBased?: boolean, -|}; + isStackBased?: boolean; +}; /* This meta object is used in subprocesses profiles. * Using https://searchfox.org/mozilla-central/rev/7556a400affa9eb99e522d2d17c40689fa23a729/tools/profiler/core/platform.cpp#1829 * as source of truth. (Please update the link whenever there's a new property). * */ -export type GeckoProfileShortMeta = {| - version: number, +export type GeckoProfileShortMeta = { + version: number; // When the main process started. Timestamp expressed in milliseconds since // midnight January 1, 1970 GMT. - startTime: Milliseconds, - startTimeAsClockMonotonicNanosecondsSinceBoot?: number, - startTimeAsMachAbsoluteTimeNanoseconds?: number, - startTimeAsQueryPerformanceCounterValue?: number, - shutdownTime: Milliseconds | null, - categories: CategoryList, - markerSchema: GeckoMetaMarkerSchema[], -|}; + startTime: Milliseconds; + startTimeAsClockMonotonicNanosecondsSinceBoot?: number; + startTimeAsMachAbsoluteTimeNanoseconds?: number; + startTimeAsQueryPerformanceCounterValue?: number; + shutdownTime: Milliseconds | null; + categories: CategoryList; + markerSchema: GeckoMetaMarkerSchema[]; +}; /* This meta object is used on the top level profile object. * Using https://searchfox.org/mozilla-central/rev/7556a400affa9eb99e522d2d17c40689fa23a729/tools/profiler/core/platform.cpp#1829 * as source of truth. (Please update the link whenever there's a new property). * */ -export type GeckoProfileFullMeta = {| - ...GeckoProfileShortMeta, +export type GeckoProfileFullMeta = GeckoProfileShortMeta & { // When the recording started (in milliseconds after startTime). - profilingStartTime?: Milliseconds, + profilingStartTime?: Milliseconds; // When the recording ended (in milliseconds after startTime). - profilingEndTime?: Milliseconds, - interval: Milliseconds, - stackwalk: 0 | 1, + profilingEndTime?: Milliseconds; + interval: Milliseconds; + stackwalk: 0 | 1; // This value represents a boolean, but for some reason is written out as an int // value as the previous field. // It's 0 for opt builds, and 1 for debug builds. // This property was added to Firefox Profiler a long time after it was added to // Firefox, that's why we don't need to make it optional for gecko profiles. - debug: 0 | 1, - gcpoison: 0 | 1, - asyncstack: 0 | 1, - processType: number, + debug: 0 | 1; + gcpoison: 0 | 1; + asyncstack: 0 | 1; + processType: number; // The Update channel for this build of the application. // This property is landed in Firefox 67, and is optional because older // Firefox versions may not have them. No upgrader was necessary. @@ -447,54 +445,54 @@ export type GeckoProfileFullMeta = {| | 'beta' | 'release' | 'esr' // Extended Support Release channel - | string, + | string; // -- platform information -- This can be absent in some very rare situations. - platform?: string, - oscpu?: string, - misc?: string, + platform?: string; + oscpu?: string; + misc?: string; // -- Runtime -- This can be absent in some very rare situations. - abi?: string, - toolkit?: string, - product?: string, + abi?: string; + toolkit?: string; + product?: string; // -- appInfo -- This can be absent in some very rare situations. // The appBuildID, sourceURL, physicalCPUs and logicalCPUs properties landed // in Firefox 62, and are only optional because older processed profile // versions may not have them. No upgrader was written for this change. - appBuildID?: string, - sourceURL?: string, + appBuildID?: string; + sourceURL?: string; // -- system info -- This can be absent in some very rare situations. - physicalCPUs?: number, - logicalCPUs?: number, - CPUName?: string, + physicalCPUs?: number; + logicalCPUs?: number; + CPUName?: string; // -- extensions -- // The extensions property landed in Firefox 60, and is only optional because // older profile versions may not have it. No upgrader was written for this change. - extensions?: GeckoExtensionMeta, + extensions?: GeckoExtensionMeta; // -- extra properties added by the frontend -- // This boolean indicates whether this gecko profile includes already // symbolicated frames. This will be missing for profiles coming from Gecko // (which indicates that they'll need to be symbolicated) but may be specified // for profiles imported from other formats (eg: linux perf). - presymbolicated?: boolean, + presymbolicated?: boolean; // Visual metrics contains additional performance metrics such as Speed Index, // Perceptual Speed Index, and ContentfulSpeedIndex. This is optional because only // profiles generated by browsertime will have this property. Source code for // browsertime can be found at https://github.com/sitespeedio/browsertime. - visualMetrics?: VisualMetrics, + visualMetrics?: VisualMetrics; // Optional because older Firefox versions may not have the data. - configuration?: ProfilerConfiguration, + configuration?: ProfilerConfiguration; // Units of samples table values. // The sampleUnits property landed in Firefox 86, and is only optional because // older profile versions may not have it. No upgrader was written for this change. - sampleUnits?: SampleUnits, + sampleUnits?: SampleUnits; // Information of the device that profile is captured from. // Currently it's only present for Android devices and it includes brand and // model names of that device. // It's optional because profiles from non-Android devices and from older // Firefox versions may not have it. // This property landed in Firefox 88. - device?: string, -|}; + device?: string; +}; /** * Information about libraries, for instance the Firefox executables, and its memory @@ -511,26 +509,26 @@ export type GeckoProfileFullMeta = {| * to Lib objects which don't have mapping address information and which are * shared between all processes in the profile. */ -export type LibMapping = {| +export type LibMapping = { // The range in the address space of the profiled process that the mappings for // this shared library occupied. - start: MemoryOffset, - end: MemoryOffset, + start: MemoryOffset; + end: MemoryOffset; // The offset relative to the library's base address where the first mapping starts. // libBaseAddress + lib.offset = lib.start // When instruction addresses are given as library-relative offsets, they are // relative to the library's baseAddress. - offset: Bytes, - - arch: string, // e.g. "x86_64" - name: string, // e.g. "firefox" - path: string, // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" - debugName: string, // e.g. "firefox", or "firefox.pdb" on Windows - debugPath: string, // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" - breakpadId: string, // e.g. "E54D3AF274383256B9F6144F83F3F7510" - codeId?: string, // e.g. "6132B96B70fd000" -|}; + offset: Bytes; + + arch: string; // e.g. "x86_64" + name: string; // e.g. "firefox" + path: string; // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" + debugName: string; // e.g. "firefox", or "firefox.pdb" on Windows + debugPath: string; // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" + breakpadId: string; // e.g. "E54D3AF274383256B9F6144F83F3F7510" + codeId?: string; // e.g. "6132B96B70fd000" +}; /** * Log object that holds the profiling-related logging information for a @@ -538,7 +536,7 @@ export type LibMapping = {| * This type might also change in the future without warning. */ export type ProcessProfilingLog = { - [log: string]: mixed, + [log: string]: unknown; }; /** @@ -547,25 +545,25 @@ export type ProcessProfilingLog = { * This type might also change in the future without warning. */ export type ProfilingLog = { - [pid: number]: ProcessProfilingLog, + [pid: number]: ProcessProfilingLog; }; -export type GeckoProfileWithMeta = {| - counters?: GeckoCounter[], +export type GeckoProfileWithMeta = { + counters?: GeckoCounter[]; // Optional because older Firefox versions may not have that data and // no upgrader was necessary. - profilerOverhead?: GeckoProfilerOverhead, - meta: Meta, - libs: LibMapping[], - pages?: PageList, - threads: GeckoThread[], - pausedRanges: PausedRange[], - processes: GeckoSubprocessProfile[], - jsTracerDictionary?: string[], + profilerOverhead?: GeckoProfilerOverhead; + meta: Meta; + libs: LibMapping[]; + pages?: PageList; + threads: GeckoThread[]; + pausedRanges: PausedRange[]; + processes: GeckoSubprocessProfile[]; + jsTracerDictionary?: string[]; // Logs are optional because older Firefox versions may not have that data. - profilingLog?: ProfilingLog, - profileGatheringLog?: ProfilingLog, -|}; + profilingLog?: ProfilingLog; + profileGatheringLog?: ProfilingLog; +}; export type GeckoSubprocessProfile = GeckoProfileWithMeta; diff --git a/src/types/markers.js b/src/types/markers.ts similarity index 67% rename from src/types/markers.js rename to src/types/markers.ts index a753d68007..9da1460582 100644 --- a/src/types/markers.js +++ b/src/types/markers.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import type { Milliseconds, Microseconds, Seconds, Bytes } from './units'; import type { GeckoMarkerStack } from './gecko-profile'; @@ -17,7 +16,7 @@ import type { NetworkStatus, NetworkRedirectType, } from './network'; -import type { ObjectMap } from './utils'; +import type { MixedObject } from './utils'; // Provide different formatting options for strings. @@ -83,14 +82,14 @@ export type MarkerFormatType = | 'pid' | 'tid' | 'list' - | {| type: 'table', columns: TableColumnFormat[] |}; + | { type: 'table'; columns: TableColumnFormat[] }; -type TableColumnFormat = {| +type TableColumnFormat = { // type for formatting, default is string - type?: MarkerFormatType, + type?: MarkerFormatType; // header column label - label?: string, -|}; + label?: string; +}; // A list of all the valid locations to surface this marker. // We can be free to add more UI areas. @@ -117,60 +116,60 @@ export type MarkerDisplayLocation = | 'stack-chart'; export type MarkerGraphType = 'bar' | 'line' | 'line-filled'; -export type MarkerGraph = {| - key: string, - type: MarkerGraphType, - color?: GraphColor, -|}; +export type MarkerGraph = { + key: string; + type: MarkerGraphType; + color?: GraphColor; +}; -export type MarkerSchemaField = {| +export type MarkerSchemaField = { // The property key of the marker data property that carries the field value. - key: string, + key: string; // An optional user-facing label. // If no label is provided, the key is displayed instead. - label?: string, + label?: string; // The format / type of this field. This affects how the field's value is // displayed and determines which types of values are accepted for this field. - format: MarkerFormatType, + format: MarkerFormatType; // If present and set to true, this field will not be shown in the list // of fields in the tooltip or in the sidebar. Such fields can still be // used inside labels and their values are matched when searching. - hidden?: boolean, -|}; + hidden?: boolean; +}; -export type MarkerSchema = {| +export type MarkerSchema = { // The unique identifier for this marker. - name: string, // e.g. "CC" + name: string; // e.g. "CC" // The label of how this marker should be displayed in the UI. // If none is provided, then the name is used. - tooltipLabel?: string, // e.g. "Cycle Collect" + tooltipLabel?: string; // e.g. "Cycle Collect" // This is how the marker shows up in the Marker Table description. // If none is provided, then the name is used. - tableLabel?: string, // e.g. "{marker.data.eventType} – DOMEvent" + tableLabel?: string; // e.g. "{marker.data.eventType} – DOMEvent" // This is how the marker shows up in the Marker Chart, where it is drawn // on the screen as a bar. // If none is provided, then the name is used. - chartLabel?: string, + chartLabel?: string; // The locations to display - display: MarkerDisplayLocation[], + display: MarkerDisplayLocation[]; // The fields that can be present on markers of this type. // Not all listed fields have to be present on every marker (they're all optional). - fields: MarkerSchemaField[], + fields: MarkerSchemaField[]; // An optional description for markers of this type. // Will be displayed to the user. - description?: string, + description?: string; // if present, give the marker its own local track - graphs?: Array, + graphs?: Array; // If set to true, markers of this type are assumed to be well-nested with all // other stack-based markers on the same thread. Stack-based markers may @@ -181,10 +180,10 @@ export type MarkerSchema = {| // well-nested means that, for all marker-defined timestamp intervals A and B, // A either fully encompasses B or is fully encompassed by B - there is no // partial overlap. - isStackBased?: boolean, -|}; + isStackBased?: boolean; +}; -export type MarkerSchemaByName = ObjectMap; +export type MarkerSchemaByName = { [key: string]: MarkerSchema }; /** * Markers can include a stack. These are converted to a cause backtrace, which includes @@ -192,62 +191,56 @@ export type MarkerSchemaByName = ObjectMap; * the marker, or it can be synchronous, and the time is contained within the marker's * start and end time. */ -export type CauseBacktrace = {| +export type CauseBacktrace = { // `tid` is optional because older processed profiles may not have it. // No upgrader was written for this change. - tid?: Tid, - time?: Milliseconds, - stack: IndexIntoStackTable, -|}; + tid?: Tid; + time?: Milliseconds; + stack: IndexIntoStackTable; +}; /** * This type holds data that should be synchronized across the various phases * associated with an IPC message. */ -export type IPCSharedData = {| +export type IPCSharedData = { // Each of these fields comes from a specific marker corresponding to each // phase of an IPC message; since we can't guarantee that any particular // marker was recorded, all of the fields are optional. - startTime?: Milliseconds, - sendStartTime?: Milliseconds, - sendEndTime?: Milliseconds, - recvEndTime?: Milliseconds, - endTime?: Milliseconds, - sendTid?: number, - recvTid?: number, - sendThreadName?: string, - recvThreadName?: string, -|}; + startTime?: Milliseconds; + sendStartTime?: Milliseconds; + sendEndTime?: Milliseconds; + recvEndTime?: Milliseconds; + endTime?: Milliseconds; + sendTid?: number; + recvTid?: number; + sendThreadName?: string | void; + recvThreadName?: string | void; +}; /** * This utility type removes the "cause" property from a payload, and replaces it with * a stack. This effectively converts it from a processed payload to a Gecko payload. */ -export type $ReplaceCauseWithStack< - // False positive, generic type bounds are alright: - // eslint-disable-next-line flowtype/no-weak-types - T: Object, -> = {| - ...$Diff< - T, - // Remove the cause property. - {| cause: any |}, - >, +export type ReplaceCauseWithStack> = Omit< + T, + 'cause' +> & { // Add on the stack property: - stack?: GeckoMarkerStack, -|}; + stack?: GeckoMarkerStack; +}; /** * Measurement for how long draw calls take for the compositor. */ -export type GPUMarkerPayload = {| - type: 'gpu_timer_query', - cpustart: Milliseconds, - cpuend: Milliseconds, - gpustart: Milliseconds, // Always 0. - gpuend: Milliseconds, // The time the GPU took to execute the command. -|}; +export type GPUMarkerPayload = { + type: 'gpu_timer_query'; + cpustart: Milliseconds; + cpuend: Milliseconds; + gpustart: Milliseconds; // Always 0. + gpuend: Milliseconds; // The time the GPU took to execute the command. +}; /** * These markers don't have a start and end time. They work in pairs, one @@ -255,196 +248,192 @@ export type GPUMarkerPayload = {| * marker. */ -export type PaintProfilerMarkerTracing = {| - type: 'tracing', - category: 'Paint', - cause?: CauseBacktrace, -|}; - -export type ArbitraryEventTracing = {| - +type: 'tracing', - +category: string, -|}; - -export type CcMarkerTracing = {| - type: 'tracing', - category: 'CC', - first?: string, - desc?: string, - second?: string, -|}; +export type PaintProfilerMarkerTracing = { + type: 'tracing'; + category: 'Paint'; + cause?: CauseBacktrace; +}; + +export type ArbitraryEventTracing = { + readonly type: 'tracing'; + readonly category: string; +}; + +export type CcMarkerTracing = { + type: 'tracing'; + category: 'CC'; + first?: string; + desc?: string; + second?: string; +}; export type PhaseTimes = { [phase: string]: Unit }; -type GCSliceData_Shared = {| +type GCSliceData_Shared = { // Slice number within the GCMajor collection. - slice: number, + slice: number; - pause: Milliseconds, + pause: Milliseconds; // The reason for this slice. - reason: string, + reason: string; // The GC state at the start and end of this slice. - initial_state: string, - final_state: string, + initial_state: string; + final_state: string; // The incremental GC budget for this slice (see pause above). - budget: string, + budget: string; // The number of the GCMajor that this slice belongs to. - major_gc_number: number, + major_gc_number: number; // These are present if the collection was triggered by exceeding some // threshold. The reason field says how they should be interpreted. - trigger_amount?: number, - trigger_threshold?: number, + trigger_amount?: number; + trigger_threshold?: number; // The number of page faults that occured during the slice. If missing // there were 0 page faults. - page_faults?: number, - - start_timestamp: Seconds, -|}; -export type GCSliceData_Gecko = {| - ...GCSliceData_Shared, - times: PhaseTimes, -|}; -export type GCSliceData = {| - ...GCSliceData_Shared, - phase_times: PhaseTimes, -|}; - -export type GCMajorAborted = {| - status: 'aborted', -|}; - -type GCMajorCompleted_Shared = {| - status: 'completed', - max_pause: Milliseconds, + page_faults?: number; + + start_timestamp: Seconds; +}; +export type GCSliceData_Gecko = GCSliceData_Shared & { + times: PhaseTimes; +}; +export type GCSliceData = GCSliceData_Shared & { + phase_times: PhaseTimes; +}; + +export type GCMajorAborted = { + status: 'aborted'; +}; + +type GCMajorCompleted_Shared = { + status: 'completed'; + max_pause: Milliseconds; // The sum of all the slice durations - total_time: Milliseconds, + total_time: Milliseconds; // The reason from the first slice. see JS::gcreason::Reason - reason: string, + reason: string; // Counts. - zones_collected: number, - total_zones: number, - total_compartments: number, - minor_gcs: number, + zones_collected: number; + total_zones: number; + total_compartments: number; + minor_gcs: number; // Present when non-zero. - store_buffer_overflows?: number, - slices: number, + store_buffer_overflows?: number; + slices: number; // Timing for the SCC sweep phase. - scc_sweep_total: Milliseconds, - scc_sweep_max_pause: Milliseconds, + scc_sweep_total: Milliseconds; + scc_sweep_max_pause: Milliseconds; // The reason why this GC ran non-incrementally. Older profiles could have the string // 'None' as a reason. - nonincremental_reason?: 'None' | string, + nonincremental_reason?: 'None' | string; // The total size of GC things before and after the GC. - allocated_bytes: number, - post_heap_size?: number, + allocated_bytes: number; + post_heap_size?: number; // The total size of malloc data owned by GC things before and after the GC. // Added in Firefox v135 (Bug 1933205). - pre_malloc_heap_size?: number, - post_malloc_heap_size?: number, + pre_malloc_heap_size?: number; + post_malloc_heap_size?: number; // Only present if non-zero. - added_chunks?: number, - removed_chunks?: number, + added_chunks?: number; + removed_chunks?: number; // The number for the start of this GC event. - major_gc_number: number, - minor_gc_number: number, + major_gc_number: number; + minor_gc_number: number; // Slice number isn't in older profiles. - slice_number?: number, + slice_number?: number; // This usually isn't present with the gecko profiler, but it's the same // as all of the slice markers themselves. - slices_list?: GCSliceData[], -|}; + slices_list?: GCSliceData[]; +}; -export type GCMajorCompleted = {| - ...GCMajorCompleted_Shared, +export type GCMajorCompleted = GCMajorCompleted_Shared & { // MMU (Minimum mutator utilisation) A measure of GC's affect on // responsiveness See Statistics::computeMMU(), these percentages in the // rage of 0-100. // Percentage of time the mutator ran in a 20ms window. - mmu_20ms: number, + mmu_20ms: number; // Percentage of time the mutator ran in a 50ms window. - mmu_50ms: number, + mmu_50ms: number; // The duration of each phase. - phase_times: PhaseTimes, -|}; + phase_times: PhaseTimes; +}; -export type GCMajorCompleted_Gecko = {| - ...GCMajorCompleted_Shared, +export type GCMajorCompleted_Gecko = GCMajorCompleted_Shared & { // As above except in parts of 100. - mmu_20ms: number, - mmu_50ms: number, - totals: PhaseTimes, -|}; + mmu_20ms: number; + mmu_50ms: number; + totals: PhaseTimes; +}; -export type GCMajorMarkerPayload = {| - type: 'GCMajor', - timings: GCMajorAborted | GCMajorCompleted, -|}; +export type GCMajorMarkerPayload = { + type: 'GCMajor'; + timings: GCMajorAborted | GCMajorCompleted; +}; -export type GCMajorMarkerPayload_Gecko = {| - type: 'GCMajor', - timings: GCMajorAborted | GCMajorCompleted_Gecko, -|}; +export type GCMajorMarkerPayload_Gecko = { + type: 'GCMajor'; + timings: GCMajorAborted | GCMajorCompleted_Gecko; +}; -export type GCMinorCompletedData = {| - status: 'complete', +export type GCMinorCompletedData = { + status: 'complete'; // The reason for initiating the GC. - reason: string, + reason: string; // The size of the data moved into the tenured heap. - bytes_tenured: number, + bytes_tenured: number; // The number of cells tenured (since // https://bugzilla.mozilla.org/show_bug.cgi?id=1473213) - cells_tenured?: number, + cells_tenured?: number; // The number of strings that were tenured, not counting deduplicated // strings (since https://bugzilla.mozilla.org/show_bug.cgi?id=1507379). - strings_tenured?: number, + strings_tenured?: number; // The number of strings that were deduplicated during tenuring // (since https://bugzilla.mozilla.org/show_bug.cgi?id=1658866). - strings_deduplicated?: number, + strings_deduplicated?: number; // The allocation rate when promoting live GC things in bytes per second // (since https://bugzilla.mozilla.org/show_bug.cgi?id=1963597). - tenured_allocation_rate?: number, + tenured_allocation_rate?: number; // The numbers of cells allocated since the previous minor GC. // These were added in // https://bugzilla.mozilla.org/show_bug.cgi?id=1473213 and are only // present in Nightly builds. - cells_allocated_nursery?: number, - cells_allocated_tenured?: number, + cells_allocated_nursery?: number; + cells_allocated_tenured?: number; // The total amount of data that was allocated in the nursery. - bytes_used: number, + bytes_used: number; // The total capacity of the nursery before and after this GC. // Capacity may change as the nursery size is tuned after each collection. // cur_capacity isn't in older profiles. - cur_capacity?: number, + cur_capacity?: number; // If the nursery is resized after this collection then this field is // present giving the new size. - new_capacity?: number, + new_capacity?: number; // The nursery may be dynamically resized (since version 58) // this field is the lazy-allocated size. It is not present in older @@ -452,38 +441,38 @@ export type GCMinorCompletedData = {| // If the currently allocated size is different from the size // (cur_capacity) then this field is present and shows how much memory is // actually allocated. - lazy_capacity?: number, + lazy_capacity?: number; - chunk_alloc_us?: Microseconds, + chunk_alloc_us?: Microseconds; // Added in https://bugzilla.mozilla.org/show_bug.cgi?id=1507379 - groups_pretenured?: number, + groups_pretenured?: number; - phase_times: PhaseTimes, -|}; + phase_times: PhaseTimes; +}; -export type GCMinorDisabledData = {| - status: 'nursery disabled', -|}; -export type GCMinorEmptyData = {| - status: 'nursery empty', -|}; +export type GCMinorDisabledData = { + status: 'nursery disabled'; +}; +export type GCMinorEmptyData = { + status: 'nursery empty'; +}; -export type GCMinorMarkerPayload = {| - type: 'GCMinor', +export type GCMinorMarkerPayload = { + type: 'GCMinor'; // nursery is only present in newer profile format. - nursery?: GCMinorCompletedData | GCMinorDisabledData | GCMinorEmptyData, -|}; + nursery?: GCMinorCompletedData | GCMinorDisabledData | GCMinorEmptyData; +}; -export type GCSliceMarkerPayload = {| - type: 'GCSlice', - timings: GCSliceData, -|}; +export type GCSliceMarkerPayload = { + type: 'GCSlice'; + timings: GCSliceData; +}; -export type GCSliceMarkerPayload_Gecko = {| - type: 'GCSlice', - timings: GCSliceData_Gecko, -|}; +export type GCSliceMarkerPayload_Gecko = { + type: 'GCSlice'; + timings: GCSliceData_Gecko; +}; /** * Network http/https loads - one marker for each load that reaches the @@ -499,35 +488,35 @@ export type GCSliceMarkerPayload_Gecko = {| * be included depending on what states happen during the load. Also note * that redirects are logged as well. */ -export type NetworkPayload = {| - type: 'Network', - innerWindowID?: number, - URI: string, - RedirectURI?: string, - id: number, - pri: number, // priority of the load; always included as it can change - count?: number, // Total size of transfer, if any +export type NetworkPayload = { + type: 'Network'; + innerWindowID?: number; + URI: string; + RedirectURI?: string; + id: number; + pri: number; // priority of the load; always included as it can change + count?: number; // Total size of transfer, if any // See all possible values in tools/profiler/core/platform.cpp - status: NetworkStatus, + status: NetworkStatus; // The following property is present only when this marker is for a // redirection. Note it is present since Gecko v91 only. - redirectType?: NetworkRedirectType, + redirectType?: NetworkRedirectType; // The following property is present only when this marker is for a // redirection. Note it is present since Gecko v91 only. - isHttpToHttpsRedirect?: boolean, + isHttpToHttpsRedirect?: boolean; // When present in a redirect marker, this is the id of the next request, // started because of the redirection. Note it is present since Gecko v91 // only. - redirectId?: number, - cache?: string, - cause?: CauseBacktrace, + redirectId?: number; + cache?: string; + cause?: CauseBacktrace; // contentType is the value of the Content-Type header from the HTTP // response. An empty string means the response had no content type, // while a value of null means no HTTP response was received. If // this property is absent then it means this profiler came from an // older version of the Gecko profiler without content type support. - contentType?: string | null, + contentType?: string | null; // If present and true, this network marker originated from a request in a // private browsing session. @@ -537,21 +526,21 @@ export type NetworkPayload = {| // innerWindowID property that we also have. // It's always absent in Firefox < 98 because we couldn't capture private // browsing data back then. - isPrivateBrowsing?: boolean, - httpVersion?: NetworkHttpVersion, + isPrivateBrowsing?: boolean; + httpVersion?: NetworkHttpVersion; // Used to express class dependencies and characteristics. // Possible flags: Leader, Follower, Speculative, Background, Unblocked, // Throttleable, UrgentStart, DontThrottle, Tail, TailAllowed, and // TailForbidden. Multiple flags can be set, separated by '|', // or we use 'Unset' if no flag is set. - classOfService?: string, + classOfService?: string; // Used to show the request status (nsresult nsIRequest::status) - requestStatus?: string, + requestStatus?: string; // Used to show the HTTP response status code - responseStatus?: number, + responseStatus?: number; // NOTE: the following comments are valid for the merged markers. For the raw // markers, startTime and endTime have different meanings. Please look @@ -559,17 +548,17 @@ export type NetworkPayload = {| // startTime is when the channel opens. This happens on the process' main // thread. - startTime: Milliseconds, + startTime: Milliseconds; // endTime is the time when the response is sent back to the caller, this // happens on the process' main thread. - endTime: Milliseconds, + endTime: Milliseconds; // fetchStart doesn't exist directly in raw markers. This is added in the // deriving process and represents the junction between START and END markers. // This is the same value as the start marker's endTime and the end marker's // startTime (which are the same values). // We don't expose it directly but this is useful for debugging. - fetchStart?: Milliseconds, + fetchStart?: Milliseconds; // The following properties are present only in non-START markers. // domainLookupStart, if present, should be the first timestamp for an event @@ -578,27 +567,27 @@ export type NetworkPayload = {| // `tcpConnectEnd`, `secureConnectionStart`, and `connectEnd`. // NOTE: If you add a new property, don't forget to adjust its timestamp in // `adjustMarkerTimestamps` in `process-profile.js`. - domainLookupStart?: Milliseconds, - domainLookupEnd?: Milliseconds, - connectStart?: Milliseconds, - tcpConnectEnd?: Milliseconds, - secureConnectionStart?: Milliseconds, - connectEnd?: Milliseconds, + domainLookupStart?: Milliseconds; + domainLookupEnd?: Milliseconds; + connectStart?: Milliseconds; + tcpConnectEnd?: Milliseconds; + secureConnectionStart?: Milliseconds; + connectEnd?: Milliseconds; // `requestStart`, `responseStart` and `responseEnd` should always be present // for STOP markers. - requestStart?: Milliseconds, - responseStart?: Milliseconds, + requestStart?: Milliseconds; + responseStart?: Milliseconds; // responseEnd is when we received the response from the server, this happens // on the socket thread. - responseEnd?: Milliseconds, -|}; - -export type FileIoPayload = {| - type: 'FileIO', - cause?: CauseBacktrace, - source: string, - operation: string, - filename?: string, + responseEnd?: Milliseconds; +}; + +export type FileIoPayload = { + type: 'FileIO'; + cause?: CauseBacktrace; + source: string; + operation: string; + filename?: string; // FileIO markers that are happening on the current thread don't have a threadId, // but they have threadId field if the markers belong to a different (potentially // non-profiled) thread. @@ -606,172 +595,172 @@ export type FileIoPayload = {| // previous FileIO markers were also belonging to the threads they are in only. // We still don't serialize this field if the marker belongs to the thread they // are being captured. - threadId?: number, -|}; + threadId?: number; +}; /** * The payload for the UserTimings API. These are added through performance.measure() * and performance.mark(). https://developer.mozilla.org/en-US/docs/Web/API/Performance */ -export type UserTimingMarkerPayload = {| - type: 'UserTiming', - name: string, - entryType: 'measure' | 'mark', -|}; - -export type TextMarkerPayload = {| - type: 'Text', - name: string, - cause?: CauseBacktrace, - innerWindowID?: number, -|}; +export type UserTimingMarkerPayload = { + type: 'UserTiming'; + name: string; + entryType: 'measure' | 'mark'; +}; + +export type TextMarkerPayload = { + type: 'Text'; + name: string; + cause?: CauseBacktrace; + innerWindowID?: number; +}; // Any import from a Chrome profile -export type ChromeEventPayload = {| - type: string, - category: string, - data: MixedObject | null, -|}; +export type ChromeEventPayload = { + type: string; + category: string; + data: MixedObject | null; +}; /** * Gecko includes rich log information. This marker payload is used to mirror that * log information in the profile. */ -export type LogMarkerPayload = {| - type: 'Log', - name: string, - module: string, -|}; - -export type DOMEventMarkerPayload = {| - type: 'DOMEvent', - latency?: Milliseconds, - eventType: string, - innerWindowID?: number, -|}; - -export type PrefMarkerPayload = {| - type: 'PreferenceRead', - prefAccessTime: Milliseconds, - prefName: string, - prefKind: string, - prefType: string, - prefValue: string, -|}; - -export type NavigationMarkerPayload = {| - type: 'tracing', - category: 'Navigation', - eventType?: string, - innerWindowID?: number, -|}; - -type VsyncTimestampPayload = {| - type: 'VsyncTimestamp', -|}; +export type LogMarkerPayload = { + type: 'Log'; + name: string; + module: string; +}; + +export type DOMEventMarkerPayload = { + type: 'DOMEvent'; + latency?: Milliseconds; + eventType: string; + innerWindowID?: number; +}; + +export type PrefMarkerPayload = { + type: 'PreferenceRead'; + prefAccessTime: Milliseconds; + prefName: string; + prefKind: string; + prefType: string; + prefValue: string; +}; + +export type NavigationMarkerPayload = { + type: 'tracing'; + category: 'Navigation'; + eventType?: string; + innerWindowID?: number; +}; + +type VsyncTimestampPayload = { + type: 'VsyncTimestamp'; +}; export type ScreenshotPayload = - | {| - type: 'CompositorScreenshot', + | { + type: 'CompositorScreenshot'; // This field represents the data url of the image. It is saved in the string table. - url: IndexIntoStringTable, + url: IndexIntoStringTable; // A memory address that can uniquely identify a window. It has no meaning other than // a way to identify a window. - windowID: string, + windowID: string; // The original dimensions of the window that was captured. The actual image that is // stored in the string table will be scaled down from the original size. - windowWidth: number, - windowHeight: number, - |} + windowWidth: number; + windowHeight: number; + } // Markers that represent the closing of a window (name === 'CompositorScreenshotWindowDestroyed') // only have a windowID data. - | {| - type: 'CompositorScreenshot', + | { + type: 'CompositorScreenshot'; // A memory address that can uniquely identify a window. It has no meaning other than // a way to identify a window. - windowID: string, + windowID: string; // Having the property present but void makes it easier to deal with Flow in // our flow version. - url: void, - |}; + url: void; + }; -export type StyleMarkerPayload = {| - type: 'Styles', - category: 'Paint', - cause?: CauseBacktrace, +export type StyleMarkerPayload = { + type: 'Styles'; + category: 'Paint'; + cause?: CauseBacktrace; // Counts - elementsTraversed: number, - elementsStyled: number, - elementsMatched: number, - stylesShared: number, - stylesReused: number, -|}; - -export type BHRMarkerPayload = {| - type: 'BHR-detected hang', -|}; - -export type LongTaskMarkerPayload = {| - type: 'MainThreadLongTask', - category: 'LongTask', -|}; - -export type JsAllocationPayload_Gecko = {| - type: 'JS allocation', - className: string, - typeName: string, // Currently only 'JSObject' - coarseType: string, // Currently only 'Object', - size: Bytes, - inNursery: boolean, - stack: GeckoMarkerStack, -|}; - -export type NativeAllocationPayload_Gecko = {| - type: 'Native allocation', - size: Bytes, - stack: GeckoMarkerStack, + elementsTraversed: number; + elementsStyled: number; + elementsMatched: number; + stylesShared: number; + stylesReused: number; +}; + +export type BHRMarkerPayload = { + type: 'BHR-detected hang'; +}; + +export type LongTaskMarkerPayload = { + type: 'MainThreadLongTask'; + category: 'LongTask'; +}; + +export type JsAllocationPayload_Gecko = { + type: 'JS allocation'; + className: string; + typeName: string; // Currently only 'JSObject' + coarseType: string; // Currently only 'Object', + size: Bytes; + inNursery: boolean; + stack: GeckoMarkerStack; +}; + +export type NativeAllocationPayload_Gecko = { + type: 'Native allocation'; + size: Bytes; + stack: GeckoMarkerStack; // Older versions of the Gecko format did not have these values. - memoryAddress?: number, - threadId?: number, -|}; - -export type IPCMarkerPayload_Gecko = {| - type: 'IPC', - startTime: Milliseconds, - endTime: Milliseconds, + memoryAddress?: number; + threadId?: number; +}; + +export type IPCMarkerPayload_Gecko = { + type: 'IPC'; + startTime: Milliseconds; + endTime: Milliseconds; // otherPid is a number in the Gecko format. - otherPid: number, - messageType: string, - messageSeqno: number, - side: 'parent' | 'child', - direction: 'sending' | 'receiving', + otherPid: number; + messageType: string; + messageSeqno: number; + side: 'parent' | 'child'; + direction: 'sending' | 'receiving'; // Phase is not present in older profiles (in this case the phase is "endpoint"). - phase?: 'endpoint' | 'transferStart' | 'transferEnd', - sync: boolean, + phase?: 'endpoint' | 'transferStart' | 'transferEnd'; + sync: boolean; // `tid` of the thread that this marker is originated from. It is undefined // when the IPC marker is originated from the same thread. Also, this field is // added in Firefox 100. It will always be undefined for the older profiles. - threadId?: Tid, -|}; + threadId?: number; +}; -export type IPCMarkerPayload = {| - type: 'IPC', - startTime: Milliseconds, - endTime: Milliseconds, +export type IPCMarkerPayload = { + type: 'IPC'; + startTime: Milliseconds; + endTime: Milliseconds; // otherPid is a string in the processed format. - otherPid: Pid, - messageType: string, - messageSeqno: number, - side: 'parent' | 'child', - direction: 'sending' | 'receiving', + otherPid: Pid; + messageType: string; + messageSeqno: number; + side: 'parent' | 'child'; + direction: 'sending' | 'receiving'; // Phase is not present in older profiles (in this case the phase is "endpoint"). - phase?: 'endpoint' | 'transferStart' | 'transferEnd', - sync: boolean, + phase?: 'endpoint' | 'transferStart' | 'transferEnd'; + sync: boolean; // `tid` of the thread that this marker is originated from. It is undefined // when the IPC marker is originated from the same thread. Also, this field is // added in Firefox 100. It will always be undefined for the older profiles. - threadId?: Tid, + threadId?: number; // These fields are added in the deriving process from `IPCSharedData`, and // correspond to data from all the markers associated with a particular IPC @@ -779,50 +768,50 @@ export type IPCMarkerPayload = {| // We mark these fields as optional because we represent non-derived markers // and derived markers with the same type. These fields are always present on // derived markers and never present on non-derived markers. - sendStartTime?: Milliseconds, - sendEndTime?: Milliseconds, - recvEndTime?: Milliseconds, - sendTid?: Tid, - recvTid?: Tid, - sendThreadName?: string, - recvThreadName?: string, + sendStartTime?: Milliseconds; + sendEndTime?: Milliseconds; + recvEndTime?: Milliseconds; + sendTid?: Tid; + recvTid?: Tid; + sendThreadName?: string | void; + recvThreadName?: string | void; // This field is a nicely formatted field for the direction. - niceDirection?: string, -|}; + niceDirection?: string; +}; -export type MediaSampleMarkerPayload = {| - type: 'MediaSample', - sampleStartTimeUs: Microseconds, - sampleEndTimeUs: Microseconds, -|}; +export type MediaSampleMarkerPayload = { + type: 'MediaSample'; + sampleStartTimeUs: Microseconds; + sampleEndTimeUs: Microseconds; +}; /** * This type is generated on the Firefox Profiler side, and doesn't come from Gecko. */ -export type JankPayload = {| type: 'Jank' |}; - -export type BrowsertimeMarkerPayload = {| - type: 'VisualMetricProgress', - percentage: number, -|}; - -export type NoPayloadUserData = {| - type: 'NoPayloadUserData', - innerWindowID?: number, -|}; - -export type UrlMarkerPayload = {| - type: 'Url', - url: string, -|}; - -export type HostResolverPayload = {| - type: 'HostResolver', - host: string, - originSuffix: string, - flags: string, -|}; +export type JankPayload = { type: 'Jank' }; + +export type BrowsertimeMarkerPayload = { + type: 'VisualMetricProgress'; + percentage: number; +}; + +export type NoPayloadUserData = { + type: 'NoPayloadUserData'; + innerWindowID?: number; +}; + +export type UrlMarkerPayload = { + type: 'Url'; + url: string; +}; + +export type HostResolverPayload = { + type: 'HostResolver'; + host: string; + originSuffix: string; + flags: string; +}; /** * The union of all the different marker payloads that profiler.firefox.com knows about, @@ -881,7 +870,7 @@ export type MarkerPayload_Gecko = // The following payloads come in with a stack property. During the profile processing // the "stack" property is are converted into a "cause". See the CauseBacktrace type // for more information. - | $ReplaceCauseWithStack - | $ReplaceCauseWithStack - | $ReplaceCauseWithStack - | $ReplaceCauseWithStack; + | ReplaceCauseWithStack + | ReplaceCauseWithStack + | ReplaceCauseWithStack + | ReplaceCauseWithStack; diff --git a/src/types/profile.js b/src/types/profile.ts similarity index 79% rename from src/types/profile.js rename to src/types/profile.ts index 5c34ca9ece..23503ce928 100644 --- a/src/types/profile.js +++ b/src/types/profile.ts @@ -2,8 +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/. */ -// @flow - import type { Milliseconds, Address, Microseconds, Bytes } from './units'; import type { MarkerPayload, MarkerSchema, MarkerFormatType } from './markers'; import type { MarkerPhase, ProfilingLog } from './gecko-profile'; @@ -55,11 +53,11 @@ export type Pid = string; * shared prefix; storing these stacks as a tree saves a lot of space compared * to storing them as actual lists of frames. */ -export type RawStackTable = {| - frame: IndexIntoFrameTable[], - prefix: Array, - length: number, -|}; +export type RawStackTable = { + frame: IndexIntoFrameTable[]; + prefix: Array; + length: number; +}; /** * Profile samples can come in a variety of forms and represent different information. @@ -105,23 +103,23 @@ export type WeightType = 'samples' | 'tracing-ms' | 'bytes'; * information that is needed to represent that sampled function. Most of the entries * are indices into other tables. */ -export type RawSamplesTable = {| +export type RawSamplesTable = { // Responsiveness is the older version of eventDelay. It injects events every 16ms. // This is optional because newer profiles don't have that field anymore. - responsiveness?: Array, + responsiveness?: Array; // Event delay is the newer version of responsiveness. It allow us to get a finer-grained // view of jank by inferring what would be the delay of a hypothetical input event at // any point in time. It requires a pre-processing to be able to visualize properly. // This is optional because older profiles didn't have that field. - eventDelay?: Array, - stack: Array, - time?: Milliseconds[], + eventDelay?: Array; + stack: Array; + time?: Milliseconds[]; // If the `time` column is not present, then the `timeDeltas` column must be present. - timeDeltas?: Milliseconds[], + timeDeltas?: Milliseconds[]; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. - weight: null | number[], - weightType: WeightType, + weight: null | number[]; + weightType: WeightType; // CPU usage value of the current thread. Its values are null only if the back-end // fails to get the CPU usage from operating system. // It's landed in Firefox 86, and it is optional because older profile @@ -129,53 +127,53 @@ export type RawSamplesTable = {| // written for this change because it's a completely new data source. // The first value is ignored - it's not meaningful because there is no previous // sample. - threadCPUDelta?: Array, + threadCPUDelta?: Array; // This property isn't present in normal threads. However it's present for // merged threads, so that we know the origin thread for these samples. - threadId?: Tid[], - length: number, -|}; + threadId?: Tid[]; + length: number; +}; /** * JS allocations are recorded as a marker payload, but in profile processing they * are moved to the Thread. This allows them to be part of the stack processing pipeline. */ -export type JsAllocationsTable = {| - time: Milliseconds[], - className: string[], - typeName: string[], // Currently only 'JSObject' - coarseType: string[], // Currently only 'Object', +export type JsAllocationsTable = { + time: Milliseconds[]; + className: string[]; + typeName: string[]; // Currently only 'JSObject' + coarseType: string[]; // Currently only 'Object', // "weight" is used here rather than "bytes", so that this type will match the // SamplesLikeTableShape. - weight: Bytes[], - weightType: 'bytes', - inNursery: boolean[], - stack: Array, - length: number, -|}; + weight: Bytes[]; + weightType: 'bytes'; + inNursery: boolean[]; + stack: Array; + length: number; +}; /** * This variant is the original version of the table, before the memory address * and threadId were added. */ -export type UnbalancedNativeAllocationsTable = {| - time: Milliseconds[], +export type UnbalancedNativeAllocationsTable = { + time: Milliseconds[]; // "weight" is used here rather than "bytes", so that this type will match the // SamplesLikeTableShape. - weight: Bytes[], - weightType: 'bytes', - stack: Array, - length: number, -|}; + weight: Bytes[]; + weightType: 'bytes'; + stack: Array; + length: number; +}; /** * The memory address and thread ID were added later. */ -export type BalancedNativeAllocationsTable = {| - ...UnbalancedNativeAllocationsTable, - memoryAddress: number[], - threadId: number[], -|}; +export type BalancedNativeAllocationsTable = + UnbalancedNativeAllocationsTable & { + memoryAddress: number[]; + threadId: number[]; + }; /** * Native allocations are recorded as a marker payload, but in profile processing they @@ -199,24 +197,24 @@ export type NativeAllocationsTable = * create markers with durations, or even take a string-only marker and parse * it into a structured marker. */ -export type RawMarkerTable = {| - data: Array, - name: IndexIntoStringTable[], - startTime: Array, - endTime: Array, - phase: MarkerPhase[], - category: IndexIntoCategoryList[], +export type RawMarkerTable = { + data: Array; + name: IndexIntoStringTable[]; + startTime: Array; + endTime: Array; + phase: MarkerPhase[]; + category: IndexIntoCategoryList[]; // This property isn't present in normal threads. However it's present for // merged threads, so that we know the origin thread for these markers. - threadId?: Tid[], - length: number, -|}; + threadId?: Array; + length: number; +}; /** * Frames contain the context information about the function execution at the moment in * time. The caller/callee relationship between frames is defined by the StackTable. */ -export type FrameTable = {| +export type FrameTable = { // If this is a frame for native code, the address is the address of the frame's // assembly instruction, relative to the native library that contains it. // @@ -228,7 +226,7 @@ export type FrameTable = {| // // The library which this address is relative to is given by the frame's nativeSymbol: // frame -> nativeSymbol -> lib. - address: Array
, + address: Array
; // The inline depth for this frame. If there is an inline stack at an address, // we create multiple frames with the same address, one for each depth. @@ -256,29 +254,29 @@ export type FrameTable = {| // // The frames of an inline stack at an address all have the same address and the same // nativeSymbol, but each has a different func and line. - inlineDepth: number[], + inlineDepth: number[]; // The category of the frame. This is used to calculate the category of the stack nodes // which use this frame: // - If the frame has a null category, the stack node inherits its parent node's category // and subcategory. If there is no parent node, we use the "default category" (see ProfileMeta.categories). // - If the frame has a non-null category, this category and subcategory is used for the stack node. - category: (IndexIntoCategoryList | null)[], + category: (IndexIntoCategoryList | null)[]; // The subcategory of a frame. This is used to calculate the subcategory of the stack nodes // which use this frame. // Must be non-null if the frame's category is non-null. // Ignored if the frame's category is null. // 0 is always a valid value and refers to the "Other" subcategory (see Category.subcategories). - subcategory: (IndexIntoSubcategoryListForCategory | null)[], + subcategory: (IndexIntoSubcategoryListForCategory | null)[]; // The frame's function. - func: IndexIntoFuncTable[], + func: IndexIntoFuncTable[]; // The symbol index (referring into this thread's nativeSymbols table) corresponding // to symbol that covers the frame address of this frame. Only non-null for native // frames (e.g. C / C++ / Rust code). Null before symbolication. - nativeSymbol: (IndexIntoNativeSymbolTable | null)[], + nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; // Inner window ID of JS frames. JS frames can be correlated to a Page through this value. // It's used to determine which JS frame belongs to which web page so we can display @@ -287,12 +285,12 @@ export type FrameTable = {| // because that's what Firefox platform DOM side assigns when it fails to get the ID or // something bad happens during that process. It's not `null` or `-1` because that information // is being stored as `uint64_t` there. - innerWindowID: (InnerWindowID | null)[], + innerWindowID: (InnerWindowID | null)[]; - line: (number | null)[], - column: (number | null)[], - length: number, -|}; + line: (number | null)[]; + column: (number | null)[]; + length: number; +}; /** * The funcTable stores the functions that were called in the profile. @@ -311,34 +309,34 @@ export type FrameTable = {| * were created upfront to become orphaned, as the frames that originally referred * to them get reassigned to the canonical func for their actual function. */ -export type FuncTable = {| +export type FuncTable = { // The function name. - name: Array, + name: Array; // isJS and relevantForJS describe the function type. Non-JavaScript functions // can be marked as "relevant for JS" so that for example DOM API label functions // will show up in any JavaScript stack views. // It may be worth combining these two fields into one: // https://github.com/firefox-devtools/profiler/issues/2543 - isJS: Array, - relevantForJS: Array, + isJS: Array; + relevantForJS: Array; // The resource describes "Which bag of code did this function come from?". // For JS functions, the resource is of type addon, webhost, otherhost, or url. // For native functions, the resource is of type library. // For labels and for other unidentified functions, we set the resource to -1. - resource: Array, + resource: Array; // These are non-null for JS functions only. The line and column describe the // location of the *start* of the JS function. As for the information about which // which lines / columns inside the function were actually hit during execution, // that information is stored in the frameTable, not in the funcTable. - fileName: Array, - lineNumber: Array, - columnNumber: Array, + fileName: Array; + lineNumber: Array; + columnNumber: Array; - length: number, -|}; + length: number; +}; /** * The nativeSymbols table stores the addresses and symbol names for all symbols @@ -350,30 +348,30 @@ export type FuncTable = {| * contains *all* symbols of a given library. But this table only contains a * subset of those symbols, and mixes symbols from multiple libraries. */ -export type NativeSymbolTable = {| +export type NativeSymbolTable = { // The library that this native symbol is in. - libIndex: Array, + libIndex: Array; // The library-relative offset of this symbol. - address: Array
, + address: Array
; // The symbol name, demangled. - name: Array, + name: Array; // The size of the function's machine code (if known), in bytes. - functionSize: Array, + functionSize: Array; - length: number, -|}; + length: number; +}; /** * The ResourceTable holds additional information about functions. It tends to contain * sparse arrays. Multiple functions can point to the same resource. */ -export type ResourceTable = {| - length: number, - lib: Array, - name: Array, - host: Array, - type: resourceTypeEnum[], -|}; +export type ResourceTable = { + length: number; + lib: Array; + name: Array; + host: Array; + type: resourceTypeEnum[]; +}; /** * Information about the shared libraries that were loaded into the processes in @@ -381,13 +379,13 @@ export type ResourceTable = {| * the symbolication API requires a debugName + breakpadId for each set of * unsymbolicated addresses, to know where to obtain symbols for those addresses. */ -export type Lib = {| - arch: string, // e.g. "x86_64" - name: string, // e.g. "firefox" - path: string, // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" - debugName: string, // e.g. "firefox", or "firefox.pdb" on Windows - debugPath: string, // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" - breakpadId: string, // e.g. "E54D3AF274383256B9F6144F83F3F7510" +export type Lib = { + arch: string; // e.g. "x86_64" + name: string; // e.g. "firefox" + path: string; // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" + debugName: string; // e.g. "firefox", or "firefox.pdb" on Windows + debugPath: string; // e.g. "/Applications/FirefoxNightly.app/Contents/MacOS/firefox" + breakpadId: string; // e.g. "E54D3AF274383256B9F6144F83F3F7510" // The codeId is currently always null. // In the future, it will have the following values: @@ -400,8 +398,8 @@ export type Lib = {| // - On Windows, it will be the codeId for the binary (.exe / .dll), as used // by Windows symbol servers. This will allow us to get assembly code for // Windows system libraries for profiles which were captured on another machine. - codeId: string | null, // e.g. "6132B96B70fd000" -|}; + codeId: string | null; // e.g. "6132B96B70fd000" +}; // The list of available category colors. // @@ -423,18 +421,18 @@ export type CategoryColor = | 'grey'; // <-- "grey" marks the default category // A category in profile.meta.categories, used for stack frames and call nodes. -export type Category = {| +export type Category = { // The category name. - name: string, + name: string; // The category color. Must be picked from the CategoryColor list. At least one // category with color "grey" must be present in the category list. - color: CategoryColor, + color: CategoryColor; // The list of subcategories. Must always have at least one element; subcategory // zero must be the "Other" subcategory and is used to refer to the category itself. - subcategories: string[], -|}; + subcategories: string[]; +}; export type CategoryList = Array; @@ -447,65 +445,65 @@ export type CategoryList = Array; * * The unique field for a page is innerWindowID. */ -export type Page = {| +export type Page = { // Tab ID of the page. This ID is the same for all the pages inside a tab's // session history. - tabID: TabID, + tabID: TabID; // ID of the JS `window` object in a `Document`. It's unique for every page. - innerWindowID: InnerWindowID, + innerWindowID: InnerWindowID; // Url of this page. - url: string, + url: string; // Each page describes a frame in websites. A frame can either be the top-most // one or inside of another one. For the children frames, `embedderInnerWindowID` // points to the innerWindowID of the parent (embedder). It's `0` if there is // no embedder, which means that it's the top-most frame. That way all pages // can create a tree of pages that can be navigated. - embedderInnerWindowID: number, + embedderInnerWindowID: number; // If true, this page has been opened in a private browsing window. // It's optional because it appeared in Firefox 98, and is absent before when // capturing was disabled when a private browsing window was open. // The property is always present in Firefox 98+. - isPrivateBrowsing?: boolean, + isPrivateBrowsing?: boolean; // Favicon data of the page if it was successfully retrieved from Firefox. // It's a base64 encoded URI string when available. // It's null when Firefox can't get the favicon. // This is added in Firefox 134, earlier profiles will not have it. - favicon?: string | null, -|}; + favicon?: string | null; +}; export type PageList = Array; /** * Information about a period of time during which no samples were collected. */ -export type PausedRange = {| +export type PausedRange = { // null if the profiler was already paused at the beginning of the period of // time that was present in the profile buffer - startTime: Milliseconds | null, + startTime: Milliseconds | null; // null if the profiler was still paused when the profile was captured - endTime: Milliseconds | null, - reason: 'profiler-paused' | 'collecting', -|}; - -export type JsTracerTable = {| - events: Array, - timestamps: Array, - durations: Array, - line: Array, // Line number. - column: Array, // Column number. - length: number, -|}; - -export type RawCounterSamplesTable = {| - time?: Milliseconds[], - timeDeltas?: Milliseconds[], + endTime: Milliseconds | null; + reason: 'profiler-paused' | 'collecting'; +}; + +export type JsTracerTable = { + events: Array; + timestamps: Array; + durations: Array; + line: Array; // Line number. + column: Array; // Column number. + length: number; +}; + +export type RawCounterSamplesTable = { + time?: Milliseconds[]; + timeDeltas?: Milliseconds[]; // The number of times the Counter's "number" was changed since the previous sample. // This property was mandatory until the format version 42, it was made optional in 43. - number?: number[], + number?: number[]; // The count of the data, for instance for memory this would be bytes. - count: number[], - length: number, -|}; + count: number[]; + length: number; +}; export type GraphColor = | 'blue' @@ -519,61 +517,61 @@ export type GraphColor = | 'teal' | 'yellow'; -export type RawCounter = {| - name: string, - category: string, - description: string, - color?: GraphColor, - pid: Pid, - mainThreadIndex: ThreadIndex, - samples: RawCounterSamplesTable, -|}; +export type RawCounter = { + name: string; + category: string; + description: string; + color?: GraphColor; + pid: Pid; + mainThreadIndex: ThreadIndex; + samples: RawCounterSamplesTable; +}; /** * The statistics about profiler overhead. It includes max/min/mean values of * individual and overall overhead timings. */ -export type ProfilerOverheadStats = {| - maxCleaning: Microseconds, - maxCounter: Microseconds, - maxInterval: Microseconds, - maxLockings: Microseconds, - maxOverhead: Microseconds, - maxThread: Microseconds, - meanCleaning: Microseconds, - meanCounter: Microseconds, - meanInterval: Microseconds, - meanLockings: Microseconds, - meanOverhead: Microseconds, - meanThread: Microseconds, - minCleaning: Microseconds, - minCounter: Microseconds, - minInterval: Microseconds, - minLockings: Microseconds, - minOverhead: Microseconds, - minThread: Microseconds, - overheadDurations: Microseconds, - overheadPercentage: Microseconds, - profiledDuration: Microseconds, - samplingCount: Microseconds, -|}; +export type ProfilerOverheadStats = { + maxCleaning: Microseconds; + maxCounter: Microseconds; + maxInterval: Microseconds; + maxLockings: Microseconds; + maxOverhead: Microseconds; + maxThread: Microseconds; + meanCleaning: Microseconds; + meanCounter: Microseconds; + meanInterval: Microseconds; + meanLockings: Microseconds; + meanOverhead: Microseconds; + meanThread: Microseconds; + minCleaning: Microseconds; + minCounter: Microseconds; + minInterval: Microseconds; + minLockings: Microseconds; + minOverhead: Microseconds; + minThread: Microseconds; + overheadDurations: Microseconds; + overheadPercentage: Microseconds; + profiledDuration: Microseconds; + samplingCount: Microseconds; +}; /** * This object represents the configuration of the profiler when the profile was recorded. */ -export type ProfilerConfiguration = {| - threads: string[], - features: string[], - capacity: Bytes, - duration?: number, +export type ProfilerConfiguration = { + threads: string[]; + features: string[]; + capacity: Bytes; + duration?: number; // Optional because that field is introduced in Firefox 72. // Active Tab ID indicates a Firefox tab. // `0` means null value. Firefox only outputs `0` and not null, that's why we // should take care of this case while we are consuming it. If it's `0`, we // should revert back to the full view since there isn't enough data to show // the active tab view. - activeTabID?: TabID, -|}; + activeTabID?: TabID; +}; /** * Gecko Profiler records profiler overhead samples of specific tasks that take time. @@ -582,27 +580,27 @@ export type ProfilerConfiguration = {| * lockings: Time spent during acquiring locks. * threads: Time spent during threads sampling and marker collection. */ -export type ProfilerOverheadSamplesTable = {| - counters: Array, - expiredMarkerCleaning: Array, - locking: Array, - threads: Array, - time: Array, - length: number, -|}; +export type ProfilerOverheadSamplesTable = { + counters: Array; + expiredMarkerCleaning: Array; + locking: Array; + threads: Array; + time: Array; + length: number; +}; /** * Information about profiler overhead. It includes overhead timings for * counters, expired marker cleanings, mutex locking and threads. Also it * includes statistics about those individual and overall overhead. */ -export type ProfilerOverhead = {| - samples: ProfilerOverheadSamplesTable, +export type ProfilerOverhead = { + samples: ProfilerOverheadSamplesTable; // There is no statistics object if there is no sample. - statistics?: ProfilerOverheadStats, - pid: Pid, - mainThreadIndex: ThreadIndex, -|}; + statistics?: ProfilerOverheadStats; + pid: Pid; + mainThreadIndex: ThreadIndex; +}; // This list of process types is defined here: // https://searchfox.org/mozilla-central/rev/819cd31a93fd50b7167979607371878c4d6f18e8/xpcom/build/nsXULAppAPI.h#383 @@ -630,67 +628,67 @@ export type ProcessType = * * There is also a derived `Thread` type, see profile-derived.js. */ -export type RawThread = {| - processType: ProcessType, - processStartupTime: Milliseconds, - processShutdownTime: Milliseconds | null, - registerTime: Milliseconds, - unregisterTime: Milliseconds | null, - pausedRanges: PausedRange[], - showMarkersInTimeline?: boolean, - name: string, - isMainThread: boolean, +export type RawThread = { + processType: ProcessType; + processStartupTime: Milliseconds; + processShutdownTime: Milliseconds | null; + registerTime: Milliseconds; + unregisterTime: Milliseconds | null; + pausedRanges: PausedRange[]; + showMarkersInTimeline?: boolean; + name: string; + isMainThread: boolean; // The eTLD+1 of the isolated content process if provided by the back-end. // It will be undefined if: // - Fission is not enabled. // - It's not an isolated content process. // - It's a sanitized profile. // - It's a profile from an older Firefox which doesn't include this field (introduced in Firefox 80). - 'eTLD+1'?: string, - processName?: string, - isJsTracer?: boolean, - pid: Pid, - tid: Tid, - samples: RawSamplesTable, - jsAllocations?: JsAllocationsTable, - nativeAllocations?: NativeAllocationsTable, - markers: RawMarkerTable, - stackTable: RawStackTable, - frameTable: FrameTable, - funcTable: FuncTable, - resourceTable: ResourceTable, - nativeSymbols: NativeSymbolTable, - jsTracer?: JsTracerTable, + 'eTLD+1'?: string; + processName?: string; + isJsTracer?: boolean; + pid: Pid; + tid: Tid; + samples: RawSamplesTable; + jsAllocations?: JsAllocationsTable; + nativeAllocations?: NativeAllocationsTable; + markers: RawMarkerTable; + stackTable: RawStackTable; + frameTable: FrameTable; + funcTable: FuncTable; + resourceTable: ResourceTable; + nativeSymbols: NativeSymbolTable; + jsTracer?: JsTracerTable; // If present and true, this thread was launched for a private browsing session only. // When false, it can still contain private browsing data if the profile was // captured in a non-fission browser. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. - isPrivateBrowsing?: boolean, + isPrivateBrowsing?: boolean; // If present and non-0, the number represents the container this thread was loaded in. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. - userContextId?: number, -|}; + userContextId?: number; +}; -export type ExtensionTable = {| - baseURL: string[], - id: string[], - name: string[], - length: number, -|}; +export type ExtensionTable = { + baseURL: string[]; + id: string[]; + name: string[]; + length: number; +}; /** * Visual progress describes the visual progression during page load. A sample is generated * everytime the visual completeness of the webpage changes. */ -export type ProgressGraphData = {| +export type ProgressGraphData = { // A percentage that describes the visual completeness of the webpage, ranging from 0% - 100% - percent: number, + percent: number; // The time in milliseconds which the sample was taken. // This can be null due to https://github.com/sitespeedio/browsertime/issues/1746. - timestamp: Milliseconds | null, -|}; + timestamp: Milliseconds | null; +}; /** * Visual metrics are performance metrics that measure above-the-fold webpage visual performance, @@ -709,25 +707,25 @@ export type ProgressGraphData = {| * and https://github.com/sitespeedio/browsertime/blob/6e88284930c1d3ded8d9d95252d2e13c252d361c/lib/core/engine/iteration.js#L261-L264. * Finally they're inserted into the JSON profile in https://github.com/sitespeedio/browsertime/blob/6e88284930c1d3ded8d9d95252d2e13c252d361c/lib/firefox/webdriver/firefox.js#L215-L230 */ -export type VisualMetrics = {| - FirstVisualChange: number, - LastVisualChange: number, - SpeedIndex: number, - VisualProgress: ProgressGraphData[], +export type VisualMetrics = { + FirstVisualChange: number; + LastVisualChange: number; + SpeedIndex: number; + VisualProgress: ProgressGraphData[]; // Contentful and Perceptual values may be missing. They're generated only if // the user specifies the options --visualMetricsContentful and // --visualMetricsPerceptual in addition to --visualMetrics. - ContentfulSpeedIndex?: number, - ContentfulSpeedIndexProgress?: ProgressGraphData[], - PerceptualSpeedIndex?: number, - PerceptualSpeedIndexProgress?: ProgressGraphData[], + ContentfulSpeedIndex?: number; + ContentfulSpeedIndexProgress?: ProgressGraphData[]; + PerceptualSpeedIndex?: number; + PerceptualSpeedIndexProgress?: ProgressGraphData[]; // VisualReadiness and VisualCompleteXX values are generated in // https://github.com/sitespeedio/browsertime/blob/main/lib/video/postprocessing/visualmetrics/extraMetrics.js - VisualReadiness: number, - VisualComplete85: number, - VisualComplete95: number, - VisualComplete99: number, -|}; + VisualReadiness: number; + VisualComplete85: number; + VisualComplete95: number; + VisualComplete99: number; +}; // Units of ThreadCPUDelta values for different platforms. export type ThreadCPUDeltaUnit = 'ns' | 'µs' | 'variable CPU cycles'; @@ -738,70 +736,70 @@ export type TimelineUnit = 'ms' | 'bytes'; // Object that holds the units of samples table values. Some of the values can be // different depending on the platform, e.g. threadCPUDelta. // See https://searchfox.org/mozilla-central/rev/851bbbd9d9a38c2785a24c13b6412751be8d3253/tools/profiler/core/platform.cpp#2601-2606 -export type SampleUnits = {| - +time: TimelineUnit, - +eventDelay: 'ms', - +threadCPUDelta: ThreadCPUDeltaUnit, -|}; +export type SampleUnits = Readonly<{ + time: TimelineUnit; + eventDelay: 'ms'; + threadCPUDelta: ThreadCPUDeltaUnit; +}>; -export type ExtraProfileInfoSection = {| +export type ExtraProfileInfoSection = { // section label - label: string, - entries: Array<{| - label: string, - format: MarkerFormatType, + label: string; + entries: Array<{ + label: string; + format: MarkerFormatType; // any value valid for the formatter - value: any, - |}>, -|}; + value: any; + }>; +}; /** * Meta information associated for the entire profile. */ -export type ProfileMeta = {| +export type ProfileMeta = { // The interval at which the threads are sampled. - interval: Milliseconds, + interval: Milliseconds; // When the main process started. Timestamp expressed in milliseconds since // midnight January 1, 1970 GMT. - startTime: Milliseconds, - startTimeAsClockMonotonicNanosecondsSinceBoot?: number, - startTimeAsMachAbsoluteTimeNanoseconds?: number, - startTimeAsQueryPerformanceCounterValue?: number, + startTime: Milliseconds; + startTimeAsClockMonotonicNanosecondsSinceBoot?: number; + startTimeAsMachAbsoluteTimeNanoseconds?: number; + startTimeAsQueryPerformanceCounterValue?: number; // The number of milliseconds since midnight January 1, 1970 GMT. - endTime?: Milliseconds, + endTime?: Milliseconds; // When the recording started (in milliseconds after startTime). - profilingStartTime?: Milliseconds, + profilingStartTime?: Milliseconds; // When the recording ended (in milliseconds after startTime). - profilingEndTime?: Milliseconds, + profilingEndTime?: Milliseconds; // The process type where the Gecko profiler was started. This is the raw enum // numeric value as defined here: // https://searchfox.org/mozilla-central/rev/819cd31a93fd50b7167979607371878c4d6f18e8/xpcom/build/nsXULAppAPI.h#365 - processType: number, + processType: number; // The extensions property landed in Firefox 60, and is only optional because older // processed profile versions may not have it. No upgrader was written for this change. - extensions?: ExtensionTable, + extensions?: ExtensionTable; // The list of categories used in this profile. If present, it must contain at least the // "default category" which is defined as the first category whose color is "grey" - this // category usually has the name "Other". // If meta.categories is not present, a default list is substituted. - categories?: CategoryList, + categories?: CategoryList; // The name of the product, most likely "Firefox". - product: 'Firefox' | string, + product: 'Firefox' | string; // This value represents a boolean, but for some reason is written out as an int value. // It's 0 for the stack walking feature being turned off, and 1 for stackwalking being // turned on. - stackwalk: 0 | 1, + stackwalk: 0 | 1; // A boolean flag indicating whether the profiled application is using a debug build. // It's false for opt builds, and true for debug builds. // This property is optional because older processed profiles don't have this but // this property was added to Firefox a long time ago. It should work on older Firefox // versions without any problem. - debug?: boolean, + debug?: boolean; // This is the Gecko profile format version (the unprocessed version received directly // from the browser.) - version: number, + version: number; // This is the processed profile format version. - preprocessedProfileVersion: number, + preprocessedProfileVersion: number; // The following fields are most likely included in Gecko profiles, but are marked // optional for imported or converted profiles. @@ -809,15 +807,15 @@ export type ProfileMeta = {| // The XPCOM ABI (Application Binary Interface) name, taking the form: // {CPU_ARCH}-{TARGET_COMPILER_ABI} e.g. "x86_64-gcc3" // See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/XPCOM_ABI - abi?: string, + abi?: string; // The "misc" value of the browser's user agent, typically the revision of the browser. // e.g. "rv:63.0", which would be Firefox 63.0 // See https://searchfox.org/mozilla-central/rev/819cd31a93fd50b7167979607371878c4d6f18e8/netwerk/protocol/http/nsHttpHandler.h#543 - misc?: string, + misc?: string; // The OS and CPU. e.g. "Intel Mac OS X" - oscpu?: string, + oscpu?: string; // The size of the main memory in bytes - mainMemory?: Bytes, + mainMemory?: Bytes; // The current platform, as taken from the user agent string. // See https://searchfox.org/mozilla-central/rev/819cd31a93fd50b7167979607371878c4d6f18e8/netwerk/protocol/http/nsHttpHandler.cpp#992 platform?: @@ -826,11 +824,11 @@ export type ProfileMeta = {| | 'Macintosh' // X11 is used for historic reasons, but this value means that it is a Unix platform. | 'X11' - | string, + | string; // The widget toolkit used for GUI rendering. // Older versions of Firefox for Linux had the 2 flavors gtk2/gtk3, and so // we could find the value "gtk3". - toolkit?: 'gtk' | 'gtk3' | 'windows' | 'cocoa' | 'android' | string, + toolkit?: 'gtk' | 'gtk3' | 'windows' | 'cocoa' | 'android' | string; // The appBuildID, sourceURL, physicalCPUs and logicalCPUs properties landed // in Firefox 62, and are optional because older processed profile @@ -838,26 +836,26 @@ export type ProfileMeta = {| // The CPUName property landed in Firefox 108. // The build ID/date of the application. - appBuildID?: string, + appBuildID?: string; // Arguments to the program (currently only used for imported profiles) - arguments?: string, + arguments?: string; // The URL to the source revision for this build of the application. - sourceURL?: string, + sourceURL?: string; // The physical number of CPU cores for the machine. - physicalCPUs?: number, + physicalCPUs?: number; // The amount of logically available CPU cores for the program. - logicalCPUs?: number, + logicalCPUs?: number; // The name of the CPU (typically a string of up to 48 characters). - CPUName?: string, + CPUName?: string; // A boolean flag indicating whether we symbolicated this profile. If this is // false we'll start a symbolication process when the profile is loaded. // A missing property means that it's an older profile, it stands for an // "unknown" state. For now we don't do much with it but we may want to // propose a manual symbolication in the future. - symbolicated?: boolean, + symbolicated?: boolean; // A boolean flag indicating that symbolication is not supported // Used for imported profiles that cannot be symbolicated - symbolicationNotSupported?: boolean, + symbolicationNotSupported?: boolean; // The Update channel for this build of the application. // This property is landed in Firefox 67, and is optional because older // processed profile versions may not have them. No upgrader was necessary. @@ -869,82 +867,82 @@ export type ProfileMeta = {| | 'beta' | 'release' | 'esr' // Extended Support Release channel - | string, + | string; // Visual metrics contains additional performance metrics such as Speed Index, // Perceptual Speed Index, and ContentfulSpeedIndex. This is optional because only // profiles generated by browsertime will have this property. Source code for // browsertime can be found at https://github.com/sitespeedio/browsertime. - visualMetrics?: VisualMetrics, + visualMetrics?: VisualMetrics; // The configuration of the profiler at the time of recording. Optional since older // versions of Firefox did not include it. - configuration?: ProfilerConfiguration, + configuration?: ProfilerConfiguration; // Markers are displayed in the UI according to a schema definition. See the // MarkerSchema type for more information. - markerSchema: MarkerSchema[], + markerSchema: MarkerSchema[]; // Units of samples table values. // The sampleUnits property landed in Firefox 86, and is only optional because // older profile versions may not have it. No upgrader was written for this change. - sampleUnits?: SampleUnits, + sampleUnits?: SampleUnits; // Information of the device that profile is captured from. // Currently it's only present for Android devices and it includes brand and // model names of that device. // It's optional because profiles from non-Android devices and from older // Firefox versions may not have it. // This property landed in Firefox 88. - device?: string, + device?: string; // Profile importers can optionally add information about where they are imported from. // They also use the "product" field in the meta information, but this is somewhat // ambiguous. This field, if present, is unambiguous that it was imported. - importedFrom?: string, + importedFrom?: string; // The following are settings that are used to configure the views for // imported profiles, as some features do not make sense for them // Do not distinguish between different stack types? - usesOnlyOneStackType?: boolean, + usesOnlyOneStackType?: boolean; // Hide the "Look up the function name on Searchfox" menu entry? - sourceCodeIsNotOnSearchfox?: boolean, + sourceCodeIsNotOnSearchfox?: boolean; // Extra information about the profile, not shown in the "Profile Info" panel, // but in the more info panel - extra?: ExtraProfileInfoSection[], + extra?: ExtraProfileInfoSection[]; // Indexes of the threads that are initially visible in the UI. // This is useful for imported profiles for which the internal visibility score // ranking does not make sense. - initialVisibleThreads?: ThreadIndex[], + initialVisibleThreads?: ThreadIndex[]; // Indexes of the threads that are initially selected in the UI. // This is also most useful for imported profiles where just using the first thread // of each process might not make sense. - initialSelectedThreads?: ThreadIndex[], + initialSelectedThreads?: ThreadIndex[]; // Keep the defined thread order - keepProfileThreadOrder?: boolean, + keepProfileThreadOrder?: boolean; // Grams of CO2 equivalent per kWh. Used to display power track tooltips. // Will fallback to the global average if this is missing. - gramsOfCO2ePerKWh?: number, -|}; + gramsOfCO2ePerKWh?: number; +}; -export type RawProfileSharedData = {| +export type RawProfileSharedData = { // Strings for profiles are collected into a single table, and are referred to by // their index by other tables. - stringArray: string[], -|}; + stringArray: string[]; +}; /** * All of the data for a processed profile. */ -export type Profile = {| - meta: ProfileMeta, - libs: Lib[], - pages?: PageList, +export type Profile = { + meta: ProfileMeta; + libs: Lib[]; + pages?: PageList; // The counters list is optional only because old profilers may not have them. // An upgrader could be written to make this non-optional. - counters?: RawCounter[], + counters?: RawCounter[]; // The profilerOverhead list is optional only because old profilers may not // have them. An upgrader could be written to make this non-optional. // This is list because there is a profiler overhead per process. - profilerOverhead?: ProfilerOverhead[], - shared: RawProfileSharedData, - threads: RawThread[], - profilingLog?: ProfilingLog, - profileGatheringLog?: ProfilingLog, -|}; + profilerOverhead?: ProfilerOverhead[]; + shared: RawProfileSharedData; + threads: RawThread[]; + profilingLog?: ProfilingLog; + profileGatheringLog?: ProfilingLog; +}; From ce97bc1cbd083baf7681bae69d84946f1c19155c Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:13:38 -0400 Subject: [PATCH 047/124] Convert more files under src/utils. --- src/utils/{gz.js => gz.ts} | 20 +++++++++++--------- src/utils/{time-code.js => time-code.ts} | 7 +++---- 2 files changed, 14 insertions(+), 13 deletions(-) rename src/utils/{gz.js => gz.ts} (83%) rename src/utils/{time-code.js => time-code.ts} (93%) diff --git a/src/utils/gz.js b/src/utils/gz.ts similarity index 83% rename from src/utils/gz.js rename to src/utils/gz.ts index cd3ba67bce..2482ff37ed 100644 --- a/src/utils/gz.js +++ b/src/utils/gz.ts @@ -1,23 +1,25 @@ /* 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/. */ -// @flow // This worker is imported as WebWorker since it's conflicting with the Worker // global type. import WebWorker from './worker-factory'; -const zeeCallbacks = []; +const zeeCallbacks: Array<{ + success: (data: any) => void; + error: (error: any) => void; +} | null> = []; type ZeeWorkerData = { - callbackID: number, - type: 'success' | 'error', - data: any, + callbackID: number; + type: 'success' | 'error'; + data: any; }; function workerOnMessage(zeeWorker: Worker) { zeeWorker.onmessage = function (msg: MessageEvent) { - const data = ((msg.data: any): ZeeWorkerData); + const data = msg.data as ZeeWorkerData; const callbacks = zeeCallbacks[data.callbackID]; if (callbacks) { callbacks[data.type](data.data); @@ -30,8 +32,8 @@ function workerOnMessage(zeeWorker: Worker) { export function compress( data: string | Uint8Array, compressionLevel?: number -): Promise { - const zeeWorker = new WebWorker('zee-worker'); +): Promise> { + const zeeWorker = new WebWorker('zee-worker') as Worker; workerOnMessage(zeeWorker); const arrayData = @@ -56,7 +58,7 @@ export function compress( // Neuters data's buffer, if data is a typed array. export function decompress(data: Uint8Array): Promise { return new Promise(function (resolve, reject) { - const zeeWorker = new WebWorker('zee-worker'); + const zeeWorker = new WebWorker('zee-worker') as Worker; workerOnMessage(zeeWorker); zeeWorker.postMessage( { diff --git a/src/utils/time-code.js b/src/utils/time-code.ts similarity index 93% rename from src/utils/time-code.js rename to src/utils/time-code.ts index 7f53af8092..3fdae52dae 100644 --- a/src/utils/time-code.js +++ b/src/utils/time-code.ts @@ -2,11 +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/. */ -// @flow import { sendAnalytics } from './analytics'; const MAX_TIMINGS_PER_LABEL = 3; -const _timingsPerLabel = {}; +const _timingsPerLabel: Record = {}; let _performanceMeasureGeneration = 0; /** @@ -15,7 +14,7 @@ let _performanceMeasureGeneration = 0; */ export function timeCode(label: string, codeAsACallback: () => T): T { if (typeof performance !== 'undefined') { - let markName; + let markName: string | undefined; if (process.env.NODE_ENV === 'development') { if (performance.mark) { markName = `time-code-${_performanceMeasureGeneration++}`; @@ -30,7 +29,7 @@ export function timeCode(label: string, codeAsACallback: () => T): T { // Only log timing information in development mode. if (process.env.NODE_ENV === 'development') { // Record a UserTiming for this timeCode call. - if (performance.measure) { + if (performance.measure && markName) { performance.measure(`TimeCode: ${label}`, markName); } const style = 'font-weight: bold; color: #f0a'; From 2b88259526cd583ece91397cb99691355d15a1f3 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:17:30 -0400 Subject: [PATCH 048/124] Convert src/types/mocks/ftl.ts. --- src/types/mocks/{ftl.js => ftl.ts} | 1 - 1 file changed, 1 deletion(-) rename src/types/mocks/{ftl.js => ftl.ts} (67%) diff --git a/src/types/mocks/ftl.js b/src/types/mocks/ftl.ts similarity index 67% rename from src/types/mocks/ftl.js rename to src/types/mocks/ftl.ts index 62a86147ac..08d725cd4e 100644 --- a/src/types/mocks/ftl.js +++ b/src/types/mocks/ftl.ts @@ -1,2 +1 @@ -// @flow export default ''; From ed02f3f5820313da1e9f55db94054500b0612b89 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:19:40 -0400 Subject: [PATCH 049/124] Convert src/app-logic, src/profile-logic, and the rest of src/types and src/utils. --- package.json | 2 +- ...er-connection.js => browser-connection.ts} | 50 +- src/app-logic/{constants.js => constants.ts} | 2 - src/app-logic/{l10n.js => l10n.ts} | 2 - .../{tabs-handling.js => tabs-handling.ts} | 14 +- ...profiles-db.js => uploaded-profiles-db.ts} | 115 +- .../{url-handling.js => url-handling.ts} | 280 +++-- .../{web-channel.js => web-channel.ts} | 334 ++--- ...{address-locator.js => address-locator.ts} | 14 +- ...{address-timings.js => address-timings.ts} | 6 +- .../{call-node-info.js => call-node-info.ts} | 74 +- .../{call-tree.js => call-tree.ts} | 91 +- ...ommitted-ranges.js => committed-ranges.ts} | 7 +- src/profile-logic/{cpu.js => cpu.ts} | 4 +- ...{data-structures.js => data-structures.ts} | 28 +- src/profile-logic/{errors.js => errors.ts} | 4 +- .../{flame-graph.js => flame-graph.ts} | 17 +- .../{function-info.js => function-info.ts} | 2 - ...sioning.js => gecko-profile-versioning.ts} | 183 +-- .../{graph-color.js => graph-color.ts} | 2 - .../import/{art-trace.js => art-trace.ts} | 225 ++-- .../import/{chrome.js => chrome.ts} | 346 +++--- src/profile-logic/import/{dhat.js => dhat.ts} | 83 +- .../import/{linux-perf.js => linux-perf.ts} | 34 +- .../import/proto/simpleperf_report.d.ts | 1079 +++++++++++++++++ .../import/proto/simpleperf_report.js | 36 +- .../import/{simpleperf.js => simpleperf.ts} | 52 +- .../{js-tracer.js => js-tracer.tsx} | 34 +- .../{line-timings.js => line-timings.ts} | 6 +- .../{marker-data.js => marker-data.ts} | 133 +- .../{marker-schema.js => marker-schema.tsx} | 186 +-- .../{marker-styles.js => marker-styles.ts} | 20 +- .../{marker-timing.js => marker-timing.ts} | 16 +- .../{merge-compare.js => merge-compare.ts} | 143 +-- ...on-api.js => mozilla-symbolication-api.ts} | 46 +- src/profile-logic/{network.js => network.ts} | 1 - ...{process-profile.js => process-profile.ts} | 206 ++-- ...ing.js => processed-profile-versioning.ts} | 308 ++--- ...le-compacting.js => profile-compacting.ts} | 18 +- .../{profile-data.js => profile-data.ts} | 215 ++-- ...rofile-metainfo.js => profile-metainfo.ts} | 12 +- .../{profile-store.js => profile-store.ts} | 12 +- .../{sanitize.js => sanitize.ts} | 32 +- .../{stack-timing.js => stack-timing.ts} | 48 +- ...{symbol-store-db.js => symbol-store-db.ts} | 46 +- .../{symbol-store.js => symbol-store.ts} | 91 +- .../{symbolication.js => symbolication.ts} | 36 +- src/profile-logic/{tracks.js => tracks.ts} | 78 +- .../{transforms.js => transforms.ts} | 103 +- .../{zip-files.js => zip-files.ts} | 34 +- src/types/actions.js | 672 ---------- src/types/actions.ts | 718 +++++++++++ src/types/globals/ClipboardEvent.js | 8 - src/types/globals/Image.js | 9 - src/types/globals/WheelEvent.js | 14 - src/types/globals/Window.d.ts | 41 + src/types/globals/Window.js | 98 -- src/types/globals/l10n.js | 8 - src/types/{index.js => index.ts} | 2 - ...{profile-derived.js => profile-derived.ts} | 499 ++++---- src/types/state.js | 393 ------ src/types/state.ts | 386 ++++++ src/types/{store.js => store.ts} | 32 +- src/types/{transforms.js => transforms.ts} | 117 +- src/utils/{flow.js => flow.ts} | 48 +- .../{format-numbers.js => format-numbers.ts} | 12 +- src/utils/{index.js => index.ts} | 22 +- src/utils/{path.js => path.ts} | 21 +- src/utils/{query-api.js => query-api.ts} | 18 +- src/utils/{range-set.js => range-set.ts} | 1 - src/utils/{shorten-url.js => shorten-url.ts} | 2 - .../{special-paths.js => special-paths.ts} | 54 +- .../{string-table.js => string-table.ts} | 3 +- 73 files changed, 4589 insertions(+), 3499 deletions(-) rename src/app-logic/{browser-connection.js => browser-connection.ts} (91%) rename src/app-logic/{constants.js => constants.ts} (99%) rename src/app-logic/{l10n.js => l10n.ts} (99%) rename src/app-logic/{tabs-handling.js => tabs-handling.ts} (77%) rename src/app-logic/{uploaded-profiles-db.js => uploaded-profiles-db.ts} (74%) rename src/app-logic/{url-handling.js => url-handling.ts} (90%) rename src/app-logic/{web-channel.js => web-channel.ts} (72%) rename src/profile-logic/{address-locator.js => address-locator.ts} (92%) rename src/profile-logic/{address-timings.js => address-timings.ts} (99%) rename src/profile-logic/{call-node-info.js => call-node-info.ts} (97%) rename src/profile-logic/{call-tree.js => call-tree.ts} (96%) rename src/profile-logic/{committed-ranges.js => committed-ranges.ts} (98%) rename src/profile-logic/{cpu.js => cpu.ts} (99%) rename src/profile-logic/{data-structures.js => data-structures.ts} (97%) rename src/profile-logic/{errors.js => errors.ts} (91%) rename src/profile-logic/{flame-graph.js => flame-graph.ts} (97%) rename src/profile-logic/{function-info.js => function-info.ts} (99%) rename src/profile-logic/{gecko-profile-versioning.js => gecko-profile-versioning.ts} (93%) rename src/profile-logic/{graph-color.js => graph-color.ts} (99%) rename src/profile-logic/import/{art-trace.js => art-trace.ts} (87%) rename src/profile-logic/import/{chrome.js => chrome.ts} (85%) rename src/profile-logic/import/{dhat.js => dhat.ts} (94%) rename src/profile-logic/import/{linux-perf.js => linux-perf.ts} (93%) create mode 100644 src/profile-logic/import/proto/simpleperf_report.d.ts rename src/profile-logic/import/{simpleperf.js => simpleperf.ts} (92%) rename src/profile-logic/{js-tracer.js => js-tracer.tsx} (98%) rename src/profile-logic/{line-timings.js => line-timings.ts} (99%) rename src/profile-logic/{marker-data.js => marker-data.ts} (94%) rename src/profile-logic/{marker-schema.js => marker-schema.tsx} (83%) rename src/profile-logic/{marker-styles.js => marker-styles.ts} (93%) rename src/profile-logic/{marker-timing.js => marker-timing.ts} (97%) rename src/profile-logic/{merge-compare.js => merge-compare.ts} (94%) rename src/profile-logic/{mozilla-symbolication-api.js => mozilla-symbolication-api.ts} (94%) rename src/profile-logic/{network.js => network.ts} (99%) rename src/profile-logic/{process-profile.js => process-profile.ts} (94%) rename src/profile-logic/{processed-profile-versioning.js => processed-profile-versioning.ts} (94%) rename src/profile-logic/{profile-compacting.js => profile-compacting.ts} (96%) rename src/profile-logic/{profile-data.js => profile-data.ts} (97%) rename src/profile-logic/{profile-metainfo.js => profile-metainfo.ts} (96%) rename src/profile-logic/{profile-store.js => profile-store.ts} (95%) rename src/profile-logic/{sanitize.js => sanitize.ts} (96%) rename src/profile-logic/{stack-timing.js => stack-timing.ts} (93%) rename src/profile-logic/{symbol-store-db.js => symbol-store-db.ts} (92%) rename src/profile-logic/{symbol-store.js => symbol-store.ts} (92%) rename src/profile-logic/{symbolication.js => symbolication.ts} (98%) rename src/profile-logic/{tracks.js => tracks.ts} (97%) rename src/profile-logic/{transforms.js => transforms.ts} (96%) rename src/profile-logic/{zip-files.js => zip-files.ts} (93%) delete mode 100644 src/types/actions.js create mode 100644 src/types/actions.ts delete mode 100644 src/types/globals/ClipboardEvent.js delete mode 100644 src/types/globals/Image.js delete mode 100644 src/types/globals/WheelEvent.js create mode 100644 src/types/globals/Window.d.ts delete mode 100644 src/types/globals/Window.js delete mode 100644 src/types/globals/l10n.js rename src/types/{index.js => index.ts} (93%) rename src/types/{profile-derived.js => profile-derived.ts} (77%) delete mode 100644 src/types/state.js create mode 100644 src/types/state.ts rename src/types/{store.js => store.ts} (82%) rename src/types/{transforms.js => transforms.ts} (88%) rename src/utils/{flow.js => flow.ts} (83%) rename src/utils/{format-numbers.js => format-numbers.ts} (99%) rename src/utils/{index.js => index.ts} (91%) rename src/utils/{path.js => path.ts} (81%) rename src/utils/{query-api.js => query-api.ts} (91%) rename src/utils/{range-set.js => range-set.ts} (99%) rename src/utils/{shorten-url.js => shorten-url.ts} (99%) rename src/utils/{special-paths.js => special-paths.ts} (93%) rename src/utils/{string-table.js => string-table.ts} (97%) diff --git a/package.json b/package.json index 3fb9aa86c2..56ded42ce3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "prettier-run": "node bin/output-fixing-commands.js prettier --check . --cache --cache-strategy content --cache-location .prettiercache", "prettier-fix": "prettier --write . --cache --cache-strategy content --cache-location .prettiercache", "typecheck": "tsc --noEmit", - "protoc": "npx -p protobufjs-cli pbjs -t static-module -w commonjs -o ./src/profile-logic/import/proto/simpleperf_report.js ./src/profile-logic/import/proto/simpleperf_report.proto", + "protoc": "npx -p protobufjs-cli pbjs -t static-module -w commonjs -o ./src/profile-logic/import/proto/simpleperf_report.js ./src/profile-logic/import/proto/simpleperf_report.proto && npx -p protobufjs-cli pbts -o ./src/profile-logic/import/proto/simpleperf_report.d.ts ./src/profile-logic/import/proto/simpleperf_report.js", "license-check": "devtools-license-check", "preinstall": "node bin/pre-install.js", "publish": "rimraf public_html && cp -r dist public_html", diff --git a/src/app-logic/browser-connection.js b/src/app-logic/browser-connection.ts similarity index 91% rename from src/app-logic/browser-connection.js rename to src/app-logic/browser-connection.ts index 107f09c6dd..446d79b140 100644 --- a/src/app-logic/browser-connection.js +++ b/src/app-logic/browser-connection.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { oneLine } from 'common-tags'; import { @@ -14,7 +13,12 @@ import { getPageFaviconsViaWebChannel, showFunctionInDevtoolsViaWebChannel, } from './web-channel'; -import type { Milliseconds, FaviconData } from 'firefox-profiler/types'; +import type { + Milliseconds, + FaviconData, + MixedObject, + SymbolTableAsTuple, +} from 'firefox-profiler/types'; /** * This file manages the communication between the profiler and the browser. @@ -22,23 +26,23 @@ import type { Milliseconds, FaviconData } from 'firefox-profiler/types'; export type BrowserConnectionStatus = // The initial state. - | {| status: 'NO_ATTEMPT' |} + | { status: 'NO_ATTEMPT' } // In non-Firefox browsers we don't attempt to establish a connection. // This is determined via the userAgent. - | {| status: 'NOT_FIREFOX' |} + | { status: 'NOT_FIREFOX' } // We are in Firefox, and have sent the initial WebChannel event. - | {| status: 'WAITING' |} + | { status: 'WAITING' } // We are in Firefox but the WebChannel connection has been denied. // This usually means that this profiler instance is running on a // different host than the one that's specified in the // preference `devtools.performance.recording.ui-base-url`. - | {| status: 'DENIED', error: Error |} + | { status: 'DENIED'; error: Error } // We are in Firefox but the WebChannel did not respond within 5 seconds. // This is unexpected. It could mean that we are running in an old Firefox // (older than Firefox 76) which did not have a profiler WebChannel. - | {| status: 'TIMED_OUT' |} + | { status: 'TIMED_OUT' } // The WebChannel connection has been established. - | {| status: 'ESTABLISHED', browserConnection: BrowserConnection |}; + | { status: 'ESTABLISHED'; browserConnection: BrowserConnection }; /** * The interface of communication with the browser. Can be backed by a WebChannel @@ -47,9 +51,9 @@ export type BrowserConnectionStatus = */ export interface BrowserConnection { // Get the profile for this tab from the browser. - getProfile(options: {| - onThirtySecondTimeout: () => void, - |}): Promise; + getProfile(options: { + onThirtySecondTimeout: () => void; + }): Promise; getExternalMarkers( startTime: Milliseconds, @@ -59,7 +63,7 @@ export interface BrowserConnection { getExternalPowerTracks( startTime: Milliseconds, endTime: Milliseconds - ): Promise; + ): Promise; // Query the browser-internal symbolication API. This provides richer // information than getSymbolTable. @@ -94,7 +98,7 @@ class BrowserConnectionImpl implements BrowserConnection { _webChannelSupportsGetExternalMarkers: boolean; _webChannelSupportsGetPageFavicons: boolean; _webChannelSupportsOpenDebuggerInTab: boolean; - _geckoProfiler: $GeckoProfiler | void; + _geckoProfiler: $GeckoProfiler | undefined; constructor(webChannelVersion: number) { this._webChannelSupportsGetProfileAndSymbolication = webChannelVersion >= 1; @@ -115,9 +119,9 @@ class BrowserConnectionImpl implements BrowserConnection { return this._geckoProfiler; } - async getProfile(options: {| - onThirtySecondTimeout: () => void, - |}): Promise { + async getProfile(options: { + onThirtySecondTimeout: () => void; + }): Promise { const timeoutId = setTimeout(options.onThirtySecondTimeout, 30000); // On Firefox 96 and above, we can get the profile from the WebChannel. @@ -132,7 +136,7 @@ class BrowserConnectionImpl implements BrowserConnection { const geckoProfiler = await this._getConnectionViaFrameScript(); const profile = await geckoProfiler.getProfile(); clearTimeout(timeoutId); - return profile; + return profile as MixedObject; } async getExternalMarkers( @@ -144,13 +148,13 @@ class BrowserConnectionImpl implements BrowserConnection { return getExternalMarkersViaWebChannel(startTime, endTime); } - return []; + return [] as unknown as MixedObject; } async getExternalPowerTracks( startTime: Milliseconds, endTime: Milliseconds - ): Promise { + ): Promise { // On Firefox 121 and above, we can get additional power tracks recorded outside the browser. if (this._webChannelSupportsGetExternalPowerTracks) { return getExternalPowerTracksViaWebChannel(startTime, endTime); @@ -240,10 +244,10 @@ function _isFirefox(userAgent: string): boolean { } class TimeoutError extends Error { - name = 'TimeoutError'; + override name = 'TimeoutError'; } -function makeTimeoutRejectionPromise(durationInMs) { +function makeTimeoutRejectionPromise(durationInMs: number) { return new Promise((_resolve, reject) => { setTimeout(() => { reject(new TimeoutError(`Timed out after ${durationInMs}ms`)); @@ -258,10 +262,10 @@ export async function createBrowserConnection( return { status: 'NOT_FIREFOX' }; } try { - const webChannelVersion = await Promise.race([ + const webChannelVersion = (await Promise.race([ queryWebChannelVersionViaWebChannel(), makeTimeoutRejectionPromise(5000), - ]); + ])) as number; // If we get here, it means queryWebChannelVersionViaWebChannel() // did not throw an exception. This means that a WebChannel exists. const browserConnection = new BrowserConnectionImpl(webChannelVersion); diff --git a/src/app-logic/constants.js b/src/app-logic/constants.ts similarity index 99% rename from src/app-logic/constants.js rename to src/app-logic/constants.ts index 1b99f2f8b0..4ee3d1ad56 100644 --- a/src/app-logic/constants.js +++ b/src/app-logic/constants.ts @@ -2,8 +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/. */ -// @flow - import type { MarkerPhase } from 'firefox-profiler/types'; // The current version of the Gecko profile format. diff --git a/src/app-logic/l10n.js b/src/app-logic/l10n.ts similarity index 99% rename from src/app-logic/l10n.js rename to src/app-logic/l10n.ts index 2cc481bbd2..baf1503491 100644 --- a/src/app-logic/l10n.js +++ b/src/app-logic/l10n.ts @@ -2,8 +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/. */ -// @flow - import { FluentBundle, FluentResource } from '@fluent/bundle'; import { PSEUDO_STRATEGIES, diff --git a/src/app-logic/tabs-handling.js b/src/app-logic/tabs-handling.ts similarity index 77% rename from src/app-logic/tabs-handling.js rename to src/app-logic/tabs-handling.ts index 28850d0312..3e2f205c3b 100644 --- a/src/app-logic/tabs-handling.js +++ b/src/app-logic/tabs-handling.ts @@ -2,8 +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/. */ -// @flow - /** * This object contains all our tab slugs with their associated title l10n Ids. * This is the "main list of tabs". This is in object form because that's how we @@ -19,29 +17,29 @@ export const tabsWithTitleL10nId = { 'js-tracer': 'TabBar--js-tracer-tab', }; -export type TabSlug = $Keys; -export type TabsWithTitleL10nId = {| name: TabSlug, title: string |}; +export type TabSlug = keyof typeof tabsWithTitleL10nId; +export type TabsWithTitleL10nId = { name: TabSlug; title: string }; /** * This array contains the list of all tab slugs that we use as codes throughout * the codebase, and especially in the URL. */ -export const tabSlugs: $ReadOnlyArray = +export const tabSlugs: readonly TabSlug[] = // getOwnPropertyNames is guaranteed to keep the order in which properties // were defined, and this order is important for us. - Object.getOwnPropertyNames(tabsWithTitleL10nId); + Object.getOwnPropertyNames(tabsWithTitleL10nId) as TabSlug[]; /** * This array contains the same data as tabsWithTitleL10nId above, but in an ordered * array so that we can use it directly in some of our components. */ -export const tabsWithTitleL10nIdArray: $ReadOnlyArray = +export const tabsWithTitleL10nIdArray: readonly TabsWithTitleL10nId[] = tabSlugs.map((tabSlug) => ({ name: tabSlug, title: tabsWithTitleL10nId[tabSlug], })); -export const tabsShowingSampleData: $ReadOnlyArray = [ +export const tabsShowingSampleData: readonly TabSlug[] = [ 'calltree', 'flame-graph', 'stack-chart', diff --git a/src/app-logic/uploaded-profiles-db.js b/src/app-logic/uploaded-profiles-db.ts similarity index 74% rename from src/app-logic/uploaded-profiles-db.js rename to src/app-logic/uploaded-profiles-db.ts index e58fca01bd..cc58509003 100644 --- a/src/app-logic/uploaded-profiles-db.js +++ b/src/app-logic/uploaded-profiles-db.ts @@ -1,53 +1,56 @@ /* 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/. */ -// @flow // This file contains the code responsible for storing informations about // published profiles. +import type { IDBPDatabase } from 'idb'; import { openDB, deleteDB } from 'idb'; import { stripIndent } from 'common-tags'; import { stateFromLocation, urlFromState, } from 'firefox-profiler/app-logic/url-handling'; -import { ensureExists } from 'firefox-profiler/utils/flow'; -import type { DB as Database } from 'idb'; import type { StartEndRange } from 'firefox-profiler/types'; // This type is closely tied to the IndexedDB operation. Indeed it represents // the data we store and retrieve in the local DB. That's especially why it's // defined in this file, close to the DB operations. Indeed we don't want that // this type evolves without implementing a migration step for the stored data. -export type UploadedProfileInformation = {| - +profileToken: string, // This is the primary key. - +jwtToken: string | null, - +publishedDate: Date, // This key is indexed as well, to provide automatic sorting. - +name: string, - +preset: string | null, - +meta: {| +export type UploadedProfileInformation = { + readonly profileToken: string; // This is the primary key. + readonly jwtToken: string | null; + readonly publishedDate: Date; // This key is indexed as well, to provide automatic sorting. + readonly name: string; + readonly preset: string | null; + readonly meta: { // We're using some of the properties of the profile meta, but we're not // reusing the type ProfileMeta completely because we don't want to be // impacted from future changes to ProfileMeta. // Look at ProfileMeta definition to know more about these fields. - +product: string, - +abi?: string, - +platform?: + readonly product: string; + readonly abi?: string; + readonly platform?: | 'Android' | 'Windows' | 'Macintosh' // X11 is used for historic reasons, but this value means that it is a Unix platform. | 'X11' - | string, - +toolkit?: string, - +misc?: string, - +oscpu?: string, + | string; + readonly misc?: string; + readonly oscpu?: string; // Older versions of Firefox for Linux had the 2 flavors gtk2/gtk3, and so // we could find the value "gtk3". - +toolkit?: 'gtk' | 'gtk3' | 'windows' | 'cocoa' | 'android' | string, - +updateChannel?: + readonly toolkit?: + | 'gtk' + | 'gtk3' + | 'windows' + | 'cocoa' + | 'android' + | string; + readonly updateChannel?: | 'default' // Local builds | 'nightly' | 'nightly-try' // Nightly try builds for QA @@ -55,57 +58,55 @@ export type UploadedProfileInformation = {| | 'beta' | 'release' | 'esr' // Extended Support Release channel - | string, - +appBuildID?: string, - |}, + | string; + readonly appBuildID?: string; + }; // Storing the state as the path makes it easy to reuse our URL upgrade mechanism. - +urlPath: string, - +publishedRange: StartEndRange, -|}; + readonly urlPath: string; + readonly publishedRange: StartEndRange; +}; // Exported for tests. export const DATABASE_NAME = 'published-profiles-store'; export const OBJECTSTORE_NAME = 'published-profiles'; export const DATABASE_VERSION = 3; -async function reallyOpen(): Promise { +async function reallyOpen(): Promise { const db = await openDB(DATABASE_NAME, DATABASE_VERSION, { - upgrade(db, oldVersion, newVersion, transaction) { - // In the following switch block, we don't "break" for each case block - // because we want to run all migration steps in sequence, starting with - // the right step. - /* eslint-disable no-fallthrough */ - switch (oldVersion) { - case 0: { - // Version 1: this is the first version of the DB. - const store = db.createObjectStore(OBJECTSTORE_NAME, { - keyPath: 'profileToken', - }); - store.createIndex('originHostname', 'originHostname'); - } - case 1: { - // Version 2: we create a new index to allow retrieving the values - // ordered by date. - const store = ensureExists(transaction.store); - store.createIndex('publishedDate', 'publishedDate'); - } - case 2: { - // Version 3: we remove the originHostname index that was used by the - // active tab view since it's been removed. - const store = ensureExists(transaction.store); - store.deleteIndex('originHostname'); - } - default: - // Nothing more here. + upgrade(db, oldVersion, _newVersion, transaction) { + // Run all migration steps in sequence. + if (oldVersion < 1) { + // Version 1: this is the first version of the DB. + const store = db.createObjectStore(OBJECTSTORE_NAME, { + keyPath: 'profileToken', + }); + store.createIndex('originHostname', 'originHostname'); + } + if (oldVersion < 2) { + // Version 2: we create a new index to allow retrieving the values + // ordered by date. + const store = transaction.objectStore(OBJECTSTORE_NAME); + store.createIndex('publishedDate', 'publishedDate'); + } + if (oldVersion < 3) { + // Version 3: we remove the originHostname index that was used by the + // active tab view since it's been removed. + const store = transaction.objectStore(OBJECTSTORE_NAME); + store.deleteIndex('originHostname'); } - /* eslint-enable */ }, }); return db; } -async function open(): Promise { +declare global { + interface Window { + deleteDB?: () => void; + } +} + +async function open(): Promise { if (!window.indexedDB) { throw new Error('Could not find indexedDB on the window object.'); } @@ -123,7 +124,7 @@ async function open(): Promise { // changes. // Let's explain that in an error, that will be output to the console by // the caller. - (window: any).deleteDB = () => deleteDB(DATABASE_NAME); + window.deleteDB = () => deleteDB(DATABASE_NAME); throw new Error(stripIndent` We tried to open an existing published profiles store database with a smaller version than the current one. We can't do that with IndexedDB. @@ -158,7 +159,7 @@ export async function persistUploadedProfileInformationToDb( * This returns the list of all the stored data. */ export async function listAllUploadedProfileInformationFromDb(): Promise< - UploadedProfileInformation[], + UploadedProfileInformation[] > { const db = await open(); return db.getAllFromIndex(OBJECTSTORE_NAME, 'publishedDate'); diff --git a/src/app-logic/url-handling.js b/src/app-logic/url-handling.ts similarity index 90% rename from src/app-logic/url-handling.js rename to src/app-logic/url-handling.ts index c27f679b21..dbd7010a3d 100644 --- a/src/app-logic/url-handling.js +++ b/src/app-logic/url-handling.ts @@ -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/. */ -// @flow import queryString from 'query-string'; import { stringifyCommittedRanges, @@ -39,6 +38,8 @@ import type { SourceViewState, AssemblyViewState, NativeSymbolInfo, + Transform, + IndexIntoFrameTable, } from 'firefox-profiler/types'; import { decodeUintArrayFromUrlComponent, @@ -148,87 +149,93 @@ function getPathParts(urlState: UrlState): string[] { // "null | void" in the query objects are flags which map to true for null, and false // for void. False flags do not show up the URL. -type BaseQuery = {| - v: number, - globalTrackOrder: string, // "3201" - hiddenGlobalTracks: string, // "01" - hiddenLocalTracksByPid: string, // "1549-0w8~1593-23~1598-01~1602-02~1607-1" - localTrackOrderByPid: string, // "1549-780w6~1560-01" - tabID: TabID, +type BaseQuery = { + v: number; + globalTrackOrder: string; // "3201" + hiddenGlobalTracks: string; // "01" + hiddenLocalTracksByPid: string; // "1549-0w8~1593-23~1598-01~1602-02~1607-1" + localTrackOrderByPid: string; // "1549-780w6~1560-01" + tabID: TabID; // The following values are legacy, and will be converted to track-based values. These // value can't be upgraded using the typical URL upgrading process, as the full profile // must be fetched to compute the tracks. - threadOrder: string, // "3-2-0-1" - hiddenThreads: string, // "0-1" - range: string, // - thread: string, // "3" - file: string, // Path into a zip file. - transforms: string, - profiles: string[], - profileName: string, - symbolServer: string, - view: string, - implementation: string, - timelineType: string, - sourceView: string, - assemblyView: string, -|}; - -type CallTreeQuery = {| - ...BaseQuery, - search: string, // "js::RunScript" - invertCallstack: null | void, - ctSummary: string, -|}; - -type MarkersQuery = {| - ...BaseQuery, - markerSearch: string, // "DOMEvent" -|}; - -type NetworkQuery = {| - ...BaseQuery, - networkSearch?: string, // "DOMEvent" -|}; - -type StackChartQuery = {| - ...BaseQuery, - search: string, // "js::RunScript" - invertCallstack: null | void, - showUserTimings: null | void, - sameWidths: null | void, - ctSummary: string, -|}; - -type JsTracerQuery = {| - ...BaseQuery, - summary: null | void, -|}; - -type Query = - | CallTreeQuery - | MarkersQuery - | NetworkQuery - | StackChartQuery - | JsTracerQuery; - -type $MakeOptional = (T) => T | void; + threadOrder: string; // "3-2-0-1" + hiddenThreads: string; // "0-1" + range: string; // + thread: string; // "3" + file: string; // Path into a zip file. + transforms: string; + profiles: string[]; + profileName: string; + symbolServer: string; + view: string; + implementation: string; + timelineType: string; + sourceView: string; + assemblyView: string; +}; + +type CallTreeQuery = BaseQuery & { + search: string; // "js::RunScript" + invertCallstack: null | undefined; + ctSummary: string; +}; + +type MarkersQuery = BaseQuery & { + markerSearch: string; // "DOMEvent" +}; + +type NetworkQuery = BaseQuery & { + networkSearch?: string; // "DOMEvent" +}; + +type StackChartQuery = BaseQuery & { + search: string; // "js::RunScript" + invertCallstack: null | undefined; + showUserTimings: null | undefined; + sameWidths: null | undefined; + ctSummary: string; +}; + +type JsTracerQuery = BaseQuery & { + summary: null | undefined; +}; + +// Make Query a union that includes all possible properties +type Query = BaseQuery & { + // CallTree/StackChart specific + search?: string; + invertCallstack?: null | undefined; + ctSummary?: string; + transforms?: string; + sourceView?: string; + assemblyView?: string; + + // StackChart specific + showUserTimings?: null | undefined; + sameWidths?: null | undefined; + + // Markers specific + markerSearch?: string; + + // Network specific + networkSearch?: string; + + // JsTracer specific + summary?: null | undefined; +}; + // Base query shape is needed for the typechecking during the URL query initialization. -type BaseQueryShape = $Shape<$ObjMap>; +type BaseQueryShape = Partial; // Query shapes for individual query paths. These are needed for QueryShape union type. -type CallTreeQueryShape = $Shape<$ObjMap>; -type MarkersQueryShape = $Shape<$ObjMap>; -type NetworkQueryShape = $Shape<$ObjMap>; -type StackChartQueryShape = $Shape<$ObjMap>; -type JsTracerQueryShape = $Shape<$ObjMap>; - -type QueryShape = - | CallTreeQueryShape - | MarkersQueryShape - | NetworkQueryShape - | StackChartQueryShape - | JsTracerQueryShape; +type CallTreeQueryShape = Partial; +type MarkersQueryShape = Partial; +type NetworkQueryShape = Partial; +type StackChartQueryShape = Partial; +type JsTracerQueryShape = Partial; + +type QueryShape = Partial; /** * Take the UrlState and map it into a query string. @@ -262,7 +269,7 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { const selectedThreadsKey = selectedThreads !== null ? getThreadsKey(selectedThreads) : null; - const baseQuery = ({ + const baseQuery = { globalTrackOrder: convertGlobalTrackOrderToString( urlState.profileSpecific.globalTrackOrder ), @@ -299,7 +306,7 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { urlState.profileSpecific.timelineType === 'cpu-category' ? undefined : urlState.profileSpecific.timelineType, - }: BaseQueryShape); + } as BaseQueryShape; // Depending on which panel is active, also show tab-specific query parameters. let query: QueryShape; @@ -308,7 +315,7 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { case 'stack-chart': // Stack chart uses all of the CallTree's query strings but also has // additional query strings. - query = (baseQuery: StackChartQueryShape); + query = baseQuery as StackChartQueryShape; query.showUserTimings = urlState.profileSpecific.showUserTimings ? null : undefined; @@ -318,7 +325,7 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { /* fallsthrough */ case 'flame-graph': case 'calltree': { - query = (baseQuery: CallTreeQueryShape); + query = baseQuery as CallTreeQueryShape; query.search = urlState.profileSpecific.callTreeSearchString || undefined; query.invertCallstack = urlState.profileSpecific.invertCallstack @@ -355,17 +362,17 @@ export function getQueryStringFromUrlState(urlState: UrlState): string { } case 'marker-table': case 'marker-chart': - query = (baseQuery: MarkersQueryShape); + query = baseQuery as MarkersQueryShape; query.markerSearch = urlState.profileSpecific.markersSearchString || undefined; break; case 'network-chart': - query = (baseQuery: NetworkQueryShape); + query = baseQuery as NetworkQueryShape; query.networkSearch = urlState.profileSpecific.networkSearchString || undefined; break; case 'js-tracer': { - query = (baseQuery: JsTracerQueryShape); + query = baseQuery as JsTracerQueryShape; query.summary = urlState.profileSpecific.showJsTracerSummary ? null : undefined; @@ -427,9 +434,9 @@ export function ensureIsValidDataSource( * so that it can be mocked in tests. */ type Location = { - pathname: string, - search: string, - hash: string, + pathname: string; + search: string; + hash: string; }; /** @@ -478,14 +485,14 @@ export function stateFromLocation( // The selected tab is the last path part in the URL. const selectedTabPathPart = hasProfileHash || hasProfileUrl ? 2 : 1; - let implementation = 'combined'; + let implementation: 'combined' | 'js' | 'cpp' = 'combined'; // Don't trust the implementation values from the user. Make sure it conforms // to known values. if (query.implementation === 'js' || query.implementation === 'cpp') { implementation = query.implementation; } - const transforms = {}; + const transforms: { [key: string]: Transform[] } = {}; if (selectedThreadsKey !== null) { transforms[selectedThreadsKey] = parseTransforms(query.transforms); } @@ -509,7 +516,7 @@ export function stateFromLocation( nativeSymbol: null, allNativeSymbolsForInitiatingCallNode: [], }; - const isBottomBoxOpenPerPanel = {}; + const isBottomBoxOpenPerPanel: any = {}; tabSlugs.forEach((tabSlug) => (isBottomBoxOpenPerPanel[tabSlug] = false)); if (query.sourceView) { sourceView.sourceFile = query.sourceView; @@ -714,19 +721,20 @@ function convertLocalTrackOrderByPidToString( // errors. // Exported for tests. export class UrlUpgradeError extends Error { - name = 'UrlUpgradeError'; + override name = 'UrlUpgradeError'; } -type ProcessedLocation = {| - pathname: string, - hash: string, - query: Query, -|}; +type ProcessedLocation = { + pathname: string; + hash: string; + query: Query; +}; -type ProcessedLocationBeforeUpgrade = {| - ...ProcessedLocation, - query: any, -|}; +type ProcessedLocationBeforeUpgrade = { + pathname: string; + hash: string; + query: any; +}; // URL upgrading is skipped if the profile argument is null. // URL upgrading is performed if the profile argument is missing (undefined) or if it's an actual profile. @@ -775,16 +783,18 @@ export function upgradeLocationToCurrentVersion( return processedLocation; } +type ProcessedLocationUpgrader = ( + location: ProcessedLocationBeforeUpgrade, + profile?: Profile +) => void; + // _upgraders[i] converts from version i - 1 to version i. // Every "upgrader" takes the processedLocation as its first argument and mutates it. // If available, the profile is passed as the second argument, for any upgraders that need it. /* eslint-disable no-useless-computed-key */ -const _upgraders: {| - [number]: ( - location: ProcessedLocationBeforeUpgrade, - profile?: Profile - ) => void, -|} = { +const _upgraders: { + [key: number]: ProcessedLocationUpgrader; +} = { [1]: (processedLocation: ProcessedLocationBeforeUpgrade) => { // Version 1 is the first versioned url. Do some best-effort upgrading from // un-versioned URLs. @@ -820,7 +830,7 @@ const _upgraders: {| processedLocation.query.transforms = processedLocation.query.callTreeFilters .split('~') - .map((s) => { + .map((s: string) => { const [type, val] = s.split('-'); switch (type) { case 'prefix': @@ -835,7 +845,7 @@ const _upgraders: {| return undefined; } }) - .filter((f) => f) + .filter((f: string | undefined) => f) .join('~'); delete processedLocation.query.callTreeFilters; } @@ -894,8 +904,9 @@ const _upgraders: {| for (let i = 0; i < transforms.length; i++) { const transform = transforms[i]; if ( - !transform.implementation || + !('implementation' in transform) || transform.implementation !== 'js' || + !('callNodePath' in transform) || !transform.callNodePath ) { // Only transforms with JS implementation filters that have callNodePaths @@ -917,8 +928,7 @@ const _upgraders: {| // If we can't find the stack index of given call node path, just abort. continue; } - // This property is not writable, make it an "any" - (transform: any).callNodePath = getVersion4JSCallNodePathFromStackIndex( + transform.callNodePath = getVersion4JSCallNodePathFromStackIndex( thread, callNodeStackIndex ); @@ -944,7 +954,7 @@ const _upgraders: {| query.range = query.range .split('~') - .map((committedRange) => { + .map((committedRange: string) => { // This regexp captures two (positive or negative) numbers, separated by a `_`. const m = committedRange.match(/^(-?[0-9.]+)_(-?[0-9.]+)$/); if (!m) { @@ -971,7 +981,7 @@ const _upgraders: {| // The tracks-related query arguments have been converted to use uintarray-encoding. // Update them from the 0-10-9-8-1-2-3-4-5-6-7 syntax to the "0aw81w7" syntax. if (query.globalTrackOrder) { - const globalTrackOrder = (query.globalTrackOrder: string) + const globalTrackOrder = (query.globalTrackOrder as string) .split('-') .map((s) => +s); query.globalTrackOrder = @@ -979,13 +989,13 @@ const _upgraders: {| } if (query.hiddenGlobalTracks) { const hiddenGlobalTracks = new Set( - (query.hiddenGlobalTracks: string).split('-').map((s) => +s) + (query.hiddenGlobalTracks as string).split('-').map((s) => +s) ); query.hiddenGlobalTracks = encodeUintSetForUrlComponent(hiddenGlobalTracks) || undefined; } if (query.hiddenLocalTracksByPid) { - query.hiddenLocalTracksByPid = (query.hiddenLocalTracksByPid: string) + query.hiddenLocalTracksByPid = (query.hiddenLocalTracksByPid as string) .split('~') .map((pidAndTracks) => { // TODO: handle escaped dashes and tildes in pid strings (#4512) @@ -996,7 +1006,7 @@ const _upgraders: {| .join('~'); } if (query.localTrackOrderByPid) { - query.localTrackOrderByPid = (query.localTrackOrderByPid: string) + query.localTrackOrderByPid = (query.localTrackOrderByPid as string) .split('~') .map((pidAndTracks) => { // TODO: handle escaped dashes and tildes in pid strings (#4512) @@ -1007,8 +1017,12 @@ const _upgraders: {| .join('~'); } if (query.thread) { - const selectedThreads = new Set(query.thread.split(',').map((n) => +n)); - query.thread = encodeUintSetForUrlComponent(selectedThreads); + const selectedThreads = new Set( + query.thread.split(',').map((n: string) => +n) + ); + query.thread = encodeUintSetForUrlComponent( + selectedThreads as Set + ); } // In this version, uintarray-encoding started supporting a range syntax: @@ -1063,7 +1077,7 @@ const _upgraders: {| // The "collapse recursion" transforms have been renamed: // irec-{implementation}-{funcIndex} -> rec-{funcIndex} // rec-{implementation}-{funcIndex} -> drec-{implementation}-{funcIndex} - function upgradeTransformString(transformString) { + function upgradeTransformString(transformString: string) { // Collapse recursion (formerly "collapse indirect recursion") if (transformString.startsWith('irec-')) { // irec-{implementation}-{funcIndex} -> rec-{funcIndex} @@ -1121,7 +1135,7 @@ const _upgraders: {| // cr-{implementation}-{resourceIndex}-{wrongFuncIndex} // -> cr-{implementation}-{resourceIndex}-{correctFuncIndex} - function upgradeTransformString(transformString) { + function upgradeTransformString(transformString: string) { if (transformString.startsWith('cr-')) { const [, implementation, resourceIndex] = transformString.split('-'); const funcIndex = funcTableLength + +resourceIndex; @@ -1223,9 +1237,9 @@ function getVersion4JSCallNodePathFromStackIndex( ): CallNodePath { const { funcTable, stackTable, frameTable } = thread; const callNodePath = []; - let nextStackIndex = stackIndex; + let nextStackIndex: IndexIntoStackTable | null = stackIndex; while (nextStackIndex !== null) { - const frameIndex = stackTable.frame[nextStackIndex]; + const frameIndex: IndexIntoFrameTable = stackTable.frame[nextStackIndex]; const funcIndex = frameTable.func[frameIndex]; if (funcTable.isJS[funcIndex] || funcTable.relevantForJS[funcIndex]) { callNodePath.unshift(funcIndex); @@ -1239,20 +1253,18 @@ function getVersion4JSCallNodePathFromStackIndex( * Validate the timeline type and fall back to the category type if it's not * provided or something else is provided for some reason. */ -function validateTimelineType(type: ?string): TimelineType { - // Pretend this is a TimelineType so that we can exhaustively go through - // each option. - const timelineType: TimelineType = (type: any); - switch (timelineType) { - case 'stack': - case 'cpu-category': - case 'category': - return timelineType; - default: - // Type assert we've checked everything: - (timelineType: empty); - return 'cpu-category'; +function validateTimelineType( + timelineType: string | null | undefined +): TimelineType { + const VALID_TIMELINE_TYPES: Record = { + stack: true, + category: true, + 'cpu-category': true, + }; + if (timelineType && timelineType in VALID_TIMELINE_TYPES) { + return timelineType as TimelineType; } + return 'cpu-category'; } /** diff --git a/src/app-logic/web-channel.js b/src/app-logic/web-channel.ts similarity index 72% rename from src/app-logic/web-channel.js rename to src/app-logic/web-channel.ts index 81921c7625..2dbe25fbf0 100644 --- a/src/app-logic/web-channel.js +++ b/src/app-logic/web-channel.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import type { SymbolTableAsTuple } from '../profile-logic/symbol-store-db'; import type { Milliseconds, @@ -17,9 +15,9 @@ import type { * allows us to safely send messages between the browser and an allowed domain. */ -export type MessageToBrowser = {| - requestId: number, -|} & Request; +export type MessageToBrowser = { + requestId: number; +} & Request; export type Request = | StatusQueryRequest @@ -32,61 +30,61 @@ export type Request = | GetPageFaviconsRequest | OpenScriptInTabDebuggerRequest; -type StatusQueryRequest = {| type: 'STATUS_QUERY' |}; -type EnableMenuButtonRequest = {| type: 'ENABLE_MENU_BUTTON' |}; -type GetProfileRequest = {| type: 'GET_PROFILE' |}; -type GetExternalMarkersRequest = {| - type: 'GET_EXTERNAL_MARKERS', - startTime: Milliseconds, - endTime: Milliseconds, -|}; -type GetExternalPowerTracksRequest = {| - type: 'GET_EXTERNAL_POWER_TRACKS', - startTime: Milliseconds, - endTime: Milliseconds, -|}; -type GetSymbolTableRequest = {| - type: 'GET_SYMBOL_TABLE', - debugName: string, - breakpadId: string, -|}; -type QuerySymbolicationApiRequest = {| - type: 'QUERY_SYMBOLICATION_API', - path: string, - requestJson: string, -|}; -type GetPageFaviconsRequest = {| - type: 'GET_PAGE_FAVICONS', - pageUrls: Array, -|}; -type OpenScriptInTabDebuggerRequest = {| - type: 'OPEN_SCRIPT_IN_DEBUGGER', - tabId: number, - scriptUrl: string, - line: number | null, - column: number | null, -|}; +type StatusQueryRequest = { type: 'STATUS_QUERY' }; +type EnableMenuButtonRequest = { type: 'ENABLE_MENU_BUTTON' }; +type GetProfileRequest = { type: 'GET_PROFILE' }; +type GetExternalMarkersRequest = { + type: 'GET_EXTERNAL_MARKERS'; + startTime: Milliseconds; + endTime: Milliseconds; +}; +type GetExternalPowerTracksRequest = { + type: 'GET_EXTERNAL_POWER_TRACKS'; + startTime: Milliseconds; + endTime: Milliseconds; +}; +type GetSymbolTableRequest = { + type: 'GET_SYMBOL_TABLE'; + debugName: string; + breakpadId: string; +}; +type QuerySymbolicationApiRequest = { + type: 'QUERY_SYMBOLICATION_API'; + path: string; + requestJson: string; +}; +type GetPageFaviconsRequest = { + type: 'GET_PAGE_FAVICONS'; + pageUrls: Array; +}; +type OpenScriptInTabDebuggerRequest = { + type: 'OPEN_SCRIPT_IN_DEBUGGER'; + tabId: number; + scriptUrl: string; + line: number | null; + column: number | null; +}; -export type MessageFromBrowser = +export type MessageFromBrowser = | OutOfBandErrorMessageFromBrowser | ErrorResponseMessageFromBrowser | SuccessResponseMessageFromBrowser; -type OutOfBandErrorMessageFromBrowser = {| - errno: number, - error: string, -|}; - -type ErrorResponseMessageFromBrowser = {| - type: 'ERROR_RESPONSE', - requestId: number, - error: string, -|}; - -type SuccessResponseMessageFromBrowser = { - type: 'SUCCESS_RESPONSE', - requestId: number, - response: R, +type OutOfBandErrorMessageFromBrowser = { + errno: number; + error: string; +}; + +type ErrorResponseMessageFromBrowser = { + type: 'ERROR_RESPONSE'; + requestId: number; + error: string; +}; + +type SuccessResponseMessageFromBrowser = { + type: 'SUCCESS_RESPONSE'; + requestId: number; + response: R; }; export type ResponseFromBrowser = @@ -100,8 +98,8 @@ export type ResponseFromBrowser = | GetPageFaviconsResponse | OpenScriptInTabDebuggerResponse; -type StatusQueryResponse = {| - menuButtonIsEnabled: boolean, +type StatusQueryResponse = { + menuButtonIsEnabled: boolean; // The version indicates which message types are supported by the browser. // No version: // Shipped in Firefox 76. @@ -130,8 +128,8 @@ type StatusQueryResponse = {| // Shipped in Firefox 136. // Adds support for showing the JS script in DevTools debugger. // - OPEN_SCRIPT_IN_DEBUGGER - version?: number, -|}; + version?: number; +}; type EnableMenuButtonResponse = void; type GetProfileResponse = ArrayBuffer | MixedObject; type GetExternalMarkersResponse = ExternalMarkersData; @@ -141,36 +139,127 @@ type QuerySymbolicationApiResponse = string; type GetPageFaviconsResponse = Array; type OpenScriptInTabDebuggerResponse = void; -// Manually declare all pairs of request + response for Flow. -/* eslint-disable no-redeclare */ -declare function _sendMessageWithResponse( - StatusQueryRequest +// TypeScript function overloads for request/response pairs. +function _sendMessageWithResponse( + request: StatusQueryRequest ): Promise; -declare function _sendMessageWithResponse( - EnableMenuButtonRequest +function _sendMessageWithResponse( + request: EnableMenuButtonRequest ): Promise; -declare function _sendMessageWithResponse( - GetProfileRequest +function _sendMessageWithResponse( + request: GetProfileRequest ): Promise; -declare function _sendMessageWithResponse( - GetExternalMarkersRequest +function _sendMessageWithResponse( + request: GetExternalMarkersRequest ): Promise; -declare function _sendMessageWithResponse( - GetExternalPowerTracksRequest +function _sendMessageWithResponse( + request: GetExternalPowerTracksRequest ): Promise; -declare function _sendMessageWithResponse( - GetSymbolTableRequest +function _sendMessageWithResponse( + request: GetSymbolTableRequest ): Promise; -declare function _sendMessageWithResponse( - QuerySymbolicationApiRequest +function _sendMessageWithResponse( + request: QuerySymbolicationApiRequest ): Promise; -declare function _sendMessageWithResponse( - GetPageFaviconsRequest +function _sendMessageWithResponse( + request: GetPageFaviconsRequest ): Promise; -declare function _sendMessageWithResponse( - OpenScriptInTabDebuggerRequest +function _sendMessageWithResponse( + request: OpenScriptInTabDebuggerRequest ): Promise; -/* eslint-enable no-redeclare */ +function _sendMessageWithResponse(request: Request): Promise { + const requestId = _requestId++; + const type = request.type; + + return new Promise((resolve, reject) => { + function listener(event: any) { + const { id, message } = event.detail; + + // Don't trust the message too much, and do some checking for known properties. + if ( + id === 'profiler.firefox.com' && + message && + typeof message === 'object' + ) { + _fixupOldResponseMessageIfNeeded(message); + + // Make the type system assume that we have the right message. + const messageFromBrowser: MessageFromBrowser = + message as MessageFromBrowser; + + if ('type' in messageFromBrowser && messageFromBrowser.type) { + if ( + 'requestId' in messageFromBrowser && + messageFromBrowser.requestId === requestId + ) { + if (process.env.NODE_ENV === 'development') { + console.log( + `[webchannel] %creceived response to "${type}"`, + LOG_STYLE, + messageFromBrowser + ); + } + window.removeEventListener( + 'WebChannelMessageToContent', + listener, + true + ); + + if (messageFromBrowser.type === 'SUCCESS_RESPONSE') { + resolve( + ( + messageFromBrowser as SuccessResponseMessageFromBrowser + ).response + ); + } else { + reject( + new Error( + (messageFromBrowser as ErrorResponseMessageFromBrowser).error + ) + ); + } + } + } else if ( + 'error' in messageFromBrowser && + typeof messageFromBrowser.error === 'string' + ) { + // There was some kind of error with the message. This is expected for older + // versions of Firefox that don't have this WebChannel set up yet, or + // if the about:config preference points to a different URL. + console.error( + `[webchannel] %c${(messageFromBrowser as OutOfBandErrorMessageFromBrowser).error}`, + LOG_STYLE + ); + window.removeEventListener( + 'WebChannelMessageToContent', + listener, + true + ); + reject( + new WebChannelError( + messageFromBrowser as OutOfBandErrorMessageFromBrowser + ) + ); + } + } else { + reject(new Error('A malformed WebChannel event was received.')); + console.error( + `[webchannel] %cmalformed event received`, + LOG_STYLE, + event + ); + } + } + + window.addEventListener('WebChannelMessageToContent', listener, true); + + // Add the requestId to the message. + _sendMessage({ + requestId, + ...request, + } as MessageToBrowser); + }); +} /** * Ask the browser if the menu button is enabled. @@ -218,7 +307,7 @@ export async function getSymbolTableViaWebChannel( } export async function getProfileViaWebChannel(): Promise< - ArrayBuffer | MixedObject, + ArrayBuffer | MixedObject > { return _sendMessageWithResponse({ type: 'GET_PROFILE', @@ -312,7 +401,7 @@ function _sendMessage(message: MessageToBrowser) { let _requestId = 0; export class WebChannelError extends Error { - name = 'WebChannelError'; + override name = 'WebChannelError'; errno: number; constructor(rawError: OutOfBandErrorMessageFromBrowser) { super(`${rawError.error} (errno: ${rawError.errno})`); @@ -320,87 +409,6 @@ export class WebChannelError extends Error { } } -// eslint-disable-next-line no-redeclare -function _sendMessageWithResponse( - Request: Request -): Promise { - const requestId = _requestId++; - const type = Request.type; - - return new Promise((resolve, reject) => { - function listener(event) { - const { id, message } = event.detail; - - // Don't trust the message too much, and do some checking for known properties. - if ( - id === 'profiler.firefox.com' && - message && - typeof message === 'object' - ) { - _fixupOldResponseMessageIfNeeded(message); - - // Make the type system assume that we have the right message. - const messageFromBrowser: MessageFromBrowser = - (message: any); - - if (messageFromBrowser.type) { - if (messageFromBrowser.requestId === requestId) { - if (process.env.NODE_ENV === 'development') { - console.log( - `[webchannel] %creceived response to "${type}"`, - LOG_STYLE, - messageFromBrowser - ); - } - window.removeEventListener( - 'WebChannelMessageToContent', - listener, - true - ); - - if (messageFromBrowser.type === 'SUCCESS_RESPONSE') { - resolve(messageFromBrowser.response); - } else { - reject(new Error(messageFromBrowser.error)); - } - } - } else if (typeof messageFromBrowser.error === 'string') { - // There was some kind of error with the message. This is expected for older - // versions of Firefox that don't have this WebChannel set up yet, or - // if the about:config preference points to a different URL. - console.error( - `[webchannel] %c${messageFromBrowser.error}`, - LOG_STYLE - ); - window.removeEventListener( - 'WebChannelMessageToContent', - listener, - true - ); - reject(new WebChannelError(messageFromBrowser)); - } - } else { - reject(new Error('A malformed WebChannel event was received.')); - console.error( - `[webchannel] %cmalformed event received`, - LOG_STYLE, - event - ); - } - } - - window.addEventListener('WebChannelMessageToContent', listener, true); - - // Add the requestId to the message. - _sendMessage( - ({ - requestId, - ...Request, - }: any) - ); - }); -} - // This can be removed once the oldest supported Firefox ESR version is 93 or newer. function _fixupOldResponseMessageIfNeeded(message: MixedObject) { if (message.type === 'STATUS_RESPONSE') { diff --git a/src/profile-logic/address-locator.js b/src/profile-logic/address-locator.ts similarity index 92% rename from src/profile-logic/address-locator.js rename to src/profile-logic/address-locator.ts index 0ec915acd1..5440e66504 100644 --- a/src/profile-logic/address-locator.js +++ b/src/profile-logic/address-locator.ts @@ -1,7 +1,6 @@ /* 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/. */ -/* eslint-disable flowtype/require-valid-file-annotation */ // Find an address in the list of libraries, and convert the address from // an absolute virtual memory address into a library-relative address. @@ -23,9 +22,11 @@ // always be represented accurately by a JS number, because libraries are always // small enough. +import type { LibMapping } from 'firefox-profiler/types'; + export class AddressLocator { - _libs /* : LibMapping[] */; - _libRanges /* : Array<{| baseAddress: BigInt, start: BigInt, end: BigInt |}> */; + _libs: LibMapping[]; + _libRanges: Array<{ baseAddress: bigint; start: bigint; end: bigint }>; /** * Create an AddressLocator for an array of libs. @@ -33,7 +34,7 @@ export class AddressLocator { * ranges of the libraries need to be non-overlapping. * @param {Libs[]} libs The array of libraries, ordered by start address. */ - constructor(libs) { + constructor(libs: LibMapping[]) { this._libs = libs; this._libRanges = libs.map((lib) => { const start = BigInt(lib.start); @@ -55,7 +56,10 @@ export class AddressLocator { * @param {string} addressHexString The address, as a hex string, including the leading "0x". * @return {Object} The library object (and its index) if found, and the address relative to that library. */ - locateAddress(addressHexString) { + locateAddress(addressHexString: string): { + lib: LibMapping | null; + relativeAddress: number; + } { // Diagram of the various offsets and spaces: // // process virtual memory diff --git a/src/profile-logic/address-timings.js b/src/profile-logic/address-timings.ts similarity index 99% rename from src/profile-logic/address-timings.js rename to src/profile-logic/address-timings.ts index 657895bddc..b1872d9ea4 100644 --- a/src/profile-logic/address-timings.js +++ b/src/profile-logic/address-timings.ts @@ -2,8 +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/. */ -// @flow - /** * In this file, "address" always means "instruction address", expressed as a * byte offset into a given library ("relative address"). @@ -140,7 +138,7 @@ export function getStackAddressInfo( // "self address" == "the address which a stack's self time is contributed to" const selfAddressForAllStacks = []; // "total addresses" == "the set of addresses whose total time this stack contributes to" - const totalAddressesForAllStacks = []; + const totalAddressesForAllStacks: Array | null> = []; // This loop takes advantage of the fact that the stack table is topologically ordered: // Prefix stacks are always visited before their descendants. @@ -358,7 +356,7 @@ export function getStackAddressInfoForCallNodeNonInverted( const callNodeSelfAddressForAllStacks = []; // "total addresses" == "the set of addresses whose total time this stack contributes to" // Either null or a single-element set. - const callNodeTotalAddressesForAllStacks = []; + const callNodeTotalAddressesForAllStacks: Array | null> = []; // This loop takes advantage of the fact that the stack table is topologically ordered: // Prefix stacks are always visited before their descendants. diff --git a/src/profile-logic/call-node-info.js b/src/profile-logic/call-node-info.ts similarity index 97% rename from src/profile-logic/call-node-info.js rename to src/profile-logic/call-node-info.ts index d5a1943d16..f00b47bbf8 100644 --- a/src/profile-logic/call-node-info.js +++ b/src/profile-logic/call-node-info.ts @@ -2,8 +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/. */ -// @flow - import { hashPath, concatHash, @@ -213,7 +211,7 @@ export class CallNodeInfoNonInverted implements CallNodeInfo { } // Contributing to the shared cache - const hash = sliceHashes.pop(); + const hash = sliceHashes.pop()!; cache.set(hash, nextNodeIndex); index = nextNodeIndex; @@ -307,37 +305,37 @@ type IndexIntoInvertedNonRootCallNodeTable = number; // information upfront for all roots. The root count is fixed, so most of the // arrays in this struct are fixed-size typed arrays. // The number of roots is the same as the number of functions in the funcTable. -type InvertedRootCallNodeTable = {| - category: Int32Array, // IndexIntoFuncTable -> IndexIntoCategoryList - subcategory: Int32Array, // IndexIntoFuncTable -> IndexIntoSubcategoryListForCategory - innerWindowID: Float64Array, // IndexIntoFuncTable -> InnerWindowID +type InvertedRootCallNodeTable = { + category: Int32Array; // IndexIntoFuncTable -> IndexIntoCategoryList + subcategory: Int32Array; // IndexIntoFuncTable -> IndexIntoSubcategoryListForCategory + innerWindowID: Float64Array; // IndexIntoFuncTable -> InnerWindowID // IndexIntoNativeSymbolTable: all frames that collapsed into this call node inlined into the same native symbol // -1: divergent: some, but not all, frames that collapsed into this call node were inlined, or they are from different symbols // -2: no inlining - sourceFramesInlinedIntoSymbol: Int32Array, // IndexIntoFuncTable -> IndexIntoNativeSymbolTable | -1 | -2 + sourceFramesInlinedIntoSymbol: Int32Array; // IndexIntoFuncTable -> IndexIntoNativeSymbolTable | -1 | -2 // The (exclusive) end of the suffix order index range for each root node. // The beginning of the range is given by suffixOrderIndexRangeEnd[i - 1], or by // zero. This is possible because both the inverted root order and the suffix order // are determined by the func order. - suffixOrderIndexRangeEnd: Uint32Array, // IndexIntoFuncTable -> SuffixOrderIndex, - length: number, -|}; + suffixOrderIndexRangeEnd: Uint32Array; // IndexIntoFuncTable -> SuffixOrderIndex, + length: number; +}; // Information about the non-root nodes of the inverted call tree. This table // grows on-demand, as new inverted call nodes are materialized. -type InvertedNonRootCallNodeTable = {| - prefix: InvertedCallNodeHandle[], - func: IndexIntoFuncTable[], // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoFuncTable - pathHash: string[], // IndexIntoInvertedNonRootCallNodeTable -> string - category: IndexIntoCategoryList[], // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoCategoryList - subcategory: IndexIntoSubcategoryListForCategory[], // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoSubcategoryListForCategory - innerWindowID: InnerWindowID[], // IndexIntoInvertedNonRootCallNodeTable -> InnerWindowID +type InvertedNonRootCallNodeTable = { + prefix: InvertedCallNodeHandle[]; + func: IndexIntoFuncTable[]; // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoFuncTable + pathHash: string[]; // IndexIntoInvertedNonRootCallNodeTable -> string + category: IndexIntoCategoryList[]; // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoCategoryList + subcategory: IndexIntoSubcategoryListForCategory[]; // IndexIntoInvertedNonRootCallNodeTable -> IndexIntoSubcategoryListForCategory + innerWindowID: InnerWindowID[]; // IndexIntoInvertedNonRootCallNodeTable -> InnerWindowID // IndexIntoNativeSymbolTable: all frames that collapsed into this call node inlined into the same native symbol // -1: divergent: some, but not all, frames that collapsed into this call node were inlined, or they are from different symbols // -2: no inlining - sourceFramesInlinedIntoSymbol: Array, - suffixOrderIndexRangeStart: SuffixOrderIndex[], // IndexIntoInvertedNonRootCallNodeTable -> SuffixOrderIndex - suffixOrderIndexRangeEnd: SuffixOrderIndex[], // IndexIntoInvertedNonRootCallNodeTable -> SuffixOrderIndex + sourceFramesInlinedIntoSymbol: Array; + suffixOrderIndexRangeStart: SuffixOrderIndex[]; // IndexIntoInvertedNonRootCallNodeTable -> SuffixOrderIndex + suffixOrderIndexRangeEnd: SuffixOrderIndex[]; // IndexIntoInvertedNonRootCallNodeTable -> SuffixOrderIndex // Non-null for non-root nodes whose children haven't been created yet. // The array at index x caches ancestors of the non-inverted nodes belonging @@ -353,11 +351,11 @@ type InvertedNonRootCallNodeTable = {| // For every suffix order index i in suffixOrderIndexRangeStart[x]..suffixOrderIndexRangeEnd[x], // the k'th parent node of suffixOrderedCallNodes[i] is stored at // deepNodes[x][i - suffixOrderIndexRangeStart[x]], with k = depth[x]. - deepNodes: Array, // IndexIntoInvertedNonRootCallNodeTable -> (Uint32Array | null) + deepNodes: Array; // IndexIntoInvertedNonRootCallNodeTable -> (Uint32Array | null) - depth: number[], // IndexIntoInvertedNonRootCallNodeTable -> number - length: number, -|}; + depth: number[]; // IndexIntoInvertedNonRootCallNodeTable -> number + length: number; +}; // Compute the InvertedRootCallNodeTable. // We compute this information upfront for all roots. The root count is fixed - @@ -493,11 +491,11 @@ function _createEmptyInvertedNonRootCallNodeTable(): InvertedNonRootCallNodeTabl // refined up to depth zero. It is refined enough so that every root has a // contiguous range in the suffix order, where each range contains the root's // corresponding non-inverted nodes. -type SuffixOrderForInvertedRoots = {| - suffixOrderedCallNodes: Uint32Array, - suffixOrderIndexes: Uint32Array, - rootSuffixOrderIndexRangeEndCol: Uint32Array, -|}; +type SuffixOrderForInvertedRoots = { + suffixOrderedCallNodes: Uint32Array; + suffixOrderIndexes: Uint32Array; + rootSuffixOrderIndexRangeEndCol: Uint32Array; +}; /** * Computes an ordering for the non-inverted call node table where all @@ -563,21 +561,21 @@ function _computeSuffixOrderForInvertedRoots( } // Information used to create the children of a node in the inverted tree. -type ChildrenInfo = {| +type ChildrenInfo = { // The func for each child. Duplicate-free and sorted by func. - funcPerChild: Uint32Array, // IndexIntoFuncTable[] + funcPerChild: Uint32Array; // IndexIntoFuncTable[] // The number of deep nodes for each child. Every entry is non-zero. - deepNodeCountPerChild: Uint32Array, + deepNodeCountPerChild: Uint32Array; // The subset of the parent's self nodes which are not part of childrenSelfNodes. - selfNodesWhichEndAtParent: IndexIntoCallNodeTable[], + selfNodesWhichEndAtParent: IndexIntoCallNodeTable[]; // The self nodes and their corresponding deep nodes for all children, each // flattened into a single array. // The length of these arrays is the sum of the values in deepNodeCountPerChild. - childrenSelfNodes: Uint32Array, - childrenDeepNodes: Uint32Array, + childrenSelfNodes: Uint32Array; + childrenDeepNodes: Uint32Array; // The suffixOrderIndexRangeStart of the first child. - childrenSuffixOrderIndexRangeStart: number, -|}; + childrenSuffixOrderIndexRangeStart: number; +}; // An index into SuffixOrderedCallNodes. export type SuffixOrderIndex = number; diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.ts similarity index 96% rename from src/profile-logic/call-tree.js rename to src/profile-logic/call-tree.ts index c14f6001aa..72b0376d31 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { oneLine } from 'common-tags'; import { timeCode } from '../utils/time-code'; import { @@ -29,6 +27,7 @@ import type { BottomBoxInfo, CallNodeSelfAndSummary, SelfAndTotal, + BalancedNativeAllocationsTable, } from 'firefox-profiler/types'; import ExtensionIcon from '../../res/img/svg/extension.svg'; @@ -41,39 +40,39 @@ import type { CallNodeInfo, CallNodeInfoInverted } from './call-node-info'; type CallNodeChildren = IndexIntoCallNodeTable[]; -export type CallTreeTimingsNonInverted = {| - callNodeHasChildren: Uint8Array, - self: Float64Array, - total: Float64Array, - rootTotalSummary: number, // sum of absolute values, this is used for computing percentages -|}; - -type TotalAndHasChildren = {| total: number, hasChildren: boolean |}; - -export type InvertedCallTreeRoot = {| - totalAndHasChildren: TotalAndHasChildren, - func: IndexIntoFuncTable, -|}; - -export type CallTreeTimingsInverted = {| - callNodeSelf: Float64Array, - rootTotalSummary: number, - sortedRoots: IndexIntoFuncTable[], - totalPerRootFunc: Float64Array, - hasChildrenPerRootFunc: Uint8Array, -|}; - -export type CallTreeTimingsFunctionList = {| - funcSelf: Float64Array, - funcTotal: Float64Array, - sortedFuncs: IndexIntoFuncTable[], - rootTotalSummary: number, -|}; +export type CallTreeTimingsNonInverted = { + callNodeHasChildren: Uint8Array; + self: Float64Array; + total: Float64Array; + rootTotalSummary: number; // sum of absolute values, this is used for computing percentages +}; + +type TotalAndHasChildren = { total: number; hasChildren: boolean }; + +export type InvertedCallTreeRoot = { + totalAndHasChildren: TotalAndHasChildren; + func: IndexIntoFuncTable; +}; + +export type CallTreeTimingsInverted = { + callNodeSelf: Float64Array; + rootTotalSummary: number; + sortedRoots: IndexIntoFuncTable[]; + totalPerRootFunc: Float64Array; + hasChildrenPerRootFunc: Uint8Array; +}; + +export type CallTreeTimingsFunctionList = { + funcSelf: Float64Array; + funcTotal: Float64Array; + sortedFuncs: IndexIntoFuncTable[]; + rootTotalSummary: number; +}; export type CallTreeTimings = - | {| type: 'NON_INVERTED', timings: CallTreeTimingsNonInverted |} - | {| type: 'FUNCTION_LIST', timings: CallTreeTimingsFunctionList |} - | {| type: 'INVERTED', timings: CallTreeTimingsInverted |}; + | { type: 'NON_INVERTED'; timings: CallTreeTimingsNonInverted } + | { type: 'FUNCTION_LIST'; timings: CallTreeTimingsFunctionList } + | { type: 'INVERTED'; timings: CallTreeTimingsInverted }; /** * Gets the CallTreeTimingsNonInverted out of a CallTreeTimings object. @@ -202,7 +201,6 @@ export class CallTreeInternalNonInverted implements CallTreeInternal { export class CallTreeInternalFunctionList implements CallTreeInternal { _timings: CallTreeTimingsFunctionList; - _roots: IndexIntoCallNodeTable[]; constructor(timings: CallTreeTimingsFunctionList) { this._timings = timings; @@ -245,7 +243,7 @@ class CallTreeInternalInverted implements CallTreeInternal { _hasChildrenPerRootFunc: Uint8Array; _totalAndHasChildrenPerNonRootNode: Map< IndexIntoCallNodeTable, - TotalAndHasChildren, + TotalAndHasChildren > = new Map(); constructor( @@ -421,7 +419,7 @@ export class CallTree { getAllDescendants( callNodeIndex: IndexIntoCallNodeTable ): Set { - const result = new Set(); + const result = new Set(); this._addDescendantsToSet(callNodeIndex, result); return result; } @@ -458,7 +456,7 @@ export class CallTree { _getInliningBadge( callNodeIndex: IndexIntoCallNodeTable, funcName: string - ): ExtraBadgeInfo | void { + ): ExtraBadgeInfo | undefined { const calledFunction = getFunctionName(funcName); const inlinedIntoNativeSymbol = this._callNodeInfo.sourceFramesInlinedIntoSymbolForNode(callNodeIndex); @@ -723,7 +721,10 @@ export function getSelfAndTotalForCallNode( return { self: 0, total }; } default: - throw assertExhaustiveCheck(callTreeTimings.type); + throw assertExhaustiveCheck( + callTreeTimings as never, + 'callTreeTimings.type' + ); } } @@ -921,7 +922,7 @@ function _computeFuncTotal( callNodeFuncIsDuplicate: CallNodeTableBitSet, callNodeSelf: Float64Array, funcCount: number -): { funcTotal: Float64Array, sortedFuncs: IndexIntoFuncTable[] } { +): { funcTotal: Float64Array; sortedFuncs: IndexIntoFuncTable[] } { const callNodeTableFuncCol = callNodeTable.func; const callNodeTablePrefixCol = callNodeTable.prefix; const callNodeCount = callNodeTable.length; @@ -1094,7 +1095,7 @@ export function getCallTree( } default: throw assertExhaustiveCheck( - callTreeTimings.type, + callTreeTimings as never, 'Unhandled CallTreeTimings type.' ); } @@ -1133,12 +1134,14 @@ export function extractSamplesLikeTable( ); /* istanbul ignore if */ - if (!nativeAllocations.memoryAddress) { + if (!('memoryAddress' in nativeAllocations)) { throw new Error( 'Attempting to filter by retained allocations data that is missing the memory addresses.' ); } - return ProfileData.filterToRetainedAllocations(nativeAllocations); + return ProfileData.filterToRetainedAllocations( + nativeAllocations as BalancedNativeAllocationsTable + ); } case 'native-allocations': return ProfileData.filterToAllocations( @@ -1161,7 +1164,7 @@ export function extractSamplesLikeTable( ); /* istanbul ignore if */ - if (!nativeAllocations.memoryAddress) { + if (!('memoryAddress' in nativeAllocations)) { throw new Error( 'Attempting to filter by retained allocations data that is missing the memory addresses.' ); @@ -1169,7 +1172,7 @@ export function extractSamplesLikeTable( return ProfileData.filterToDeallocationsMemory( ensureExists( - nativeAllocations, + nativeAllocations as BalancedNativeAllocationsTable, 'Expected the NativeAllocationTable to exist when using a "js-allocation" strategy' ) ); diff --git a/src/profile-logic/committed-ranges.js b/src/profile-logic/committed-ranges.ts similarity index 98% rename from src/profile-logic/committed-ranges.js rename to src/profile-logic/committed-ranges.ts index a15bbf26d2..c3f353478d 100644 --- a/src/profile-logic/committed-ranges.js +++ b/src/profile-logic/committed-ranges.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { formatBytes, @@ -96,7 +95,7 @@ export function parseCommittedRanges( return { start: startInMs, end: endInMs }; }) // Filter out possible null values coming from bad inputs. - .filter(Boolean) + .filter((r) => r !== null) ); } @@ -134,7 +133,7 @@ export function stringifyStartEnd({ start, end }: StartEndRange): string { let durationInMs = strDurationInNs.slice(0, -6); if (!strDurationInNs.endsWith('000000')) { // We round up the duration, this is like running Math.ceil on the integer part. - durationInMs = Number(durationInMs) + 1; + durationInMs = (+durationInMs + 1).toString(); } result = `${startInMs}m${durationInMs}`; } else if (durationInNs > 9000) { @@ -143,7 +142,7 @@ export function stringifyStartEnd({ start, end }: StartEndRange): string { let durationInUs = strDurationInNs.slice(0, -3); if (!strDurationInNs.endsWith('000')) { // We round up the duration, this is like running Math.ceil on the integer part. - durationInUs = Number(durationInUs) + 1; + durationInUs = (+durationInUs + 1).toString(); } result = `${startInUs}u${durationInUs}`; } else if (durationInNs === 0) { diff --git a/src/profile-logic/cpu.js b/src/profile-logic/cpu.ts similarity index 99% rename from src/profile-logic/cpu.js rename to src/profile-logic/cpu.ts index b15dee35c1..bd57ab11dd 100644 --- a/src/profile-logic/cpu.js +++ b/src/profile-logic/cpu.ts @@ -2,8 +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/. */ -// @flow - import { ensureExists, assertExhaustiveCheck, @@ -110,7 +108,7 @@ export function computeThreadCPURatio( sampleUnits: SampleUnits, timeDeltas: number[], referenceCPUDeltaPerMs: number -): Float64Array | void { +): Float64Array | undefined { const { threadCPUDelta } = samples; if (!threadCPUDelta) { diff --git a/src/profile-logic/data-structures.js b/src/profile-logic/data-structures.ts similarity index 97% rename from src/profile-logic/data-structures.js rename to src/profile-logic/data-structures.ts index 9321850c14..5a83b94db3 100644 --- a/src/profile-logic/data-structures.js +++ b/src/profile-logic/data-structures.ts @@ -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/. */ -// @flow import { GECKO_PROFILE_VERSION, PROCESSED_PROFILE_VERSION, @@ -319,23 +318,14 @@ export function shallowCloneRawMarkerTable( }; } -export function getResourceTypes() { - return { - unknown: 0, - library: 1, - addon: 2, - webhost: 3, - otherhost: 4, - url: 5, - }; -} - -/** - * Export a read-only copy of the resource types. - */ -export const resourceTypes = (getResourceTypes(): $Exact< - $ReadOnly<$Call>, ->); +export const resourceTypes = { + unknown: 0, + library: 1, + addon: 2, + webhost: 3, + otherhost: 4, + url: 5, +}; export function getEmptyExtensions(): ExtensionTable { return { @@ -380,7 +370,7 @@ export function getEmptyJsTracerTable(): JsTracerTable { }; } -export function getEmptyThread(overrides?: $Shape): RawThread { +export function getEmptyThread(overrides?: Partial): RawThread { const defaultThread: RawThread = { processType: 'default', processStartupTime: 0, diff --git a/src/profile-logic/errors.js b/src/profile-logic/errors.ts similarity index 91% rename from src/profile-logic/errors.js rename to src/profile-logic/errors.ts index 71c6cde031..863abc0a2f 100644 --- a/src/profile-logic/errors.js +++ b/src/profile-logic/errors.ts @@ -2,8 +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/. */ -// @flow - import type { RequestedLib } from 'firefox-profiler/types'; // Used during the symbolication process to express that we couldn't find @@ -17,7 +15,7 @@ export class SymbolsNotFoundError extends Error { [message, ...errors.map((e) => ` - ${e.name}: ${e.message}`)].join('\n') ); // Workaround for a babel issue when extending Errors - (this: any).__proto__ = SymbolsNotFoundError.prototype; + (this as any).__proto__ = SymbolsNotFoundError.prototype; this.name = 'SymbolsNotFoundError'; this.library = library; this.errors = errors; diff --git a/src/profile-logic/flame-graph.js b/src/profile-logic/flame-graph.ts similarity index 97% rename from src/profile-logic/flame-graph.js rename to src/profile-logic/flame-graph.ts index 6d7cb751ee..2cf8e3701d 100644 --- a/src/profile-logic/flame-graph.js +++ b/src/profile-logic/flame-graph.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import type { UnitIntervalOfProfileRange, CallNodeTable, @@ -33,11 +31,11 @@ export type IndexIntoFlameGraphTiming = number; * which is used to color the drawn functions. */ export type FlameGraphTiming = Array<{ - start: UnitIntervalOfProfileRange[], - end: UnitIntervalOfProfileRange[], - selfRelative: Array, - callNode: IndexIntoCallNodeTable[], - length: number, + start: UnitIntervalOfProfileRange[]; + end: UnitIntervalOfProfileRange[]; + selfRelative: Array; + callNode: IndexIntoCallNodeTable[]; + length: number; }>; /** @@ -113,7 +111,10 @@ export function computeFlameGraphRows( // children. // We need to queue up nodes before we can process their children because // we can only process children once their parents are in the right order. - const flameGraphRows = Array.from({ length: maxDepth + 1 }, () => []); + const flameGraphRows: FlameGraphRows = Array.from( + { length: maxDepth + 1 }, + () => [] + ); const pendingRangeStartAtDepth = new Int32Array(maxDepth + 1); // At the beginning of each turn of this loop, add currentCallNode and all its diff --git a/src/profile-logic/function-info.js b/src/profile-logic/function-info.ts similarity index 99% rename from src/profile-logic/function-info.js rename to src/profile-logic/function-info.ts index 34ec4f983f..c4fe309c48 100644 --- a/src/profile-logic/function-info.js +++ b/src/profile-logic/function-info.ts @@ -2,8 +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/. */ -// @flow - /** * Strip any function arguments from the given string. * diff --git a/src/profile-logic/gecko-profile-versioning.js b/src/profile-logic/gecko-profile-versioning.ts similarity index 93% rename from src/profile-logic/gecko-profile-versioning.js rename to src/profile-logic/gecko-profile-versioning.ts index 9520905b59..db1050fa02 100644 --- a/src/profile-logic/gecko-profile-versioning.js +++ b/src/profile-logic/gecko-profile-versioning.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow /** * This file deals with old versions of the Gecko profile format, i.e. the * format that the Gecko profiler platform outputs. We want to be able to @@ -21,10 +19,11 @@ import { GECKO_PROFILE_VERSION } from '../app-logic/constants'; // Treat those as version zero. const UNANNOTATED_VERSION = 0; -function getProfileMeta(profile: mixed): MixedObject { +function getProfileMeta(profile: unknown): any { if ( profile && typeof profile === 'object' && + 'meta' in profile && profile.meta && typeof profile.meta === 'object' ) { @@ -39,7 +38,7 @@ function getProfileMeta(profile: mixed): MixedObject { * Throws an exception if the profile is too new. * @param {object} profile The profile in the "Gecko profile" format. */ -export function upgradeGeckoProfileToCurrentVersion(json: mixed) { +export function upgradeGeckoProfileToCurrentVersion(json: unknown) { const profileVersion = getProfileMeta(json).version || UNANNOTATED_VERSION; if (profileVersion === GECKO_PROFILE_VERSION) { return; @@ -67,17 +66,21 @@ export function upgradeGeckoProfileToCurrentVersion(json: mixed) { getProfileMeta(json).version = GECKO_PROFILE_VERSION; } -function _archFromAbi(abi) { +function _archFromAbi(abi: string) { if (abi === 'x86_64-gcc3') { return 'x86_64'; } return abi; } +type GeckoProfileUpgrader = (profile: any) => void; + // _upgraders[i] converts from version i - 1 to version i. // Every "upgrader" takes the profile as its single argument and mutates it. /* eslint-disable no-useless-computed-key */ -const _upgraders = { +const _upgraders: { + [key: number]: GeckoProfileUpgrader; +} = { [1]: () => { throw new Error( 'Gecko profiles without version numbers are very old and no conversion code has been written for that version of the profile format.' @@ -93,14 +96,14 @@ const _upgraders = { 'Gecko profile version 2 is very old and no conversion code has been written for that version of the profile format.' ); }, - [4]: (profile) => { - function convertToVersionFourRecursive(p) { + [4]: (profile: any) => { + function convertToVersionFourRecursive(p: any) { // In version < 3, p.libs was a JSON string. // Starting with version 4, libs is an actual array, each lib has // "debugName", "debugPath", "breakpadId" and "path" fields, and the // array is sorted by start address. p.libs = JSON.parse(p.libs) - .map((lib) => { + .map((lib: any) => { if ('breakpadId' in lib) { lib.debugName = lib.name.substr(lib.name.lastIndexOf('/') + 1); } else { @@ -119,7 +122,7 @@ const _upgraders = { lib.debugPath = ''; return lib; }) - .sort((a, b) => a.start - b.start); + .sort((a: any, b: any) => a.start - b.start); for (let threadIndex = 0; threadIndex < p.threads.length; threadIndex++) { if (typeof p.threads[threadIndex] === 'string') { @@ -150,12 +153,12 @@ const _upgraders = { } convertToVersionFourRecursive(profile); }, - [5]: (profile) => { + [5]: (profile: any) => { // In version 4, profiles from other processes were embedded as JSON // strings in the threads array. Version 5 breaks those out into a // separate "processes" array and no longer stringifies them. - function convertToVersionFiveRecursive(p) { - const allThreadsAndProcesses = p.threads.map((threadOrProcess) => { + function convertToVersionFiveRecursive(p: any) { + const allThreadsAndProcesses = p.threads.map((threadOrProcess: any) => { if (typeof threadOrProcess === 'string') { const processProfile = JSON.parse(threadOrProcess); convertToVersionFiveRecursive(processProfile); @@ -170,18 +173,18 @@ const _upgraders = { }; }); p.processes = allThreadsAndProcesses - .filter((x) => x.type === 'process') - .map((p) => p.data); + .filter((x: any) => x.type === 'process') + .map((p: any) => p.data); p.threads = allThreadsAndProcesses - .filter((x) => x.type === 'thread') - .map((t) => t.data); + .filter((x: any) => x.type === 'thread') + .map((t: any) => t.data); p.meta.version = 5; } convertToVersionFiveRecursive(profile); }, - [6]: (profile) => { + [6]: (profile: any) => { // The frameNumber column was removed from the samples table. - function convertToVersionSixRecursive(p) { + function convertToVersionSixRecursive(p: any) { for (const thread of p.threads) { delete thread.samples.schema.frameNumber; for ( @@ -200,9 +203,9 @@ const _upgraders = { } convertToVersionSixRecursive(profile); }, - [7]: (profile) => { + [7]: (profile: any) => { // The type field for DOMEventMarkerPayload was renamed to eventType. - function convertToVersionSevenRecursive(p) { + function convertToVersionSevenRecursive(p: any) { for (const thread of p.threads) { const nameIndex = thread.markers.schema.name; const dataIndex = thread.markers.schema.data; @@ -221,7 +224,7 @@ const _upgraders = { } convertToVersionSevenRecursive(profile); }, - [8]: (profile) => { + [8]: (profile: any) => { // Profiles have the following new attributes: // - meta.shutdownTime: null if the process is still running, otherwise // the shutdown time of the process in milliseconds relative to @@ -233,7 +236,7 @@ const _upgraders = { // in milliseconds since meta.startTime // - unregisterTime: The time this thread was unregistered from the // profiler, in milliseconds since meta.startTime, or null - function convertToVersionEightRecursive(p) { + function convertToVersionEightRecursive(p: any) { // We can't invent missing data, so just initialize everything with some // kind of empty value. @@ -257,13 +260,13 @@ const _upgraders = { } convertToVersionEightRecursive(profile); }, - [9]: (profile) => { + [9]: (profile: any) => { // Upgrade GC markers /* * Upgrade a GCMajor marker in the Gecko profile format. */ - function upgradeGCMajorMarker_Gecko8To9(marker) { + function upgradeGCMajorMarker_Gecko8To9(marker: any) { if ('timings' in marker) { if (!('status' in marker.timings)) { /* @@ -291,7 +294,7 @@ const _upgraders = { return marker; } - function upgradeGCMinorMarker(marker8) { + function upgradeGCMinorMarker(marker8: any) { if ('nursery' in marker8) { if ('status' in marker8.nursery) { if (marker8.nursery.status === 'no collection') { @@ -325,7 +328,7 @@ const _upgraders = { return marker8; } - function convertToVersionNineRecursive(p) { + function convertToVersionNineRecursive(p: any) { for (const thread of p.threads) { const dataIndex = thread.markers.schema.data; for (let i = 0; i < thread.markers.data.length; i++) { @@ -351,13 +354,13 @@ const _upgraders = { } convertToVersionNineRecursive(profile); }, - [10]: (profile) => { + [10]: (profile: any) => { // Removed the startDate and endDate from DOMEventMarkerPayload and // made it a tracing marker instead. DOMEventMarkerPayload is no longer a // single marker, it requires a start and an end marker. Therefore, we have // to change the old DOMEvent marker and also create an end marker for each // DOMEvent. - function convertToVersionTenRecursive(p) { + function convertToVersionTenRecursive(p: any) { for (const thread of p.threads) { const { markers } = thread; const nameIndex = markers.schema.name; @@ -404,7 +407,7 @@ const _upgraders = { } convertToVersionTenRecursive(profile); }, - [11]: (profile) => { + [11]: (profile: any) => { // Ensure there is always a pid in the profile meta AND upgrade // profile.meta categories. @@ -414,7 +417,7 @@ const _upgraders = { // version 11, but is unrelated to the actual version bump. If no pid number exists, // then a unique string label is created. let unknownPid = 0; - function ensurePidsRecursive(p) { + function ensurePidsRecursive(p: any) { for (const thread of p.threads) { if (thread.pid === null || thread.pid === undefined) { thread.pid = `Unknown Process ${++unknownPid}`; @@ -480,7 +483,7 @@ const _upgraders = { [1 << 11 /* STORAGE */]: 1 /* Other */, [1 << 12 /* EVENTS */]: 1 /* Other */, }; - function convertToVersionElevenRecursive(p) { + function convertToVersionElevenRecursive(p: any) { p.meta.categories = categories; for (const thread of p.threads) { const schemaIndexCategory = thread.frameTable.schema.category; @@ -503,14 +506,14 @@ const _upgraders = { } convertToVersionElevenRecursive(profile); }, - [12]: (profile) => { + [12]: (profile: any) => { // This version will add column numbers to the JS functions and scripts. // There is also a new property in the frameTable called "column" which // swaps positions with the "category" property. The new value for // "category" in the frameTable schema will be 5. const oldSchemaCategoryIndex = 4; const newSchemaCategoryIndex = 5; - function convertToVersionTwelveRecursive(p) { + function convertToVersionTwelveRecursive(p: any) { for (const thread of p.threads) { const schemaIndexCategory = thread.frameTable.schema.category; for (const frame of thread.frameTable.data) { @@ -531,11 +534,11 @@ const _upgraders = { } convertToVersionTwelveRecursive(profile); }, - [13]: (profile) => { + [13]: (profile: any) => { // The type field on some markers were missing. Renamed category field of // VsyncTimestamp and LayerTranslation marker payloads to type and added // a type field to Screenshot marker payload. - function convertToVersionThirteenRecursive(p) { + function convertToVersionThirteenRecursive(p: any) { for (const thread of p.threads) { const nameIndex = thread.markers.schema.name; const dataIndex = thread.markers.schema.data; @@ -560,7 +563,7 @@ const _upgraders = { } convertToVersionThirteenRecursive(profile); }, - [14]: (profile) => { + [14]: (profile: any) => { // Profiles now have a relevantForJS property in the frameTable. // This column is false on C++ and JS frames, and true on label frames that // are entry and exit points to JS. @@ -576,7 +579,7 @@ const _upgraders = { // get Element.scrollTop // set CSS2Properties.height const domCallRegex = /^(get |set )?\w+(\.\w+| constructor)$/; - function convertToVersionFourteenRecursive(p) { + function convertToVersionFourteenRecursive(p: any) { for (const thread of p.threads) { thread.frameTable.schema = { location: 0, @@ -611,9 +614,9 @@ const _upgraders = { } convertToVersionFourteenRecursive(profile); }, - [15]: (profile) => { + [15]: (profile: any) => { // The type field for DOMEventMarkerPayload was renamed to eventType. - function convertToVersion15Recursive(p) { + function convertToVersion15Recursive(p: any) { for (const thread of p.threads) { const stringTable = StringTable.withBackingArray(thread.stringTable); if (!stringTable.hasString('DiskIO')) { @@ -639,11 +642,11 @@ const _upgraders = { } convertToVersion15Recursive(profile); }, - [16]: (profile) => { + [16]: (profile: any) => { // profile.meta.categories now has a subcategories property on each element, // with an array of subcategories for that category. // And the frameTable has another column, subcategory. - function convertToVersion16Recursive(p) { + function convertToVersion16Recursive(p: any) { for (const category of p.meta.categories) { category.subcategories = ['Other']; } @@ -723,7 +726,7 @@ const _upgraders = { { name: 'DOM', color: 'blue', subcategories: ['Other'] }, ]) { const index = profile.meta.categories.findIndex( - (category) => category.name === defaultCategory.name + (category: any) => category.name === defaultCategory.name ); if (index === -1) { // Add on any unknown categories. @@ -732,13 +735,13 @@ const _upgraders = { } const otherCategory = profile.meta.categories.findIndex( - (category) => category.name === 'Other' + (category: any) => category.name === 'Other' ); const keyToCategoryIndex: Map = new Map( keyToCategoryName.map(([key, categoryName]) => { const index = profile.meta.categories.findIndex( - (category) => category.name === categoryName + (category: any) => category.name === categoryName ); if (index === -1) { throw new Error('Could not find a category index to map to.'); @@ -747,7 +750,7 @@ const _upgraders = { }) ); - function addMarkerCategoriesRecursively(p) { + function addMarkerCategoriesRecursively(p: any) { for (const thread of p.threads) { const { markers, stringTable } = thread; if (markers.schema.category !== undefined) { @@ -781,7 +784,7 @@ const _upgraders = { } addMarkerCategoriesRecursively(profile); }, - [17]: (profile) => { + [17]: (profile: any) => { // Previously, we had DocShell ID and DocShell History ID in the page object // to identify a specific page. We changed these IDs in the gecko side to // Browsing Context ID and Inner Window ID. Inner Window ID is enough to @@ -791,7 +794,7 @@ const _upgraders = { // Contexts doesn't change after a navigation. let browsingContextID = 1; let innerWindowID = 1; - function convertToVersion17Recursive(p) { + function convertToVersion17Recursive(p: any) { if (p.pages && p.pages.length > 0) { // It's not possible to have a marker belongs to a different DocShell in // different processes currently(pre-fission). It's not necessary to put @@ -874,12 +877,12 @@ const _upgraders = { } convertToVersion17Recursive(profile); }, - [18]: (profile) => { + [18]: (profile: any) => { // Due to a bug in gecko side, we were keeping the sample_group inside an // object instead of an array. Usually there is only one sample group, that's // why it wasn't a problem before. To future proof it, we are fixing it by // moving it inside an array. See: https://bugzilla.mozilla.org/show_bug.cgi?id=1584190 - function convertToVersion18Recursive(p) { + function convertToVersion18Recursive(p: any) { if (p.counters && p.counters.length > 0) { for (const counter of p.counters) { // It's possible to have an empty sample_groups object due to gecko bug. @@ -897,10 +900,10 @@ const _upgraders = { } convertToVersion18Recursive(profile); }, - [19]: (profile) => { + [19]: (profile: any) => { // Profiles now have an innerWindowID property in the frameTable. // We are filling this array with 0 values because we have no idea what that value might be. - function convertToVersion19Recursive(p) { + function convertToVersion19Recursive(p: any) { for (const thread of p.threads) { const { frameTable } = thread; frameTable.schema = { @@ -930,18 +933,18 @@ const _upgraders = { } convertToVersion19Recursive(profile); }, - [20]: (profile) => { + [20]: (profile: any) => { // The idea of phased markers was added to profiles. This upgrader removes the `time` // field from markers and replaces it with startTime, endTime and phase. // // It also removes the startTime and endTime from payloads, except for IPC and // Network markers. - type OldSchema = {| name: 0, time: 1, category: 2, data: 3 |}; - type Payload = $Shape<{ - startTime: number, - endTime: number, - type: string, - interval: string, + type OldSchema = { name: 0; time: 1; category: 2; data: 3 }; + type Payload = Partial<{ + startTime: number; + endTime: number; + type: string; + interval: string; }>; const INSTANT = 0; @@ -949,7 +952,7 @@ const _upgraders = { const INTERVAL_START = 2; const INTERVAL_END = 3; - function convertToVersion20Recursive(p) { + function convertToVersion20Recursive(p: any) { for (const thread of p.threads) { const { markers } = thread; const oldSchema: OldSchema = markers.schema; @@ -1032,32 +1035,31 @@ const _upgraders = { } convertToVersion20Recursive(profile); }, - [21]: (profile) => { + [21]: (profile: any) => { // Migrate DOMEvent markers to Markers 2.0 // This is a fairly permissive type, but helps ensure the logic below is type checked. type DOMEventPayload20_to_21 = { // Tracing -> DOMEvent - type: 'tracing' | 'DOMEvent', - category: 'DOMEvent', - eventType: string, + type: 'tracing' | 'DOMEvent'; + category: 'DOMEvent'; + eventType: string; // These are removed: - timeStamp: number, + timeStamp?: number; // This gets added: - latency: number, + latency: number; }; type UnknownArityTuple = any[]; type ProfileV20 = { threads: Array<{ - markers: {| - data: UnknownArityTuple[], - schema: { name: number, startTime: number, data: number }, - |}, - ... - }>, - processes: ProfileV20[], + markers: { + data: UnknownArityTuple[]; + schema: { name: number; startTime: number; data: number }; + }; + }>; + processes: ProfileV20[]; }; // DOMEvents are tracing markers with a little bit more information about them, @@ -1096,13 +1098,12 @@ const _upgraders = { } convertToVersion21Recursive(profile); }, - [22]: (untypedProfile) => { + [22]: (untypedProfile: any) => { // The marker schema, which details how to display markers was added. Back-fill // any old profiles with a default schema. type GeckoProfileVersion20To21 = { - meta: { markerSchema: mixed, ... }, - processes: GeckoProfileVersion20To21[], - ... + meta: { markerSchema: unknown }; + processes: GeckoProfileVersion20To21[]; }; const geckoProfile: GeckoProfileVersion20To21 = untypedProfile; @@ -1317,7 +1318,7 @@ const _upgraders = { processes.meta.markerSchema = []; } }, - [23]: (profile) => { + [23]: (profile: any) => { // The browsingContextID inside the pages array and activeBrowsingContextID // have been renamed to tabID and activeTabID. // Previously, we were using the browsingcontextID to figure out which tab @@ -1327,7 +1328,7 @@ const _upgraders = { // indicate the tabIDs. With the back-end work, we are not getting the // browserId, which corresponds to ID of a tab directly. See the back-end // bug for more details: https://bugzilla.mozilla.org/show_bug.cgi?id=1698129 - function convertToVersion23Recursive(p) { + function convertToVersion23Recursive(p: any) { if ( profile.meta.configuration && profile.meta.configuration.activeBrowsingContextID @@ -1351,14 +1352,14 @@ const _upgraders = { } convertToVersion23Recursive(profile); }, - [24]: (_) => { + [24]: (_: any) => { // This version bumps happened when a new end status "STATUS_CANCELED" // appeared for network markers, to ensure that a new version of the // frontend will handle it. // No upgrade is needed though, because previous versions of firefox weren't // generating anything in this case. }, - [25]: (_) => { + [25]: (_: any) => { // This version bumps happened when private browsing data could be captured // by the profiler. We want to ensure that the frontend will be able to // sanitize it if needed. @@ -1366,14 +1367,14 @@ const _upgraders = { // capturing this data and no new mandatory values are present in this // version. }, - [26]: (profile) => { + [26]: (profile: any) => { // `searchable` property in the marker schema wasn't implemented before and // we had some manual checks for the marker fields below. With this version, // we removed this manual check and started to use the `searchable` property // of the marker schema. - function convertToVersion26Recursive(p) { + function convertToVersion26Recursive(p: any) { for (const schema of p.meta.markerSchema) { - let searchableFieldKeys; + let searchableFieldKeys: string[]; switch (schema.name) { case 'FileIO': { // threadId wasn't in the schema before, so we need to add manually. @@ -1430,9 +1431,9 @@ const _upgraders = { convertToVersion26Recursive(profile); }, - [27]: (profile) => { + [27]: (profile: any) => { // The "optimizations" column was removed from the frame table. - function convertToVersion27Recursive(p) { + function convertToVersion27Recursive(p: any) { for (const thread of p.threads) { delete thread.frameTable.schema.optimizations; } @@ -1443,16 +1444,16 @@ const _upgraders = { } convertToVersion27Recursive(profile); }, - [28]: (_) => { + [28]: (_: any) => { // This version bump added a new marker schema format type, named "unique-string", // which older frontends will not be able to display. // No upgrade is needed, as older versions of firefox would not generate // marker data with unique-string typed data, and no modification is needed in the // frontend to display older formats. }, - [29]: (profile) => { + [29]: (profile: any) => { // Remove the 'sample_groups' object from the GeckoCounter structure. - function convertToVersion29Recursive(p) { + function convertToVersion29Recursive(p: any) { if (p.counters && p.counters.length > 0) { for (const counter of p.counters) { if (!counter.sample_groups) { @@ -1471,14 +1472,14 @@ const _upgraders = { } convertToVersion29Recursive(profile); }, - [30]: (_) => { + [30]: (_: any) => { // This version bump added a new marker schema format type, named "sanitized-string", // which older frontends will not be able to display. // No upgrade is needed, as older versions of firefox would not generate // marker data with sanitized-string typed data, and no modification is needed in the // frontend to display older formats. }, - [31]: (_) => { + [31]: (_: any) => { // This version bump added two new form types for new marker schema field: // "flow-id" and "terminating-flow-id". // Older frontends will not be able to display these fields. diff --git a/src/profile-logic/graph-color.js b/src/profile-logic/graph-color.ts similarity index 99% rename from src/profile-logic/graph-color.js rename to src/profile-logic/graph-color.ts index 74b40785f8..ced7600eb8 100644 --- a/src/profile-logic/graph-color.js +++ b/src/profile-logic/graph-color.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { BLUE_50, BLUE_60, diff --git a/src/profile-logic/import/art-trace.js b/src/profile-logic/import/art-trace.ts similarity index 87% rename from src/profile-logic/import/art-trace.js rename to src/profile-logic/import/art-trace.ts index 0665d36980..a57f4008af 100644 --- a/src/profile-logic/import/art-trace.js +++ b/src/profile-logic/import/art-trace.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - // Parses the ART trace format and converts it to the Gecko profile format. // These profiles are obtained from Android in two ways: @@ -26,45 +24,46 @@ // The type definitions below are very coarse and just enough to catch the // biggest mistakes. type GeckoThreadVersion11 = { - tid: number, - pid: number, - name: string, - registerTime: number, - unregisterTime: number | null, - // eslint-disable-next-line flowtype/no-weak-types - markers: Object, - // eslint-disable-next-line flowtype/no-weak-types - samples: Object, - // eslint-disable-next-line flowtype/no-weak-types - frameTable: Object, - // eslint-disable-next-line flowtype/no-weak-types - stackTable: Object, - stringTable: string[], + tid: number; + pid: number; + name: string; + registerTime: number; + unregisterTime: number | null; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + markers: Object; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + samples: Object; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + frameTable: Object; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + stackTable: Object; + stringTable: string[]; }; type GeckoCategoryVersion11 = { - name: string, - color: string, + name: string; + color: string; }; type GeckoProfileVersion11 = { meta: { - version: 11, - interval: number, - processType: 0, - product: string, - pid?: string, - stackwalk: 1, - startTime: number, - shutdownTime: null, - presymbolicated: true, - categories: GeckoCategoryVersion11[], - }, - // eslint-disable-next-line flowtype/no-weak-types - libs: Object[], - threads: GeckoThreadVersion11[], - // eslint-disable-next-line flowtype/no-weak-types - processes: Object[], - // eslint-disable-next-line flowtype/no-weak-types - pausedRanges: Object[], + version: 11; + interval: number; + processType: 0; + product: string; + importedFrom?: string; + pid?: string; + stackwalk: 1; + startTime: number; + shutdownTime: null; + presymbolicated: true; + categories: GeckoCategoryVersion11[]; + }; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + libs: Object[]; + threads: GeckoThreadVersion11[]; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + processes: Object[]; + // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types + pausedRanges: Object[]; }; // From VmTraceParser.java: @@ -102,7 +101,7 @@ class ByteReader { return this._pos; } - setCurPos(newPos) { + setCurPos(newPos: number) { this._pos = newPos; } @@ -130,7 +129,7 @@ class ByteReader { return high * Math.pow(2, 32) + low; } - getBytesUntil(endPos) { + getBytesUntil(endPos: number) { if (endPos < this._pos) { throw new Error( `getBytesUntil() called with a target position in the past (curPos: ${this._pos}, endPos: ${endPos})` @@ -141,11 +140,11 @@ class ByteReader { return buffer; } - getBytes(byteCount) { + getBytes(byteCount: number) { return this.getBytesUntil(this._pos + byteCount); } - getString(byteLength) { + getString(byteLength: number) { const stringBytes = this.getBytes(byteLength); return this._decoder.decode(stringBytes); } @@ -163,37 +162,39 @@ class ByteReader { } } -type ArtTraceThread = {| - tid: number, - threadName: string, -|}; - -export type ArtTraceMethod = {| - methodId: number, - className: string, - methodName: string, - signature: string, -|}; - -type ArtTrace = {| - summaryDetails: { - clock: string, - pid?: string, - }, - startTimeInUsecSinceBoot: number, - threads: ArtTraceThread[], - methods: ArtTraceMethod[], - methodActions: {| - tid: number, - methodId: number, - globalTime: number, - threadTime: number, - action: 'enter' | 'exit' | 'exit-unroll', - |}[], -|}; +type ArtTraceThread = { + tid: number; + threadName: string; +}; + +export type ArtTraceMethod = { + methodId: number; + className: string; + methodName: string; + signature: string; +}; + +type ArtTraceSummaryDetails = { + clock: string; + pid?: string; +}; + +type ArtTrace = { + summaryDetails: ArtTraceSummaryDetails; + startTimeInUsecSinceBoot: number; + threads: ArtTraceThread[]; + methods: ArtTraceMethod[]; + methodActions: { + tid: number; + methodId: number; + globalTime: number; + threadTime: number; + action: 'enter' | 'exit' | 'exit-unroll'; + }[]; +}; function detectArtTraceFormat( - traceBuffer: ArrayBuffer + traceBuffer: ArrayBufferLike ): 'regular' | 'streaming' | 'unrecognized' { try { const lengthOfExpectedFirstTwoLinesOfSummarySection = '*version\nX\n' @@ -224,7 +225,7 @@ function detectArtTraceFormat( return 'unrecognized'; } -function validateMagicHeader(magicHeader) { +function validateMagicHeader(magicHeader: number) { if (magicHeader !== TRACE_MAGIC) { const expectedString = `0x${TRACE_MAGIC.toString(16)}`; const gotString = `0x${magicHeader.toString(16)}`; @@ -233,7 +234,7 @@ function validateMagicHeader(magicHeader) { ); } } -function validateVersion(version) { +function validateVersion(version: number) { if (version < 1 || version > 3) { throw new Error( `This code only knows how to parse versions 1 to 3, got version ${version}.` @@ -241,7 +242,7 @@ function validateVersion(version) { } } -function validateMatchingVersions(version, summaryVersion) { +function validateMatchingVersions(version: number, summaryVersion: number) { if (version !== summaryVersion) { throw new Error( `Error: version number mismatch; got ${summaryVersion} in summary but ${version} in data section` @@ -249,7 +250,7 @@ function validateMatchingVersions(version, summaryVersion) { } } -function parseSummary(reader) { +function parseSummary(reader: ByteReader) { // Example: // // *version @@ -269,7 +270,9 @@ function parseSummary(reader) { const summaryVersion = +reader.getLine(); validateVersion(summaryVersion); - const summaryDetails = { summaryVersion, clock: 'thread-cpu' }; + const summaryDetails: ArtTraceSummaryDetails & Record = { + clock: 'thread-cpu', + }; while (true) { line = reader.getLine(); if (!line || line.startsWith('*')) { @@ -281,7 +284,7 @@ function parseSummary(reader) { return { summaryVersion, summaryDetails, lineAfterSummary: line }; } -function parseThreads(reader) { +function parseThreads(reader: ByteReader) { // Example: // // *threads @@ -307,14 +310,14 @@ function parseThreads(reader) { return { threads, lineAfterThreads: line }; } -function parseOneMethod(s) { +function parseOneMethod(s: string) { // Example: // 0x3f4 android.view.Window adjustLayoutParamsForSubWindow (Landroid/view/WindowManager$LayoutParams;)V Window.java const [methodId, className, methodName, signature] = s.split('\t'); return { methodId: +methodId, className, methodName, signature }; } -function parseMethods(reader) { +function parseMethods(reader: ByteReader) { // Example: // // *methods @@ -334,7 +337,7 @@ function parseMethods(reader) { return { methods, lineAfterMethods: line }; } -function parseRecordSize(reader, version) { +function parseRecordSize(reader: ByteReader, version: number) { switch (version) { case 1: return 9; @@ -345,7 +348,12 @@ function parseRecordSize(reader, version) { } } -function parseRecord(reader, version, recordSize, clock) { +function parseRecord( + reader: ByteReader, + version: number, + recordSize: number, + clock: string +) { const recordStart = reader.curPos(); const tid = version === 1 ? reader.getU8() : reader.getU16(); const methodIdAndAction = reader.getU32(); @@ -379,11 +387,14 @@ function parseRecord(reader, version, recordSize, clock) { methodId, globalTime, threadTime, - action: ['enter', 'exit', 'exit-unroll'][action], + action: ['enter', 'exit', 'exit-unroll'][action] as + | 'enter' + | 'exit' + | 'exit-unroll', }; } -function parseRegularFormat(reader) { +function parseRegularFormat(reader: ByteReader) { // *version const { summaryVersion, summaryDetails, lineAfterSummary } = parseSummary(reader); @@ -432,7 +443,7 @@ function parseRegularFormat(reader) { }; } -function parseStreamingFormat(reader) { +function parseStreamingFormat(reader: ByteReader) { // The "streaming" format interleaves method declarations and thread // declarations with the method actions that refer to them. This is different // from the regular format, which collects all methods and threads and neatly @@ -512,7 +523,7 @@ function parseStreamingFormat(reader) { }; } -function parseArtTrace(buffer: ArrayBuffer): ArtTrace { +function parseArtTrace(buffer: ArrayBufferLike): ArtTrace { try { const reader = new ByteReader(new Uint8Array(buffer)); switch (detectArtTraceFormat(buffer)) { @@ -546,7 +557,7 @@ function parseArtTrace(buffer: ArrayBuffer): ArtTrace { // // This function returns the average of the lowest 20% of timestamp deltas that // can be observed among the first 500 method actions. -function procureSamplingInterval(trace) { +function procureSamplingInterval(trace: ArtTrace) { const { methodActions } = trace; // Gather up to 500 time deltas between method actions on a thread. @@ -578,8 +589,8 @@ function procureSamplingInterval(trace) { } export type SpecialCategoryInfo = { - prefixes: string[], - name: string, + prefixes: string[]; + name: string; }; // Make a category for a frequently-encountered bag of code that is not covered @@ -591,7 +602,7 @@ export type SpecialCategoryInfo = { export function getSpecialCategory( methods: ArtTraceMethod[] ): SpecialCategoryInfo | void { - function getSignificantNamespaceSegment(className) { + function getSignificantNamespaceSegment(className: string) { // Cut off leading "org." or "com.". Those are boring. const s = className.startsWith('org.') || className.startsWith('com.') @@ -743,7 +754,7 @@ class ThreadBuilder { time: 1, data: 2, }, - data: [], + data: [] as Array<[number, number, any]>, }; _samples = { schema: { @@ -753,7 +764,7 @@ class ThreadBuilder { rss: 3, uss: 4, }, - data: [], + data: [] as Array<[number | null, number]>, }; _frameTable = { schema: { @@ -763,18 +774,18 @@ class ThreadBuilder { line: 3, category: 4, }, - data: [], + data: [] as Array<[number, null, null, null, number]>, }; _stackTable = { schema: { frame: 0, prefix: 1, }, - data: [], + data: [] as Array<[number, number | null]>, }; - _stringTable = []; + _stringTable = [] as Array; - _currentStack = null; + _currentStack: number | null = null; _nextSampleTimestamp = 0; _stackMap = new Map(); _frameMap = new Map(); @@ -788,13 +799,13 @@ class ThreadBuilder { _categoryInfo; constructor( - name, - pid, - tid, - methodMap, - intervalInMsec, - honorOriginalSamplingTimestamps, - categoryInfo + name: string, + pid: number, + tid: number, + methodMap: Map, + intervalInMsec: number, + honorOriginalSamplingTimestamps: boolean, + categoryInfo: any ) { this._name = name; this._pid = pid; @@ -805,7 +816,7 @@ class ThreadBuilder { this._categoryInfo = categoryInfo; } - _getOrCreateStack(frame, prefix) { + _getOrCreateStack(frame: number, prefix: number | null) { const key = prefix === null ? `${frame}` : `${frame},${prefix}`; let stack = this._stackMap.get(key); if (stack === undefined) { @@ -816,7 +827,7 @@ class ThreadBuilder { return stack; } - _getOrCreateFrameForMethodId(methodId) { + _getOrCreateFrameForMethodId(methodId: number) { let frame = this._frameMap.get(methodId); if (frame === undefined) { const methodInfo = this._methodMap.get(methodId); @@ -839,12 +850,12 @@ class ThreadBuilder { return frame; } - enterMethod(methodId) { + enterMethod(methodId: number) { const frame = this._getOrCreateFrameForMethodId(methodId); this._currentStack = this._getOrCreateStack(frame, this._currentStack); } - exitMethod(_methodId) { + exitMethod(_methodId: number) { if (this._currentStack === null) { // This has been observed to happen in tracing-based traces (rather than sampling-based traces). Not sure why. // console.warn('exiting method when stack is empty'); @@ -854,7 +865,7 @@ class ThreadBuilder { } // Called before enter/exitMethod are called for this time - advanceTimeTo(timestampInMSSinceStartTime) { + advanceTimeTo(timestampInMSSinceStartTime: number) { if (this._nextSampleTimestamp === 0) { this._nextSampleTimestamp = timestampInMSSinceStartTime; if (this._name !== 'main') { @@ -904,13 +915,13 @@ class ThreadBuilder { } } -export function isArtTraceFormat(traceBuffer: ArrayBuffer) { +export function isArtTraceFormat(traceBuffer: ArrayBufferLike) { return detectArtTraceFormat(traceBuffer) !== 'unrecognized'; } // Convert an ART trace to the Gecko profile format. export function convertArtTraceProfile( - traceBuffer: ArrayBuffer + traceBuffer: ArrayBufferLike ): GeckoProfileVersion11 { const trace = parseArtTrace(traceBuffer); const originalIntervalInUsec = procureSamplingInterval(trace); diff --git a/src/profile-logic/import/chrome.js b/src/profile-logic/import/chrome.ts similarity index 85% rename from src/profile-logic/import/chrome.js rename to src/profile-logic/import/chrome.ts index efc563ffb1..34e7316bca 100644 --- a/src/profile-logic/import/chrome.js +++ b/src/profile-logic/import/chrome.ts @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import type { Profile, RawThread, @@ -12,10 +12,7 @@ import type { MixedObject, } from 'firefox-profiler/types'; -import { - getEmptyProfile, - getEmptyThread, -} from '../../profile-logic/data-structures'; +import { getEmptyProfile, getEmptyThread } from '../data-structures'; import { StringTable } from '../../utils/string-table'; import { ensureExists, coerce } from '../../utils/flow'; import { @@ -25,10 +22,7 @@ import { INTERVAL_END, } from 'firefox-profiler/app-logic/constants'; -import { - getOrCreateURIResource, - getTimeRangeForThread, -} from '../../profile-logic/profile-data'; +import { getOrCreateURIResource, getTimeRangeForThread } from '../profile-data'; // Chrome Tracing Event Spec: // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview @@ -48,67 +42,66 @@ export type TracingEventUnion = | FallbackEndEvent | TracingStartedInBrowserEvent; -type TracingEvent = {| - cat: string, +type TracingEvent = { + cat: string; // List out all known phase values, but then also allow strings. This will get // overwritten by the `...Event` line, which will put in the exact phase. - ph: string, // Phase - pid: number, // Process ID - tid: number, // Thread ID - ts: number, // Timestamp - tts?: number, // Thread Timestamp - tdur?: number, // Time duration - dur?: number, // Time duration - ...Event, -|}; + ph: string; // Phase + pid: number; // Process ID + tid: number; // Thread ID + ts: number; // Timestamp + tts?: number; // Thread Timestamp + tdur?: number; // Time duration + dur?: number; // Time duration +} & Event; // V8 can generate this backward compatible event. // See https://github.com/firefox-devtools/profiler/issues/4308#issuecomment-1303551614 -type FallbackEndEvent = TracingEvent<{| - name: 'ProfileChunk', - id: string, - args: {| - data: {| - endTime: number, - |}, - |}, -|}>; - -type ProfileEvent = TracingEvent<{| - name: 'Profile', +type FallbackEndEvent = TracingEvent<{ + name: 'ProfileChunk'; + id: string; args: { data: { - startTime: number, - }, - }, - ph: 'P', - id: string, -|}>; + endTime: number; + }; + }; +}>; + +type ProfileEvent = TracingEvent<{ + name: 'Profile'; + args: { + data: { + startTime: number; + }; + }; + ph: 'P'; + id: string; +}>; -type ProfileChunkEvent = TracingEvent<{| - name: 'ProfileChunk', +type ProfileChunkEvent = TracingEvent<{ + name: 'ProfileChunk'; args: { data: { cpuProfile: { nodes?: Array<{ callFrame: { - functionName: string, - scriptId: number, - lineNumber?: number, - columnNumber?: number, - url?: string, - }, - id: number, - parent?: number, - }>, - samples: number[], // Index into cpuProfile nodes - }, - timeDeltas: number[], - }, - }, - ph: 'P', - id: string, -|}>; + functionName: string; + scriptId: number; + lineNumber?: number; + columnNumber?: number; + url?: string; + }; + id: number; + parent?: number; + }>; + samples: number[]; // Index into cpuProfile nodes + }; + timeDeltas: number[]; + }; + }; + ph: 'P'; + id: string; +}>; // The CpuProfileEvent format is similar to the ProfileChunkEvent format. // Presumably, one of them is the newer format the other is the older format. @@ -117,76 +110,76 @@ type ProfileChunkEvent = TracingEvent<{| // - The parent <-> child relationship between nodes is indicated in the // opposite direction: ProfileChunkEvent has a "parent" field on each nodes, // CpuProfileEvent has a "children" field on each node. -export type CpuProfileEvent = TracingEvent<{| - name: 'CpuProfile', +export type CpuProfileEvent = TracingEvent<{ + name: 'CpuProfile'; args: { data: { - cpuProfile: CpuProfileData, - }, - }, - ph: 'I', -|}>; + cpuProfile: CpuProfileData; + }; + }; + ph: 'I'; +}>; // A node performance profile only outputs this. // See https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile type CpuProfileData = { nodes?: Array<{ callFrame: { - functionName: string, - scriptId: number, - lineNumber?: number, - columnNumber?: number, - url?: string, - }, - id: number, - children?: number[], - }>, - samples: number[], // Index into cpuProfile nodes - timeDeltas: number[], - startTime: number, // microseconds - endTime: number, // microseconds + functionName: string; + scriptId: number; + lineNumber?: number; + columnNumber?: number; + url?: string; + }; + id: number; + children?: number[]; + }>; + samples: number[]; // Index into cpuProfile nodes + timeDeltas: number[]; + startTime: number; // microseconds + endTime: number; // microseconds }; -type ThreadNameEvent = TracingEvent<{| - name: 'thread_name', - ph: 'm' | 'M', - args: { name: string }, -|}>; - -type ProcessNameEvent = TracingEvent<{| - name: 'process_name', - ph: 'm' | 'M', - args: { name: string }, -|}>; - -type ProcessLabelsEvent = TracingEvent<{| - name: 'process_labels', - ph: 'm' | 'M', - args: { labels: string }, -|}>; - -type ProcessSortIndexEvent = TracingEvent<{| - name: 'process_sort_index', - ph: 'm' | 'M', - args: { sort_index: number }, -|}>; - -type ThreadSortIndexEvent = TracingEvent<{| - name: 'thread_sort_index', - ph: 'm' | 'M', - args: { sort_index: number }, -|}>; - -type ScreenshotEvent = TracingEvent<{| - name: 'Screenshot', - ph: 'O', - args: { snapshot: string }, -|}>; - -type TracingStartedInBrowserEvent = TracingEvent<{| - name: 'TracingStartedInBrowser', - ph: 'I', -|}>; +type ThreadNameEvent = TracingEvent<{ + name: 'thread_name'; + ph: 'm' | 'M'; + args: { name: string }; +}>; + +type ProcessNameEvent = TracingEvent<{ + name: 'process_name'; + ph: 'm' | 'M'; + args: { name: string }; +}>; + +type ProcessLabelsEvent = TracingEvent<{ + name: 'process_labels'; + ph: 'm' | 'M'; + args: { labels: string }; +}>; + +type ProcessSortIndexEvent = TracingEvent<{ + name: 'process_sort_index'; + ph: 'm' | 'M'; + args: { sort_index: number }; +}>; + +type ThreadSortIndexEvent = TracingEvent<{ + name: 'thread_sort_index'; + ph: 'm' | 'M'; + args: { sort_index: number }; +}>; + +type ScreenshotEvent = TracingEvent<{ + name: 'Screenshot'; + ph: 'O'; + args: { snapshot: string }; +}>; + +type TracingStartedInBrowserEvent = TracingEvent<{ + name: 'TracingStartedInBrowser'; + ph: 'I'; +}>; function wrapCpuProfileInEvent(cpuProfile: CpuProfileData): CpuProfileEvent { return { @@ -204,14 +197,14 @@ function wrapCpuProfileInEvent(cpuProfile: CpuProfileData): CpuProfileEvent { } export function attemptToConvertChromeProfile( - json: mixed, + json: unknown, profileUrl?: string ): Promise | null { if (!json) { return null; } - let events: TracingEventUnion[] | void; + let events: TracingEventUnion[] | undefined; if (Array.isArray(json)) { // Chrome profiles come as a list of events. @@ -222,7 +215,7 @@ export function attemptToConvertChromeProfile( (event) => event && typeof event === 'object' && 'ph' in event ) ) { - events = coerce(json); + events = json as TracingEventUnion[]; } } else if ( // A node.js profile is a single CpuProfileData, as opposed to a list of events. @@ -242,7 +235,7 @@ export function attemptToConvertChromeProfile( Array.isArray(json.traceEvents) ) { // This is Google Tracing Event format, for example from chrome://tracing. - events = coerce(json.traceEvents); + events = json.traceEvents as TracingEventUnion[]; } if (!events) { @@ -267,48 +260,44 @@ export function attemptToConvertChromeProfile( list = []; eventsByName.set(name, list); } - list.push((tracingEvent: any)); + list.push(tracingEvent); } return processTracingEvents(eventsByName, profileUrl); } type ThreadInfo = { - thread: RawThread, - funcKeyToFuncId: Map, - nodeIdToStackId: Map, - originToResourceIndex: Map, - lastSeenTime: number, - lastSampledTime: number, - pid: number, - processSortIndex: number, - threadSortIndex: number, - tieBreakerIndex: number, + thread: RawThread; + funcKeyToFuncId: Map; + nodeIdToStackId: Map; + originToResourceIndex: Map; + lastSeenTime: number; + lastSampledTime: number; + pid: number; + processSortIndex: number; + threadSortIndex: number; + tieBreakerIndex: number; }; -function findEvent( +function findEvent( eventsByName: Map, name: string, - f: (T) => boolean -): T | void { - const events: T[] | void = (eventsByName.get(name): any); - return events ? events.find(f) : undefined; + f: (param: T) => boolean +): T | undefined { + const events = eventsByName.get(name); + return events ? (events as T[]).find(f) : undefined; } -function findEvents< - // False positive, generic type bounds: - // eslint-disable-next-line flowtype/no-weak-types - T: Object, ->( +function findEvents( eventsByName: Map, name: string, - f: (T) => boolean + f: (param: T) => boolean ): T[] { - const events: T[] | void = (eventsByName.get(name): any); + const events = eventsByName.get(name); if (!events) { return []; } - return events.filter(f); + return (events as T[]).filter(f); } function getThreadInfo( @@ -440,17 +429,17 @@ function getTimeDeltas( } type FunctionInfo = { - category: number, - isJS: boolean, - relevantForJS: boolean, + category: number; + isJS: boolean; + relevantForJS: boolean; }; -function makeFunctionInfoFinder(categories) { - const jsCat = categories.findIndex((c) => c.name === 'JavaScript'); - const gcCat = categories.findIndex((c) => c.name === 'GC / CC'); - const nativeCat = categories.findIndex((c) => c.name === 'Native'); - const otherCat = categories.findIndex((c) => c.name === 'Other'); - const idleCat = categories.findIndex((c) => c.name === 'Idle'); +function makeFunctionInfoFinder(categories: any) { + const jsCat = categories.findIndex((c: any) => c.name === 'JavaScript'); + const gcCat = categories.findIndex((c: any) => c.name === 'GC / CC'); + const nativeCat = categories.findIndex((c: any) => c.name === 'Native'); + const otherCat = categories.findIndex((c: any) => c.name === 'Other'); + const idleCat = categories.findIndex((c: any) => c.name === 'Idle'); if ( jsCat === -1 || gcCat === -1 || @@ -464,8 +453,8 @@ function makeFunctionInfoFinder(categories) { } return function getFunctionInfo( - functionName, - hasURLOrLineNumber + functionName: any, + hasURLOrLineNumber: any ): FunctionInfo { switch (functionName) { case '(idle)': @@ -514,13 +503,12 @@ async function processTracingEvents( const stringTable = StringTable.withBackingArray(profile.shared.stringArray); - let profileEvents: (ProfileEvent | CpuProfileEvent)[] = - (eventsByName.get('Profile'): any) || []; + let profileEvents: (ProfileEvent | CpuProfileEvent)[] = (eventsByName.get( + 'Profile' + ) || []) as ProfileEvent[]; if (eventsByName.has('CpuProfile')) { - const cpuProfiles: CpuProfileEvent[] = (eventsByName.get( - 'CpuProfile' - ): any); + const cpuProfiles = eventsByName.get('CpuProfile') as CpuProfileEvent[]; profileEvents = profileEvents.concat(cpuProfiles); } @@ -543,9 +531,9 @@ async function processTracingEvents( const { thread, funcKeyToFuncId, nodeIdToStackId, originToResourceIndex } = threadInfo; - let profileChunks = []; + let profileChunks: any[] = []; if (profileEvent.name === 'Profile') { - threadInfo.lastSeenTime = (profileEvent.args.data.startTime: any) / 1000; + threadInfo.lastSeenTime = profileEvent.args.data.startTime / 1000; const { id, pid } = profileEvent; profileChunks = findEvents( eventsByName, @@ -589,12 +577,12 @@ async function processTracingEvents( const { callFrame, id: nodeIndex } = node; let parent: number | void = undefined; if (node.parent !== undefined) { - parent = (node.parent: any); + parent = node.parent; } else { parent = parentMap.get(nodeIndex); } if (node.children !== undefined) { - const children: number[] = (node.children: any); + const children: number[] = node.children; for (let i = 0; i < children.length; i++) { parentMap.set(children[i], nodeIndex); } @@ -730,7 +718,7 @@ async function processTracingEvents( threadInfoByThread, eventsByName, profile, - (eventsByName.get('Screenshot'): any) + (eventsByName.get('Screenshot') ?? []) as ScreenshotEvent[] ); extractMarkers( @@ -820,13 +808,9 @@ async function extractScreenshots( threadInfoByThread: Map, eventsByName: Map, profile: Profile, - screenshots: ?(ScreenshotEvent[]) + screenshots: ScreenshotEvent[] ): Promise { - if (!screenshots) { - return; - } - - if (!screenshots || screenshots.length === 0) { + if (screenshots.length === 0) { // No screenshots were found, exit early. return; } @@ -881,7 +865,7 @@ async function extractScreenshots( */ function getImageSize( url: string -): Promise { +): Promise { return new Promise((resolve) => { const image = new Image(); image.src = url; @@ -904,7 +888,7 @@ function getImageSize( * always preceeds the current stack index in the StackTable. */ function assertStackOrdering(stackTable: RawStackTable) { - const visitedStacks = new Set([null]); + const visitedStacks = new Set([null]); for (let i = 0; i < stackTable.length; i++) { if (!visitedStacks.has(stackTable.prefix[i])) { throw new Error('The stack ordering is incorrect'); @@ -987,7 +971,7 @@ function extractMarkers( // Mark events event.ph === 'R' ) { - const time: number = (event.ts: any) / 1000; + const time: number = event.ts / 1000; const threadInfo = getThreadInfo( threadInfoByPidAndTid, threadInfoByThread, @@ -997,9 +981,11 @@ function extractMarkers( ); const { thread } = threadInfo; const { markers } = thread; - let argData: MixedObject | null = null; - if (event.args && typeof event.args === 'object') { - argData = (event.args: any).data || null; + let argData: + | (object & { type2?: unknown; category2?: unknown }) + | null = null; + if ('args' in event && event.args && typeof event.args === 'object') { + argData = event.args.data || null; } markers.name.push(stringTable.indexForString(name)); markers.category.push(otherCategoryIndex); @@ -1017,13 +1003,13 @@ function extractMarkers( category: event.cat, }; - // $FlowExpectError Opt out of Flow checking for this one. + // @ts-expect-error Opt out of type checking for this one. markers.data.push(newData); if (event.ph === 'X') { // Complete Event // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.lpfof2aylapb - const duration: number = (event.dur: any) / 1000; + const duration: number = event.dur! / 1000; markers.phase.push(INTERVAL); markers.startTime.push(time); markers.endTime.push(time + duration); diff --git a/src/profile-logic/import/dhat.js b/src/profile-logic/import/dhat.ts similarity index 94% rename from src/profile-logic/import/dhat.js rename to src/profile-logic/import/dhat.ts index b311565ecc..5cef0b4188 100644 --- a/src/profile-logic/import/dhat.js +++ b/src/profile-logic/import/dhat.ts @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import type { Profile, Pid, @@ -17,7 +17,7 @@ import { } from 'firefox-profiler/profile-logic/data-structures'; import { StringTable } from 'firefox-profiler/utils/string-table'; -import { coerce, ensureExists } from 'firefox-profiler/utils/flow'; +import { ensureExists } from 'firefox-profiler/utils/flow'; /** * DHAT is a heap memory analysis tool in valgrind. It's also available as rust component. @@ -28,57 +28,57 @@ import { coerce, ensureExists } from 'firefox-profiler/utils/flow'; * git clone git://sourceware.org/git/valgrind.git * dhat/dh_main.c */ -type DhatJson = $ReadOnly<{| +type DhatJson = Readonly<{ // Version number of the format. Incremented on each // backwards-incompatible change. A mandatory integer. - dhatFileVersion: 2, + dhatFileVersion: 2; // The invocation mode. A mandatory, free-form string. - mode: 'heap', + mode: 'heap'; // The verb used before above stack frames, i.e. " at {". A // mandatory string. - verb: 'Allocated', + verb: 'Allocated'; // Are block lifetimes recorded? Affects whether some other fields are // present. A mandatory boolean. - bklt: boolean, + bklt: boolean; // Are block accesses recorded? Affects whether some other fields are // present. A mandatory boolean. - bkacc: boolean, + bkacc: boolean; // Byte/bytes/blocks-position units. Optional strings. "byte", "bytes", // and "blocks" are the values used if these fields are omitted. - bu: 'byte', - bsu: 'bytes', - bksu: 'blocks', + bu: 'byte'; + bsu: 'bytes'; + bksu: 'blocks'; // Time units (individual and 1,000,000x). Mandatory strings. - tu: 'instrs', - Mtu: 'Minstr', + tu: 'instrs'; + Mtu: 'Minstr'; // The "short-lived" time threshold, measures in "tu"s. // - bklt=true: a mandatory integer. // - bklt=false: omitted. - tuth: 500, + tuth: 500; // The executed command. A mandatory string. - cmd: string, + cmd: string; // The process ID. A mandatory integer. - pid: Pid, + pid: Pid; // The time at the end of execution (t-end). A mandatory integer. - te: InstructionCounts, + te: InstructionCounts; // The time of the global max (t-gmax). // - bklt=true: a mandatory integer. // - bklt=false: omitted. - tg: InstructionCounts, + tg: InstructionCounts; // The program points. A mandatory array. - pps: ProgramPoint[], + pps: ProgramPoint[]; // Frame table. A mandatory array of strings. // e.g. @@ -88,42 +88,42 @@ type DhatJson = $ReadOnly<{| // '0x4A9B414: _nl_load_locale_from_archive (loadarchive.c:173)', // '0x4A9A2BE: _nl_find_locale (findlocale.c:153)' // ], - ftbl: string[], -|}>; + ftbl: string[]; +}>; -type ProgramPoint = $ReadOnly<{| +type ProgramPoint = Readonly<{ // Total bytes and blocks. Mandatory integers. - tb: Bytes, - tbk: Blocks, + tb: Bytes; + tbk: Blocks; // Total lifetimes of all blocks allocated at this PP. // - bklt=true: a mandatory integer. // - bklt=false: omitted. - tl: InstructionCounts, + tl: InstructionCounts; // The maximum bytes and blocks for this PP. // - bklt=true: mandatory integers. // - bklt=false: omitted. - mb: Bytes, - mbk: Blocks, + mb: Bytes; + mbk: Blocks; // The bytes and blocks at t-gmax for this PP. // - bklt=true: mandatory integers. // - bklt=false: omitted. - gb: Bytes, - gbk: Blocks, + gb: Bytes; + gbk: Blocks; // The bytes and blocks at t-end for this PP. // - bklt=true: mandatory integers. // - bklt=false: omitted. - eb: Bytes, - ebk: Blocks, + eb: Bytes; + ebk: Blocks; // The reads and writes of blocks for this PP. // - bkacc=true: mandatory integers. // - bkacc=false: omitted. - rb: ReadCount, - wb: WriteCount, + rb: ReadCount; + wb: WriteCount; // The exact accesses of blocks for this PP. Only used when all // allocations are the same size and sufficiently small. A negative @@ -133,13 +133,13 @@ type ProgramPoint = $ReadOnly<{| // - bkacc=false: omitted. // // e.g. [5, -3, 4, 2] - acc: number[], + acc: number[]; // Frames. Each element is an index into the "ftbl" array above. // The array is ordered from leaf to root. // - All modes: A mandatory array of integers. - fs: IndexIntoDhatFrames[], -|}>; + fs: IndexIntoDhatFrames[]; +}>; // All units of time are in instruction counts. // Per: https://valgrind.org/docs/manual/dh-manual.html @@ -159,23 +159,26 @@ type WriteCount = number; * in the Gecko format. In the Gecko format, that data comes in the form of markers, which * would be awkard to target. */ -export function attemptToConvertDhat(json: mixed): Profile | null { +export function attemptToConvertDhat(json: unknown): Profile | null { if (!json || typeof json !== 'object') { return null; } - const { dhatFileVersion } = json; - if (typeof dhatFileVersion !== 'number') { + if ( + !('dhatFileVersion' in json) || + typeof json.dhatFileVersion !== 'number' + ) { // This is not a dhat file. return null; } + const { dhatFileVersion } = json; if (dhatFileVersion !== 2) { throw new Error( `This importer only supports dhat version 2. The file provided was version ${dhatFileVersion}.` ); } - const dhat = coerce(json); + const dhat = json as DhatJson; const profile = getEmptyProfile(); profile.meta.product = dhat.cmd + ' (dhat)'; diff --git a/src/profile-logic/import/linux-perf.js b/src/profile-logic/import/linux-perf.ts similarity index 93% rename from src/profile-logic/import/linux-perf.js rename to src/profile-logic/import/linux-perf.ts index 13d6cf6401..b20c6185b5 100644 --- a/src/profile-logic/import/linux-perf.js +++ b/src/profile-logic/import/linux-perf.ts @@ -2,8 +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/. */ -// @flow - import type { MixedObject } from 'firefox-profiler/types'; /** @@ -75,7 +73,7 @@ const KERNEL_CATEGORY_INDEX = 1; export function convertPerfScriptProfile( profile: string ): GeckoProfileVersion24 { - function _createThread(name, pid, tid) { + function _createThread(name: string, pid: number, tid: number) { const markers = { schema: { name: 0, @@ -93,7 +91,7 @@ export function convertPerfScriptProfile( time: 1, responsiveness: 2, }, - data: [], + data: [] as Array<[number | null, number, number]>, }; const frameTable = { schema: { @@ -107,19 +105,21 @@ export function convertPerfScriptProfile( category: 7, subcategory: 8, }, - data: [], + data: [] as Array< + [number, boolean, number, null, null, null, null, number, null] + >, }; const stackTable = { schema: { prefix: 0, frame: 1, }, - data: [], + data: [] as Array<[number | null, number]>, }; - const stringTable = []; + const stringTable: string[] = []; const stackMap = new Map(); - function getOrCreateStack(frame, prefix) { + function getOrCreateStack(frame: number, prefix: number | null) { const key = prefix === null ? `${frame}` : `${frame},${prefix}`; let stack = stackMap.get(key); if (stack === undefined) { @@ -131,7 +131,7 @@ export function convertPerfScriptProfile( } const frameMap = new Map(); - function getOrCreateFrame(frameString: string) { + function getOrCreateFrame(frameString: string): number { let frame = frameMap.get(frameString); if (frame === undefined) { frame = frameTable.data.length; @@ -175,14 +175,14 @@ export function convertPerfScriptProfile( return frame; } - function addSample(threadName, stackArray, time) { + function addSample(threadName: string, stackArray: string[], time: number) { // often we create a thread which inherits the name of the parent, and // set the thread's name slightly later. Avoid having the first // sample's name stick. if (name !== threadName) { name = threadName; } - const stack = stackArray.reduce((prefix, stackFrame) => { + const stack = stackArray.reduce((prefix, stackFrame) => { const frame = getOrCreateFrame(stackFrame); return getOrCreateStack(frame, prefix); }, null); @@ -212,9 +212,15 @@ export function convertPerfScriptProfile( }; } - const threadMap = new Map(); + const threadMap = new Map(); - function _addThreadSample(pid, tid, threadName, timeStamp, stack) { + function _addThreadSample( + pid: number, + tid: number, + threadName: string, + timeStamp: number, + stack: string[] + ) { // Right now this assumes that you can't have two identical tids in // different pids, which is true in linux at least. let thread = threadMap.get(tid); @@ -346,7 +352,7 @@ export function convertPerfScriptProfile( for (const thread of threadArray) { // The samples are not guaranteed to be in order, sort them so that they are. const key = thread.samples.schema.time; - (thread.samples.data: Array).sort((a, b) => a[key] - b[key]); + thread.samples.data.sort((a: any, b: any) => a[key] - b[key]); } return { diff --git a/src/profile-logic/import/proto/simpleperf_report.d.ts b/src/profile-logic/import/proto/simpleperf_report.d.ts new file mode 100644 index 0000000000..18308ae97d --- /dev/null +++ b/src/profile-logic/import/proto/simpleperf_report.d.ts @@ -0,0 +1,1079 @@ +import * as $protobuf from "protobufjs"; +import Long = require("long"); +/** Namespace simpleperf_report_proto. */ +export namespace simpleperf_report_proto { + + /** Properties of a Sample. */ + interface ISample { + + /** Sample time */ + time?: (number|Long|null); + + /** Sample threadId */ + threadId?: (number|null); + + /** Sample callchain */ + callchain?: (simpleperf_report_proto.Sample.ICallChainEntry[]|null); + + /** Sample eventCount */ + eventCount?: (number|Long|null); + + /** Sample eventTypeId */ + eventTypeId?: (number|null); + + /** Sample unwindingResult */ + unwindingResult?: (simpleperf_report_proto.Sample.IUnwindingResult|null); + } + + /** Represents a Sample. */ + class Sample implements ISample { + + /** + * Constructs a new Sample. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.ISample); + + /** Sample time. */ + public time: (number|Long); + + /** Sample threadId. */ + public threadId: number; + + /** Sample callchain. */ + public callchain: simpleperf_report_proto.Sample.ICallChainEntry[]; + + /** Sample eventCount. */ + public eventCount: (number|Long); + + /** Sample eventTypeId. */ + public eventTypeId: number; + + /** Sample unwindingResult. */ + public unwindingResult?: (simpleperf_report_proto.Sample.IUnwindingResult|null); + + /** + * Creates a new Sample instance using the specified properties. + * @param [properties] Properties to set + * @returns Sample instance + */ + public static create(properties?: simpleperf_report_proto.ISample): simpleperf_report_proto.Sample; + + /** + * Encodes the specified Sample message. Does not implicitly {@link simpleperf_report_proto.Sample.verify|verify} messages. + * @param message Sample message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.ISample, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified Sample message, length delimited. Does not implicitly {@link simpleperf_report_proto.Sample.verify|verify} messages. + * @param message Sample message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.ISample, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Sample message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns Sample + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.Sample; + + /** + * Decodes a Sample message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns Sample + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.Sample; + + /** + * Verifies a Sample message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a Sample message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Sample + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.Sample; + + /** + * Creates a plain object from a Sample message. Also converts values to other types if specified. + * @param message Sample + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.Sample, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Sample to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Sample + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + namespace Sample { + + /** Properties of a CallChainEntry. */ + interface ICallChainEntry { + + /** CallChainEntry vaddrInFile */ + vaddrInFile?: (number|Long|null); + + /** CallChainEntry fileId */ + fileId?: (number|null); + + /** CallChainEntry symbolId */ + symbolId?: (number|null); + + /** CallChainEntry executionType */ + executionType?: (simpleperf_report_proto.Sample.CallChainEntry.ExecutionType|null); + } + + /** Represents a CallChainEntry. */ + class CallChainEntry implements ICallChainEntry { + + /** + * Constructs a new CallChainEntry. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.Sample.ICallChainEntry); + + /** CallChainEntry vaddrInFile. */ + public vaddrInFile: (number|Long); + + /** CallChainEntry fileId. */ + public fileId: number; + + /** CallChainEntry symbolId. */ + public symbolId: number; + + /** CallChainEntry executionType. */ + public executionType: simpleperf_report_proto.Sample.CallChainEntry.ExecutionType; + + /** + * Creates a new CallChainEntry instance using the specified properties. + * @param [properties] Properties to set + * @returns CallChainEntry instance + */ + public static create(properties?: simpleperf_report_proto.Sample.ICallChainEntry): simpleperf_report_proto.Sample.CallChainEntry; + + /** + * Encodes the specified CallChainEntry message. Does not implicitly {@link simpleperf_report_proto.Sample.CallChainEntry.verify|verify} messages. + * @param message CallChainEntry message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.Sample.ICallChainEntry, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified CallChainEntry message, length delimited. Does not implicitly {@link simpleperf_report_proto.Sample.CallChainEntry.verify|verify} messages. + * @param message CallChainEntry message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.Sample.ICallChainEntry, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a CallChainEntry message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns CallChainEntry + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.Sample.CallChainEntry; + + /** + * Decodes a CallChainEntry message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns CallChainEntry + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.Sample.CallChainEntry; + + /** + * Verifies a CallChainEntry message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a CallChainEntry message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns CallChainEntry + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.Sample.CallChainEntry; + + /** + * Creates a plain object from a CallChainEntry message. Also converts values to other types if specified. + * @param message CallChainEntry + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.Sample.CallChainEntry, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this CallChainEntry to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for CallChainEntry + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + namespace CallChainEntry { + + /** ExecutionType enum. */ + enum ExecutionType { + NATIVE_METHOD = 0, + INTERPRETED_JVM_METHOD = 1, + JIT_JVM_METHOD = 2, + ART_METHOD = 3 + } + } + + /** Properties of an UnwindingResult. */ + interface IUnwindingResult { + + /** UnwindingResult rawErrorCode */ + rawErrorCode?: (number|null); + + /** UnwindingResult errorAddr */ + errorAddr?: (number|Long|null); + + /** UnwindingResult errorCode */ + errorCode?: (simpleperf_report_proto.Sample.UnwindingResult.ErrorCode|null); + } + + /** Represents an UnwindingResult. */ + class UnwindingResult implements IUnwindingResult { + + /** + * Constructs a new UnwindingResult. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.Sample.IUnwindingResult); + + /** UnwindingResult rawErrorCode. */ + public rawErrorCode: number; + + /** UnwindingResult errorAddr. */ + public errorAddr: (number|Long); + + /** UnwindingResult errorCode. */ + public errorCode: simpleperf_report_proto.Sample.UnwindingResult.ErrorCode; + + /** + * Creates a new UnwindingResult instance using the specified properties. + * @param [properties] Properties to set + * @returns UnwindingResult instance + */ + public static create(properties?: simpleperf_report_proto.Sample.IUnwindingResult): simpleperf_report_proto.Sample.UnwindingResult; + + /** + * Encodes the specified UnwindingResult message. Does not implicitly {@link simpleperf_report_proto.Sample.UnwindingResult.verify|verify} messages. + * @param message UnwindingResult message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.Sample.IUnwindingResult, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified UnwindingResult message, length delimited. Does not implicitly {@link simpleperf_report_proto.Sample.UnwindingResult.verify|verify} messages. + * @param message UnwindingResult message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.Sample.IUnwindingResult, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes an UnwindingResult message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns UnwindingResult + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.Sample.UnwindingResult; + + /** + * Decodes an UnwindingResult message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns UnwindingResult + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.Sample.UnwindingResult; + + /** + * Verifies an UnwindingResult message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates an UnwindingResult message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns UnwindingResult + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.Sample.UnwindingResult; + + /** + * Creates a plain object from an UnwindingResult message. Also converts values to other types if specified. + * @param message UnwindingResult + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.Sample.UnwindingResult, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this UnwindingResult to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for UnwindingResult + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + namespace UnwindingResult { + + /** ErrorCode enum. */ + enum ErrorCode { + ERROR_NONE = 0, + ERROR_UNKNOWN = 1, + ERROR_NOT_ENOUGH_STACK = 2, + ERROR_MEMORY_INVALID = 3, + ERROR_UNWIND_INFO = 4, + ERROR_INVALID_MAP = 5, + ERROR_MAX_FRAME_EXCEEDED = 6, + ERROR_REPEATED_FRAME = 7, + ERROR_INVALID_ELF = 8 + } + } + } + + /** Properties of a LostSituation. */ + interface ILostSituation { + + /** LostSituation sampleCount */ + sampleCount?: (number|Long|null); + + /** LostSituation lostCount */ + lostCount?: (number|Long|null); + } + + /** Represents a LostSituation. */ + class LostSituation implements ILostSituation { + + /** + * Constructs a new LostSituation. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.ILostSituation); + + /** LostSituation sampleCount. */ + public sampleCount: (number|Long); + + /** LostSituation lostCount. */ + public lostCount: (number|Long); + + /** + * Creates a new LostSituation instance using the specified properties. + * @param [properties] Properties to set + * @returns LostSituation instance + */ + public static create(properties?: simpleperf_report_proto.ILostSituation): simpleperf_report_proto.LostSituation; + + /** + * Encodes the specified LostSituation message. Does not implicitly {@link simpleperf_report_proto.LostSituation.verify|verify} messages. + * @param message LostSituation message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.ILostSituation, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified LostSituation message, length delimited. Does not implicitly {@link simpleperf_report_proto.LostSituation.verify|verify} messages. + * @param message LostSituation message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.ILostSituation, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a LostSituation message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns LostSituation + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.LostSituation; + + /** + * Decodes a LostSituation message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns LostSituation + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.LostSituation; + + /** + * Verifies a LostSituation message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a LostSituation message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns LostSituation + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.LostSituation; + + /** + * Creates a plain object from a LostSituation message. Also converts values to other types if specified. + * @param message LostSituation + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.LostSituation, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this LostSituation to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for LostSituation + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a File. */ + interface IFile { + + /** File id */ + id?: (number|null); + + /** File path */ + path?: (string|null); + + /** File symbol */ + symbol?: (string[]|null); + + /** File mangledSymbol */ + mangledSymbol?: (string[]|null); + } + + /** Represents a File. */ + class File implements IFile { + + /** + * Constructs a new File. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.IFile); + + /** File id. */ + public id: number; + + /** File path. */ + public path: string; + + /** File symbol. */ + public symbol: string[]; + + /** File mangledSymbol. */ + public mangledSymbol: string[]; + + /** + * Creates a new File instance using the specified properties. + * @param [properties] Properties to set + * @returns File instance + */ + public static create(properties?: simpleperf_report_proto.IFile): simpleperf_report_proto.File; + + /** + * Encodes the specified File message. Does not implicitly {@link simpleperf_report_proto.File.verify|verify} messages. + * @param message File message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.IFile, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified File message, length delimited. Does not implicitly {@link simpleperf_report_proto.File.verify|verify} messages. + * @param message File message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.IFile, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a File message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns File + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.File; + + /** + * Decodes a File message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns File + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.File; + + /** + * Verifies a File message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a File message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns File + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.File; + + /** + * Creates a plain object from a File message. Also converts values to other types if specified. + * @param message File + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.File, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this File to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for File + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a Thread. */ + interface IThread { + + /** Thread threadId */ + threadId?: (number|null); + + /** Thread processId */ + processId?: (number|null); + + /** Thread threadName */ + threadName?: (string|null); + } + + /** Represents a Thread. */ + class Thread implements IThread { + + /** + * Constructs a new Thread. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.IThread); + + /** Thread threadId. */ + public threadId: number; + + /** Thread processId. */ + public processId: number; + + /** Thread threadName. */ + public threadName: string; + + /** + * Creates a new Thread instance using the specified properties. + * @param [properties] Properties to set + * @returns Thread instance + */ + public static create(properties?: simpleperf_report_proto.IThread): simpleperf_report_proto.Thread; + + /** + * Encodes the specified Thread message. Does not implicitly {@link simpleperf_report_proto.Thread.verify|verify} messages. + * @param message Thread message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.IThread, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified Thread message, length delimited. Does not implicitly {@link simpleperf_report_proto.Thread.verify|verify} messages. + * @param message Thread message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.IThread, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Thread message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns Thread + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.Thread; + + /** + * Decodes a Thread message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns Thread + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.Thread; + + /** + * Verifies a Thread message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a Thread message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Thread + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.Thread; + + /** + * Creates a plain object from a Thread message. Also converts values to other types if specified. + * @param message Thread + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.Thread, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Thread to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Thread + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a MetaInfo. */ + interface IMetaInfo { + + /** MetaInfo eventType */ + eventType?: (string[]|null); + + /** MetaInfo appPackageName */ + appPackageName?: (string|null); + + /** MetaInfo appType */ + appType?: (string|null); + + /** MetaInfo androidSdkVersion */ + androidSdkVersion?: (string|null); + + /** MetaInfo androidBuildType */ + androidBuildType?: (string|null); + + /** MetaInfo traceOffcpu */ + traceOffcpu?: (boolean|null); + } + + /** Represents a MetaInfo. */ + class MetaInfo implements IMetaInfo { + + /** + * Constructs a new MetaInfo. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.IMetaInfo); + + /** MetaInfo eventType. */ + public eventType: string[]; + + /** MetaInfo appPackageName. */ + public appPackageName: string; + + /** MetaInfo appType. */ + public appType: string; + + /** MetaInfo androidSdkVersion. */ + public androidSdkVersion: string; + + /** MetaInfo androidBuildType. */ + public androidBuildType: string; + + /** MetaInfo traceOffcpu. */ + public traceOffcpu: boolean; + + /** + * Creates a new MetaInfo instance using the specified properties. + * @param [properties] Properties to set + * @returns MetaInfo instance + */ + public static create(properties?: simpleperf_report_proto.IMetaInfo): simpleperf_report_proto.MetaInfo; + + /** + * Encodes the specified MetaInfo message. Does not implicitly {@link simpleperf_report_proto.MetaInfo.verify|verify} messages. + * @param message MetaInfo message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.IMetaInfo, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified MetaInfo message, length delimited. Does not implicitly {@link simpleperf_report_proto.MetaInfo.verify|verify} messages. + * @param message MetaInfo message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.IMetaInfo, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a MetaInfo message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns MetaInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.MetaInfo; + + /** + * Decodes a MetaInfo message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns MetaInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.MetaInfo; + + /** + * Verifies a MetaInfo message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a MetaInfo message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns MetaInfo + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.MetaInfo; + + /** + * Creates a plain object from a MetaInfo message. Also converts values to other types if specified. + * @param message MetaInfo + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.MetaInfo, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this MetaInfo to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for MetaInfo + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a ContextSwitch. */ + interface IContextSwitch { + + /** ContextSwitch switchOn */ + switchOn?: (boolean|null); + + /** ContextSwitch time */ + time?: (number|Long|null); + + /** ContextSwitch threadId */ + threadId?: (number|null); + } + + /** Represents a ContextSwitch. */ + class ContextSwitch implements IContextSwitch { + + /** + * Constructs a new ContextSwitch. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.IContextSwitch); + + /** ContextSwitch switchOn. */ + public switchOn: boolean; + + /** ContextSwitch time. */ + public time: (number|Long); + + /** ContextSwitch threadId. */ + public threadId: number; + + /** + * Creates a new ContextSwitch instance using the specified properties. + * @param [properties] Properties to set + * @returns ContextSwitch instance + */ + public static create(properties?: simpleperf_report_proto.IContextSwitch): simpleperf_report_proto.ContextSwitch; + + /** + * Encodes the specified ContextSwitch message. Does not implicitly {@link simpleperf_report_proto.ContextSwitch.verify|verify} messages. + * @param message ContextSwitch message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.IContextSwitch, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified ContextSwitch message, length delimited. Does not implicitly {@link simpleperf_report_proto.ContextSwitch.verify|verify} messages. + * @param message ContextSwitch message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.IContextSwitch, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a ContextSwitch message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns ContextSwitch + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.ContextSwitch; + + /** + * Decodes a ContextSwitch message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns ContextSwitch + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.ContextSwitch; + + /** + * Verifies a ContextSwitch message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a ContextSwitch message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ContextSwitch + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.ContextSwitch; + + /** + * Creates a plain object from a ContextSwitch message. Also converts values to other types if specified. + * @param message ContextSwitch + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.ContextSwitch, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ContextSwitch to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ContextSwitch + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a Record. */ + interface IRecord { + + /** Record sample */ + sample?: (simpleperf_report_proto.ISample|null); + + /** Record lost */ + lost?: (simpleperf_report_proto.ILostSituation|null); + + /** Record file */ + file?: (simpleperf_report_proto.IFile|null); + + /** Record thread */ + thread?: (simpleperf_report_proto.IThread|null); + + /** Record metaInfo */ + metaInfo?: (simpleperf_report_proto.IMetaInfo|null); + + /** Record contextSwitch */ + contextSwitch?: (simpleperf_report_proto.IContextSwitch|null); + } + + /** Represents a Record. */ + class Record implements IRecord { + + /** + * Constructs a new Record. + * @param [properties] Properties to set + */ + constructor(properties?: simpleperf_report_proto.IRecord); + + /** Record sample. */ + public sample?: (simpleperf_report_proto.ISample|null); + + /** Record lost. */ + public lost?: (simpleperf_report_proto.ILostSituation|null); + + /** Record file. */ + public file?: (simpleperf_report_proto.IFile|null); + + /** Record thread. */ + public thread?: (simpleperf_report_proto.IThread|null); + + /** Record metaInfo. */ + public metaInfo?: (simpleperf_report_proto.IMetaInfo|null); + + /** Record contextSwitch. */ + public contextSwitch?: (simpleperf_report_proto.IContextSwitch|null); + + /** Record recordData. */ + public recordData?: ("sample"|"lost"|"file"|"thread"|"metaInfo"|"contextSwitch"); + + /** + * Creates a new Record instance using the specified properties. + * @param [properties] Properties to set + * @returns Record instance + */ + public static create(properties?: simpleperf_report_proto.IRecord): simpleperf_report_proto.Record; + + /** + * Encodes the specified Record message. Does not implicitly {@link simpleperf_report_proto.Record.verify|verify} messages. + * @param message Record message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encode(message: simpleperf_report_proto.IRecord, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Encodes the specified Record message, length delimited. Does not implicitly {@link simpleperf_report_proto.Record.verify|verify} messages. + * @param message Record message or plain object to encode + * @param [writer] Writer to encode to + * @returns Writer + */ + public static encodeDelimited(message: simpleperf_report_proto.IRecord, writer?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Record message from the specified reader or buffer. + * @param reader Reader or buffer to decode from + * @param [length] Message length if known beforehand + * @returns Record + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): simpleperf_report_proto.Record; + + /** + * Decodes a Record message from the specified reader or buffer, length delimited. + * @param reader Reader or buffer to decode from + * @returns Record + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): simpleperf_report_proto.Record; + + /** + * Verifies a Record message. + * @param message Plain object to verify + * @returns `null` if valid, otherwise the reason why it is not + */ + public static verify(message: { [k: string]: any }): (string|null); + + /** + * Creates a Record message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Record + */ + public static fromObject(object: { [k: string]: any }): simpleperf_report_proto.Record; + + /** + * Creates a plain object from a Record message. Also converts values to other types if specified. + * @param message Record + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: simpleperf_report_proto.Record, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Record to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Record + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } +} diff --git a/src/profile-logic/import/proto/simpleperf_report.js b/src/profile-logic/import/proto/simpleperf_report.js index 4fc8bd3e84..415ca07856 100644 --- a/src/profile-logic/import/proto/simpleperf_report.js +++ b/src/profile-logic/import/proto/simpleperf_report.js @@ -160,12 +160,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - Sample.decode = function decode(reader, length) { + Sample.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.Sample(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.time = reader.uint64(); @@ -507,12 +509,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - CallChainEntry.decode = function decode(reader, length) { + CallChainEntry.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.Sample.CallChainEntry(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.vaddrInFile = reader.uint64(); @@ -830,12 +834,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - UnwindingResult.decode = function decode(reader, length) { + UnwindingResult.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.Sample.UnwindingResult(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.rawErrorCode = reader.uint32(); @@ -1168,12 +1174,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - LostSituation.decode = function decode(reader, length) { + LostSituation.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.LostSituation(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.sampleCount = reader.uint64(); @@ -1449,12 +1457,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - File.decode = function decode(reader, length) { + File.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.File(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.id = reader.uint32(); @@ -1741,12 +1751,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - Thread.decode = function decode(reader, length) { + Thread.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.Thread(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.threadId = reader.uint32(); @@ -2026,12 +2038,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - MetaInfo.decode = function decode(reader, length) { + MetaInfo.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.MetaInfo(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { if (!(message.eventType && message.eventType.length)) @@ -2327,12 +2341,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - ContextSwitch.decode = function decode(reader, length) { + ContextSwitch.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.ContextSwitch(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.switchOn = reader.bool(); @@ -2638,12 +2654,14 @@ $root.simpleperf_report_proto = (function() { * @throws {Error} If the payload is not a reader or valid buffer * @throws {$protobuf.util.ProtocolError} If required fields are missing */ - Record.decode = function decode(reader, length) { + Record.decode = function decode(reader, length, error) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length, message = new $root.simpleperf_report_proto.Record(); while (reader.pos < end) { var tag = reader.uint32(); + if (tag === error) + break; switch (tag >>> 3) { case 1: { message.sample = $root.simpleperf_report_proto.Sample.decode(reader, reader.uint32()); diff --git a/src/profile-logic/import/simpleperf.js b/src/profile-logic/import/simpleperf.ts similarity index 92% rename from src/profile-logic/import/simpleperf.js rename to src/profile-logic/import/simpleperf.ts index 32998023c4..ac9758c7bd 100644 --- a/src/profile-logic/import/simpleperf.js +++ b/src/profile-logic/import/simpleperf.ts @@ -1,5 +1,3 @@ -// @flow - import { simpleperf_report_proto as report } from './proto/simpleperf_report'; import { PROCESSED_PROFILE_VERSION } from 'firefox-profiler/app-logic/constants'; @@ -91,15 +89,15 @@ class FirefoxResourceTable { } findOrAddResource(file: report.IFile): IndexIntoResourceTable { - let resourceIndex = this.resourcesMap.get(file.id); + let resourceIndex = this.resourcesMap.get(file.id!); if (!resourceIndex) { this.resourceTable.lib.push(null); - this.resourceTable.name.push(this.strings.indexForString(file.path)); + this.resourceTable.name.push(this.strings.indexForString(file.path!)); this.resourceTable.host.push(null); this.resourceTable.type.push(1); // Library resourceIndex = this.resourceTable.length++; - this.resourcesMap.set(file.id, resourceIndex); + this.resourcesMap.set(file.id!, resourceIndex); } return resourceIndex; @@ -235,8 +233,8 @@ class FirefoxThread { cpuClockEventId: number = -1; constructor(thread: report.IThread, stringTable: StringTable) { - this.tid = thread.threadId; - this.pid = thread.processId; + this.tid = thread.threadId!; + this.pid = thread.processId!; this.isMainThread = thread.threadId === thread.processId; this.name = thread.threadName ?? ''; @@ -282,14 +280,14 @@ class FirefoxThread { addSample(sample: report.ISample, fileMap: Map): void { let prefixStackId: number | null = null; - for (const frame of sample.callchain.reverse()) { - const file: report.IFile = fileMap.get(frame.fileId); + for (const frame of sample.callchain!.reverse()) { + const file: report.IFile = fileMap.get(frame.fileId!)!; const resourceIndex = this.resourceTable.findOrAddResource(file); const methodName = - frame.symbolId >= 0 - ? file.symbol[frame.symbolId] - : `${file.path.split(/[\\/]/).pop()}+0x${frame.vaddrInFile.toString(16)}`; + frame.symbolId! >= 0 + ? file.symbol![frame.symbolId!] + : `${file.path!.split(/[\\/]/).pop()}+0x${frame.vaddrInFile!.toString(16)}`; const funcIndex = this.funcTable.findOrAddFunc(methodName, resourceIndex); @@ -356,10 +354,10 @@ class FirefoxProfile { fileMap: Map = new Map(); - eventTypes: string[]; - cpuClockEventId: number; + eventTypes: string[] = []; + cpuClockEventId: number = -1; - appPackageName: ?string | null; + appPackageName: string | null = null; sampleCount: number = 0; lostCount: number = 0; @@ -434,7 +432,7 @@ class FirefoxProfile { setMetaInfo(metaInfo: report.IMetaInfo | null) { this.eventTypes = metaInfo?.eventType ?? []; - this.appPackageName = metaInfo?.appPackageName; + this.appPackageName = metaInfo?.appPackageName ?? null; this.cpuClockEventId = (this.eventTypes && this.eventTypes.indexOf('cpu-clock')) ?? -1; @@ -446,13 +444,13 @@ class FirefoxProfile { } addFile(file: report.IFile) { - this.fileMap.set(file.id, file); + this.fileMap.set(file.id!, file); } addThread(thread: report.IThread) { const firefoxThread = new FirefoxThread(thread, this.stringTable); this.threads.push(firefoxThread); - this.threadMap.set(thread.threadId, firefoxThread); + this.threadMap.set(thread.threadId!, firefoxThread); } finalizeThreads() { @@ -462,7 +460,7 @@ class FirefoxProfile { } addSample(sample: report.ISample): void { - const thread = this.threadMap.get(sample.threadId); + const thread = this.threadMap.get(sample.threadId!); if (!thread) { console.warn(`Thread not found for sample: ${sample.threadId}`); @@ -474,11 +472,11 @@ class FirefoxProfile { } export class SimpleperfReportConverter { - buffer: ArrayBuffer; + buffer: ArrayBufferLike; bufferView: DataView; bufferOffset: number = 0; - constructor(buffer: ArrayBuffer) { + constructor(buffer: ArrayBufferLike) { this.buffer = buffer; this.bufferView = new DataView(buffer); } @@ -537,22 +535,22 @@ export class SimpleperfReportConverter { switch (record.recordData) { case 'sample': - samples.push(record.sample); + samples.push(record.sample!); break; case 'lost': // Expected only once sampleCount = toNumber(record.lost?.sampleCount ?? 0); - targetProfile.setLostSituation(record.lost); + targetProfile.setLostSituation(record.lost!); break; case 'file': - targetProfile.addFile(record.file); + targetProfile.addFile(record.file!); break; case 'thread': - targetProfile.addThread(record.thread); + targetProfile.addThread(record.thread!); break; case 'metaInfo': // Expected only once - targetProfile.setMetaInfo(record.metaInfo); + targetProfile.setMetaInfo(record.metaInfo!); break; case 'contextSwitch': // Not handled @@ -577,7 +575,7 @@ export class SimpleperfReportConverter { } export function convertSimpleperfTraceProfile( - traceBuffer: ArrayBuffer + traceBuffer: ArrayBufferLike ): Profile { return new SimpleperfReportConverter(traceBuffer).process(); } diff --git a/src/profile-logic/js-tracer.js b/src/profile-logic/js-tracer.tsx similarity index 98% rename from src/profile-logic/js-tracer.js rename to src/profile-logic/js-tracer.tsx index f1f0dbfe09..c5e39f1f5f 100644 --- a/src/profile-logic/js-tracer.js +++ b/src/profile-logic/js-tracer.tsx @@ -1,15 +1,13 @@ /* 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/. */ -// @flow - import { getEmptyFrameTable, getEmptyRawStackTable, getEmptySamplesTableWithEventDelay, getEmptyRawMarkerTable, } from './data-structures'; -import { StringTable } from '../utils/string-table'; +import type { StringTable } from '../utils/string-table'; import { ensureExists } from '../utils/flow'; import type { JsTracerTable, @@ -192,7 +190,7 @@ export function getJsTracerLeafTiming( // Each event type will have it's own timing information, later collapse these into // a single array. const jsTracerTimingMap: Map = new Map(); - const isUrlCache = []; + const isUrlCache: boolean[] = []; const isUrlRegex = /:\/\//; function isUrl(index: IndexIntoStringTable): boolean { @@ -500,8 +498,8 @@ export function convertJsTracerToThreadWithoutSamples( jsTracer: JsTracerFixed, categories: CategoryList ): { - thread: RawThread, - stackMap: Map, + thread: RawThread; + stackMap: Map; } { // Create a new thread, with empty information, but preserve some of the existing // thread information. @@ -627,21 +625,21 @@ export function convertJsTracerToThreadWithoutSamples( // Reached a new root, reset the index to 1. unmatchedIndex = 1; } - unmatchedEventIndexes[unmatchedIndex] = tracerEventIndex; + (unmatchedEventIndexes as any)[unmatchedIndex] = tracerEventIndex; unmatchedEventEnds[unmatchedIndex] = end; } return { thread, stackMap }; } -type JsTracerFixed = {| - events: Array, - start: Array, - end: Array, - line: Array, // Line number. - column: Array, // Column number. - length: number, -|}; +type JsTracerFixed = { + events: Array; + start: Array; + end: Array; + line: Array; // Line number. + column: Array; // Column number. + length: number; +}; /** * JS Tracer information has a start and duration, but due to precision issues, this @@ -663,7 +661,7 @@ export function getJsTracerFixed(jsTracer: JsTracerTable): JsTracerFixed { }; } let prevStart = jsTracer.timestamps[0]; - let prevEnd = prevStart + jsTracer.durations[0]; + let prevEnd = prevStart + (jsTracer.durations![0] || 0); const start = [prevStart]; const end = [prevEnd]; const jsTracerFixed = { @@ -796,11 +794,11 @@ export function getSelfTimeSamplesFromJsTracer( ): RawSamplesTable { // Give more leeway for floating number precision issues. const epsilon = 1e-5; - const isNearlyEqual = (a, b) => Math.abs(a - b) < epsilon; + const isNearlyEqual = (a: number, b: number) => Math.abs(a - b) < epsilon; // Each event type will have it's own timing information, later collapse these into // a single array. const samples = getEmptySamplesTableWithEventDelay(); - const sampleWeights = []; + const sampleWeights: number[] = []; samples.weight = sampleWeights; function addSelfTimeAsASample( diff --git a/src/profile-logic/line-timings.js b/src/profile-logic/line-timings.ts similarity index 99% rename from src/profile-logic/line-timings.js rename to src/profile-logic/line-timings.ts index eac106e0af..4b821ed0fd 100644 --- a/src/profile-logic/line-timings.js +++ b/src/profile-logic/line-timings.ts @@ -2,8 +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/. */ -// @flow - import type { FrameTable, FuncTable, @@ -65,7 +63,7 @@ export function getStackLineInfo( // "self line" == "the line which a stack's self time is contributed to" const selfLineForAllStacks = []; // "total lines" == "the set of lines whose total time this stack contributes to" - const totalLinesForAllStacks = []; + const totalLinesForAllStacks: Array | null> = []; // This loop takes advantage of the fact that the stack table is topologically ordered: // Prefix stacks are always visited before their descendants. @@ -217,7 +215,7 @@ export function getStackLineInfoForCallNodeNonInverted( const callNodeSelfLineForAllStacks = []; // "total lines" == "the set of lines whose total time this stack contributes to" // Either null or a single-element set. - const callNodeTotalLinesForAllStacks = []; + const callNodeTotalLinesForAllStacks: Array | null> = []; // This loop takes advantage of the fact that the stack table is topologically ordered: // Prefix stacks are always visited before their descendants. diff --git a/src/profile-logic/marker-data.js b/src/profile-logic/marker-data.ts similarity index 94% rename from src/profile-logic/marker-data.js rename to src/profile-logic/marker-data.ts index 39fab49886..68e303512c 100644 --- a/src/profile-logic/marker-data.js +++ b/src/profile-logic/marker-data.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { getEmptyRawMarkerTable } from './data-structures'; import { getFriendlyThreadName } from './profile-data'; import { removeFilePath, removeURLs, stringsToRegExp } from '../utils/string'; @@ -114,7 +112,7 @@ export function deriveJankMarkers( } export function getSearchFilteredMarkerIndexes( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], markerSchemaByName: MarkerSchemaByName, searchRegExps: MarkerRegExps | null, @@ -183,7 +181,7 @@ function positiveFilterMarker( // passing it inside a function below. const regExps = searchRegExps; - function test(value, key) { + function test(value: string, key: string) { key = key.toLowerCase(); const fieldRegexp = regExps.fieldMap.get(key); const found = @@ -247,7 +245,7 @@ function negativeFilterMarker( // passing it inside a function below. const regExps = searchRegExps; - function test(value, key) { + function test(value: string, key: string) { key = key.toLowerCase(); const fieldRegexp = regExps.fieldMap.get(key); const negativeRegexp = fieldRegexp?.negative; @@ -321,7 +319,7 @@ export class IPCMarkerCorrelations { threadData.set(index, data); } - get(tid: Tid, index: number): ?IPCSharedData { + get(tid: Tid, index: number): IPCSharedData | undefined { const threadData = this._correlations.get(tid); if (!threadData) { return undefined; @@ -363,7 +361,7 @@ export function correlateIPCMarkers( // message seqno, and message type. Since the seqno is only unique for each // message channel pair, we use the PIDs and message type as a way of // identifying which channel pair generated this message. - function makeIPCMessageID(thread, data): string { + function makeIPCMessageID(thread: RawThread, data: IPCMarkerPayload): string { let pids; if (data.direction === 'sending') { pids = `${thread.pid},${data.otherPid}`; @@ -373,7 +371,7 @@ export function correlateIPCMarkers( return pids + `,${data.messageSeqno},${data.messageType}`; } - function formatThreadName(tid: ?number): string | void { + function formatThreadName(tid: number | undefined): string | undefined { if (tid !== null && tid !== undefined) { const name = threadNames.get(tid); if (name !== undefined) { @@ -436,7 +434,7 @@ export function correlateIPCMarkers( // this processing later. const markersByKey: Map< string, - Array<{ tid: number, index: number, data: IPCMarkerPayload } | void>, + Array<{ tid: number; index: number; data: IPCMarkerPayload } | void> > = new Map(); const threadNames: Map = new Map(); for (const thread of threads) { @@ -515,10 +513,17 @@ export function correlateIPCMarkers( // make the main thread crowded. It's important to add all the threads to // the marker. for (const m of markers.slice(1, 4)) { - if (m !== undefined && !addedThreadIds.has(m.tid)) { - // Add the marker to a thread only if it's not already added. - correlations.set(m.tid, m.index, sharedData); - addedThreadIds.add(m.tid); + if (m !== undefined) { + const marker = m as { + tid: number; + index: number; + data: IPCMarkerPayload; + }; + if (!addedThreadIds.has(marker.tid)) { + // Add the marker to a thread only if it's not already added. + correlations.set(marker.tid, marker.index, sharedData); + addedThreadIds.add(marker.tid); + } } } } @@ -551,7 +556,7 @@ export function correlateIPCMarkers( */ export function deriveMarkersFromRawMarkerTable( rawMarkers: RawMarkerTable, - stringArray: $ReadOnlyArray, + stringArray: ReadonlyArray, threadId: Tid, threadRange: StartEndRange, ipcCorrelations: IPCMarkerCorrelations @@ -559,7 +564,7 @@ export function deriveMarkersFromRawMarkerTable( const markers: Marker[] = []; const markerIndexToRawMarkerIndexes: IndexedArray< MarkerIndex, - IndexIntoRawMarkerTable[], + IndexIntoRawMarkerTable[] > = []; // These maps contain the start markers we find while looping the marker @@ -586,13 +591,16 @@ export function deriveMarkersFromRawMarkerTable( startData: MarkerPayload | null, endData: MarkerPayload | null ): MarkerPayload | null { - if (!startData && !endData) { - return null; + if (startData === null) { + return endData; } - return ({ + if (endData === null) { + return startData; + } + return { ...startData, ...endData, - }: any); + }; } // We don't add a screenshot marker as we find it, because to know its @@ -653,9 +661,7 @@ export function deriveMarkersFromRawMarkerTable( openNetworkMarkers.delete(data.id); // We know this startIndex points to a Network marker. - const startData: NetworkPayload = (rawMarkers.data[ - startIndex - ]: any); + const startData = rawMarkers.data[startIndex] as NetworkPayload; const startStartTime = ensureExists( rawMarkers.startTime[startIndex], @@ -1003,9 +1009,8 @@ export function filterRawMarkerTableToRange( rangeEnd: number ): RawMarkerTable { const newMarkerTable = getEmptyRawMarkerTable(); - const newThreadId = []; if (markerTable.threadId) { - newMarkerTable.threadId = newThreadId; + newMarkerTable.threadId = []; } const filteredMarkerIndexes = filterRawMarkerTableIndexesToRange( @@ -1022,8 +1027,8 @@ export function filterRawMarkerTableToRange( newMarkerTable.name.push(markerTable.name[index]); newMarkerTable.data.push(markerTable.data[index]); newMarkerTable.category.push(markerTable.category[index]); - if (markerTable.threadId) { - newThreadId.push(markerTable.threadId[index]); + if (markerTable.threadId && newMarkerTable.threadId) { + newMarkerTable.threadId.push(markerTable.threadId[index]); } newMarkerTable.length++; } @@ -1077,17 +1082,17 @@ export function filterRawMarkerTableToRangeWithMarkersToDelete( markersToDelete: Set, filterRange: StartEndRange | null ): { - rawMarkerTable: RawMarkerTable, - oldMarkerIndexToNew: Map, + rawMarkerTable: RawMarkerTable; + oldMarkerIndexToNew: Map; } { const newMarkerTable = getEmptyRawMarkerTable(); - const newThreadId = []; + const newThreadId: (Tid | null)[] = []; if (oldMarkerTable.threadId) { newMarkerTable.threadId = newThreadId; } const oldMarkerIndexToNew: Map< IndexIntoRawMarkerTable, - IndexIntoRawMarkerTable, + IndexIntoRawMarkerTable > = new Map(); const addMarkerIndexIfIncluded = (index: IndexIntoRawMarkerTable) => { if (markersToDelete.has(index)) { @@ -1138,9 +1143,9 @@ export function filterRawMarkerTableToRangeWithMarkersToDelete( * markers, with marker indexes both as input and output. */ export function filterMarkerIndexes( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], - filterFunc: (Marker) => boolean + filterFunc: (marker: Marker) => boolean ): MarkerIndex[] { return markerIndexes.filter((markerIndex) => { return filterFunc(getMarker(markerIndex)); @@ -1148,7 +1153,7 @@ export function filterMarkerIndexes( } export function filterMarkerIndexesToRange( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], rangeStart: number, rangeEnd: number @@ -1188,11 +1193,15 @@ export function isNavigationMarker({ name, data }: Marker) { return false; } - if (data.innerWindowID && name === 'Navigation::Start') { + if ( + 'innerWindowID' in data && + data.innerWindowID && + name === 'Navigation::Start' + ) { return true; } - if (data.category === 'Navigation') { + if ('category' in data && data.category === 'Navigation') { // Filter by payloads. if (name === 'Load' || name === 'DOMContentLoaded') { return true; @@ -1230,7 +1239,7 @@ export function isOnThreadFileIoMarker(marker: Marker): boolean | void { */ export function getAllowMarkersWithNoSchema( markerSchemaByName: MarkerSchemaByName -): (Marker) => boolean | void { +): (marker: Marker) => boolean | void { return (marker) => { const { data } = marker; @@ -1319,7 +1328,7 @@ export function getColorClassNameForMimeType( } export function groupScreenshotsById( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[] ): Map { const idToScreenshotMarkers = new Map(); @@ -1401,20 +1410,20 @@ export function sanitizeFromMarkerSchema( // doesn't like much our enormous enum of non-exact objects that's used as // MarkerPayload type, and this code is too generic for Flow in this context. if (format === 'url') { - markerPayload = ({ + markerPayload = { ...markerPayload, - [key]: removeURLs(markerPayload[key]), - }: any); + [key]: removeURLs((markerPayload as any)[key]), + } as any; } else if (format === 'file-path') { - markerPayload = ({ + markerPayload = { ...markerPayload, - [key]: removeFilePath(markerPayload[key]), - }: any); + [key]: removeFilePath((markerPayload as any)[key]), + } as any; } else if (format === 'sanitized-string') { - markerPayload = ({ + markerPayload = { ...markerPayload, [key]: '', - }: any); + } as any; } } @@ -1427,9 +1436,9 @@ export function sanitizeFromMarkerSchema( */ export function getMarkerTypesForDisplay( markerSchema: MarkerSchema[], - displayArea: string + displayArea: MarkerDisplayLocation ): Set { - const types = new Set(); + const types = new Set(); for (const { display, name } of markerSchema) { if (display.includes(displayArea)) { types.add(name); @@ -1438,7 +1447,7 @@ export function getMarkerTypesForDisplay( return types; } -function _doNotAutomaticallyAdd(_data: Marker) { +function _doNotAutomaticallyAdd(_data: Marker): boolean | void { return undefined; } @@ -1446,7 +1455,7 @@ function _doNotAutomaticallyAdd(_data: Marker) { * Filter markers to a smaller set based on the location. */ export function filterMarkerByDisplayLocation( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], markerSchema: MarkerSchema[], markerSchemaByName: MarkerSchemaByName, @@ -1454,17 +1463,17 @@ export function filterMarkerByDisplayLocation( // This argument allows a filtering function to customize the result, without having // to loop through all of the markers again. Return a boolean if making a decision, // or undefined if not. - preemptiveFilterFunc?: ( + preemptiveFilterFunc: ( data: Marker ) => boolean | void = _doNotAutomaticallyAdd ): MarkerIndex[] { const markerTypes = getMarkerTypesForDisplay(markerSchema, displayLocation); - return filterMarkerIndexes(getMarker, markerIndexes, (marker) => { + return filterMarkerIndexes(getMarker, markerIndexes, (marker): boolean => { const additionalResult = preemptiveFilterFunc(marker); if (additionalResult !== undefined) { // This is a boolean value, use it rather than the schema. - return additionalResult; + return additionalResult as boolean; } const schemaName = marker.data ? (marker.data.type ?? null) : null; @@ -1476,9 +1485,9 @@ export function filterMarkerByDisplayLocation( * Compute the Screenshot image's thumbnail size. */ export function computeScreenshotSize( - payload: { windowWidth: number, windowHeight: number }, + payload: { windowWidth: number; windowHeight: number }, maximumSize: number -): {| +width: number, +height: number |} { +): { readonly width: number; readonly height: number } { const { windowWidth, windowHeight } = payload; // Coefficient should be according to bigger side. @@ -1500,13 +1509,13 @@ export function computeScreenshotSize( export type MarkerSearchFieldMap = Map< string, - {| positive: RegExp | null, negative: RegExp | null |}, + { positive: RegExp | null; negative: RegExp | null } >; -export type MarkerRegExps = $ReadOnly<{| - generic: RegExp | null, - fieldMap: MarkerSearchFieldMap, -|}>; +export type MarkerRegExps = Readonly<{ + generic: RegExp | null; + fieldMap: MarkerSearchFieldMap; +}>; /** * Concatenate an array of strings into multiple RegExps that match on all @@ -1521,10 +1530,8 @@ export const stringsToMarkerRegExps = ( // We create this map to group all the field specific search strings and then // we aggregate them to create a single regexp for each field later. - const fieldStrings: Map< - string, - {| positive: string[], negative: string[] |}, - > = new Map(); + const fieldStrings: Map = + new Map(); // These are the non-field specific search strings. They have to be positive // as we don't support negative generic filtering. const genericPositiveStrings = []; diff --git a/src/profile-logic/marker-schema.js b/src/profile-logic/marker-schema.tsx similarity index 83% rename from src/profile-logic/marker-schema.js rename to src/profile-logic/marker-schema.tsx index da91be37f1..b6ab3c1209 100644 --- a/src/profile-logic/marker-schema.js +++ b/src/profile-logic/marker-schema.tsx @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import * as React from 'react'; import { oneLine } from 'common-tags'; import { @@ -110,7 +110,7 @@ export function parseLabel( categories: CategoryList, stringTable: StringTable, label: string -): (Marker) => string { +): (marker: Marker) => string { // Split the label on the "{key}" capture groups. // Each (zero-indexed) even entry will be a raw string label. // Each (zero-indexed) odd entry will be a key to the payload. @@ -149,96 +149,98 @@ export function parseLabel( } // This is a list of functions that will compute each part of the label. - const computeLabelParts: Array<(Marker) => string> = splits.map((part, i) => { - if (i % 2 === 0) { - // This is a normal string part. Return it. + const computeLabelParts: Array<(marker: Marker) => string> = splits.map( + (part, i) => { + if (i % 2 === 0) { + // This is a normal string part. Return it. + // Given: "Marker information: {marker.name} – {marker.data.info}" + // Handle: ^^^^^^^^^^^^^^^^^^^^ ^^^ + return () => part; + } + // Now consider each keyed property: // Given: "Marker information: {marker.name} – {marker.data.info}" - // Handle: ^^^^^^^^^^^^^^^^^^^^ ^^^ - return () => part; - } - // Now consider each keyed property: - // Given: "Marker information: {marker.name} – {marker.data.info}" - // Handle: ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ - - const keys = part.trim().split('.'); - - if (keys.length !== 2 && keys.length !== 3) { - // The following examples would trigger this error: - // Given: "Marker information: {name} – {marker.data.info.subinfo}" - // Handle: ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ - return parseError(label, part); - } + // Handle: ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ - const [marker, markerKey, payloadKey] = keys; - if (marker !== 'marker') { - // The following examples would trigger this error: - // Given: "Value: {property.name}" - // Handle: ^^^^^^^^ - return parseError(label, part); - } + const keys = part.trim().split('.'); - if (keys.length === 2) { - // Access parts of the payload - // Given: "Marker information: {marker.name} – {marker.data.info}" - // Handle: ^^^^^^^^^^^ - switch (markerKey) { - case 'start': - return (marker) => formatTimestamp(marker.start); - case 'end': - return (marker) => - marker.end === null ? 'unknown' : formatTimestamp(marker.end); - case 'duration': - return (marker) => - marker.end === null - ? 'unknown' - : formatTimestamp(marker.end - marker.start); - case 'name': - return (marker) => marker.name; - case 'category': - return (marker) => categories[marker.category].name; - case 'data': - default: - return parseError(label, part); + if (keys.length !== 2 && keys.length !== 3) { + // The following examples would trigger this error: + // Given: "Marker information: {name} – {marker.data.info.subinfo}" + // Handle: ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^ + return parseError(label, part); } - } - if (markerKey === 'data') { - // This is accessing the payload. - // Given: "Marker information: {marker.name} – {marker.data.info}" - // Handle: ^^^^^^^^^^^^^^^^ + const [marker, markerKey, payloadKey] = keys; + if (marker !== 'marker') { + // The following examples would trigger this error: + // Given: "Value: {property.name}" + // Handle: ^^^^^^^^ + return parseError(label, part); + } - let format = null; - for (const field of markerSchema.fields) { - if (field.key === payloadKey) { - format = field.format; - break; + if (keys.length === 2) { + // Access parts of the payload + // Given: "Marker information: {marker.name} – {marker.data.info}" + // Handle: ^^^^^^^^^^^ + switch (markerKey) { + case 'start': + return (marker) => formatTimestamp(marker.start); + case 'end': + return (marker) => + marker.end === null ? 'unknown' : formatTimestamp(marker.end); + case 'duration': + return (marker) => + marker.end === null + ? 'unknown' + : formatTimestamp(marker.end - marker.start); + case 'name': + return (marker) => marker.name; + case 'category': + return (marker) => categories[marker.category].name; + case 'data': + default: + return parseError(label, part); } } - return (marker) => { - if (!marker.data) { - // There was no data. - return ''; - } + if (markerKey === 'data') { + // This is accessing the payload. + // Given: "Marker information: {marker.name} – {marker.data.info}" + // Handle: ^^^^^^^^^^^^^^^^ - const value = marker.data[payloadKey]; - if (value === undefined || value === null) { - // This would return "undefined" or "null" otherwise. - return ''; + let format = null; + for (const field of markerSchema.fields) { + if (field.key === payloadKey) { + format = field.format; + break; + } } - return format - ? formatFromMarkerSchema( - markerSchema.name, - format, - value, - stringTable - ) - : value; - }; - } - return parseError(label, part); - }); + return (marker) => { + if (!marker.data) { + // There was no data. + return ''; + } + + const value = (marker.data as any)[payloadKey]; + if (value === undefined || value === null) { + // This would return "undefined" or "null" otherwise. + return ''; + } + return format + ? formatFromMarkerSchema( + markerSchema.name, + format, + value, + stringTable + ) + : value; + }; + } + + return parseError(label, part); + } + ); return (marker: Marker) => { let result: string = ''; @@ -254,7 +256,7 @@ type LabelKey = 'tooltipLabel' | 'tableLabel' | 'chartLabel' | 'copyLabel'; // If no label making rule, these functions provide the fallbacks for how // to label things. It also allows for a place to do some custom handling // in the cases where the marker schema is not enough. -const fallbacks: { [LabelKey]: (Marker) => string } = { +const fallbacks: Record string> = { tooltipLabel: (marker) => marker.name, chartLabel: (_marker) => '', @@ -295,15 +297,15 @@ const fallbacks: { [LabelKey]: (Marker) => string } = { * This function should only be used behind a selector. */ export function getLabelGetter( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerSchemaList: MarkerSchema[], markerSchemaByName: MarkerSchemaByName, categoryList: CategoryList, stringTable: StringTable, labelKey: LabelKey -): (MarkerIndex) => string { +): (markerIndex: MarkerIndex) => string { // Build up a list of label functions, that are tied to the schema name. - const labelFns: Map string> = new Map(); + const labelFns: Map string> = new Map(); const markerNamePrefixRe = /^{marker.name}\s[-—]\s/; for (const schema of markerSchemaList) { let labelString; @@ -407,7 +409,7 @@ export function formatFromMarkerSchema( ); } return row.map((cell, j) => { - const { format } = columns[j]; + const { type: format } = columns[j]; return formatFromMarkerSchema( markerType, format || 'string', @@ -423,7 +425,7 @@ export function formatFromMarkerSchema( } default: throw new Error( - `Unknown format type ${JSON.stringify((format.type: empty))}` + `Unknown format type ${JSON.stringify(format.type as never)}` ); } } @@ -477,7 +479,7 @@ export function formatFromMarkerSchema( default: console.warn( `A marker schema of type "${markerType}" had an unknown format ${JSON.stringify( - (format: empty) + format as never )}` ); return value; @@ -499,7 +501,7 @@ export function formatMarkupFromMarkerSchema( stringTable: StringTable, threadIdToNameMap?: Map, processIdToNameMap?: Map -): React.Element | string { +): React.ReactElement | string { if (value === undefined || value === null) { console.warn(`Formatting ${value} for ${JSON.stringify(markerType)}`); return '(empty)'; @@ -569,7 +571,7 @@ export function formatMarkupFromMarkerSchema( } default: throw new Error( - `Unknown format type ${JSON.stringify((format: empty))}` + `Unknown format type ${JSON.stringify(format as never)}` ); } } @@ -608,7 +610,7 @@ export function formatMarkupFromMarkerSchema( ); } default: - throw new Error(`Unknown format type ${JSON.stringify((format: empty))}`); + throw new Error(`Unknown format type ${JSON.stringify(format as never)}`); } } @@ -620,7 +622,7 @@ export function markerPayloadMatchesSearch( markerSchema: MarkerSchema, marker: Marker, stringTable: StringTable, - testFun: (string, string) => boolean + testFun: (a: string, b: string) => boolean ): boolean { const { data } = marker; if (!data) { @@ -629,7 +631,7 @@ export function markerPayloadMatchesSearch( // Check if fields match the search regular expression. for (const payloadField of markerSchema.fields) { - let value = data[payloadField.key]; + let value = (data as any)[payloadField.key]; if (value === undefined || value === null) { // The value is missing, but this is OK, values are optional. continue; diff --git a/src/profile-logic/marker-styles.js b/src/profile-logic/marker-styles.ts similarity index 93% rename from src/profile-logic/marker-styles.js rename to src/profile-logic/marker-styles.ts index c62bb79a33..5b71b2a061 100644 --- a/src/profile-logic/marker-styles.js +++ b/src/profile-logic/marker-styles.ts @@ -1,19 +1,19 @@ /* 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/. */ -// @flow + import * as colors from 'photon-colors'; import type { CssPixels, Marker } from 'firefox-profiler/types'; -type MarkerStyle = {| - +top: CssPixels, - +height: CssPixels, - +background: string, - +squareCorners: boolean, - +borderLeft: null | string, - +borderRight: null | string, -|}; +type MarkerStyle = { + readonly top: CssPixels; + readonly height: CssPixels; + readonly background: string; + readonly squareCorners: boolean; + readonly borderLeft: null | string; + readonly borderRight: null | string; +}; const defaultStyle = { top: 0, @@ -51,7 +51,7 @@ export function getMarkerStyle(marker: Marker): MarkerStyle { return markerStyles.default; } -const markerStyles: { +[styleName: string]: MarkerStyle } = { +const markerStyles: { readonly [styleName: string]: MarkerStyle } = { default: defaultStyle, RefreshDriverTick: { ...defaultStyle, diff --git a/src/profile-logic/marker-timing.js b/src/profile-logic/marker-timing.ts similarity index 97% rename from src/profile-logic/marker-timing.js rename to src/profile-logic/marker-timing.ts index 9bf6b13f0d..b8ee8dd440 100644 --- a/src/profile-logic/marker-timing.js +++ b/src/profile-logic/marker-timing.ts @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import type { CategoryList, Marker, @@ -82,11 +82,11 @@ const MAX_STACKING_DEPTH = 300; * they're inserted in `getMarkerTimingAndBuckets`. */ export function getMarkerTiming( - getMarker: (MarkerIndex) => Marker, + getMarker: (param: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], // Categories can be null for things like Network Markers, where we don't care to // break things up by category. - categories: ?CategoryList + categories: CategoryList | null ): MarkerTiming[] { // Each marker type will have it's own timing information, later collapse these into // a single array. @@ -119,7 +119,11 @@ export function getMarkerTiming( ? 'Network Requests' : marker.name; - const emptyTiming = ({ instantOnly }): MarkerTiming => ({ + const emptyTiming = ({ + instantOnly, + }: { + instantOnly: boolean; + }): MarkerTiming => ({ start: [], end: [], index: [], @@ -282,11 +286,11 @@ export function getMarkerTiming( * ] */ export function getMarkerTimingAndBuckets( - getMarker: (MarkerIndex) => Marker, + getMarker: (param: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], // Categories can be null for things like Network Markers, where we don't care to // break things up by category. - categories: ?CategoryList + categories: CategoryList | null ): MarkerTimingAndBuckets { const allMarkerTimings = getMarkerTiming( getMarker, diff --git a/src/profile-logic/merge-compare.js b/src/profile-logic/merge-compare.ts similarity index 94% rename from src/profile-logic/merge-compare.js rename to src/profile-logic/merge-compare.ts index 57e9491355..7e01110781 100644 --- a/src/profile-logic/merge-compare.js +++ b/src/profile-logic/merge-compare.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - /* * This file contains all functions that are needed to achieve profiles * comparison: how to merge profiles, how to diff them, etc. @@ -65,6 +63,7 @@ import type { MarkerPayload, MarkerIndex, Milliseconds, + Tid, } from 'firefox-profiler/types'; /** @@ -78,11 +77,11 @@ import type { export function mergeProfilesForDiffing( profiles: Profile[], profileStates: UrlState[] -): {| - profile: Profile, - transformStacks: TransformStacksPerThread, - implementationFilters: ImplementationFilter[], -|} { +): { + profile: Profile; + transformStacks: TransformStacksPerThread; + implementationFilters: ImplementationFilter[]; +} { if (profiles.length !== profileStates.length) { throw new Error( 'Passed arrays do not have the same length. This should not happen.' @@ -103,8 +102,8 @@ export function mergeProfilesForDiffing( if (['profilingStartTime', 'profilingEndTime'].includes(key)) { continue; } - if (profiles.every((profile) => profile.meta[key] === value)) { - resultProfile.meta[key] = value; + if (profiles.every((profile) => (profile.meta as any)[key] === value)) { + (resultProfile.meta as any)[key] = value; } } // Ensure it has a copy of the marker schema and categories, even though these could @@ -157,8 +156,8 @@ export function mergeProfilesForDiffing( // Then we loop over all profiles and do the necessary changes according // to the states we computed earlier. - const transformStacks = {}; - const implementationFilters = []; + const transformStacks: any = {}; + const implementationFilters: ImplementationFilter[] = []; // These may be needed for filtering markers. let ipcCorrelations; @@ -402,16 +401,16 @@ function _adjustSampleTimestamps( } type TranslationMapForCategories = Map< IndexIntoCategoryList, - IndexIntoCategoryList, + IndexIntoCategoryList >; type TranslationMapForFuncs = Map; type TranslationMapForResources = Map< IndexIntoResourceTable, - IndexIntoResourceTable, + IndexIntoResourceTable >; type TranslationMapForNativeSymbols = Map< IndexIntoNativeSymbolTable, - IndexIntoNativeSymbolTable, + IndexIntoNativeSymbolTable >; type TranslationMapForFrames = Map; type TranslationMapForStacks = Map; @@ -419,18 +418,18 @@ type TranslationMapForLibs = Map; type TranslationMapForStrings = Map; type TranslationMapForSamples = Map< IndexIntoSamplesTable, - IndexIntoSamplesTable, + IndexIntoSamplesTable >; /** * Merges several categories lists into one, resolving duplicates if necessary. * It returns a translation map that can be used in `adjustCategories` later. */ -function mergeCategories(categoriesPerProfile: Array): {| - categories: CategoryList, - translationMaps: TranslationMapForCategories[], -|} { - const newCategories = []; +function mergeCategories(categoriesPerProfile: Array): { + categories: CategoryList; + translationMaps: TranslationMapForCategories[]; +} { + const newCategories: CategoryList = []; const newCategoryIndexByName: Map = new Map(); const translationMaps = categoriesPerProfile.map((categories) => { @@ -464,11 +463,11 @@ function mergeCategories(categoriesPerProfile: Array): {| return { categories: newCategories, translationMaps }; } -function mergeStringArrays(stringArraysPerProfile: Array): {| - stringArray: string[], - translationMaps: TranslationMapForStrings[], -|} { - const newStringArray = []; +function mergeStringArrays(stringArraysPerProfile: Array): { + stringArray: string[]; + translationMaps: TranslationMapForStrings[]; +} { + const newStringArray: string[] = []; const newStringTable = StringTable.withBackingArray(newStringArray); const translationMaps = stringArraysPerProfile.map((stringArray) => { @@ -532,7 +531,7 @@ function adjustNativeSymbolLibs( * safety. */ function adjustNullableCategories( - categories: $ReadOnlyArray, + categories: ReadonlyArray, translationMap: TranslationMapForCategories ): Array { return categories.map((category) => { @@ -553,7 +552,7 @@ function adjustNullableCategories( } function adjustStringIndexes( - stringIndexes: $ReadOnlyArray, + stringIndexes: ReadonlyArray, translationMap: TranslationMapForStrings ): Array { return stringIndexes.map((stringIndex) => { @@ -571,7 +570,7 @@ function adjustStringIndexes( } function adjustMarkerDataStringIndexes( - dataCol: $ReadOnlyArray, + dataCol: ReadonlyArray, translationMap: TranslationMapForStrings, stringIndexMarkerFieldsByDataType: Map ): Array { @@ -589,7 +588,7 @@ function adjustMarkerDataStringIndexes( let newData: MarkerPayload = data; for (const fieldKey of stringIndexMarkerFields) { - const stringIndex = data[fieldKey]; + const stringIndex = (data as any)[fieldKey]; if (typeof stringIndex === 'number') { const newStringIndex = translationMap.get(stringIndex); if (newStringIndex === undefined) { @@ -600,10 +599,10 @@ function adjustMarkerDataStringIndexes( ` ); } - newData = ({ + newData = { ...newData, [fieldKey]: newStringIndex, - }: any); + } as any; } } return newData; @@ -611,7 +610,7 @@ function adjustMarkerDataStringIndexes( } function adjustNullableStringIndexes( - stringIndexes: $ReadOnlyArray, + stringIndexes: ReadonlyArray, translationMap: TranslationMapForStrings ): Array { return stringIndexes.map((stringIndex) => { @@ -637,13 +636,13 @@ function adjustNullableStringIndexes( * when merging lib references in other tables. */ function mergeLibs(libsPerProfile: Lib[][]): { - libs: Lib[], - translationMaps: TranslationMapForLibs[], + libs: Lib[]; + translationMaps: TranslationMapForLibs[]; } { const mapOfInsertedLibs: Map = new Map(); - const translationMaps = []; - const newLibTable = []; + const translationMaps: Array> = []; + const newLibTable: Lib[] = []; for (const libs of libsPerProfile) { const translationMap = new Map(); @@ -673,12 +672,12 @@ function mergeLibs(libsPerProfile: Lib[][]): { * resource table with the translation maps to be used in subsequent merging * functions. */ -function combineResourceTables(threads: $ReadOnlyArray): { - resourceTable: ResourceTable, - translationMaps: TranslationMapForResources[], +function combineResourceTables(threads: ReadonlyArray): { + resourceTable: ResourceTable; + translationMaps: TranslationMapForResources[]; } { const mapOfInsertedResources: Map = new Map(); - const translationMaps = []; + const translationMaps: TranslationMapForResources[] = []; const newResourceTable = getEmptyResourceTable(); threads.forEach((thread) => { @@ -718,13 +717,13 @@ function combineResourceTables(threads: $ReadOnlyArray): { /** * This combines the nativeSymbols tables for the threads. */ -function combineNativeSymbolTables(threads: $ReadOnlyArray): { - nativeSymbols: NativeSymbolTable, - translationMaps: TranslationMapForNativeSymbols[], +function combineNativeSymbolTables(threads: ReadonlyArray): { + nativeSymbols: NativeSymbolTable; + translationMaps: TranslationMapForNativeSymbols[]; } { const mapOfInsertedNativeSymbols: Map = new Map(); - const translationMaps = []; + const translationMaps: TranslationMapForNativeSymbols[] = []; const newNativeSymbols = getEmptyNativeSymbolTable(); threads.forEach((thread) => { @@ -770,10 +769,10 @@ function combineNativeSymbolTables(threads: $ReadOnlyArray): { */ function combineFuncTables( translationMapsForResources: TranslationMapForResources[], - threads: $ReadOnlyArray -): { funcTable: FuncTable, translationMaps: TranslationMapForFuncs[] } { + threads: ReadonlyArray +): { funcTable: FuncTable; translationMaps: TranslationMapForFuncs[] } { const mapOfInsertedFuncs: Map = new Map(); - const translationMaps = []; + const translationMaps: TranslationMapForFuncs[] = []; const newFuncTable = getEmptyFuncTable(); threads.forEach((thread, threadIndex) => { @@ -841,9 +840,9 @@ function combineFuncTables( function combineFrameTables( translationMapsForFuncs: TranslationMapForFuncs[], translationMapsForNativeSymbols: TranslationMapForNativeSymbols[], - threads: $ReadOnlyArray -): { frameTable: FrameTable, translationMaps: TranslationMapForFrames[] } { - const translationMaps = []; + threads: ReadonlyArray +): { frameTable: FrameTable; translationMaps: TranslationMapForFrames[] } { + const translationMaps: TranslationMapForFrames[] = []; const newFrameTable = getEmptyFrameTable(); threads.forEach((thread, threadIndex) => { @@ -903,9 +902,9 @@ function combineFrameTables( */ function combineStackTables( translationMapsForFrames: TranslationMapForFrames[], - threads: $ReadOnlyArray -): { stackTable: RawStackTable, translationMaps: TranslationMapForStacks[] } { - const translationMaps = []; + threads: ReadonlyArray +): { stackTable: RawStackTable; translationMaps: TranslationMapForStacks[] } { + const translationMaps: TranslationMapForStacks[] = []; const newStackTable = getEmptyRawStackTable(); threads.forEach((thread, threadIndex) => { @@ -958,7 +957,7 @@ function combineSamplesDiffing( ThreadAndWeightMultiplier, ThreadAndWeightMultiplier, ] -): { samples: RawSamplesTable, translationMaps: TranslationMapForSamples[] } { +): { samples: RawSamplesTable; translationMaps: TranslationMapForSamples[] } { const translationMaps = [new Map(), new Map()]; const [ { @@ -971,8 +970,8 @@ function combineSamplesDiffing( }, ] = threadsAndWeightMultipliers; - const newWeight = []; - const newThreadId = []; + const newWeight: number[] = []; + const newThreadId: Tid[] = []; const newSamples = { ...getEmptySamplesTableWithEventDelay(), weight: newWeight, @@ -1011,8 +1010,8 @@ function combineSamplesDiffing( newSamples.stack.push(newStackIndex); // Diffing event delay values doesn't make sense since interleaved values // of eventDelay/responsiveness don't mean anything. - newSamples.eventDelay.push(null); - newSamples.time.push(samples1Time[i]); + newSamples.eventDelay!.push(null); + newSamples.time!.push(samples1Time[i]); newThreadId.push(samples1.threadId ? samples1.threadId[i] : tid1); // TODO (issue #3151): Figure out a way to diff CPU usage numbers. // We add the first thread with a negative weight, because this is the @@ -1039,8 +1038,8 @@ function combineSamplesDiffing( newSamples.stack.push(newStackIndex); // Diffing event delay values doesn't make sense since interleaved values // of eventDelay/responsiveness don't mean anything. - newSamples.eventDelay.push(null); - newSamples.time.push(samples2Time[j]); + newSamples.eventDelay!.push(null); + newSamples.time!.push(samples2Time[j]); newThreadId.push(samples2.threadId ? samples2.threadId[j] : tid2); const sampleWeight = samples2.weight ? samples2.weight[j] : 1; newWeight.push(weightMultiplier2 * sampleWeight); @@ -1057,10 +1056,10 @@ function combineSamplesDiffing( }; } -type ThreadAndWeightMultiplier = {| - thread: RawThread, - weightMultiplier: number, -|}; +type ThreadAndWeightMultiplier = { + thread: RawThread; + weightMultiplier: number; +}; /** * This function will compute a diffing thread from 2 different threads, using @@ -1249,7 +1248,7 @@ function combineSamplesForMerging( ).fill(0); // This array will contain the source thread ids. It will be added to the // samples table after the loop. - const newThreadId = []; + const newThreadId: Tid[] = []; // Creating a new empty samples table to fill. const newSamples = { ...getEmptySamplesTableWithEventDelay(), @@ -1311,7 +1310,7 @@ function combineSamplesForMerging( // It doesn't make sense to combine event delay values. We need to use jank markers // from independent threads instead. ensureExists(newSamples.eventDelay).push(null); - newSamples.time.push(sourceThreadSamplesTimeCol[sourceThreadSampleIndex]); + newSamples.time!.push(sourceThreadSamplesTimeCol[sourceThreadSampleIndex]); newThreadId.push( sourceThreadSamples.threadId ? sourceThreadSamples.threadId[sourceThreadSampleIndex] @@ -1334,13 +1333,13 @@ function mergeMarkers( translationMapsForStacks: TranslationMapForStacks[], threads: RawThread[] ): { - markerTable: RawMarkerTable, - translationMaps: TranslationMapForMarkers[], + markerTable: RawMarkerTable; + translationMaps: TranslationMapForMarkers[]; } { - const newThreadId = []; + const newThreadId: Tid[] = []; const newMarkerTable = { ...getEmptyRawMarkerTable(), threadId: newThreadId }; - const translationMaps = []; + const translationMaps: TranslationMapForMarkers[] = []; threads.forEach((thread, threadIndex) => { const translationMapForStacks = translationMapsForStacks[threadIndex]; @@ -1384,7 +1383,9 @@ function mergeMarkers( newMarkerTable.phase.push(markers.phase[markerIndex]); newMarkerTable.category.push(markers.category[markerIndex]); newThreadId.push( - markers.threadId ? markers.threadId[markerIndex] : thread.tid + markers.threadId + ? (markers.threadId[markerIndex] ?? thread.tid) + : thread.tid ); // Set the translation map and increase the table length. diff --git a/src/profile-logic/mozilla-symbolication-api.js b/src/profile-logic/mozilla-symbolication-api.ts similarity index 94% rename from src/profile-logic/mozilla-symbolication-api.js rename to src/profile-logic/mozilla-symbolication-api.ts index 8c41301e34..2c8ecfc092 100644 --- a/src/profile-logic/mozilla-symbolication-api.js +++ b/src/profile-logic/mozilla-symbolication-api.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import type { AddressResult, LibSymbolicationRequest, @@ -28,83 +26,83 @@ import type { export type QuerySymbolicationApiCallback = ( path: string, requestJson: string -) => Promise; +) => Promise; type APIFoundModulesV5 = { // For every requested library in the memoryMap, this object contains a string // key of the form `${debugName}/${breakpadId}`. The value is null if no // address with the module index was requested, and otherwise a boolean that // says whether the symbol server had symbols for this library. - [string]: null | boolean, + [key: string]: null | boolean; }; type APIInlineFrameInfoV5 = { // The name of the function this inline frame was in, if known. - function?: string, + function?: string; // The path of the file that contains the function this inline frame was in, optional. - file?: string, + file?: string; // The line number that contains the source code for this inline frame that // contributed to the instruction at the looked-up address, optional. // e.g. 543 - line?: number, + line?: number; }; type APIFrameInfoV5 = { // The hex version of the address that we requested (e.g. "0x5ab"). - module_offset: string, + module_offset: string; // The debugName of the library that this frame was in. - module: string, + module: string; // The index of this APIFrameInfo in its enclosing APIStack. - frame: number, + frame: number; // The name of the function this frame was in, if symbols were found. - function?: string, + function?: string; // The hex offset between the requested address and the start of the function, // e.g. "0x3c". - function_offset?: string, + function_offset?: string; // An optional size, in bytes, of the machine code of the outer function that // this address belongs to, as a hex string, e.g. "0x270". - function_size?: string, + function_size?: string; // The path of the file that contains the function this frame was in, optional. // As of June 2021, this is only supported on the staging symbolication server // ("Eliot") but not on the implementation that's currently in production ("Tecken"). // e.g. "hg:hg.mozilla.org/mozilla-central:js/src/vm/Interpreter.cpp:24938c537a55f9db3913072d33b178b210e7d6b5" - file?: string, + file?: string; // The line number that contains the source code that generated the instructions at the address, optional. // (Same support as file.) // e.g. 543 - line?: number, + line?: number; // Information about functions that were inlined at this address. // Ordered from inside to outside. // As of November 2021, this is only supported by profiler-symbol-server. // Adding this functionality to the Mozilla symbol server is tracked in // https://bugzilla.mozilla.org/show_bug.cgi?id=1636194 - inlines?: APIInlineFrameInfoV5[], + inlines?: APIInlineFrameInfoV5[]; }; type APIStackV5 = APIFrameInfoV5[]; type APIJobResultV5 = { - found_modules: APIFoundModulesV5, - stacks: APIStackV5[], + found_modules: APIFoundModulesV5; + stacks: APIStackV5[]; }; type APIResultV5 = { - results: APIJobResultV5[], + results: APIJobResultV5[]; }; // Make sure that the JSON blob we receive from the API conforms to our flow // type definition. -function _ensureIsAPIResultV5(result: MixedObject): APIResultV5 { +function _ensureIsAPIResultV5(result: unknown): APIResultV5 { // It's possible (especially when running tests with Jest) that the parameter // inherits from a `Object` global from another realm. By using toString // this issue is solved wherever the parameter comes from. - const isObject = (subject) => + const isObject = (subject: unknown) => Object.prototype.toString.call(subject) === '[object Object]'; - if (!isObject(result) || !('results' in result)) { + if (!isObject(result) || !('results' in (result as object))) { throw new Error('Expected an object with property `results`'); } - const results = result.results; + const results = (result as { results: unknown }).results; if (!Array.isArray(results)) { throw new Error('Expected `results` to be an array'); } @@ -179,7 +177,7 @@ function _ensureIsAPIResultV5(result: MixedObject): APIResultV5 { } } } - return result; + return result as APIResultV5; } function getV5ResultForLibRequest( diff --git a/src/profile-logic/network.js b/src/profile-logic/network.ts similarity index 99% rename from src/profile-logic/network.js rename to src/profile-logic/network.ts index 274a4e3b7d..93d8f221e8 100644 --- a/src/profile-logic/network.js +++ b/src/profile-logic/network.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; diff --git a/src/profile-logic/process-profile.js b/src/profile-logic/process-profile.ts similarity index 94% rename from src/profile-logic/process-profile.js rename to src/profile-logic/process-profile.ts index f43cb4e2ff..c1594a5593 100644 --- a/src/profile-logic/process-profile.js +++ b/src/profile-logic/process-profile.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { attemptToConvertChromeProfile } from './import/chrome'; import { attemptToConvertDhat } from './import/dhat'; import { AddressLocator } from './address-locator'; @@ -17,7 +15,7 @@ import { getEmptyUnbalancedNativeAllocationsTable, getEmptyNativeSymbolTable, } from './data-structures'; -import { immutableUpdate, ensureExists, coerce } from '../utils/flow'; +import { immutableUpdate, ensureExists } from '../utils/flow'; import { verifyMagic, SIMPLEPERF as SIMPLEPERF_MAGIC } from '../utils/magic'; import { attemptToUpgradeProcessedProfileThroughMutation } from './processed-profile-versioning'; import { upgradeGeckoProfileToCurrentVersion } from './gecko-profile-versioning'; @@ -100,6 +98,7 @@ import type { BrowsertimeMarkerPayload, MarkerPhase, Pid, + GeckoMarkerSchema, } from 'firefox-profiler/types'; import { decompress, isGzip } from 'firefox-profiler/utils/gz'; @@ -121,7 +120,7 @@ type RegExpResult = null | string[]; * `{ length: number, field1: array, field2: array }` */ function _toStructOfArrays(geckoTable: any): any { - const result = { length: geckoTable.data.length }; + const result: any = { length: geckoTable.data.length }; for (const fieldName in geckoTable.schema) { const fieldIndex = geckoTable.schema[fieldName]; if (typeof fieldIndex !== 'number') { @@ -129,7 +128,7 @@ function _toStructOfArrays(geckoTable: any): any { 'fieldIndex must be a number in the Gecko profile table.' ); } - result[fieldName] = geckoTable.data.map((entry) => + result[fieldName] = geckoTable.data.map((entry: any) => fieldIndex in entry ? entry[fieldIndex] : null ); } @@ -221,25 +220,25 @@ export class GlobalDataCollector { // Package up all de-duplicated global tables so that they can be embedded in // the profile. - finish(): {| libs: Lib[], shared: RawProfileSharedData |} { + finish(): { libs: Lib[]; shared: RawProfileSharedData } { return { libs: this._libs, shared: { stringArray: this._stringArray } }; } } type ExtractionInfo = { - funcTable: FuncTable, - resourceTable: ResourceTable, - geckoThreadStringArray: string[], - stringTable: StringTable, - addressLocator: AddressLocator, - libToResourceIndex: Map, - originToResourceIndex: Map, - libNameToResourceIndex: Map, + funcTable: FuncTable; + resourceTable: ResourceTable; + geckoThreadStringArray: string[]; + stringTable: StringTable; + addressLocator: AddressLocator; + libToResourceIndex: Map; + originToResourceIndex: Map; + libNameToResourceIndex: Map; stringToNewFuncIndexAndFrameAddress: Map< string, - { funcIndex: IndexIntoFuncTable, frameAddress: Address | null }, - >, - globalDataCollector: GlobalDataCollector, + { funcIndex: IndexIntoFuncTable; frameAddress: Address | null } + >; + globalDataCollector: GlobalDataCollector; }; /** @@ -258,10 +257,10 @@ export function extractFuncsAndResourcesFromFrameLocations( extensions: ExtensionTable = getEmptyExtensions(), globalDataCollector: GlobalDataCollector ): { - funcTable: FuncTable, - resourceTable: ResourceTable, - frameFuncs: IndexIntoFuncTable[], - frameAddresses: (Address | null)[], + funcTable: FuncTable; + resourceTable: ResourceTable; + frameFuncs: IndexIntoFuncTable[]; + frameAddresses: (Address | null)[]; } { // Important! If the flow type for the FuncTable was changed, update all the functions // in this file that start with the word "extract". @@ -373,7 +372,7 @@ function _extractUnsymbolicatedFunction( extractionInfo: ExtractionInfo, locationString: string, locationIndex: IndexIntoStringTable -): { funcIndex: IndexIntoFuncTable, frameAddress: Address } | null { +): { funcIndex: IndexIntoFuncTable; frameAddress: Address } | null { if (!locationString.startsWith('0x')) { return null; } @@ -406,8 +405,10 @@ function _extractUnsymbolicatedFunction( const libIndex = extractionInfo.globalDataCollector.indexForLib(lib); - resourceIndex = libToResourceIndex.get(libIndex); - if (resourceIndex === undefined) { + const resourceIndexOrUndefined = libToResourceIndex.get(libIndex); + if (resourceIndexOrUndefined !== undefined) { + resourceIndex = resourceIndexOrUndefined; + } else { // This library doesn't exist in the libs array, insert it. This resou // A lib resource is a systems-level compiled library, for example "XUL", // "AppKit", or "CoreFoundation". @@ -677,7 +678,7 @@ function _convertPayloadStackToIndex( if (!data) { return null; } - if (data.stack && data.stack.samples.data.length > 0) { + if ('stack' in data && data.stack && data.stack.samples.data.length > 0) { const { samples } = data.stack; return samples.data[0][samples.schema.stack]; } @@ -696,11 +697,11 @@ function _processMarkers( stringArray: string[], stringIndexMarkerFieldsByDataType: Map, globalDataCollector: GlobalDataCollector -): {| - markers: RawMarkerTable, - jsAllocations: JsAllocationsTable | null, - nativeAllocations: NativeAllocationsTable | null, -|} { +): { + markers: RawMarkerTable; + jsAllocations: JsAllocationsTable | null; + nativeAllocations: NativeAllocationsTable | null; +} { const markers = getEmptyRawMarkerTable(); const jsAllocations = getEmptyJsAllocationsTable(); const inProgressNativeAllocations = @@ -839,7 +840,7 @@ function _processMarkers( function convertPhaseTimes( old_phases: PhaseTimes ): PhaseTimes { - const phases = {}; + const phases: PhaseTimes = {}; for (const phase in old_phases) { phases[phase] = old_phases[phase] * 1000; } @@ -877,13 +878,13 @@ function _processMarkerPayload( case 'GCSlice': { const { times, ...partialTimings }: GCSliceData_Gecko = payload.timings; - return ({ + return { type: 'GCSlice', timings: { ...partialTimings, phase_times: times ? convertPhaseTimes(times) : {}, }, - }: GCSliceMarkerPayload); + } as GCSliceMarkerPayload; } case 'GCMajor': { const geckoTimings: GCMajorAborted | GCMajorCompleted_Gecko = @@ -897,16 +898,16 @@ function _processMarkerPayload( mmu_20ms: geckoTimings.mmu_20ms / 100, mmu_50ms: geckoTimings.mmu_50ms / 100, }; - return ({ + return { type: 'GCMajor', timings: timings, - }: GCMajorMarkerPayload); + } as GCMajorMarkerPayload; } case 'aborted': - return ({ + return { type: 'GCMajor', timings: { status: 'aborted' }, - }: GCMajorMarkerPayload); + } as GCMajorMarkerPayload; default: // Flow cannot detect that this switch is complete. console.log('Unknown GCMajor status'); @@ -952,7 +953,7 @@ function _processMarkerPayload( // here, and then to `MarkerPayload` as the return value for this function. // This doesn't provide type safety but it shows the intent of going from an // object without much type safety, to a specific type definition. - const data: MarkerPayload = (payload: any); + const data: MarkerPayload = payload as any; if (!data.type) { return data; @@ -967,12 +968,12 @@ function _processMarkerPayload( let newData: MarkerPayload = data; for (const fieldKey of stringIndexMarkerFields) { - const stringIndex = data[fieldKey]; + const stringIndex = (data as any)[fieldKey]; if (typeof stringIndex === 'number') { - newData = ({ + newData = { ...newData, [fieldKey]: stringTable.indexForString(stringArray[stringIndex]), - }: any); + } as any; } } return newData; @@ -1027,9 +1028,9 @@ function _processSamples(geckoSamples: GeckoSampleStruct): RawSamplesTable { } } - if (geckoSamples.eventDelay) { + if ('eventDelay' in geckoSamples) { samples.eventDelay = geckoSamples.eventDelay; - } else if (geckoSamples.responsiveness) { + } else if ('responsiveness' in geckoSamples) { samples.responsiveness = geckoSamples.responsiveness; } else { throw new Error( @@ -1079,7 +1080,7 @@ function _processCounters( ); } - return geckoCounters.reduce( + return geckoCounters.reduce( (result, { name, category, description, samples }) => { if (samples.data.length === 0) { // It's possible that no sample has been collected during our capture @@ -1135,7 +1136,7 @@ function _processProfilerOverhead( // various processes. delta: Milliseconds ): ProfilerOverhead | null { - const geckoProfilerOverhead: ?GeckoProfilerOverhead = + const geckoProfilerOverhead: GeckoProfilerOverhead | undefined = geckoProfile.profilerOverhead; const mainThread = geckoProfile.threads.find( (thread) => thread.name === 'GeckoMain' @@ -1311,7 +1312,7 @@ function _processThread( * has its own timebase, and we don't want to keep converting timestamps when * we deal with the integrated profile. */ -export function adjustTableTimestamps( +export function adjustTableTimestamps( table: Table, delta: Milliseconds ): Table { @@ -1321,10 +1322,9 @@ export function adjustTableTimestamps( }; } -export function adjustTableTimeDeltas( - table: Table, - delta: Milliseconds -): Table { +export function adjustTableTimeDeltas< + Table extends { timeDeltas?: Milliseconds[] }, +>(table: Table, delta: Milliseconds): Table { const { timeDeltas } = table; if (timeDeltas === undefined) { throw new Error( @@ -1366,7 +1366,7 @@ function _adjustJsTracerTimestamps( * into milliseconds. */ export function adjustProfilerOverheadTimestamps< - Table: { time: Microseconds[] }, + Table extends { time: Microseconds[] }, >(table: Table, delta: Milliseconds): Table { return { ...table, @@ -1398,13 +1398,17 @@ export function adjustMarkerTimestamps( return data; } const newData = immutableUpdate(data); - if (typeof newData.startTime === 'number') { + if ('startTime' in newData && typeof newData.startTime === 'number') { newData.startTime += delta; } - if (typeof newData.endTime === 'number') { + if ('endTime' in newData && typeof newData.endTime === 'number') { newData.endTime += delta; } - if (newData.cause && newData.cause.time !== undefined) { + if ( + 'cause' in newData && + newData.cause && + newData.cause.time !== undefined + ) { newData.cause.time += delta; } if (newData.type === 'Network') { @@ -1463,11 +1467,10 @@ function _convertGeckoMarkerSchema( const fields: MarkerSchemaField[] = []; const staticFields: GeckoStaticFieldSchemaData[] = []; for (const f of data) { - if (f.value === undefined) { + if ('key' in f) { const { key, label, format, hidden } = f; fields.push({ key, label, format, hidden }); - } else if (f.key === undefined) { - // extra check to placate Flow + } else { staticFields.push(f); } } @@ -1536,6 +1539,7 @@ export function insertExternalMarkersIntoProfile( geckoProfile: GeckoProfile ): void { if ( + !('markerSchema' in externalMarkers) || !externalMarkers.markerSchema || !externalMarkers.categories || !externalMarkers.markers @@ -1582,7 +1586,9 @@ export function insertExternalMarkersIntoProfile( const { data, schema } = externalMarkers.markers; - for (const prop of Object.keys(mainThread.markers.schema)) { + for (const prop of Object.keys( + mainThread.markers.schema + ) as (keyof GeckoMarkerSchema)[]) { if (!(prop in schema) || mainThread.markers.schema[prop] !== schema[prop]) { throw new Error( 'Marker table schema in the gecko profile and the external marker data do not match' @@ -1600,15 +1606,13 @@ export function insertExternalMarkersIntoProfile( // The ExternalMarkerTuple and GeckoMarkerTuple types are the same except // for the marker name that is represented as a string in the former and a // string table index in the latter. - const geckoMarker = ((marker: any): GeckoMarkerTuple); + const geckoMarker = marker as any as GeckoMarkerTuple; geckoMarker[schema.name] = stringId; - if (geckoMarker[schema.startTime]) { - geckoMarker[schema.startTime] = - geckoMarker[schema.startTime] + geckoProfile.meta.profilingStartTime; + if (geckoMarker[schema.startTime] && geckoProfile.meta.profilingStartTime) { + geckoMarker[schema.startTime]! += geckoProfile.meta.profilingStartTime; } - if (geckoMarker[schema.endTime]) { - geckoMarker[schema.endTime] = - geckoMarker[schema.endTime] + geckoProfile.meta.profilingStartTime; + if (geckoMarker[schema.endTime] && geckoProfile.meta.profilingStartTime) { + geckoMarker[schema.endTime]! += geckoProfile.meta.profilingStartTime; } geckoMarker[schema.category] = categoryMap.get(geckoMarker[schema.category]) || 0; @@ -1629,7 +1633,9 @@ export function insertExternalPowerCountersIntoProfile( // and limit the precision to nanoseconds sample[timeColumnIndex] = Math.round( - (sample[timeColumnIndex] + geckoProfile.meta.profilingStartTime) * 1e6 + (sample[timeColumnIndex] + + (geckoProfile.meta.profilingStartTime ?? 0)) * + 1e6 ) / 1e6; } if (!geckoProfile.counters) { @@ -1643,7 +1649,7 @@ export function insertExternalPowerCountersIntoProfile( * Convert an unknown profile from either the Gecko format or the DevTools format * into the processed format. Throws if there is an error. */ -export function processGeckoOrDevToolsProfile(json: mixed): Profile { +export function processGeckoOrDevToolsProfile(json: unknown): Profile { if (!json) { throw new Error('The profile was empty.'); } @@ -1654,9 +1660,10 @@ export function processGeckoOrDevToolsProfile(json: mixed): Profile { // The profile can be embedded in an object if it's exported from the old DevTools // performance panel. // { profile: GeckoProfile } - const geckoProfile = coerce( - json.profile ? json.profile : json - ); + const geckoProfile = + 'profile' in json && json.profile + ? (json.profile as GeckoProfile) + : (json as GeckoProfile); // Double check that there is a meta object, since this is the first time we've // coerced a "mixed" object to a GeckoProfile. @@ -1817,7 +1824,7 @@ export function processGeckoProfile(geckoProfile: GeckoProfile): Profile { meta.profilingEndTime = geckoProfile.meta.profilingEndTime; } - const profilerOverhead: ProfilerOverhead[] = nullableProfilerOverhead.reduce( + const profilerOverhead = nullableProfilerOverhead.reduce( (acc, overhead) => { if (overhead !== null) { acc.push(overhead); @@ -1898,27 +1905,39 @@ export function serializeProfile(profile: Profile): string { // would have a `stringTable` property rather than a `stringArray` property on // each thread. function attemptToFixProcessedProfileThroughMutation( - profile: MixedObject -): MixedObject | null { + profile: unknown +): unknown | null { if (!profile || typeof profile !== 'object') { return profile; } - const { meta } = profile; - if (!meta || typeof meta !== 'object') { + if ( + !('meta' in profile) || + !profile.meta || + typeof profile.meta !== 'object' + ) { return profile; } + const { meta } = profile; - if (typeof meta.preprocessedProfileVersion !== 'number') { + if ( + !('preprocessedProfileVersion' in meta) || + typeof meta.preprocessedProfileVersion !== 'number' + ) { return profile; } - const { threads } = profile; - if (!threads || !Array.isArray(threads) || !threads.length) { + if ( + !('threads' in profile) || + !profile.threads || + !Array.isArray(profile.threads) || + !profile.threads.length + ) { // This profile doesn't look well-formed or is empty, let's return it // directly and let the following functions deal with it. return profile; } + const { threads } = profile; const [firstThread] = threads; if (firstThread.stringArray) { // This looks good, nothing to fix! @@ -1953,7 +1972,7 @@ function attemptToFixProcessedProfileThroughMutation( * - ART trace: input must be ArrayBuffer */ export async function unserializeProfileOfArbitraryFormat( - arbitraryFormat: mixed, + arbitraryFormat: unknown, profileUrl?: string ): Promise { try { @@ -1963,7 +1982,7 @@ export async function unserializeProfileOfArbitraryFormat( if (String(arbitraryFormat) === '[object ArrayBuffer]') { // Obviously Flow doesn't understand that this is correct, so let's help // Flow here. - let arrayBuffer: ArrayBuffer = (arbitraryFormat: any); + let arrayBuffer: ArrayBufferLike = arbitraryFormat as any; // Check for the gzip magic number in the header. If we find it, decompress // the data first. @@ -2070,7 +2089,9 @@ export function processVisualMetrics( const tabThread = threads[tabThreadIdx]; // These metrics are currently present inside profile.meta.visualMetrics. - const metrics = ['Visual', 'ContentfulSpeedIndex', 'PerceptualSpeedIndex']; + const metrics: Array< + 'Visual' | 'ContentfulSpeedIndex' | 'PerceptualSpeedIndex' + > = ['Visual', 'ContentfulSpeedIndex', 'PerceptualSpeedIndex']; // Find the Test category so we can add the visual metrics markers with it. if (meta.categories === undefined) { // Making Flow happy. This means that this is a very old profile. @@ -2155,7 +2176,7 @@ export function processVisualMetrics( const changeMarkerName = `${metricName} Change`; for (const { timestamp, percent } of metric) { const payload = { - type: 'VisualMetricProgress', + type: 'VisualMetricProgress' as const, // 'percentage' type expects a value between 0 and 1. percentage: percent / 100, }; @@ -2225,15 +2246,18 @@ function findTabMainThreadForVisualMetrics( const { markers } = thread; for (let markerIndex = 0; markerIndex < markers.length; markerIndex++) { - if ( - markers.name[markerIndex] === refreshDriverTickStrIndex && - markers.data[markerIndex] && - markers.data[markerIndex].innerWindowID && - topLevelPagesSet.has(markers.data[markerIndex].innerWindowID) - ) { - // Found a RefreshDriverTick marker that is coming from a top level page. - // This is the tab process main thread we are looking for. - return threadIdx; + if (markers.name[markerIndex] === refreshDriverTickStrIndex) { + const data = markers.data[markerIndex]; + if ( + data && + 'innerWindowID' in data && + data.innerWindowID && + topLevelPagesSet.has(data.innerWindowID) + ) { + // Found a RefreshDriverTick marker that is coming from a top level page. + // This is the tab process main thread we are looking for. + return threadIdx; + } } } } diff --git a/src/profile-logic/processed-profile-versioning.js b/src/profile-logic/processed-profile-versioning.ts similarity index 94% rename from src/profile-logic/processed-profile-versioning.js rename to src/profile-logic/processed-profile-versioning.ts index 8cc9ca9f00..1d9d5de2eb 100644 --- a/src/profile-logic/processed-profile-versioning.js +++ b/src/profile-logic/processed-profile-versioning.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow /** * This file deals with old versions of the "processed" profile format, * i.e. the format that profiler.firefox.com uses internally. Profiles in this format @@ -20,7 +18,6 @@ import { resourceTypes } from './data-structures'; import { StringTable } from '../utils/string-table'; import { timeCode } from '../utils/time-code'; import { PROCESSED_PROFILE_VERSION } from '../app-logic/constants'; -import { coerce } from '../utils/flow'; import type { Profile } from 'firefox-profiler/types'; // Processed profiles before version 1 did not have a profile.meta.preprocessedProfileVersion @@ -33,7 +30,7 @@ const UNANNOTATED_VERSION = 0; * to be a processed profile, then return null. */ export function attemptToUpgradeProcessedProfileThroughMutation( - profile: mixed + profile: any ): Profile | null { if (!profile || typeof profile !== 'object') { return null; @@ -69,7 +66,7 @@ export function attemptToUpgradeProcessedProfileThroughMutation( : UNANNOTATED_VERSION; if (profileVersion === PROCESSED_PROFILE_VERSION) { - return coerce(profile); + return profile; } if (profileVersion > PROCESSED_PROFILE_VERSION) { @@ -91,20 +88,20 @@ export function attemptToUpgradeProcessedProfileThroughMutation( } } - const upgradedProfile = coerce(profile); + const upgradedProfile = profile as Profile; upgradedProfile.meta.preprocessedProfileVersion = PROCESSED_PROFILE_VERSION; return upgradedProfile; } -function _archFromAbi(abi) { +function _archFromAbi(abi: string): string { if (abi === 'x86_64-gcc3') { return 'x86_64'; } return abi; } -function _getRealScriptURI(url) { +function _getRealScriptURI(url: string): string { if (url) { const urls = url.split(' -> '); return urls[urls.length - 1]; @@ -112,7 +109,7 @@ function _getRealScriptURI(url) { return url; } -function _mutateProfileToEnsureCauseBacktraces(profile) { +function _mutateProfileToEnsureCauseBacktraces(profile: any) { for (const thread of profile.threads) { for (let i = 0; i < thread.markers.length; i++) { const marker = thread.markers.data[i]; @@ -195,7 +192,7 @@ function _guessMarkerCategories(profile: any) { { name: 'DOM', color: 'blue', subcategories: ['Other'] }, ]) { const index = profile.meta.categories.findIndex( - (category) => category.name === defaultCategory.name + (category: any) => category.name === defaultCategory.name ); if (index === -1) { // Add on any unknown categories. @@ -204,13 +201,13 @@ function _guessMarkerCategories(profile: any) { } const otherCategory = profile.meta.categories.findIndex( - (category) => category.name === 'Other' + (category: any) => category.name === 'Other' ); const keyToCategoryIndex: Map = new Map( keyToCategoryName.map(([key, categoryName]) => { const index = profile.meta.categories.findIndex( - (category) => category.name === categoryName + (category: any) => category.name === categoryName ); if (index === -1) { throw new Error('Could not find a category index to map to.'); @@ -247,15 +244,23 @@ function _guessMarkerCategories(profile: any) { } } +type ProcessedProfileUpgrader = (profile: any) => void; + // _upgraders[i] converts from version i - 1 to version i. // Every "upgrader" takes the profile as its single argument and mutates it. /* eslint-disable no-useless-computed-key */ -const _upgraders = { - [1]: (profile) => { +const _upgraders: { + [key: number]: ProcessedProfileUpgrader; +} = { + [1]: (profile: any) => { // Starting with version 1, markers are sorted. timeCode('sorting thread markers', () => { for (const thread of profile.threads) { - sortDataTable(thread.markers, thread.markers.time, (a, b) => a - b); + sortDataTable( + thread.markers, + thread.markers.time, + (a, b) => a - b + ); } }); @@ -273,7 +278,7 @@ const _upgraders = { } } }, - [2]: (profile) => { + [2]: (profile: any) => { // pdbName -> debugName, add arch for (const thread of profile.threads) { for (const lib of thread.libs) { @@ -291,7 +296,7 @@ const _upgraders = { } } }, - [3]: (profile) => { + [3]: (profile: any) => { // Make sure every lib has a debugPath property. We can't infer this // value from the other properties on the lib so we just set it to the // empty string. @@ -301,8 +306,8 @@ const _upgraders = { } } }, - [4]: (profile) => { - profile.threads.forEach((thread) => { + [4]: (profile: any) => { + profile.threads.forEach((thread: any) => { const { funcTable, stringArray, resourceTable } = thread; const stringTable = StringTable.withBackingArray(stringArray); @@ -314,7 +319,15 @@ const _upgraders = { // resource. We need to keep track of such collapsing (using the // oldResourceToNewResourceMap) and then execute apply the changes to // the resource pointers in the funcTable. - const newResourceTable = { + const newResourceTable: { + length: number; + type: number[]; + name: number[]; + lib: number[]; + icon: number[]; + addonId: number[]; + host: number[]; + } = { length: 0, type: [], name: [], @@ -323,19 +336,19 @@ const _upgraders = { addonId: [], host: [], }; - function addLibResource(name, lib) { + function addLibResource(name: number, lib: number) { const index = newResourceTable.length++; newResourceTable.type[index] = resourceTypes.library; newResourceTable.name[index] = name; newResourceTable.lib[index] = lib; } - function addWebhostResource(origin, host) { + function addWebhostResource(origin: number, host: number) { const index = newResourceTable.length++; newResourceTable.type[index] = resourceTypes.webhost; newResourceTable.name[index] = origin; newResourceTable.host[index] = host; } - function addUrlResource(url) { + function addUrlResource(url: number) { const index = newResourceTable.length++; newResourceTable.type[index] = resourceTypes.url; newResourceTable.name[index] = url; @@ -427,13 +440,13 @@ const _upgraders = { thread.resourceTable = newResourceTable; }); }, - [5]: (profile) => { + [5]: (profile: any) => { // The "frameNumber" column was removed from the samples table. for (const thread of profile.threads) { delete thread.samples.frameNumber; } }, - [6]: (profile) => { + [6]: (profile: any) => { // The type field for DOMEventMarkerPayload was renamed to eventType. for (const thread of profile.threads) { const { stringArray, markers } = thread; @@ -456,7 +469,7 @@ const _upgraders = { thread.markers.data = newDataArray; } }, - [7]: (profile) => { + [7]: (profile: any) => { // Each thread has the following new attributes: // - processShutdownTime: null if the process is still running, otherwise // the shutdown time of the process in milliseconds relative to @@ -483,7 +496,7 @@ const _upgraders = { thread.unregisterTime = null; } }, - [8]: (profile) => { + [8]: (profile: any) => { // DOMEventMarkerPayload.timeStamp in content process should be in // milliseconds relative to meta.startTime. Adjust it by adding // the thread.processStartupTime which is the delta to @@ -516,13 +529,13 @@ const _upgraders = { thread.markers.data = newDataArray; } }, - [9]: (profile) => { + [9]: (profile: any) => { // Upgrade the GC markers /* * Upgrade a GCMajor marker in the Gecko profile format. */ - function upgradeGCMajorMarker_Gecko8To9(marker) { + function upgradeGCMajorMarker_Gecko8To9(marker: any) { if ('timings' in marker) { if (!('status' in marker.timings)) { /* @@ -550,7 +563,7 @@ const _upgraders = { return marker; } - function upgradeGCMajorMarker_Processed8to9(marker8) { + function upgradeGCMajorMarker_Processed8to9(marker8: any) { // The Processed 8-to-9 upgrade is a superset of the gecko 8-to-9 upgrade. const marker9 = upgradeGCMajorMarker_Gecko8To9(marker8); const mt = marker9.timings; @@ -584,7 +597,7 @@ const _upgraders = { } } - function upgradeGCMinorMarker(marker8) { + function upgradeGCMinorMarker(marker8: any) { if ('nursery' in marker8) { if ('status' in marker8.nursery) { if (marker8.nursery.status === 'no collection') { @@ -618,8 +631,8 @@ const _upgraders = { return marker8; } - function convertPhaseTimes(old_phases) { - const phases = {}; + function convertPhaseTimes(old_phases: Record) { + const phases: Record = {}; for (const phase in old_phases) { phases[phase] = old_phases[phase] * 1000; } @@ -653,7 +666,7 @@ const _upgraders = { } } }, - [10]: (profile) => { + [10]: (profile: any) => { // Cause backtraces // Styles and reflow tracing markers supply call stacks that were captured // at the time that style or layout was invalidated. In version 9, this @@ -665,7 +678,7 @@ const _upgraders = { // a simple number, the stack index. _mutateProfileToEnsureCauseBacktraces(profile); }, - [11]: (profile) => { + [11]: (profile: any) => { // Removed the startTime and endTime from DOMEventMarkerPayload and // made it a tracing marker instead. DOMEventMarkerPayload is no longer a // single marker, it requires a start and an end marker. Therefore, we have @@ -711,7 +724,12 @@ const _upgraders = { // Create a new markers table that includes both the old markers and // the markers from extraMarkers, sorted by time. - const newMarkers = { + const newMarkers: { + length: number; + name: number[]; + time: number[]; + data: Array; + } = { length: 0, name: [], time: [], @@ -758,7 +776,7 @@ const _upgraders = { } } }, - [12]: (profile) => { + [12]: (profile: any) => { // profile.meta has a new property called "categories", which contains a // list of categories, which are objects with "name" and "color" properties. // The "category" column in the frameTable now refers to elements in this @@ -924,7 +942,7 @@ const _upgraders = { } } }, - [13]: (profile) => { + [13]: (profile: any) => { // The stackTable has a new column called "category", which is computed // from the stack's frame's category, or if that is null, from the stack's // prefix's category. For root stacks whose frame doesn't have a category, @@ -934,7 +952,7 @@ const _upgraders = { // not have a category column in its stack table). const { meta, threads } = profile; const defaultCategory = meta.categories.findIndex( - (c) => c.color === 'grey' + (c: any) => c.color === 'grey' ); for (const thread of threads) { @@ -956,7 +974,7 @@ const _upgraders = { } } }, - [14]: (profile) => { + [14]: (profile: any) => { // Profiles are now required to have either a string or number pid. If the pid // is a string, then it is a generated name, if it is a number, it's the pid // generated by the system. @@ -968,7 +986,7 @@ const _upgraders = { } } }, - [15]: (profile) => { + [15]: (profile: any) => { // Profiles now have a column property in the frameTable for (const thread of profile.threads) { thread.frameTable.column = new Array(thread.frameTable.length); @@ -977,7 +995,7 @@ const _upgraders = { } } }, - [16]: (profile) => { + [16]: (profile: any) => { // The type field on some markers were missing. Renamed category field of // VsyncTimestamp and LayerTranslation marker payloads to type and added // a type field to Screenshot marker payload. @@ -1022,7 +1040,7 @@ const _upgraders = { thread.markers.data = newDataArray; } }, - [17]: (profile) => { + [17]: (profile: any) => { // Profiles now have a relevantForJS property in the funcTable. // This column is false on C++ and JS frames, and true on label frames that // are entry and exit points to JS. @@ -1055,7 +1073,7 @@ const _upgraders = { } } }, - [18]: (profile) => { + [18]: (profile: any) => { // When we added column numbers we forgot to update the func table. // As a result, when we had a column number for an entry, the line number // ended up in the `fileName` property, and the column number in the @@ -1091,7 +1109,7 @@ const _upgraders = { } } }, - [19]: (profile) => { + [19]: (profile: any) => { // When we added timing information to network markers, we forgot to shift // timestamps from subprocesses during profile processing. This upgrade // fixes that. @@ -1136,12 +1154,12 @@ const _upgraders = { } } }, - [20]: (_profile) => { + [20]: (_profile: any) => { // rss and uss was removed from the SamplesTable. The version number was bumped // to help catch errors of using an outdated version of profiler.firefox.com with a newer // profile. There's no good reason to remove the values for upgrading profiles though. }, - [21]: (profile) => { + [21]: (profile: any) => { // Before version 21, during the profile processing step, only certain markers had // their stacks converted to causes. However, in version 10, an upgrader was written // that would convert every single marker's stack to a cause. This created two types @@ -1154,7 +1172,7 @@ const _upgraders = { // markers. This upgrader upgrades profiles from case 2 above. _mutateProfileToEnsureCauseBacktraces(profile); }, - [22]: (profile) => { + [22]: (profile: any) => { // FileIO was originally called DiskIO. This profile upgrade performs the rename. for (const thread of profile.threads) { const { stringArray } = thread; @@ -1173,7 +1191,7 @@ const _upgraders = { } } }, - [23]: (profile) => { + [23]: (profile: any) => { // profile.meta.categories now has a subcategories property on each element, // with an array of subcategories for that category, with at least one // subcategory per category. @@ -1184,19 +1202,19 @@ const _upgraders = { } for (const thread of profile.threads) { const { frameTable, stackTable } = thread; - frameTable.subcategory = frameTable.category.map((c) => + frameTable.subcategory = frameTable.category.map((c: any) => c === null ? null : 0 ); - stackTable.subcategory = stackTable.category.map((c) => + stackTable.subcategory = stackTable.category.map((c: any) => c === null ? null : 0 ); } }, - [24]: (profile) => { + [24]: (profile: any) => { // Markers now have a category field. For older profiles, guess the marker category. _guessMarkerCategories(profile); }, - [25]: (profile) => { + [25]: (profile: any) => { // Previously, we had DocShell ID and DocShell History ID in the page object // to identify a specific page. We changed these IDs in the gecko side to // Browsing Context ID and Inner Window ID. Inner Window ID is enough to @@ -1249,7 +1267,7 @@ const _upgraders = { for (const thread of profile.threads) { const { markers } = thread; - markers.data = markers.data.map((data) => { + markers.data = markers.data.map((data: any) => { if ( data && data.docShellId !== undefined && @@ -1276,7 +1294,7 @@ const _upgraders = { } } }, - [26]: (profile) => { + [26]: (profile: any) => { // Due to a bug in gecko side, we were keeping the sample_group inside an // object instead of an array. Usually there is only one sample group, that's // why it wasn't a problem before. To future proof it, we are fixing it by @@ -1287,7 +1305,7 @@ const _upgraders = { } } }, - [27]: (profile) => { + [27]: (profile: any) => { // Profiles now have an innerWindowID property in the frameTable. // We are filling this array with 0 values because we have no idea what that value might be. for (const thread of profile.threads) { @@ -1295,7 +1313,7 @@ const _upgraders = { frameTable.innerWindowID = new Array(frameTable.length).fill(0); } }, - [28]: (profile) => { + [28]: (profile: any) => { // There was a bug where some markers got a null category during sanitization. for (const thread of profile.threads) { const { markers } = thread; @@ -1306,7 +1324,7 @@ const _upgraders = { } } }, - [29]: (profile) => { + [29]: (profile: any) => { // The sample and allocation properties "duration" were changed to "weight" // The weight and weightType fields were made non-optional. The sample // "duration" field was used for diffing profiles. @@ -1338,7 +1356,7 @@ const _upgraders = { } } }, - [30]: (profile) => { + [30]: (profile: any) => { // The idea of phased markers was added to profiles, where the startTime and // endTime is always in the RawMarkerTable directly, not in the payload. // @@ -1349,11 +1367,11 @@ const _upgraders = { const INTERVAL_START = 2; const INTERVAL_END = 3; - type Payload = $Shape<{ - startTime: number, - endTime: number, - type: string, - interval: string, + type Payload = Partial<{ + startTime: number; + endTime: number; + type: string; + interval: string; }>; for (const { markers } of profile.threads) { @@ -1371,13 +1389,13 @@ const _upgraders = { // Update the time information. for (let i = 0; i < markers.length; i++) { - const data: ?Payload = markers.data[i]; + const data: Payload | null = markers.data[i]; const time: number = times[i]; // Start out by assuming it's an instant marker. - let newStartTime = time; - let newEndTime = null; - let phase = INSTANT; + let newStartTime: number | null = time; + let newEndTime: number | null = null; + let phase: 0 | 1 | 2 | 3 = INSTANT; // If there is a payload, it MAY change to an interval marker. if (data) { @@ -1423,7 +1441,7 @@ const _upgraders = { } } }, - [31]: (profile) => { + [31]: (profile: any) => { // The upgrader for 30 messed up markers with type "tracing" but that don't // have an interval. This upgrader fixes them. @@ -1445,36 +1463,34 @@ const _upgraders = { } } }, - [32]: (profile) => { + [32]: (profile: any) => { // Migrate DOMEvent markers to Markers 2.0 // This is a fairly permissive type, but helps ensure the logic below is type checked. type DOMEventPayload31_to_32 = { // Tracing -> DOMEvent - type: 'tracing' | 'DOMEvent', - category: 'DOMEvent', - eventType: string, + type: 'tracing' | 'DOMEvent'; + category: 'DOMEvent'; + eventType: string; // These are removed: - timeStamp: number, + timeStamp?: number; // This gets added: - latency: number, + latency?: number; }; // This is just the useful parts of the processed profile version 31. type ProfileV31 = { threads: Array<{ markers: { - data: any[], - startTime: Array, - length: number, - ... - }, - ... - }>, - processes: ProfileV31[], + data: any[]; + startTime: Array; + length: number; + }; + }>; + processes: ProfileV31[]; }; - for (const { markers } of (profile: ProfileV31).threads) { + for (const { markers } of (profile as ProfileV31).threads) { for (let i = 0; i < markers.length; i++) { // This isn't particularly type-safe, we need to refine to this type. const data: DOMEventPayload31_to_32 = markers.data[i]; @@ -1490,7 +1506,7 @@ const _upgraders = { } } }, - [33]: (profile) => { + [33]: (profile: any) => { // The marker schema, which details how to display markers was added. Back-fill // any old profiles with a default schema. @@ -1697,7 +1713,7 @@ const _upgraders = { }, ]; }, - [34]: (profile) => { + [34]: (profile: any) => { // We were incrementing timestamps for marker' causes only for a few marker // types: 'tracing' and 'Styles'. // See https://github.com/firefox-devtools/profiler/issues/3030 @@ -1721,7 +1737,7 @@ const _upgraders = { } } }, - [35]: (profile) => { + [35]: (profile: any) => { // The browsingContextID inside the pages array and activeBrowsingContextID // have been renamed to tabID and activeTabID. // Previously, we were using the browsingcontextID to figure out which tab @@ -1748,13 +1764,18 @@ const _upgraders = { } } }, - [36]: (profile) => { + [36]: (profile: any) => { // Threads now have a nativeSymbols table. // The frame table has a new field: nativeSymbol. // The function table loses one field: address. (This field moves to the nativeSymbols table.) // The NativeSymbolsTable has the fields libIndex, address, and name. for (const thread of profile.threads) { - const nativeSymbols = { + const nativeSymbols: { + libIndex: number[]; + address: Array; + name: number[]; + length: number; + } = { libIndex: [], address: [], name: [], @@ -1788,12 +1809,12 @@ const _upgraders = { } delete funcTable.address; frameTable.nativeSymbol = frameTable.func.map( - (f) => funcToNativeSymbolMap.get(f) ?? null + (f: number) => funcToNativeSymbolMap.get(f) ?? null ); thread.nativeSymbols = nativeSymbols; } }, - [37]: (profile) => { + [37]: (profile: any) => { // "Java Main Thread" has been renamed to "AndroidUI (JVM)". // Usually thread name changes are not that important as they don't affect // the front-end logic. But this one is important because visibility of @@ -1806,7 +1827,7 @@ const _upgraders = { } } }, - [38]: (profile) => { + [38]: (profile: any) => { // The frame table no longer contains return addresses, it now contains // "nudged" return addresses, i.e. return address minus one byte. // See nudgeReturnAddresses for more details. @@ -1819,8 +1840,8 @@ const _upgraders = { // and so that the assembly view (once implemented) on an old profile will // assign the correct "total" cost to call instructions. for (const thread of profile.threads) { - const samplingSelfStacks = new Set(); - const syncBacktraceSelfStacks = new Set(); + const samplingSelfStacks = new Set(); + const syncBacktraceSelfStacks = new Set(); const { samples, @@ -1864,7 +1885,7 @@ const _upgraders = { } const oldIpFrameToNewIpFrame = new Uint32Array(frameTable.length); - const ipFrames = new Set(); + const ipFrames = new Set(); for (const stack of samplingSelfStacks) { const frame = stackTable.frame[stack]; oldIpFrameToNewIpFrame[frame] = frame; @@ -1929,7 +1950,13 @@ const _upgraders = { // Now the frame table contains adjusted / "nudged" addresses. // Make a new stack table which refers to the adjusted frames. - const newStackTable = { + const newStackTable: { + frame: number[]; + prefix: Array; + category: number[]; + subcategory: number[]; + length: number; + } = { frame: [], prefix: [], category: [], @@ -1975,26 +2002,27 @@ const _upgraders = { } thread.stackTable = newStackTable; - samples.stack = samples.stack.map((oldStackIndex) => + samples.stack = samples.stack.map((oldStackIndex: number | null) => oldStackIndex === null ? null : (mapForSamplingSelfStacks.get(oldStackIndex) ?? null) ); - markers.data.forEach((data) => { + markers.data.forEach((data: any) => { if (data && 'cause' in data && data.cause) { data.cause.stack = mapForSyncBacktraces.get(data.cause.stack); } }); if (jsAllocations !== undefined) { - jsAllocations.stack = jsAllocations.stack.map((oldStackIndex) => - oldStackIndex === null - ? null - : (mapForSyncBacktraces.get(oldStackIndex) ?? null) + jsAllocations.stack = jsAllocations.stack.map( + (oldStackIndex: number | null) => + oldStackIndex === null + ? null + : (mapForSyncBacktraces.get(oldStackIndex) ?? null) ); } if (nativeAllocations !== undefined) { nativeAllocations.stack = nativeAllocations.stack.map( - (oldStackIndex) => + (oldStackIndex: number | null) => oldStackIndex === null ? null : (mapForSyncBacktraces.get(oldStackIndex) ?? null) @@ -2002,7 +2030,7 @@ const _upgraders = { } } }, - [39]: (profile) => { + [39]: (profile: any) => { for (const thread of profile.threads) { if (thread.samples.threadCPUDelta) { // Check to see the CPU delta numbers are all null and if they are, remove @@ -2011,7 +2039,7 @@ const _upgraders = { // Instead we should remove the whole array. This call will be quick for most // of the cases because we usually have values at least in the second sample. const hasCPUDeltaValues = thread.samples.threadCPUDelta.some( - (val) => val !== null + (val: number | null) => val !== null ); if (!hasCPUDeltaValues) { delete thread.samples.threadCPUDelta; @@ -2019,7 +2047,7 @@ const _upgraders = { } } }, - [40]: (profile) => { + [40]: (profile: any) => { // The FrameTable has a new column: inlineDepth. // We can initialize this column to zero for all frames. Zero means "this is // the frame for the outer function at this address". That's correct because @@ -2029,7 +2057,7 @@ const _upgraders = { thread.frameTable.inlineDepth = Array(thread.frameTable.length).fill(0); } }, - [41]: (profile) => { + [41]: (profile: any) => { // The libs list has moved from Thread to Profile - it is now shared between // all threads in the profile. And it only contains libs which are used by // at least one resource. @@ -2050,7 +2078,7 @@ const _upgraders = { // - Resources without a "host" or "lib" field have these fields set to // null consistently. - const libs = []; + const libs: any[] = []; const libKeyToLibIndex = new Map(); for (const thread of profile.threads) { const { @@ -2063,7 +2091,7 @@ const _upgraders = { const threadLibIndexToGlobalLibIndex = new Map(); delete thread.libs; - const getOrAddNewLib = (libIndex) => { + const getOrAddNewLib = (libIndex: number) => { let newLibIndex = threadLibIndexToGlobalLibIndex.get(libIndex); if (newLibIndex === undefined) { const lib = threadLibs[libIndex]; @@ -2131,7 +2159,7 @@ const _upgraders = { } profile.libs = libs; }, - [42]: (profile) => { + [42]: (profile: any) => { // The nativeSymbols table now has a new column: functionSize. // Its values can be null. for (const thread of profile.threads) { @@ -2139,16 +2167,16 @@ const _upgraders = { nativeSymbols.functionSize = Array(nativeSymbols.length).fill(null); } }, - [43]: (_) => { + [43]: (_profile: any) => { // The number property in counters is now optional. }, - [44]: (profile) => { + [44]: (profile: any) => { // `searchable` property in the marker schema wasn't implemented before and // we had some manual checks for the marker fields below. With this version, // we removed this manual check and started to use the `searchable` property // of the marker schema. for (const schema of profile.meta.markerSchema) { - let searchableFieldKeys; + let searchableFieldKeys: string[]; switch (schema.name) { case 'FileIO': { // threadId wasn't in the schema before, so we need to add manually. @@ -2198,13 +2226,13 @@ const _upgraders = { } } }, - [45]: (profile) => { + [45]: (profile: any) => { // The "optimizations" column was removed from the frame table. for (const thread of profile.threads) { delete thread.frameTable.optimizations; } }, - [46]: (profile) => { + [46]: (profile: any) => { // An `isMainThread` field was added to the Thread type. // // This replaces the following function: @@ -2227,7 +2255,7 @@ const _upgraders = { String(thread.pid) === thread.tid; } }, - [47]: (profile) => { + [47]: (profile: any) => { // The `pid` field of the Thread type was changed from `string | number` to `string`. // The same happened to the data.otherPid field of IPC markers, and to the // pid fields in the profiler.counters and profile.profilerOverhead lists. @@ -2251,7 +2279,7 @@ const _upgraders = { } } }, - [48]: (profile) => { + [48]: (profile: any) => { // Remove the 'sampleGroups' object from the Counter structure. if (profile.counters && profile.counters.length > 0) { for (const counter of profile.counters) { @@ -2260,14 +2288,14 @@ const _upgraders = { } } }, - [49]: (_) => { + [49]: (_profile: any) => { // The 'sanitized-string' marker schema format type has been added. }, - [50]: (_) => { + [50]: (_profile: any) => { // The format can now optionally store sample and counter sample // times as time deltas instead of absolute timestamps to reduce the JSON size. }, - [51]: (_) => { + [51]: (_profile: any) => { // This version bump added two new form types for new marker schema field: // "flow-id" and "terminating-flow-id". // Older frontends will not be able to display these fields. @@ -2276,7 +2304,7 @@ const _upgraders = { // marker data with the new field types data, and no modification is needed in the // frontend to display older formats. }, - [52]: (profile) => { + [52]: (profile: any) => { // This version simplifies how markers are mapped to their schema. // The schema is now purely determined by data.type. The marker's name is ignored. // If a marker has a null data, then it has no schema. @@ -2300,7 +2328,9 @@ const _upgraders = { // Profiles from modern versions of Firefox already include a 'tracing' schema. // And they don't use tracing markers for CC markers. - const schemaNames = new Set(profile.meta.markerSchema.map((s) => s.name)); + const schemaNames = new Set( + profile.meta.markerSchema.map((s: any) => s.name) + ); const kTracingCCSchemaName = 'tracingCCFrom52Upgrader'; const shouldMigrateTracingCCMarkers = schemaNames.has('CC'); let hasTracingMarkers = false; @@ -2346,7 +2376,7 @@ const _upgraders = { }); } }, - [53]: (profile) => { + [53]: (profile: any) => { for (const thread of profile.threads) { const { frameTable, stackTable } = thread; @@ -2385,7 +2415,7 @@ const _upgraders = { delete stackTable.subcategory; } }, - [54]: (profile) => { + [54]: (profile: any) => { // The `implementation` column was removed from the frameTable. Modern // profiles from Firefox use subcategories to represent the information // about the JIT type of a JS frame. @@ -2400,19 +2430,19 @@ const _upgraders = { // Very old Gecko profiles don't have JS subcategories. Convert the // implementation information to subcategories. - function maybeConvertImplementationToSubcategories(profile) { + function maybeConvertImplementationToSubcategories(profile: any) { const { categories } = profile.meta; if (!categories) { return; } - if (categories.some((c) => c.subcategories.length !== 1)) { + if (categories.some((c: any) => c.subcategories.length !== 1)) { // This profile has subcategories. return; } const jsCategoryIndex = categories.findIndex( - (c) => c.name === 'JavaScript' + (c: any) => c.name === 'JavaScript' ); if (jsCategoryIndex === -1) { // This profile has no JavaScript category. @@ -2452,10 +2482,14 @@ const _upgraders = { // This field is no longer needed. delete profile.meta.doesNotUseFrameImplementation; }, - [55]: (profile) => { + [55]: (profile: any) => { for (const markerSchema of profile.meta.markerSchema) { - const staticFields = markerSchema.data.filter((f) => f.key === undefined); - const fields = markerSchema.data.filter((f) => f.value === undefined); + const staticFields = markerSchema.data.filter( + (f: any) => f.key === undefined + ); + const fields = markerSchema.data.filter( + (f: any) => f.value === undefined + ); markerSchema.fields = fields; delete markerSchema.data; @@ -2466,7 +2500,7 @@ const _upgraders = { // Migrate one of the static fields to the new `description` property. let staticDescriptionFieldIndex = staticFields.findIndex( - (f) => f.label === 'Description' + (f: any) => f.label === 'Description' ); if (staticDescriptionFieldIndex === -1) { staticDescriptionFieldIndex = 0; @@ -2479,22 +2513,22 @@ const _upgraders = { // old { label: "Marker", value: "UserTiming" } field which never provided // any value. (On the Gecko side, it was removed by D196332.) const discardedFields = staticFields.filter( - (_f, i) => i !== staticDescriptionFieldIndex + (_f: any, i: number) => i !== staticDescriptionFieldIndex ); const potentiallyUsefulDiscardedFields = discardedFields.filter( - (f) => f.label !== 'Marker' && f.value !== 'UserTiming' + (f: any) => f.label !== 'Marker' && f.value !== 'UserTiming' ); if (potentiallyUsefulDiscardedFields.length !== 0) { console.warn( - `Discarding the following static fields from marker schema "${markerSchema.name}": ${potentiallyUsefulDiscardedFields.map((f) => f.label + ': ' + f.value).join(', ')}` + `Discarding the following static fields from marker schema "${markerSchema.name}": ${potentiallyUsefulDiscardedFields.map((f: any) => f.label + ': ' + f.value).join(', ')}` ); } } }, - [56]: (profile) => { + [56]: (profile: any) => { // The stringArray is now shared across all threads. It is stored at // profile.shared.stringArray. - const stringArray = []; + const stringArray: string[] = []; const stringTable = StringTable.withBackingArray(stringArray); // Precompute marker fields that need adjusting. @@ -2594,7 +2628,7 @@ const _upgraders = { } profile.shared = { stringArray }; }, - [57]: (profile) => { + [57]: (profile: any) => { // The "searchable" property for fields in the marker schema was removed again. // Now all marker fields are searchable. for (const schema of profile.meta.markerSchema) { diff --git a/src/profile-logic/profile-compacting.js b/src/profile-logic/profile-compacting.ts similarity index 96% rename from src/profile-logic/profile-compacting.js rename to src/profile-logic/profile-compacting.ts index 48329c4417..53377a07ad 100644 --- a/src/profile-logic/profile-compacting.js +++ b/src/profile-logic/profile-compacting.ts @@ -2,8 +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/. */ -// @flow - import { computeStringIndexMarkerFieldsByDataType } from './marker-schema'; import type { @@ -16,10 +14,10 @@ import type { NativeSymbolTable, } from 'firefox-profiler/types'; -export type CompactedProfileWithTranslationMaps = {| - profile: Profile, - oldStringToNewStringPlusOne: Int32Array, -|}; +export type CompactedProfileWithTranslationMaps = { + profile: Profile; + oldStringToNewStringPlusOne: Int32Array; +}; /** * Returns a new profile with all unreferenced strings removed. @@ -168,7 +166,7 @@ function _gatherReferencesInMarkers( ); if (stringIndexMarkerFields !== undefined) { for (const fieldKey of stringIndexMarkerFields) { - const stringIndex = data[fieldKey]; + const stringIndex = (data as any)[fieldKey]; if (typeof stringIndex === 'number') { referencedStrings[stringIndex] = 1; } @@ -200,7 +198,7 @@ function _createMarkersWithTranslatedStringIndexes( ); if (stringIndexMarkerFields !== undefined) { for (const fieldKey of stringIndexMarkerFields) { - const stringIndex = data[fieldKey]; + const stringIndex = (data as any)[fieldKey]; if (typeof stringIndex === 'number') { newData = { ...newData, @@ -211,7 +209,7 @@ function _createMarkersWithTranslatedStringIndexes( } } - newDataCol[i] = (newData: any); + newDataCol[i] = newData as any; } return { @@ -324,7 +322,7 @@ function _createNativeSymbolsWithTranslatedStringIndexes( function _createCompactedStringArray( stringArray: string[], referencedStrings: Uint8Array -): { newStringArray: string[], oldStringToNewStringPlusOne: Int32Array } { +): { newStringArray: string[]; oldStringToNewStringPlusOne: Int32Array } { const oldStringToNewStringPlusOne = new Int32Array(stringArray.length); let nextIndex = 0; const newStringArray = []; diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.ts similarity index 97% rename from src/profile-logic/profile-data.js rename to src/profile-logic/profile-data.ts index 2a95d63837..45bc683118 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.ts @@ -2,8 +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/. */ -// @flow - import memoize from 'memoize-immutable'; import MixedTupleMap from 'mixedtuplemap'; import { oneLine } from 'common-tags'; @@ -148,9 +146,9 @@ function _computeFrameTableInlinedIntoColumn( } type CallNodeTableAndStackMap = { - callNodeTable: CallNodeTable, + callNodeTable: CallNodeTable; // IndexIntoStackTable -> IndexIntoCallNodeTable - stackIndexToCallNodeIndex: Int32Array, + stackIndexToCallNodeIndex: Int32Array; }; /** @@ -213,13 +211,13 @@ export function computeCallNodeTable( * At this point we are done with grouping stacks into call nodes. * But we haven't put the call nodes in the final order yet. */ -type CallNodeTableHierarchy = {| - prefix: Array, - firstChild: Array, - nextSibling: Array, - length: number, - stackIndexToCallNodeIndex: Int32Array, -|}; +type CallNodeTableHierarchy = { + prefix: Array; + firstChild: Array; + nextSibling: Array; + length: number; + stackIndexToCallNodeIndex: Int32Array; +}; /** * The return type of _computeCallNodeTableDFSOrder. @@ -227,15 +225,15 @@ type CallNodeTableHierarchy = {| * The values in these columns are in the final order in which they'll be in the * actual call node table. DFS here means "depth-first search". */ -type CallNodeTableDFSOrder = {| - length: number, - stackIndexToCallNodeIndex: Int32Array, - nextSiblingSorted: Int32Array, - subtreeRangeEndSorted: Uint32Array, - prefixSorted: Int32Array, - depthSorted: Int32Array, - maxDepth: number, -|}; +type CallNodeTableDFSOrder = { + length: number; + stackIndexToCallNodeIndex: Int32Array; + nextSiblingSorted: Int32Array; + subtreeRangeEndSorted: Uint32Array; + prefixSorted: Int32Array; + depthSorted: Int32Array; + maxDepth: number; +}; /** * The return type of _computeCallNodeTableExtraColumns. @@ -243,13 +241,13 @@ type CallNodeTableDFSOrder = {| * We compute these columns once we know the final size and order of the call * node table. */ -type CallNodeTableExtraColumns = {| - funcCol: Int32Array, // IndexIntoCallNodeTable -> IndexIntoFuncTable - categoryCol: Int32Array, // IndexIntoCallNodeTable -> IndexIntoCategoryList - subcategoryCol: Int32Array, // IndexIntoCallNodeTable -> IndexIntoSubcategoryListForCategory - innerWindowIDCol: Float64Array, // IndexIntoCallNodeTable -> InnerWindowID - inlinedIntoCol: Int32Array, // IndexIntoCallNodeTable -> IndexIntoNativeSymbolTable | -1 | -2 -|}; +type CallNodeTableExtraColumns = { + funcCol: Int32Array; // IndexIntoCallNodeTable -> IndexIntoFuncTable + categoryCol: Int32Array; // IndexIntoCallNodeTable -> IndexIntoCategoryList + subcategoryCol: Int32Array; // IndexIntoCallNodeTable -> IndexIntoSubcategoryListForCategory + innerWindowIDCol: Float64Array; // IndexIntoCallNodeTable -> InnerWindowID + inlinedIntoCol: Int32Array; // IndexIntoCallNodeTable -> IndexIntoNativeSymbolTable | -1 | -2 +}; /** * Used as part of creating the call node table. @@ -734,7 +732,7 @@ export function getNthPrefixStack( export function getSampleIndexToCallNodeIndex( stacks: Array, stackIndexToCallNodeIndex: { - [key: IndexIntoStackTable]: IndexIntoCallNodeTable, + [key: IndexIntoStackTable]: IndexIntoCallNodeTable; } ): Array { return stacks.map((stack) => { @@ -927,29 +925,29 @@ export function getLeafFuncIndex(path: CallNodePath): IndexIntoFuncTable { return path[path.length - 1]; } -export type OneCategoryBreakdown = {| - entireCategoryValue: Milliseconds, - subcategoryBreakdown: Milliseconds[], // { [IndexIntoSubcategoryList]: Milliseconds } -|}; +export type OneCategoryBreakdown = { + entireCategoryValue: Milliseconds; + subcategoryBreakdown: Milliseconds[]; // { [IndexIntoSubcategoryList]: Milliseconds } +}; export type BreakdownByCategory = OneCategoryBreakdown[]; // { [IndexIntoCategoryList]: OneCategoryBreakdown } -export type ItemTimings = {| - selfTime: {| +export type ItemTimings = { + selfTime: { // time spent excluding children - value: Milliseconds, - breakdownByCategory: BreakdownByCategory | null, - |}, - totalTime: {| + value: Milliseconds; + breakdownByCategory: BreakdownByCategory | null; + }; + totalTime: { // time spent including children - value: Milliseconds, - breakdownByCategory: BreakdownByCategory | null, - |}, -|}; + value: Milliseconds; + breakdownByCategory: BreakdownByCategory | null; + }; +}; -export type TimingsForPath = {| +export type TimingsForPath = { // timings for this path - forPath: ItemTimings, - rootTime: Milliseconds, // time for all the samples in the current tree -|}; + forPath: ItemTimings; + rootTime: Milliseconds; // time for all the samples in the current tree +}; /** * This function is the same as getTimingsForCallNodeIndex, but accepts a CallNodePath @@ -1030,8 +1028,8 @@ export function getTimingsForCallNodeIndex( */ function accumulateDataToTimings( timings: { - breakdownByCategory: BreakdownByCategory | null, - value: number, + breakdownByCategory: BreakdownByCategory | null; + value: number; }, sampleIndex: IndexIntoSamplesTable, duration: Milliseconds @@ -1385,7 +1383,7 @@ export function toValidImplementationFilter( } export function toValidCallTreeSummaryStrategy( - strategy: mixed + strategy: string | undefined ): CallTreeSummaryStrategy { switch (strategy) { case 'timing': @@ -1439,12 +1437,12 @@ export function filterThreadByImplementation( function _filterThreadByFunc( thread: Thread, - shouldIncludeFuncInFilteredThread: (IndexIntoFuncTable) => boolean + shouldIncludeFuncInFilteredThread: (funcIndex: IndexIntoFuncTable) => boolean ): Thread { return timeCode('_filterThreadByFunc', () => { const { stackTable, frameTable } = thread; - const newStackTable = { + const newStackTable: StackTable = { length: 0, frame: [], prefix: [], @@ -1506,7 +1504,7 @@ export function filterThreadToSearchString( const { funcTable, frameTable, stackTable, stringTable, resourceTable } = thread; - function computeFuncMatchesFilter(func) { + function computeFuncMatchesFilter(func: IndexIntoFuncTable) { const nameIndex = funcTable.name[func]; const nameString = stringTable.getString(nameIndex); if (nameString.toLowerCase().includes(lowercaseSearchString)) { @@ -1534,7 +1532,7 @@ export function filterThreadToSearchString( } const funcMatchesFilterCache = new Map(); - function funcMatchesFilter(func) { + function funcMatchesFilter(func: IndexIntoFuncTable) { let result = funcMatchesFilterCache.get(func); if (result === undefined) { result = computeFuncMatchesFilter(func); @@ -1544,7 +1542,7 @@ export function filterThreadToSearchString( } const stackMatchesFilterCache = new Map(); - function stackMatchesFilter(stackIndex) { + function stackMatchesFilter(stackIndex: IndexIntoStackTable | null) { if (stackIndex === null) { return false; } @@ -1580,7 +1578,7 @@ export function computeTimeColumnForRawSamplesTable( * A useful sample being one that isn't a "(root)" sample. */ export function hasUsefulSamples( - sampleStacks?: Array, + sampleStacks: Array | undefined, thread: RawThread, shared: RawProfileSharedData ): boolean { @@ -1619,7 +1617,7 @@ export function hasUsefulSamples( * This function takes both a SamplesTable and can be used on CounterSamplesTable. */ export function getSampleIndexRangeForSelection( - times: { time: Milliseconds[], length: number }, + times: { time: Milliseconds[]; length: number }, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -1642,7 +1640,7 @@ export function getIndexRangeForSelection( * sure that some charts will not be cut off at the edges when zoomed in to a range. */ export function getInclusiveSampleIndexRangeForSelection( - table: { time: Milliseconds[], length: number }, + table: { time: Milliseconds[]; length: number }, rangeStart: number, rangeEnd: number ): [IndexIntoSamplesTable, IndexIntoSamplesTable] { @@ -1774,7 +1772,7 @@ export function filterThreadSamplesToRange( ); const stack = nativeAllocations.stack.slice(startAllocIndex, endAllocIndex); const length = endAllocIndex - startAllocIndex; - if (nativeAllocations.memoryAddress) { + if ('memoryAddress' in nativeAllocations) { newThread.nativeAllocations = { time, weight, @@ -1900,7 +1898,7 @@ export function filterRawThreadSamplesToRange( ); const stack = nativeAllocations.stack.slice(startAllocIndex, endAllocIndex); const length = endAllocIndex - startAllocIndex; - if (nativeAllocations.memoryAddress) { + if ('memoryAddress' in nativeAllocations) { newThread.nativeAllocations = { time, weight, @@ -2277,7 +2275,7 @@ export function computeCallNodeMaxDepthPlusOne( */ export function computeSamplesTableFromRawSamplesTable( rawSamples: RawSamplesTable, - sampleUnits: SampleUnits | void, + sampleUnits: SampleUnits | undefined, referenceCPUDeltaPerMs: number ): SamplesTable { const { @@ -2405,15 +2403,15 @@ export function updateThreadStacksByGeneratingNewStackColumns( thread: Thread, newStackTable: StackTable, computeMappedStackColumn: ( - Array, - Array + oldStack: Array, + sampleTime: Array ) => Array, computeMappedSyncBacktraceStackColumn: ( - Array, - Array + oldStack: Array, + sampleTime: Array ) => Array, computeMappedMarkerDataColumn: ( - Array + markerData: Array ) => Array ): Thread { const { jsAllocations, nativeAllocations, samples, markers } = thread; @@ -2470,21 +2468,25 @@ export function updateThreadStacksByGeneratingNewStackColumns( export function updateThreadStacks( thread: Thread, newStackTable: StackTable, - convertStack: (IndexIntoStackTable | null) => IndexIntoStackTable | null + convertStack: ( + oldStack: IndexIntoStackTable | null + ) => IndexIntoStackTable | null ): Thread { function convertMarkerData( oldData: MarkerPayload | null ): MarkerPayload | null { if (oldData && 'cause' in oldData && oldData.cause) { - // Replace the cause with the right stack index. - // $FlowExpectError Flow is failing to refine oldData.type based on the `cause` field check - return { - ...oldData, - cause: { - ...oldData.cause, - stack: convertStack(oldData.cause.stack), - }, - }; + const stack = convertStack(oldData.cause.stack); + if (stack) { + // Replace the cause with the right stack index. + return { + ...oldData, + cause: { + ...oldData.cause, + stack, + }, + }; + } } return oldData; } @@ -2508,7 +2510,9 @@ export function updateThreadStacks( export function updateRawThreadStacks( thread: RawThread, newStackTable: RawStackTable, - convertStack: (IndexIntoStackTable | null) => IndexIntoStackTable | null + convertStack: ( + oldStack: IndexIntoStackTable | null + ) => IndexIntoStackTable | null ): RawThread { return updateRawThreadStacksSeparate( thread, @@ -2532,24 +2536,28 @@ export function updateRawThreadStacks( export function updateRawThreadStacksSeparate( thread: RawThread, newStackTable: RawStackTable, - convertStack: (IndexIntoStackTable | null) => IndexIntoStackTable | null, + convertStack: ( + oldStack: IndexIntoStackTable | null + ) => IndexIntoStackTable | null, convertSyncBacktraceStack: ( - IndexIntoStackTable | null + oldStack: IndexIntoStackTable | null ) => IndexIntoStackTable | null ): RawThread { function convertMarkerData( oldData: MarkerPayload | null ): MarkerPayload | null { if (oldData && 'cause' in oldData && oldData.cause) { - // Replace the cause with the right stack index. - // $FlowExpectError Flow is failing to refine oldData.type based on the `cause` field check - return { - ...oldData, - cause: { - ...oldData.cause, - stack: convertSyncBacktraceStack(oldData.cause.stack), - }, - }; + const stack = convertSyncBacktraceStack(oldData.cause.stack); + if (stack) { + // Replace the cause with the right stack index. + return { + ...oldData, + cause: { + ...oldData.cause, + stack, + }, + }; + } } return oldData; } @@ -2599,9 +2607,9 @@ export function updateRawThreadStacksSeparate( export function getMapStackUpdater( oldStackToNewStack: Map< null | IndexIntoStackTable, - null | IndexIntoStackTable, + null | IndexIntoStackTable > -): (IndexIntoStackTable | null) => IndexIntoStackTable | null { +): (oldStack: IndexIntoStackTable | null) => IndexIntoStackTable | null { return (oldStack: IndexIntoStackTable | null) => { if (oldStack === null) { return null; @@ -2708,8 +2716,8 @@ export function getFriendlyThreadName( threads: RawThread[], thread: RawThread ): string { - let label; - let homonymThreads; + let label: string | undefined; + let homonymThreads: RawThread[] | undefined; switch (thread.name) { case 'GeckoMain': { @@ -2994,7 +3002,7 @@ export function isSampleWithNonEmptyStack( export function getTreeOrderComparator( sampleNonInvertedCallNodes: Array, callNodeInfo: CallNodeInfo -): (IndexIntoSamplesTable, IndexIntoSamplesTable) => number { +): (sampleA: IndexIntoSamplesTable, sampleB: IndexIntoSamplesTable) => number { const callNodeInfoInverted = callNodeInfo.asInverted(); return callNodeInfoInverted !== null ? _getTreeOrderComparatorInverted( @@ -3006,7 +3014,7 @@ export function getTreeOrderComparator( export function _getTreeOrderComparatorNonInverted( sampleCallNodes: Array -): (IndexIntoSamplesTable, IndexIntoSamplesTable) => number { +): (sampleA: IndexIntoSamplesTable, sampleB: IndexIntoSamplesTable) => number { /** * Determine the ordering of (possibly null) call nodes for two given samples. * Returns a value < 0 if sampleA is ordered before sampleB, @@ -3041,7 +3049,7 @@ export function _getTreeOrderComparatorNonInverted( function _getTreeOrderComparatorInverted( sampleNonInvertedCallNodes: Array, callNodeInfo: CallNodeInfoInverted -): (IndexIntoSamplesTable, IndexIntoSamplesTable) => number { +): (sampleA: IndexIntoSamplesTable, sampleB: IndexIntoSamplesTable) => number { const callNodeTable = callNodeInfo.getCallNodeTable(); return function treeOrderComparator( sampleA: IndexIntoSamplesTable, @@ -3430,19 +3438,19 @@ export function hasThreadKeys( return true; } -export type StackReferences = {| +export type StackReferences = { // Stacks which were sampled by sampling. For native stacks, the // corresponding frame address was observed as a value of the instruction // pointer register. - samplingSelfStacks: Set, + samplingSelfStacks: Set; // Stacks which were obtained during a synchronous backtrace. For // native stacks, the corresponding frame address is *not* an observed // value of the instruction pointer, because synchronous backtraces have // a few frames removed from the end of the stack, which includes the // frame with the instruction pointer. This difference only matters for // "return address nudging" which happens at the end of profile processing. - syncBacktraceSelfStacks: Set, -|}; + syncBacktraceSelfStacks: Set; +}; /** * Find the sets of stacks that are referenced as "self" stacks by @@ -3454,8 +3462,8 @@ export type StackReferences = {| * The two have slightly different properties, see the type definition. */ export function gatherStackReferences(thread: RawThread): StackReferences { - const samplingSelfStacks = new Set(); - const syncBacktraceSelfStacks = new Set(); + const samplingSelfStacks: Set = new Set(); + const syncBacktraceSelfStacks: Set = new Set(); const { samples, markers, jsAllocations, nativeAllocations } = thread; @@ -3470,7 +3478,7 @@ export function gatherStackReferences(thread: RawThread): StackReferences { // Markers for (let i = 0; i < markers.length; i++) { const data = markers.data[i]; - if (data && data.cause) { + if (data && 'cause' in data && data.cause) { const stack = data.cause.stack; if (stack !== null) { syncBacktraceSelfStacks.add(stack); @@ -3872,7 +3880,7 @@ export function getNativeSymbolsForCallNodeNonInverted( ): IndexIntoNativeSymbolTable[] { const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); - const set = new Set(); + const set: Set = new Set(); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { if (stackIndexToCallNodeIndex[stackIndex] === callNodeIndex) { const frame = stackTable.frame[stackIndex]; @@ -3898,7 +3906,7 @@ export function getNativeSymbolsForCallNodeInverted( const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const suffixOrderIndexes = callNodeInfo.getSuffixOrderIndexes(); - const set = new Set(); + const set: Set = new Set(); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { const stackForNode = getMatchingAncestorStackForInvertedCallNode( stackIndex, @@ -4083,6 +4091,7 @@ export function computeTabToThreadIndexesMap( } if ( + 'innerWindowID' in markerData && markerData.innerWindowID !== null && markerData.innerWindowID !== undefined && // Zero value also means null for innerWindowID. diff --git a/src/profile-logic/profile-metainfo.js b/src/profile-logic/profile-metainfo.ts similarity index 96% rename from src/profile-logic/profile-metainfo.js rename to src/profile-logic/profile-metainfo.ts index 7ad4e976cd..d01f131aef 100644 --- a/src/profile-logic/profile-metainfo.js +++ b/src/profile-logic/profile-metainfo.ts @@ -2,8 +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/. */ -// @flow - import type { ProfileMeta } from 'firefox-profiler/types'; // This extracts the version number out of the 'misc' value we have in the @@ -35,8 +33,8 @@ function removeUselessEndZeroInVersion(version: string): string { // This returns a string to identify the product and its version out of the meta // information, eg `Firefox 77` of `Firefox Preview 78`. export function formatProductAndVersion(meta: { - +product: string, - +misc?: string, + readonly product: string; + readonly misc?: string; }): string { const product = meta.product || ''; const version = removeUselessEndZeroInVersion(formatVersionNumber(meta.misc)); @@ -51,9 +49,9 @@ export function formatProductAndVersion(meta: { // If you change something, please make sure that the CSS in // components/shared/ProfileMetaInfoSummary.css still works. export function formatPlatform(meta: { - +platform?: string, - +oscpu?: string, - +toolkit?: string, + readonly platform?: string; + readonly oscpu?: string; + readonly toolkit?: string; }): string { switch (meta.toolkit) { case 'android': diff --git a/src/profile-logic/profile-store.js b/src/profile-logic/profile-store.ts similarity index 95% rename from src/profile-logic/profile-store.js rename to src/profile-logic/profile-store.ts index 44f2434350..3ad95443c0 100644 --- a/src/profile-logic/profile-store.js +++ b/src/profile-logic/profile-store.ts @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import { oneLine } from 'common-tags'; import { PROFILER_SERVER_ORIGIN } from 'firefox-profiler/app-logic/constants'; @@ -16,7 +16,7 @@ const ACCEPT_HEADER_VALUE = 'application/vnd.firefox-profiler+json;version=1.0'; // the caller function. // It's exported because we use it in tests. export class UploadAbortedError extends Error { - name = 'UploadAbortedError'; + override name = 'UploadAbortedError'; } export function uploadBinaryProfileData() { @@ -29,8 +29,8 @@ export function uploadBinaryProfileData() { xhr.abort(); }, startUpload: ( - data: $TypedArray, - progressChangeCallback?: (number) => mixed + data: ArrayBufferView, + progressChangeCallback?: (param: number) => unknown ): Promise => new Promise((resolve, reject) => { if (isAborted) { @@ -101,8 +101,8 @@ export async function deleteProfileOnServer({ profileToken, jwtToken, }: { - profileToken: string, - jwtToken: string, + profileToken: string; + jwtToken: string; }): Promise { const ENDPOINT = `${PROFILER_SERVER_ORIGIN}/profile/${profileToken}`; diff --git a/src/profile-logic/sanitize.js b/src/profile-logic/sanitize.ts similarity index 96% rename from src/profile-logic/sanitize.js rename to src/profile-logic/sanitize.ts index f242eb0dcc..2ff0f19fb5 100644 --- a/src/profile-logic/sanitize.js +++ b/src/profile-logic/sanitize.ts @@ -2,8 +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/. */ -// @flow - import { getEmptyExtensions, shallowCloneRawMarkerTable, @@ -37,14 +35,15 @@ import type { InnerWindowID, MarkerSchemaByName, RawCounter, + ProfilerOverhead, } from 'firefox-profiler/types'; -export type SanitizeProfileResult = {| - +profile: Profile, - +oldThreadIndexToNew: Map | null, - +committedRanges: StartEndRange[] | null, - +isSanitized: boolean, -|}; +export type SanitizeProfileResult = { + readonly profile: Profile; + readonly oldThreadIndexToNew: Map | null; + readonly committedRanges: StartEndRange[] | null; + readonly isSanitized: boolean; +}; /** * Take a processed profile with PII that user wants to be removed and remove the @@ -72,7 +71,7 @@ export function sanitizePII( const oldThreadIndexToNew: Map = new Map(); // This set keeps the ids to be removed when removing private browsing data. - const windowIdFromPrivateBrowsing = new Set(); + const windowIdFromPrivateBrowsing = new Set(); let pages = profile.pages; if (pages) { @@ -131,7 +130,7 @@ export function sanitizePII( shared: { stringArray, }, - threads: profile.threads.reduce((acc, thread, threadIndex) => { + threads: profile.threads.reduce((acc, thread, threadIndex) => { const newThread: RawThread | null = sanitizeThreadPII( thread, stringTable, @@ -154,7 +153,7 @@ export function sanitizePII( // Remove counters which belong to the removed counters. // Also adjust other counters to point to the right thread. counters: profile.counters - ? profile.counters.reduce((acc, counter, counterIndex) => { + ? profile.counters.reduce((acc, counter, counterIndex) => { if (PIIToBeRemoved.shouldRemoveCounters.has(counterIndex)) { removingCounters = true; return acc; @@ -176,7 +175,7 @@ export function sanitizePII( // Remove profilerOverhead which belong to the removed threads. // Also adjust other overheads to point to the right thread. profilerOverhead: profile.profilerOverhead - ? profile.profilerOverhead.reduce((acc, overhead) => { + ? profile.profilerOverhead.reduce((acc, overhead) => { const newThreadIndex = oldThreadIndexToNew.get( overhead.mainThreadIndex ); @@ -266,7 +265,7 @@ function sanitizeThreadPII( // We iterate all the markers and remove/change data depending on the PII // status. - const markersToDelete = new Set(); + const markersToDelete = new Set(); if ( PIIToBeRemoved.shouldRemoveUrls || PIIToBeRemoved.shouldRemovePreferenceValues || @@ -355,6 +354,7 @@ function sanitizeThreadPII( if ( currentMarker && + 'innerWindowID' in currentMarker && currentMarker.innerWindowID && windowIdFromPrivateBrowsing.has(currentMarker.innerWindowID) ) { @@ -419,12 +419,12 @@ function sanitizeThreadPII( // all frames if we need to. const sanitizedFuncIndexesToFrameIndex: Map< IndexIntoFuncTable, - IndexIntoFrameTable[], + IndexIntoFrameTable[] > = new Map(); // This set holds all func indexes that shouldn't be sanitized. This will be // intersected with the previous map's keys to know which functions need to // be split in 2. - const funcIndexesToBeKept = new Set(); + const funcIndexesToBeKept = new Set(); const { frameTable, funcTable, resourceTable, stackTable, samples } = newThread; @@ -453,7 +453,7 @@ function sanitizeThreadPII( } if (sanitizedFuncIndexesToFrameIndex.size) { - const resourcesToBeSanitized = new Set(); + const resourcesToBeSanitized = new Set(); const newFuncTable = (newThread.funcTable = shallowCloneFuncTable(funcTable)); diff --git a/src/profile-logic/stack-timing.js b/src/profile-logic/stack-timing.ts similarity index 93% rename from src/profile-logic/stack-timing.js rename to src/profile-logic/stack-timing.ts index 75b59f814d..7ea7f20991 100644 --- a/src/profile-logic/stack-timing.js +++ b/src/profile-logic/stack-timing.ts @@ -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/. */ -// @flow import type { SamplesLikeTable, Milliseconds, @@ -78,25 +77,25 @@ import type { CallNodeInfo } from './call-node-info'; export type StackTimingDepth = number; export type IndexIntoStackTiming = number; -export type StackTiming = {| - start: Milliseconds[], - end: Milliseconds[], +export type StackTiming = { + start: Milliseconds[]; + end: Milliseconds[]; // These 2 properties sameWidthsStart and sameWidthsEnd increments at each // "tick", that is at each stack change. They'll make it possible to draw a // stack chart where each different stack has the same width, and can better // show very short changes. - sameWidthsStart: number[], - sameWidthsEnd: number[], - callNode: IndexIntoCallNodeTable[], - length: number, -|}; + sameWidthsStart: number[]; + sameWidthsEnd: number[]; + callNode: IndexIntoCallNodeTable[]; + length: number; +}; export type StackTimingByDepth = Array; export type SameWidthsIndexToTimestampMap = number[]; -export type StackTimingByDepthWithMap = {| - timings: StackTimingByDepth, - sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap, -|}; +export type StackTimingByDepthWithMap = { + timings: StackTimingByDepth; + sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap; +}; /** * Build a StackTimingByDepth table from a given thread. @@ -114,16 +113,19 @@ export function getStackTimingByDepth( subtreeRangeEnd: callNodeTableSubtreeRangeEndColumn, depth: callNodeTableDepthColumn, } = callNodeTable; - const stackTimingByDepth = Array.from({ length: maxDepthPlusOne }, () => ({ - start: [], - end: [], - sameWidthsStart: [], - sameWidthsEnd: [], - callNode: [], - length: 0, - })); - - const sameWidthsIndexToTimestampMap = []; + const stackTimingByDepth: StackTimingByDepth = Array.from( + { length: maxDepthPlusOne }, + (): StackTiming => ({ + start: [], + end: [], + sameWidthsStart: [], + sameWidthsEnd: [], + callNode: [], + length: 0, + }) + ); + + const sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap = []; if (samples.length === 0) { return { timings: stackTimingByDepth, sameWidthsIndexToTimestampMap }; diff --git a/src/profile-logic/symbol-store-db.js b/src/profile-logic/symbol-store-db.ts similarity index 92% rename from src/profile-logic/symbol-store-db.js rename to src/profile-logic/symbol-store-db.ts index 49af056dba..835db72568 100644 --- a/src/profile-logic/symbol-store-db.js +++ b/src/profile-logic/symbol-store-db.ts @@ -2,18 +2,8 @@ * 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/. */ -// @flow - import { SymbolsNotFoundError } from './errors'; -import type { - IDBFactory, - IDBDatabase, - IDBObjectStore, - IDBIndex, - IDBKeyRange, -} from 'firefox-profiler/types'; - // Contains a symbol table, which can be used to map addresses to strings. // Symbol tables of this format are created within Firefox's implementation of // the geckoProfiler WebExtension API, at @@ -39,18 +29,16 @@ export type SymbolTableAsTuple = [ Uint8Array, // buffer ]; -type SymbolItem = {| - debugName: string, - breakpadId: string, - addrs: Uint32Array, - index: Uint32Array, - buffer: Uint8Array, - lastUsedDate: Date, -|}; +export type SymbolItem = { + debugName: string; + breakpadId: string; + addrs: Uint32Array; + index: Uint32Array; + buffer: Uint8Array; + lastUsedDate: Date; +}; -type SymbolPrimaryKey = [string, string]; -type SymbolDateKey = $PropertyType; -type SymbolStore = IDBObjectStore; +type SymbolStore = IDBObjectStore; const kTwoWeeksInMilliseconds = 2 * 7 * 24 * 60 * 60 * 1000; @@ -90,14 +78,14 @@ export default class SymbolStoreDB { } _setupDB(dbName: string): Promise { - const indexedDB: IDBFactory | void = window.indexedDB; + const indexedDB = window.indexedDB; if (!indexedDB) { throw new Error('Could not find indexedDB on the window object.'); } return new Promise((resolve, reject) => { const openReq = indexedDB.open(dbName, 2); openReq.onerror = () => { - if (openReq.error.name === 'VersionError') { + if (openReq.error && openReq.error.name === 'VersionError') { // This error fires if the database already exists, and the existing // database has a higher version than what we requested. So either // this version of profiler.firefox.com is outdated, or somebody briefly tried @@ -262,13 +250,10 @@ export default class SymbolStoreDB { beforeDate: Date, callback: () => void ): void { - const lastUsedDateIndex: IDBIndex = - store.index('lastUsedDate'); + const lastUsedDateIndex = store.index('lastUsedDate'); // Get a cursor that walks all records whose lastUsedDate is less than beforeDate. const range = window.IDBKeyRange.upperBound(beforeDate, true); - const cursorReq = lastUsedDateIndex.openCursor( - (range: IDBKeyRange) - ); + const cursorReq = lastUsedDateIndex.openCursor(range); // Iterate over all records in this cursor and delete them. cursorReq.onsuccess = () => { const cursor = cursorReq.result; @@ -289,8 +274,7 @@ export default class SymbolStoreDB { ): void { // Get a cursor that walks the records from oldest to newest // lastUsedDate. - const lastUsedDateIndex: IDBIndex = - store.index('lastUsedDate'); + const lastUsedDateIndex = store.index('lastUsedDate'); const cursorReq = lastUsedDateIndex.openCursor(); let deletedCount = 0; cursorReq.onsuccess = () => { @@ -311,7 +295,7 @@ export default class SymbolStoreDB { }; } - _count(store: SymbolStore, callback: (number) => void): void { + _count(store: SymbolStore, callback: (count: number) => void): void { const countReq = store.count(); countReq.onsuccess = () => callback(countReq.result); } diff --git a/src/profile-logic/symbol-store.js b/src/profile-logic/symbol-store.ts similarity index 92% rename from src/profile-logic/symbol-store.js rename to src/profile-logic/symbol-store.ts index e1405239e5..31bf27ab54 100644 --- a/src/profile-logic/symbol-store.js +++ b/src/profile-logic/symbol-store.ts @@ -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/. */ -// @flow import SymbolStoreDB from './symbol-store-db'; import { SymbolsNotFoundError } from './errors'; @@ -13,54 +12,54 @@ import { ensureExists } from '../utils/flow'; import { bisectionRight } from 'firefox-profiler/utils/bisect'; export type LibSymbolicationRequest = { - lib: RequestedLib, - addresses: Set, + lib: RequestedLib; + addresses: Set; }; export type LibSymbolicationResponse = - | {| - type: 'SUCCESS', - lib: RequestedLib, - results: Map, - |} - | {| - type: 'ERROR', - request: LibSymbolicationRequest, - error: Error, - |}; - -export type AddressResult = {| + | { + type: 'SUCCESS'; + lib: RequestedLib; + results: Map; + } + | { + type: 'ERROR'; + request: LibSymbolicationRequest; + error: Error; + }; + +export type AddressResult = { // The name of the outer function that this address belongs to. - name: string, + name: string; // The address (relative to the library) where the function that // contains this address starts, i.e. the address of the function symbol. - symbolAddress: number, + symbolAddress: number; // The path of the file that contains the source code of the outer function that contains // this address. // Optional because the information may not be known by the symbolication source, or because // the symbolication method does not expose it. - file?: string, + file?: string; // The line number that contains the source code of the outer function that generated the // instructions at the address, optional. // Optional because the information may not be known by the symbolication source, or because // the symbolication method does not expose it. - line?: number, + line?: number; // An optional inline callstack, ordered from inside to outside. // addressResult.name calls addressResult.inlines[inlines.length - 1].function, which // calls addressResult.inlines[inlines.length - 2].function etc. - inlines?: Array, + inlines?: Array; // An optional size, in bytes, of the machine code of the outer function that // this address belongs to. - functionSize?: number, -|}; + functionSize?: number; +}; -export type AddressInlineFrame = {| - name: string, - file?: string, - line?: number, -|}; +export type AddressInlineFrame = { + name: string; + file?: string; + line?: number; +}; -interface SymbolProvider { +export interface SymbolProvider { // Cheap, should be called first. requestSymbolsFromServer( requests: LibSymbolicationRequest[] @@ -79,8 +78,8 @@ interface SymbolProvider { export interface AbstractSymbolStore { getSymbols( requests: LibSymbolicationRequest[], - successCb: (RequestedLib, Map) => void, - errorCb: (LibSymbolicationRequest, Error) => void, + successCb: (lib: RequestedLib, results: Map) => void, + errorCb: (request: LibSymbolicationRequest, error: Error) => void, ignoreCache?: boolean ): Promise; } @@ -95,7 +94,7 @@ const MAX_JOB_COUNT_PER_CHUNK = 10; export function readSymbolsFromSymbolTable( addresses: Set, symbolTable: SymbolTableAsTuple, - demangleCallback: (string) => string + demangleCallback: (name: string) => string ): Map { const [symbolTableAddrs, symbolTableIndex, symbolTableBuffer] = symbolTable; const addressArray = Uint32Array.from(addresses); @@ -123,7 +122,7 @@ export function readSymbolsFromSymbolTable( // bisection() returns the insertion index, which is one position after // the index that we consider the match, so we need to subtract 1 from the // result. - const symbolIndex = + const symbolIndex: number = bisectionRight(symbolTableAddrs, address, currentSymbolIndex) - 1; if (symbolIndex >= 0) { @@ -171,9 +170,9 @@ function _partitionIntoChunksOfMaxValue( array: T[], maxValue: number, maxChunkLength: number, - computeValue: (T) => number + computeValue: (element: T) => number ): T[][] { - const chunks = []; + const chunks: Array<{ value: number; elements: T[] }> = []; for (const element of array) { const elementValue = computeValue(element); // Find an existing chunk that still has enough "value space" left to @@ -194,7 +193,7 @@ function _partitionIntoChunksOfMaxValue( return chunks.map(({ elements }) => elements); } -type DemangleFunction = (string) => string; +type DemangleFunction = (name: string) => string; /** * This function returns a function that can demangle function name using a @@ -276,8 +275,8 @@ export class SymbolStore { */ async getSymbols( requests: LibSymbolicationRequest[], - successCb: (RequestedLib, Map) => void, - errorCb: (LibSymbolicationRequest, Error) => void, + successCb: (lib: RequestedLib, results: Map) => void, + errorCb: (request: LibSymbolicationRequest, error: Error) => void, ignoreCache: boolean = false ): Promise { // For each library, we have three options to obtain symbol information for @@ -306,8 +305,12 @@ export class SymbolStore { // First, try option 1 for all libraries and partition them by whether it // was successful. - const requestsForNonCachedLibs = []; - const resultsForCachedLibs = []; + const requestsForNonCachedLibs: LibSymbolicationRequest[] = []; + const resultsForCachedLibs: Array<{ + lib: RequestedLib; + addresses: Set; + symbolTable: SymbolTableAsTuple; + }> = []; if (ignoreCache) { requestsForNonCachedLibs.push(...requests); } else { @@ -360,7 +363,7 @@ export class SymbolStore { // Kick off the requests to the symbolication API, and create a list of // promises, one promise per chunk. const symbolicationApiRequestsAndResponsesPerChunk: Array< - [LibSymbolicationRequest[], Promise], + [LibSymbolicationRequest[], Promise] > = chunks.map((requests) => [ requests, this._symbolProvider.requestSymbolsFromServer(requests), @@ -390,7 +393,7 @@ export class SymbolStore { // Store any errors encountered along the way in this map. // We will report them if all avenues fail. const errorMap: Map = new Map( - requests.map((r) => [r, []]) + requests.map((r): [LibSymbolicationRequest, Error[]] => [r, []]) ); // Process the results of option 2: The response from the Mozilla symbolication APi. @@ -446,7 +449,7 @@ export class SymbolStore { requests: LibSymbolicationRequest[], responsesPromise: Promise, errorMap: Map, - successCb: (RequestedLib, Map) => void + successCb: (lib: RequestedLib, results: Map) => void ): Promise { try { const responses: LibSymbolicationResponse[] = await responsesPromise; @@ -490,8 +493,8 @@ export class SymbolStore { requests: LibSymbolicationRequest[], errorMap: Map, demangleCallback: DemangleFunction, - successCb: (RequestedLib, Map) => void, - errorCb: (LibSymbolicationRequest, Error) => void + successCb: (lib: RequestedLib, results: Map) => void, + errorCb: (request: LibSymbolicationRequest, error: Error) => void ): Promise { for (const request of requests) { const { lib, addresses } = request; diff --git a/src/profile-logic/symbolication.js b/src/profile-logic/symbolication.ts similarity index 98% rename from src/profile-logic/symbolication.js rename to src/profile-logic/symbolication.ts index 3aec6e1c89..830cfab668 100644 --- a/src/profile-logic/symbolication.js +++ b/src/profile-logic/symbolication.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { resourceTypes, getEmptyRawStackTable, @@ -202,27 +200,27 @@ export type SymbolicationStepCallback = ( symbolicationStepInfo: SymbolicationStepInfo ) => void; -type ThreadLibSymbolicationInfo = {| +type ThreadLibSymbolicationInfo = { // The resourceIndex for this lib in this thread. - resourceIndex: IndexIntoResourceTable, + resourceIndex: IndexIntoResourceTable; // The libIndex for this lib in this thread. - libIndex: IndexIntoLibs, + libIndex: IndexIntoLibs; // The set of funcs for this lib in this thread. - allFuncsForThisLib: Set, + allFuncsForThisLib: Set; // The set of native symbols for this lib in this thread. - allNativeSymbolsForThisLib: Set, + allNativeSymbolsForThisLib: Set; // All frames for this lib in this thread. - allFramesForThisLib: Array, + allFramesForThisLib: Array; // All addresses for frames for this lib in this thread, as lib-relative offsets. - frameAddresses: Array
, -|}; + frameAddresses: Array
; +}; // This type exists because we symbolicate the profile in steps in order to // provide a profile to the user faster. This type represents a single step. -export type SymbolicationStepInfo = {| - threadLibSymbolicationInfo: ThreadLibSymbolicationInfo, - resultsForLib: Map, -|}; +export type SymbolicationStepInfo = { + threadLibSymbolicationInfo: ThreadLibSymbolicationInfo; + resultsForLib: Map; +}; export type FuncToFuncsMap = Map; @@ -423,7 +421,7 @@ function _computeThreadWithAddedExpansionStacks( shouldStacksWithThisOldFrameBeRemoved: Uint8Array, frameIndexToInlineExpansionFrames: Map< IndexIntoFrameTable, - IndexIntoFrameTable[], + IndexIntoFrameTable[] > ): RawThread { if (frameIndexToInlineExpansionFrames.size === 0) { @@ -479,7 +477,7 @@ export function applySymbolicationSteps( oldThread: RawThread, shared: RawProfileSharedData, symbolicationSteps: SymbolicationStepInfo[] -): { thread: RawThread, oldFuncToNewFuncsMap: FuncToFuncsMap } { +): { thread: RawThread; oldFuncToNewFuncsMap: FuncToFuncsMap } { const oldFuncToNewFuncsMap = new Map(); const frameCount = oldThread.frameTable.length; const shouldStacksWithThisFrameBeRemoved = new Uint8Array(frameCount); @@ -537,7 +535,7 @@ function _partiallyApplySymbolicationStep( shouldStacksWithThisFrameBeRemoved: Uint8Array, frameIndexToInlineExpansionFrames: Map< IndexIntoFrameTable, - IndexIntoFrameTable[], + IndexIntoFrameTable[] > ): RawThread { const { stringArray } = shared; @@ -564,7 +562,7 @@ function _partiallyApplySymbolicationStep( const symbolAddressToInfoMap: Map = new Map(); const symbolAddressToCanonicalSymbolIndexMap: Map< Address, - IndexIntoNativeSymbolTable, + IndexIntoNativeSymbolTable > = new Map(); // If this profile was symbolicated before, we may have frames for inlined functions @@ -915,7 +913,7 @@ export function applyFuncSubstitutionToCallPath( oldFuncToNewFuncsMap: FuncToFuncsMap, path: CallNodePath ): CallNodePath { - return path.reduce((accum, oldFunc) => { + return path.reduce((accum, oldFunc) => { const newFuncs = oldFuncToNewFuncsMap.get(oldFunc); return newFuncs === undefined ? [...accum, oldFunc] diff --git a/src/profile-logic/tracks.js b/src/profile-logic/tracks.ts similarity index 97% rename from src/profile-logic/tracks.js rename to src/profile-logic/tracks.ts index 2641fa0849..b545167d7c 100644 --- a/src/profile-logic/tracks.js +++ b/src/profile-logic/tracks.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import type { ScreenshotPayload, Profile, @@ -28,17 +26,17 @@ import { StringTable } from '../utils/string-table'; import { splitSearchString, stringsToRegExp } from '../utils/string'; import { ensureExists, assertExhaustiveCheck } from '../utils/flow'; -export type TracksWithOrder = {| - +globalTracks: GlobalTrack[], - +globalTrackOrder: TrackIndex[], - +localTracksByPid: Map, - +localTrackOrderByPid: Map, -|}; +export type TracksWithOrder = { + readonly globalTracks: GlobalTrack[]; + readonly globalTrackOrder: TrackIndex[]; + readonly localTracksByPid: Map; + readonly localTrackOrderByPid: Map; +}; -export type HiddenTracks = {| - +hiddenGlobalTracks: Set, - +hiddenLocalTracksByPid: Map>, -|}; +export type HiddenTracks = { + readonly hiddenGlobalTracks: Set; + readonly hiddenLocalTracksByPid: Map>; +}; /** * This file collects all the logic that goes into validating URL-encoded view options. @@ -97,7 +95,10 @@ const GLOBAL_TRACK_DISPLAY_ORDER = { process: 4, }; -function _getDefaultLocalTrackOrder(tracks: LocalTrack[], profile: ?Profile) { +function _getDefaultLocalTrackOrder( + tracks: LocalTrack[], + profile: Profile | null +) { const trackOrder = tracks.map((_, index) => index); const naturalSort = new Intl.Collator('en-US', { numeric: true }); // In place sort! @@ -236,7 +237,7 @@ export function initializeLocalTrackOrderByPid( // If viewing an old profile URL, there were not tracks, only thread indexes. Turn // the legacy ordering into track ordering. legacyThreadOrder: ThreadIndex[] | null, - profile: ?Profile + profile: Profile | null ): Map { const trackOrderByPid = new Map(); @@ -313,7 +314,7 @@ export function computeLocalTracksByPid( // Create a new set of available pids, so we can filter out the local tracks // if their globalTracks are also filtered out by the tab selector. - const availablePids = new Set(); + const availablePids = new Set(); for (const globalTrack of availableGlobalTracks) { if (globalTrack.type === 'process') { availablePids.add(globalTrack.pid); @@ -374,7 +375,7 @@ export function computeLocalTracksByPid( const markerSchemaName = markerData ? markerData.type : null; if (markerData && markerSchemaName) { const mapEntry = markerTracksBySchemaName.get(markerSchemaName); - if (mapEntry && mapEntry.keys.every((k) => k in markerData)) { + if (mapEntry && mapEntry.keys.every((k: string) => k in markerData)) { mapEntry.markerNames.add(markerNameIndex); } } @@ -431,7 +432,7 @@ export function computeLocalTracksByPid( for (const localTracks of localTracksByPid.values()) { // In place sort! localTracks.sort( - (a, b) => + (a: LocalTrack, b: LocalTrack) => LOCAL_TRACK_INDEX_ORDER[a.type] - LOCAL_TRACK_INDEX_ORDER[b.type] ); } @@ -514,9 +515,9 @@ export function computeGlobalTracks( // the internals of this function, otherwise each GlobalTrack usage would need // to check that it's a process type. type ProcessTrack = { - type: 'process', - pid: Pid, - mainThreadIndex: number | null, + type: 'process'; + pid: Pid; + mainThreadIndex: number | null; }; const globalTracksByPid: Map = new Map(); let globalTracks: GlobalTrack[] = []; @@ -558,9 +559,9 @@ export function computeGlobalTracks( // This is a thread without a known main thread. Create a global process // track for it, but don't add a main thread for it. const globalTrack = { - type: 'process', + type: 'process' as const, pid: pid, - mainThreadIndex: null, + mainThreadIndex: null as ThreadIndex | null, }; globalTracks.push(globalTrack); globalTracksByPid.set(pid, globalTrack); @@ -574,7 +575,7 @@ export function computeGlobalTracks( if (markers.name[markerIndex] === screenshotNameIndex) { // Coerce the payload to a screenshot one. Don't do a runtime check that // this is correct. - const data: ScreenshotPayload = (markers.data[markerIndex]: any); + const data = markers.data[markerIndex] as ScreenshotPayload; ids.add(data.windowID); } } @@ -1146,7 +1147,7 @@ export function getLocalTrackName( function computeAllTrackThreads( tracksWithOrder: TracksWithOrder ): Set { - const allTrackThreads = new Set(); + const allTrackThreads = new Set(); for (const globalTrack of tracksWithOrder.globalTracks) { switch (globalTrack.type) { @@ -1241,7 +1242,10 @@ export function computeDefaultVisibleThreads( }) ); const thresholdSampleScore = highestSampleScore * IDLE_THRESHOLD_FRACTION; - const tryToHideList = []; + const tryToHideList: Array<{ + threadIndex: ThreadIndex; + score: ThreadActivityScore; + }> = []; let finalList = top15.filter((activityScore) => { const { score } = activityScore; if (score.isInParentProcess && !includeParentProcessThreads) { @@ -1273,25 +1277,25 @@ export function computeDefaultVisibleThreads( return new Set(finalList.map(({ threadIndex }) => threadIndex)); } -export type ThreadActivityScore = {| +export type ThreadActivityScore = { // Whether this thread is one of the essential threads that // should always be kept (unless there's too many of them). - isEssentialFirefoxThread: boolean, + isEssentialFirefoxThread: boolean; // Whether this thread belongs to the parent process. We do not want to show // them by default if the tab selector is used. - isInParentProcess: boolean, + isInParentProcess: boolean; // Whether this thread should be kept even if it looks very idle, // as long as there's a single sample with non-zero activity. - isInterestingEvenWithMinimalActivity: boolean, + isInterestingEvenWithMinimalActivity: boolean; // The accumulated CPU delta for the entire thread. // If the thread does not have CPU delta information, we compute // a "CPU-delta-like" number based on the number of samples which // are in a non-idle category. - sampleScore: number, + sampleScore: number; // Like sampleScore, but with a boost factor applied if this thread // is "interesting even with minimal activity". - boostedSampleScore: number, -|}; + boostedSampleScore: number; +}; // Also called "padenot factor". const AUDIO_THREAD_SAMPLE_SCORE_BOOST_FACTOR = 40; @@ -1361,7 +1365,7 @@ function _computeThreadSampleScore( if (meta.sampleUnits && samples.threadCPUDelta) { // Sum up all CPU deltas in this thread, to compute a total // CPU time for this thread (or a total CPU cycle count). - return samples.threadCPUDelta.reduce( + return samples.threadCPUDelta.reduce( (accum, delta) => accum + (delta ?? 0), 0 ); @@ -1422,7 +1426,7 @@ export function getSearchFilteredGlobalTracks( return null; } - const searchFilteredGlobalTracks = new Set(); + const searchFilteredGlobalTracks = new Set(); for (let trackIndex = 0; trackIndex < tracks.length; trackIndex++) { const globalTrack = tracks[trackIndex]; @@ -1518,7 +1522,7 @@ export function getSearchFilteredLocalTracksByPid( const searchFilteredLocalTracksByPid = new Map(); for (const [pid, tracks] of localTracksByPid) { - const searchFilteredLocalTracks = new Set(); + const searchFilteredLocalTracks = new Set(); const localTrackNames = localTrackNamesByPid.get(pid); if (localTrackNames === undefined) { throw new Error('Failed to get the local track names'); @@ -1612,7 +1616,7 @@ export function getTypeFilteredGlobalTracks( return null; } - const typeFilteredGlobalTracks = new Set(); + const typeFilteredGlobalTracks = new Set(); for (let trackIndex = 0; trackIndex < tracks.length; trackIndex++) { const globalTrack = tracks[trackIndex]; @@ -1638,7 +1642,7 @@ export function getTypeFilteredLocalTracksByPid( const typeFilteredLocalTracksByPid = new Map(); for (const [pid, tracks] of localTracksByPid) { - const typeFilteredLocalTracks = new Set(); + const typeFilteredLocalTracks = new Set(); for (let trackIndex = 0; trackIndex < tracks.length; trackIndex++) { const localTrack = tracks[trackIndex]; diff --git a/src/profile-logic/transforms.js b/src/profile-logic/transforms.ts similarity index 96% rename from src/profile-logic/transforms.js rename to src/profile-logic/transforms.ts index 70b77424f4..e03d0a88a3 100644 --- a/src/profile-logic/transforms.js +++ b/src/profile-logic/transforms.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { encodeUintArrayForUrlComponent, @@ -56,22 +55,27 @@ import type { StringTable } from 'firefox-profiler/utils/string-table'; * to profile data. */ +const TRANSFORM_OBJ: { [key in TransformType]: true } = { + 'focus-subtree': true, + 'focus-function': true, + 'merge-call-node': true, + 'merge-function': true, + 'drop-function': true, + 'collapse-resource': true, + 'collapse-direct-recursion': true, + 'collapse-recursion': true, + 'collapse-function-subtree': true, + 'focus-category': true, + 'filter-samples': true, +}; +export const ALL_TRANSFORM_TYPES: TransformType[] = Object.keys( + TRANSFORM_OBJ +) as TransformType[]; + // Create mappings from a transform name, to a url-friendly short name. -const TRANSFORM_TO_SHORT_KEY: { [TransformType]: string } = {}; -const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {}; -[ - 'focus-subtree', - 'focus-function', - 'focus-category', - 'merge-call-node', - 'merge-function', - 'drop-function', - 'collapse-resource', - 'collapse-direct-recursion', - 'collapse-recursion', - 'collapse-function-subtree', - 'filter-samples', -].forEach((transform: TransformType) => { +const TRANSFORM_TO_SHORT_KEY: Partial<{ [TT in TransformType]: string }> = {}; +const SHORT_KEY_TO_TRANSFORM: { [TT: string]: TransformType } = {}; +ALL_TRANSFORM_TYPES.forEach((transform: TransformType) => { // This is kind of an awkward switch, but it ensures we've exhaustively checked that // we have a mapping for every transform. let shortKey; @@ -134,7 +138,7 @@ export function parseTransforms(transformString: string): TransformStack { if (!transformString) { return []; } - const transforms = []; + const transforms: Transform[] = []; transformString.split('~').forEach((s) => { const tuple = s.split('-'); @@ -363,7 +367,7 @@ export function stringifyTransforms(transformStack: TransformStack): string { transform.implementation, encodeUintArrayForUrlComponent(transform.callNodePath), ].join('-'); - if (transform.inverted) { + if ('inverted' in transform && transform.inverted) { string += '-i'; } return string; @@ -379,10 +383,10 @@ export function stringifyTransforms(transformStack: TransformStack): string { .join('~'); } -export type TransformLabeL10nIds = {| - +l10nId: string, - +item: string, -|}; +export type TransformLabeL10nIds = { + readonly l10nId: string; + readonly item: string; +}; /** * Gets all applied transforms and returns their labels as l10n Ids with the @@ -716,7 +720,7 @@ export function mergeCallNode( const depthAtCallNodePathLeaf = callNodePath.length - 1; const oldStackToNewStack: Map< IndexIntoStackTable | null, - IndexIntoStackTable | null, + IndexIntoStackTable | null > = new Map(); // A root stack's prefix will be null. Maintain that relationship from old to new // stacks by mapping from null to null. @@ -735,11 +739,11 @@ export function mergeCallNode( const funcIndex = frameTable.func[frameIndex]; const doesPrefixMatch = prefix === null ? true : stackMatches[prefix]; - const prefixDepth = prefix === null ? -1 : stackDepths[prefix]; + const prefixDepth: number = prefix === null ? -1 : stackDepths[prefix]; const currentFuncOnPath = callNodePath[prefixDepth + 1]; let doMerge = false; - let stackDepth = prefixDepth; + let stackDepth: number = prefixDepth; let doesMatchCallNodePath; if (doesPrefixMatch && stackDepth < depthAtCallNodePathLeaf) { // This stack's prefixes were in our CallNodePath. @@ -902,11 +906,11 @@ export function collapseResource( const newStackTable = getEmptyStackTable(); const oldStackToNewStack: Map< IndexIntoStackTable | null, - IndexIntoStackTable | null, + IndexIntoStackTable | null > = new Map(); const prefixStackToCollapsedStack: Map< IndexIntoStackTable | null, // prefix stack index - IndexIntoStackTable | null, // collapsed stack index + IndexIntoStackTable | null // collapsed stack index > = new Map(); const collapsedStacks: Set = new Set(); const funcMatchesImplementation = FUNC_MATCHES[implementation]; @@ -1079,7 +1083,7 @@ export function collapseDirectRecursion( // E.g. B3 -> A1 in the example. const recursionChainPrefixForStack = new Map< IndexIntoStackTable, - IndexIntoStackTable | null, + IndexIntoStackTable | null >(); const funcMatchesImplementation = FUNC_MATCHES[implementation]; const newStackTablePrefixColumn = stackTable.prefix.slice(); @@ -1175,7 +1179,7 @@ export function collapseRecursion( // B1's prefix A1. const funcToCollapseSubtreePrefixForStack = new Map< IndexIntoStackTable, - IndexIntoStackTable | null, + IndexIntoStackTable | null >(); const newStackTablePrefixColumn = stackTable.prefix.slice(); @@ -1292,7 +1296,7 @@ export function focusSubtree( const funcMatchesImplementation = FUNC_MATCHES[implementation]; const oldStackToNewStack: Map< IndexIntoStackTable | null, - IndexIntoStackTable | null, + IndexIntoStackTable | null > = new Map(); // A root stack's prefix will be null. Maintain that relationship from old to new // stacks by mapping from null to null. @@ -1358,9 +1362,13 @@ export function focusInvertedSubtree( const postfixDepth = postfixCallNodePath.length; const { stackTable, frameTable } = thread; const funcMatchesImplementation = FUNC_MATCHES[implementation]; - function convertStack(leaf) { + function convertStack(leaf: IndexIntoStackTable | null) { let matchesUpToDepth = 0; // counted from the leaf - for (let stack = leaf; stack !== null; stack = stackTable.prefix[stack]) { + for ( + let stack: number | null = leaf; + stack !== null; + stack = stackTable.prefix[stack] + ) { const frame = stackTable.frame[stack]; const funcIndex = frameTable.func[frame]; if (funcIndex === postfixCallNodePath[matchesUpToDepth]) { @@ -1437,7 +1445,7 @@ export function focusCategory(thread: Thread, category: IndexIntoCategoryList) { const { stackTable } = thread; const oldStackToNewStack: Map< IndexIntoStackTable | null, - IndexIntoStackTable | null, + IndexIntoStackTable | null > = new Map(); oldStackToNewStack.set(null, null); @@ -1490,7 +1498,7 @@ export function restoreAllFunctionsInCallNodePath( // For every stackIndex, matchesUpToDepth[stackIndex] will be: // - null if stackIndex does not match the callNodePath // - if stackIndex matches callNodePath up to (and including) callNodePath[] - const matchesUpToDepth = []; + const matchesUpToDepth: Array = []; let tipStackIndex = null; // Try to find the tip most stackIndex in the CallNodePath, but skip anything // that doesn't match the previous implementation filter. @@ -1498,13 +1506,14 @@ export function restoreAllFunctionsInCallNodePath( const prefix = stackTable.prefix[stackIndex]; const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; - const prefixPathDepth = prefix === null ? -1 : matchesUpToDepth[prefix]; + const prefixPathDepth: number | null = + prefix === null ? -1 : matchesUpToDepth[prefix]; if (prefixPathDepth === null) { continue; } - const pathDepth = prefixPathDepth + 1; + const pathDepth: number = prefixPathDepth + 1; const nextPathFuncIndex = callNodePath[pathDepth]; if (nextPathFuncIndex === funcIndex) { // This function is a match. @@ -1529,7 +1538,7 @@ export function restoreAllFunctionsInCallNodePath( } const newCallNodePath = []; for ( - let stackIndex = tipStackIndex; + let stackIndex: IndexIntoStackTable | null = tipStackIndex; stackIndex !== null; stackIndex = stackTable.prefix[stackIndex] ) { @@ -1564,13 +1573,13 @@ export function filterCallNodePathByImplementation( } // User-facing properties about a stack frame. -export type BacktraceItem = {| +export type BacktraceItem = { // The function name of the stack frame. - funcName: string, + funcName: string; // The frame category of the stack frame. - category: IndexIntoCategoryList, + category: IndexIntoCategoryList; // Whether this frame is a label frame. - isFrameLabel: boolean, + isFrameLabel: boolean; // A string which is usually displayed after the function name, and which // describes, in some way, where this function or frame came from. // If known, this contains the file name of the function, and the line and @@ -1579,8 +1588,8 @@ export type BacktraceItem = {| // If the source file name is not known, this might be the name of a native // library instead. // May also be empty. - origin: string, -|}; + origin: string; +}; /** * Convert the stack into an array of "backtrace items" for each stack frame. @@ -1597,7 +1606,7 @@ export function getBacktraceItemsForStack( const { stackTable, frameTable } = thread; const unfilteredPath = []; for ( - let stackIndex = stack; + let stackIndex: IndexIntoStackTable | null = stack; stackIndex !== null; stackIndex = stackTable.prefix[stackIndex] ) { @@ -1683,7 +1692,7 @@ export function funcHasRecursiveCall( } function _findRangesByMarkerFilter( - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], markerSchemaByName: MarkerSchemaByName, stringTable: StringTable, @@ -1721,7 +1730,7 @@ function _findRangesByMarkerFilter( */ export function filterSamples( thread: Thread, - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], markerSchemaByName: MarkerSchemaByName, categoryList: CategoryList, @@ -1800,7 +1809,7 @@ export function applyTransform( thread: Thread, transform: Transform, defaultCategory: IndexIntoCategoryList, - getMarker: (MarkerIndex) => Marker, + getMarker: (markerIndex: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], markerSchemaByName: MarkerSchemaByName, categoryList: CategoryList diff --git a/src/profile-logic/zip-files.js b/src/profile-logic/zip-files.ts similarity index 93% rename from src/profile-logic/zip-files.js rename to src/profile-logic/zip-files.ts index 496a19b10d..e9a50abd6a 100644 --- a/src/profile-logic/zip-files.js +++ b/src/profile-logic/zip-files.ts @@ -1,10 +1,10 @@ /* 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/. */ -// @flow import { ensureIsValidTabSlug, objectEntries } from '../utils/flow'; -import type JSZip, { JSZipFile } from 'jszip'; +import type JSZip from 'jszip'; +import type { JSZipObject } from 'jszip'; export type IndexIntoZipFileTable = number; /** @@ -12,20 +12,20 @@ export type IndexIntoZipFileTable = number; * maps it into a hierarchical table that can be used by the TreeView component to * generate a file tree. */ -export type ZipFileTable = {| - prefix: Array, - path: string[], // e.g. "profile_tresize/tresize/cycle_0.profile" - partName: string[], // e.g. "cycle_0.profile" or "tresize" - file: Array, - depth: number[], - length: number, -|}; - -export type ZipDisplayData = {| - +name: string, - +url: null | string, - +zipTableIndex: IndexIntoZipFileTable, -|}; +export type ZipFileTable = { + prefix: Array; + path: string[]; // e.g. "profile_tresize/tresize/cycle_0.profile" + partName: string[]; // e.g. "cycle_0.profile" or "tresize" + file: Array; + depth: number[]; + length: number; +}; + +export type ZipDisplayData = { + readonly name: string; + readonly url: null | string; + readonly zipTableIndex: IndexIntoZipFileTable; +}; export function createZipTable(zipEntries: JSZip): ZipFileTable { const fullPaths = objectEntries(zipEntries.files) @@ -157,7 +157,7 @@ export class ZipFileTree { Array.from({ length: this._zipFileTable.length }, (_, i) => i) ); } - const result = new Set(); + const result: Set = new Set(); for (const child of this.getChildren(zipTableIndex)) { result.add(child); for (const descendant of this.getAllDescendants(child)) { diff --git a/src/types/actions.js b/src/types/actions.js deleted file mode 100644 index d994991c05..0000000000 --- a/src/types/actions.js +++ /dev/null @@ -1,672 +0,0 @@ -/* 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/. */ - -// @flow -import { ReactLocalization } from '@fluent/react'; -import type JSZip from 'jszip'; -import type { - Profile, - RawThread, - ThreadIndex, - Pid, - TabID, - IndexIntoCategoryList, - IndexIntoLibs, - PageList, -} from './profile'; -import type { - Thread, - CallNodePath, - GlobalTrack, - LocalTrack, - TrackIndex, - MarkerIndex, - ThreadsKey, - NativeSymbolInfo, -} from './profile-derived'; -import type { FuncToFuncsMap } from '../profile-logic/symbolication'; -import type { TemporaryError } from '../utils/errors'; -import type { Transform, TransformStacksPerThread } from './transforms'; -import type { IndexIntoZipFileTable } from '../profile-logic/zip-files'; -import type { CallNodeInfo } from '../profile-logic/call-node-info'; -import type { TabSlug } from '../app-logic/tabs-handling'; -import type { - UrlState, - UploadState, - State, - UploadedProfileInformation, - SourceCodeLoadingError, - ApiQueryError, - TableViewOptions, - DecodedInstruction, -} from './state'; -import type { CssPixels, StartEndRange, Milliseconds } from './units'; -import type { BrowserConnectionStatus } from '../app-logic/browser-connection'; - -export type DataSource = - | 'none' - // This is used when the profile is loaded from a local file, via drag and - // drop or via a file input. Reloading a URL with this data source cannot - // work automatically because the file would need to be picked again. - | 'from-file' - // This datasource is used to fetch a profile from Firefox via a frame script. - // This is the first entry-point when a profile is captured in the browser. - | 'from-browser' - // Websites can inject profiles via a postMessage call: - // postMessage({ name: "inject-profile", profile: Profile }) - | 'from-post-message' - // This is used for profiles that have been shared / uploaded to the Profiler - // Server. - | 'public' - // This is used after a public profile is deleted / unpublished. - // In the future, we may want to use the "local" data source for this, and - // remove "unpublished". - | 'unpublished' - // Reserved for future use. Once implemented, it would work as follows: - // Whenever a non-public profile is loaded into the profiler, e.g. via - // from-browser or from-file, we want to store it in a local database - // automatically, generate an ID for it, and redirect the URL to /local/{id}/. - // This would make it so that the page can be reloaded, or restored after a - // browser restart, without losing the profile. - | 'local' - // This is used to load profiles from a URL. It is used in two scenarios: - // - For public profiles which are hosted on a different server than the - // regular profiler server, for example for profiles that are captured - // automatically in Firefox CI. - // - With a localhost URL, in order to import profiles from a locally running - // script. - | 'from-url' - // This is used when comparing two profiles. The displayed profile is a - // comparison profile created from two input profiles. - | 'compare' - // This is a page which displays a list of profiles that were uploaded from - // this browser, and allows deleting / unpublishing those profiles. - | 'uploaded-recordings'; - -export type TimelineType = 'stack' | 'category' | 'cpu-category'; -export type PreviewSelection = - | {| +hasSelection: false, +isModifying: false |} - | {| - +hasSelection: true, - +isModifying: boolean, - +selectionStart: number, - +selectionEnd: number, - +draggingStart?: boolean, - +draggingEnd?: boolean, - |}; - -/** - * The counts for how many tracks are hidden in the timeline. - */ -export type HiddenTrackCount = {| - +hidden: number, - +total: number, -|}; - -/** - * A TrackReference uniquely identifies a track. - * Note that TrackIndexes aren't globally unique: they're unique among global - * tracks, and they're unique among local tracks for a specific Pid. - */ -export type GlobalTrackReference = {| - +type: 'global', - +trackIndex: TrackIndex, -|}; -export type LocalTrackReference = {| - +type: 'local', - +trackIndex: TrackIndex, - +pid: Pid, -|}; - -export type TrackReference = GlobalTrackReference | LocalTrackReference; - -export type LastNonShiftClickInformation = {| - clickedTrack: TrackReference, - selection: Set, -|}; - -export type RequestedLib = {| - +debugName: string, - +breakpadId: string, -|}; -export type ImplementationFilter = 'combined' | 'js' | 'cpp'; -// Change the strategy for computing the summarizing information for the call tree. -export type CallTreeSummaryStrategy = - | 'timing' - | 'js-allocations' - | 'native-retained-allocations' - | 'native-allocations' - | 'native-deallocations-memory' - | 'native-deallocations-sites'; - -/** - * This type determines what kind of information gets sanitized from published profiles. - */ -export type CheckedSharingOptions = {| - // The following values are for including more information in a sanitized profile. - includeHiddenThreads: boolean, - includeAllTabs: boolean, - includeFullTimeRange: boolean, - includeScreenshots: boolean, - includeUrls: boolean, - includeExtension: boolean, - includePreferenceValues: boolean, - includePrivateBrowsingData: boolean, -|}; - -export type Localization = ReactLocalization; - -// This type is used when selecting tracks in the timeline. Ctrl and Meta are -// stored in the same property to accommodate all OSes. -export type KeyboardModifiers = {| ctrlOrMeta: boolean, shift: boolean |}; - -/** - * This type gives some context about the action leading to a selection. - */ -export type SelectionContext = {| - // This is the source for this selection: is it a keyboard or a pointer event, - // or is it the result of some automatic selection. - +source: 'keyboard' | 'pointer' | 'auto', -|}; - -type ProfileAction = - | {| - +type: 'ROUTE_NOT_FOUND', - +url: string, - |} - | {| - +type: 'ASSIGN_TASK_TRACER_NAMES', - +addressIndices: number[], - +symbolNames: string[], - |} - | {| - +type: 'CHANGE_SELECTED_CALL_NODE', - +isInverted: boolean, - +threadsKey: ThreadsKey, - +selectedCallNodePath: CallNodePath, - +optionalExpandedToCallNodePath: ?CallNodePath, - +context: SelectionContext, - |} - | {| - +type: 'UPDATE_TRACK_THREAD_HEIGHT', - +height: CssPixels, - +threadsKey: ThreadsKey, - |} - | {| - +type: 'CHANGE_RIGHT_CLICKED_CALL_NODE', - +threadsKey: ThreadsKey, - +callNodePath: CallNodePath | null, - |} - | {| - +type: 'FOCUS_CALL_TREE', - |} - | {| - +type: 'CHANGE_EXPANDED_CALL_NODES', - +threadsKey: ThreadsKey, - +isInverted: boolean, - +expandedCallNodePaths: Array, - |} - | {| - +type: 'CHANGE_SELECTED_MARKER', - +threadsKey: ThreadsKey, - +selectedMarker: MarkerIndex | null, - +context: SelectionContext, - |} - | {| - +type: 'CHANGE_SELECTED_NETWORK_MARKER', - +threadsKey: ThreadsKey, - +selectedNetworkMarker: MarkerIndex | null, - +context: SelectionContext, - |} - | {| - +type: 'CHANGE_RIGHT_CLICKED_MARKER', - +threadsKey: ThreadsKey, - +markerIndex: MarkerIndex | null, - |} - | {| - +type: 'CHANGE_HOVERED_MARKER', - +threadsKey: ThreadsKey, - +markerIndex: MarkerIndex | null, - |} - | {| - +type: 'UPDATE_PREVIEW_SELECTION', - +previewSelection: PreviewSelection, - |} - | {| - +type: 'CHANGE_SELECTED_ZIP_FILE', - +selectedZipFileIndex: IndexIntoZipFileTable | null, - |} - | {| - +type: 'CHANGE_EXPANDED_ZIP_FILES', - +expandedZipFileIndexes: Array, - |} - | {| - +type: 'CHANGE_GLOBAL_TRACK_ORDER', - +globalTrackOrder: TrackIndex[], - |} - | {| - +type: 'HIDE_GLOBAL_TRACK', - +trackIndex: TrackIndex, - +pid: Pid | null, - +selectedThreadIndexes: Set, - |} - | {| - +type: 'SHOW_ALL_TRACKS', - |} - | {| - +type: 'SHOW_PROVIDED_TRACKS', - +globalTracksToShow: Set, - +localTracksByPidToShow: Map>, - |} - | {| - +type: 'HIDE_PROVIDED_TRACKS', - +globalTracksToHide: Set, - +localTracksByPidToHide: Map>, - +selectedThreadIndexes: Set, - |} - | {| - +type: 'SHOW_GLOBAL_TRACK', - +trackIndex: TrackIndex, - |} - | {| - +type: 'SHOW_GLOBAL_TRACK_INCLUDING_LOCAL_TRACKS', - +trackIndex: TrackIndex, - +pid: Pid, - |} - | {| - // Isolate only the process track, and not the local tracks. - +type: 'ISOLATE_PROCESS', - +hiddenGlobalTracks: Set, - +isolatedTrackIndex: TrackIndex, - +selectedThreadIndexes: Set, - |} - | {| - // Isolate the process track, and hide the local tracks. - type: 'ISOLATE_PROCESS_MAIN_THREAD', - pid: Pid, - hiddenGlobalTracks: Set, - isolatedTrackIndex: TrackIndex, - selectedThreadIndexes: Set, - hiddenLocalTracks: Set, - |} - | {| - // Isolate only the screenshot track - +type: 'ISOLATE_SCREENSHOT_TRACK', - +hiddenGlobalTracks: Set, - |} - | {| - +type: 'CHANGE_LOCAL_TRACK_ORDER', - +localTrackOrder: TrackIndex[], - +pid: Pid, - |} - | {| - +type: 'HIDE_LOCAL_TRACK', - +pid: Pid, - +trackIndex: TrackIndex, - +selectedThreadIndexes: Set, - |} - | {| - +type: 'SHOW_LOCAL_TRACK', - +pid: Pid, - +trackIndex: TrackIndex, - |} - | {| - +type: 'ISOLATE_LOCAL_TRACK', - +pid: Pid, - +hiddenGlobalTracks: Set, - +hiddenLocalTracks: Set, - +selectedThreadIndexes: Set, - |} - | {| - +type: 'SET_CONTEXT_MENU_VISIBILITY', - +isVisible: boolean, - |} - | {| - +type: 'INCREMENT_PANEL_LAYOUT_GENERATION', - |} - | {| +type: 'HAS_ZOOMED_VIA_MOUSEWHEEL' |} - | {| +type: 'DISMISS_NEWLY_PUBLISHED' |} - | {| - +type: 'ENABLE_EVENT_DELAY_TRACKS', - +localTracksByPid: Map, - +localTrackOrderByPid: Map, - |} - | {| - +type: 'ENABLE_EXPERIMENTAL_CPU_GRAPHS', - |} - | {| - +type: 'ENABLE_EXPERIMENTAL_PROCESS_CPU_TRACKS', - +localTracksByPid: Map, - +localTrackOrderByPid: Map, - |} - | {| - +type: 'UPDATE_BOTTOM_BOX', - +libIndex: IndexIntoLibs | null, - +sourceFile: string | null, - +nativeSymbol: NativeSymbolInfo | null, - +allNativeSymbolsForInitiatingCallNode: NativeSymbolInfo[], - +currentTab: TabSlug, - +shouldOpenBottomBox: boolean, - +shouldOpenAssemblyView: boolean, - |} - | {| - +type: 'OPEN_ASSEMBLY_VIEW', - |} - | {| - +type: 'CLOSE_ASSEMBLY_VIEW', - |} - | {| - +type: 'CLOSE_BOTTOM_BOX_FOR_TAB', - +tab: TabSlug, - |}; - -type ReceiveProfileAction = - | {| - +type: 'BULK_SYMBOLICATION', - +symbolicatedThreads: RawThread[], - +oldFuncToNewFuncsMaps: Map, - |} - | {| - +type: 'DONE_SYMBOLICATING', - |} - | {| - +type: 'TEMPORARY_ERROR', - +error: TemporaryError, - |} - | {| - +type: 'FATAL_ERROR', - +error: Error, - |} - | {| - +type: 'PROFILE_LOADED', - +profile: Profile, - +pathInZipFile: ?string, - +implementationFilter: ?ImplementationFilter, - +transformStacks: ?TransformStacksPerThread, - |} - | {| - +type: 'VIEW_FULL_PROFILE', - +selectedThreadIndexes: Set, - +globalTracks: GlobalTrack[], - +globalTrackOrder: TrackIndex[], - +hiddenGlobalTracks: Set, - +localTracksByPid: Map, - +hiddenLocalTracksByPid: Map>, - +localTrackOrderByPid: Map, - +timelineType: TimelineType | null, - +selectedTab: TabSlug, - |} - | {| - +type: 'DATA_RELOAD', - |} - | {| +type: 'RECEIVE_ZIP_FILE', +zip: JSZip |} - | {| +type: 'PROCESS_PROFILE_FROM_ZIP_FILE', +pathInZipFile: string |} - | {| +type: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE', +error: any |} - | {| +type: 'DISMISS_PROCESS_PROFILE_FROM_ZIP_ERROR' |} - | {| +type: 'RETURN_TO_ZIP_FILE_LIST' |} - | {| +type: 'FILE_NOT_FOUND_IN_ZIP_FILE', +pathInZipFile: string |} - | {| +type: 'REQUESTING_SYMBOL_TABLE', +requestedLib: RequestedLib |} - | {| +type: 'RECEIVED_SYMBOL_TABLE_REPLY', +requestedLib: RequestedLib |} - | {| +type: 'START_SYMBOLICATING' |} - | {| +type: 'WAITING_FOR_PROFILE_FROM_BROWSER' |} - | {| +type: 'WAITING_FOR_PROFILE_FROM_STORE' |} - | {| +type: 'WAITING_FOR_PROFILE_FROM_URL', +profileUrl: ?string |} - | {| +type: 'TRIGGER_LOADING_FROM_URL', +profileUrl: string |} - | {| - +type: 'UPDATE_PAGES', - +newPages: PageList, - |}; - -type UrlEnhancerAction = - | {| +type: 'START_FETCHING_PROFILES' |} - | {| +type: 'URL_SETUP_DONE' |} - | {| +type: 'UPDATE_URL_STATE', +newUrlState: UrlState | null |}; - -type UrlStateAction = - | {| +type: 'WAITING_FOR_PROFILE_FROM_FILE' |} - | {| - +type: 'PROFILE_PUBLISHED', - +hash: string, - +profileName: string, - +prePublishedState: State | null, - |} - | {| +type: 'CHANGE_SELECTED_TAB', +selectedTab: TabSlug |} - | {| +type: 'COMMIT_RANGE', +start: number, +end: number |} - | {| - +type: 'POP_COMMITTED_RANGES', - +firstPoppedFilterIndex: number, - +committedRange: StartEndRange | false, - |} - | {| - +type: 'CHANGE_SELECTED_THREAD', - +selectedThreadIndexes: Set, - |} - | {| - +type: 'SELECT_TRACK', - +lastNonShiftClickInformation: LastNonShiftClickInformation | null, - +selectedThreadIndexes: Set, - +selectedTab: TabSlug, - |} - | {| - +type: 'CHANGE_RIGHT_CLICKED_TRACK', - +trackReference: TrackReference | null, - |} - | {| +type: 'CHANGE_CALL_TREE_SEARCH_STRING', +searchString: string |} - | {| - +type: 'ADD_TRANSFORM_TO_STACK', - +threadsKey: ThreadsKey, - +transform: Transform, - +transformedThread: Thread, - +callNodeInfo: CallNodeInfo, - |} - | {| - +type: 'POP_TRANSFORMS_FROM_STACK', - +threadsKey: ThreadsKey, - +firstPoppedFilterIndex: number, - |} - | {| - +type: 'CHANGE_TIMELINE_TYPE', - +timelineType: TimelineType, - |} - | {| - +type: 'CHANGE_IMPLEMENTATION_FILTER', - +implementation: ImplementationFilter, - +threadsKey: ThreadsKey, - +transformedThread: Thread, - +previousImplementation: ImplementationFilter, - +implementation: ImplementationFilter, - |} - | {| - type: 'CHANGE_CALL_TREE_SUMMARY_STRATEGY', - callTreeSummaryStrategy: CallTreeSummaryStrategy, - |} - | {| - +type: 'CHANGE_INVERT_CALLSTACK', - +invertCallstack: boolean, - +newSelectedCallNodePath: CallNodePath, - +selectedThreadIndexes: Set, - |} - | {| - +type: 'CHANGE_SHOW_USER_TIMINGS', - +showUserTimings: boolean, - |} - | {| - +type: 'CHANGE_STACK_CHART_SAME_WIDTHS', - +stackChartSameWidths: boolean, - |} - | {| - +type: 'CHANGE_SHOW_JS_TRACER_SUMMARY', - +showSummary: boolean, - |} - | {| +type: 'CHANGE_MARKER_SEARCH_STRING', +searchString: string |} - | {| +type: 'CHANGE_NETWORK_SEARCH_STRING', +searchString: string |} - | {| +type: 'CHANGE_PROFILES_TO_COMPARE', +profiles: string[] |} - | {| +type: 'CHANGE_PROFILE_NAME', +profileName: string | null |} - | {| - +type: 'SANITIZED_PROFILE_PUBLISHED', - +hash: string, - +committedRanges: StartEndRange[] | null, - +oldThreadIndexToNew: Map | null, - +profileName: string, - +prePublishedState: State | null, - |} - | {| - +type: 'SET_DATA_SOURCE', - +dataSource: DataSource, - |} - | {| - +type: 'CHANGE_MOUSE_TIME_POSITION', - +mouseTimePosition: Milliseconds | null, - |} - | {| - +type: 'CHANGE_TABLE_VIEW_OPTIONS', - +tab: TabSlug, - +tableViewOptions: TableViewOptions, - |} - | {| - +type: 'TOGGLE_RESOURCES_PANEL', - +selectedThreadIndexes: Set, - |} - | {| - +type: 'PROFILE_REMOTELY_DELETED', - |} - | {| - +type: 'TOGGLE_SIDEBAR_OPEN_CATEGORY', - +kind: string, - +category: IndexIntoCategoryList, - |} - | {| - +type: 'CHANGE_TAB_FILTER', - +tabID: TabID | null, - +selectedThreadIndexes: Set, - +globalTracks: GlobalTrack[], - +globalTrackOrder: TrackIndex[], - +hiddenGlobalTracks: Set, - +localTracksByPid: Map, - +hiddenLocalTracksByPid: Map>, - +localTrackOrderByPid: Map, - +selectedTab: TabSlug, - |}; - -export type IconWithClassName = {| +icon: string, +className: string |}; -type IconsAction = - | {| - +type: 'ICON_HAS_LOADED', - +iconWithClassName: IconWithClassName, - |} - | {| +type: 'ICON_IN_ERROR', +icon: string |} - | {| +type: 'ICON_BATCH_ADD', icons: IconWithClassName[] |}; - -type SidebarAction = {| - +type: 'CHANGE_SIDEBAR_OPEN_STATE', - +tab: TabSlug, - +isOpen: boolean, -|}; - -type PublishAction = - | {| - +type: 'TOGGLE_CHECKED_SHARING_OPTION', - +slug: $Keys, - |} - | {| - +type: 'UPLOAD_STARTED', - |} - | {| - +type: 'UPDATE_UPLOAD_PROGRESS', - +uploadProgress: number, - |} - | {| - +type: 'UPLOAD_FAILED', - +error: mixed, - |} - | {| - +type: 'UPLOAD_ABORTED', - |} - | {| - +type: 'UPLOAD_RESET', - |} - | {| - +type: 'UPLOAD_COMPRESSION_STARTED', - +abortFunction: () => void, - |} - | {| - +type: 'CHANGE_UPLOAD_STATE', - +changes: $Shape, - |} - | {| - +type: 'REVERT_TO_PRE_PUBLISHED_STATE', - +prePublishedState: State, - |} - | {| +type: 'HIDE_STALE_PROFILE' |}; - -type DragAndDropAction = - | {| - +type: 'START_DRAGGING', - |} - | {| - +type: 'STOP_DRAGGING', - |} - | {| - +type: 'REGISTER_DRAG_AND_DROP_OVERLAY', - |} - | {| - +type: 'UNREGISTER_DRAG_AND_DROP_OVERLAY', - |}; - -type CurrentProfileUploadedInformationAction = {| - +type: 'SET_CURRENT_PROFILE_UPLOADED_INFORMATION', - +uploadedProfileInformation: UploadedProfileInformation | null, -|}; - -type SourcesAction = - | {| +type: 'SOURCE_CODE_LOADING_BEGIN_URL', file: string, url: string |} - | {| +type: 'SOURCE_CODE_LOADING_BEGIN_BROWSER_CONNECTION', file: string |} - | {| +type: 'SOURCE_CODE_LOADING_SUCCESS', file: string, code: string |} - | {| - +type: 'SOURCE_CODE_LOADING_ERROR', - file: string, - errors: SourceCodeLoadingError[], - |}; - -// nativeSymbolKey == `${lib.debugName}/${lib.breakpadID}/${nativeSymbolInfo.address.toString(16)}` - -type AssemblyAction = - | {| - +type: 'ASSEMBLY_CODE_LOADING_BEGIN_URL', - nativeSymbolKey: string, - url: string, - |} - | {| - +type: 'ASSEMBLY_CODE_LOADING_BEGIN_BROWSER_CONNECTION', - nativeSymbolKey: string, - |} - | {| - +type: 'ASSEMBLY_CODE_LOADING_SUCCESS', - nativeSymbolKey: string, - instructions: DecodedInstruction[], - |} - | {| - +type: 'ASSEMBLY_CODE_LOADING_ERROR', - nativeSymbolKey: string, - errors: ApiQueryError[], - |}; - -type AppAction = {| - +type: 'UPDATE_BROWSER_CONNECTION_STATUS', - +browserConnectionStatus: BrowserConnectionStatus, -|}; - -export type Action = - | ProfileAction - | ReceiveProfileAction - | SidebarAction - | UrlEnhancerAction - | UrlStateAction - | IconsAction - | PublishAction - | DragAndDropAction - | CurrentProfileUploadedInformationAction - | SourcesAction - | AssemblyAction - | AppAction; diff --git a/src/types/actions.ts b/src/types/actions.ts new file mode 100644 index 0000000000..3ea62bdd1c --- /dev/null +++ b/src/types/actions.ts @@ -0,0 +1,718 @@ +/* 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/. */ + +import type JSZip from 'jszip'; +import type { + Profile, + RawThread, + ThreadIndex, + Pid, + TabID, + IndexIntoCategoryList, + IndexIntoLibs, + PageList, +} from './profile'; +import type { + Thread, + CallNodePath, + GlobalTrack, + LocalTrack, + TrackIndex, + MarkerIndex, + ThreadsKey, + NativeSymbolInfo, +} from './profile-derived'; +import type { FuncToFuncsMap } from '../profile-logic/symbolication'; +import type { TemporaryError } from '../utils/errors'; +import type { Transform, TransformStacksPerThread } from './transforms'; +import type { IndexIntoZipFileTable } from '../profile-logic/zip-files'; +import type { CallNodeInfo } from '../profile-logic/call-node-info'; +import type { TabSlug } from '../app-logic/tabs-handling'; +import type { + UrlState, + UploadState, + State, + UploadedProfileInformation, + SourceCodeLoadingError, + ApiQueryError, + TableViewOptions, + DecodedInstruction, +} from './state'; +import type { CssPixels, StartEndRange, Milliseconds } from './units'; +import type { BrowserConnectionStatus } from '../app-logic/browser-connection'; + +export type DataSource = + | 'none' + // This is used when the profile is loaded from a local file, via drag and + // drop or via a file input. Reloading a URL with this data source cannot + // work automatically because the file would need to be picked again. + | 'from-file' + // This datasource is used to fetch a profile from Firefox via a frame script. + // This is the first entry-point when a profile is captured in the browser. + | 'from-browser' + // Websites can inject profiles via a postMessage call: + // postMessage({ name: "inject-profile", profile: Profile }) + | 'from-post-message' + // This is used for profiles that have been shared / uploaded to the Profiler + // Server. + | 'public' + // This is used after a public profile is deleted / unpublished. + // In the future, we may want to use the "local" data source for this, and + // remove "unpublished". + | 'unpublished' + // Reserved for future use. Once implemented, it would work as follows: + // Whenever a non-public profile is loaded into the profiler, e.g. via + // from-browser or from-file, we want to store it in a local database + // automatically, generate an ID for it, and redirect the URL to /local/{id}/. + // This would make it so that the page can be reloaded, or restored after a + // browser restart, without losing the profile. + | 'local' + // This is used to load profiles from a URL. It is used in two scenarios: + // - For public profiles which are hosted on a different server than the + // regular profiler server, for example for profiles that are captured + // automatically in Firefox CI. + // - With a localhost URL, in order to import profiles from a locally running + // script. + | 'from-url' + // This is used when comparing two profiles. The displayed profile is a + // comparison profile created from two input profiles. + | 'compare' + // This is a page which displays a list of profiles that were uploaded from + // this browser, and allows deleting / unpublishing those profiles. + | 'uploaded-recordings'; + +export type TimelineType = 'stack' | 'category' | 'cpu-category'; +export type PreviewSelection = + | { readonly hasSelection: false; readonly isModifying: false } + | { + readonly hasSelection: true; + readonly isModifying: boolean; + readonly selectionStart: number; + readonly selectionEnd: number; + readonly draggingStart?: boolean; + readonly draggingEnd?: boolean; + }; + +/** + * The counts for how many tracks are hidden in the timeline. + */ +export type HiddenTrackCount = { + readonly hidden: number; + readonly total: number; +}; + +/** + * A TrackReference uniquely identifies a track. + * Note that TrackIndexes aren't globally unique: they're unique among global + * tracks, and they're unique among local tracks for a specific Pid. + */ +export type GlobalTrackReference = { + readonly type: 'global'; + readonly trackIndex: TrackIndex; +}; +export type LocalTrackReference = { + readonly type: 'local'; + readonly trackIndex: TrackIndex; + readonly pid: Pid; +}; + +export type TrackReference = GlobalTrackReference | LocalTrackReference; + +export type LastNonShiftClickInformation = { + clickedTrack: TrackReference; + selection: Set; +}; + +export type RequestedLib = { + readonly debugName: string; + readonly breakpadId: string; +}; +export type ImplementationFilter = 'combined' | 'js' | 'cpp'; +// Change the strategy for computing the summarizing information for the call tree. +export type CallTreeSummaryStrategy = + | 'timing' + | 'js-allocations' + | 'native-retained-allocations' + | 'native-allocations' + | 'native-deallocations-memory' + | 'native-deallocations-sites'; + +/** + * This type determines what kind of information gets sanitized from published profiles. + */ +export type CheckedSharingOptions = { + // The following values are for including more information in a sanitized profile. + includeHiddenThreads: boolean; + includeAllTabs: boolean; + includeFullTimeRange: boolean; + includeScreenshots: boolean; + includeUrls: boolean; + includeExtension: boolean; + includePreferenceValues: boolean; + includePrivateBrowsingData: boolean; +}; + +// This type is used when selecting tracks in the timeline. Ctrl and Meta are +// stored in the same property to accommodate all OSes. +export type KeyboardModifiers = { ctrlOrMeta: boolean; shift: boolean }; + +/** + * This type gives some context about the action leading to a selection. + */ +export type SelectionContext = { + // This is the source for this selection: is it a keyboard or a pointer event, + // or is it the result of some automatic selection. + readonly source: 'keyboard' | 'pointer' | 'auto'; +}; + +type ProfileAction = + | { + readonly type: 'ROUTE_NOT_FOUND'; + readonly url: string; + } + | { + readonly type: 'ASSIGN_TASK_TRACER_NAMES'; + readonly addressIndices: number[]; + readonly symbolNames: string[]; + } + | { + readonly type: 'CHANGE_SELECTED_CALL_NODE'; + readonly isInverted: boolean; + readonly threadsKey: ThreadsKey; + readonly selectedCallNodePath: CallNodePath; + readonly optionalExpandedToCallNodePath: CallNodePath | undefined; + readonly context: SelectionContext; + } + | { + readonly type: 'UPDATE_TRACK_THREAD_HEIGHT'; + readonly height: CssPixels; + readonly threadsKey: ThreadsKey; + } + | { + readonly type: 'CHANGE_RIGHT_CLICKED_CALL_NODE'; + readonly threadsKey: ThreadsKey; + readonly callNodePath: CallNodePath | null; + } + | { + readonly type: 'FOCUS_CALL_TREE'; + } + | { + readonly type: 'CHANGE_EXPANDED_CALL_NODES'; + readonly threadsKey: ThreadsKey; + readonly isInverted: boolean; + readonly expandedCallNodePaths: Array; + } + | { + readonly type: 'CHANGE_SELECTED_MARKER'; + readonly threadsKey: ThreadsKey; + readonly selectedMarker: MarkerIndex | null; + readonly context: SelectionContext; + } + | { + readonly type: 'CHANGE_SELECTED_NETWORK_MARKER'; + readonly threadsKey: ThreadsKey; + readonly selectedNetworkMarker: MarkerIndex | null; + readonly context: SelectionContext; + } + | { + readonly type: 'CHANGE_RIGHT_CLICKED_MARKER'; + readonly threadsKey: ThreadsKey; + readonly markerIndex: MarkerIndex | null; + } + | { + readonly type: 'CHANGE_HOVERED_MARKER'; + readonly threadsKey: ThreadsKey; + readonly markerIndex: MarkerIndex | null; + } + | { + readonly type: 'UPDATE_PREVIEW_SELECTION'; + readonly previewSelection: PreviewSelection; + } + | { + readonly type: 'CHANGE_SELECTED_ZIP_FILE'; + readonly selectedZipFileIndex: IndexIntoZipFileTable | null; + } + | { + readonly type: 'CHANGE_EXPANDED_ZIP_FILES'; + readonly expandedZipFileIndexes: Array; + } + | { + readonly type: 'CHANGE_GLOBAL_TRACK_ORDER'; + readonly globalTrackOrder: TrackIndex[]; + } + | { + readonly type: 'HIDE_GLOBAL_TRACK'; + readonly trackIndex: TrackIndex; + readonly pid: Pid | null; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'SHOW_ALL_TRACKS'; + } + | { + readonly type: 'SHOW_PROVIDED_TRACKS'; + readonly globalTracksToShow: Set; + readonly localTracksByPidToShow: Map>; + } + | { + readonly type: 'HIDE_PROVIDED_TRACKS'; + readonly globalTracksToHide: Set; + readonly localTracksByPidToHide: Map>; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'SHOW_GLOBAL_TRACK'; + readonly trackIndex: TrackIndex; + } + | { + readonly type: 'SHOW_GLOBAL_TRACK_INCLUDING_LOCAL_TRACKS'; + readonly trackIndex: TrackIndex; + readonly pid: Pid; + } + | { + // Isolate only the process track, and not the local tracks. + readonly type: 'ISOLATE_PROCESS'; + readonly hiddenGlobalTracks: Set; + readonly isolatedTrackIndex: TrackIndex; + readonly selectedThreadIndexes: Set; + } + | { + // Isolate the process track, and hide the local tracks. + readonly type: 'ISOLATE_PROCESS_MAIN_THREAD'; + readonly pid: Pid; + readonly hiddenGlobalTracks: Set; + readonly isolatedTrackIndex: TrackIndex; + readonly selectedThreadIndexes: Set; + readonly hiddenLocalTracks: Set; + } + | { + // Isolate only the screenshot track + readonly type: 'ISOLATE_SCREENSHOT_TRACK'; + readonly hiddenGlobalTracks: Set; + } + | { + readonly type: 'CHANGE_LOCAL_TRACK_ORDER'; + readonly localTrackOrder: TrackIndex[]; + readonly pid: Pid; + } + | { + readonly type: 'HIDE_LOCAL_TRACK'; + readonly pid: Pid; + readonly trackIndex: TrackIndex; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'SHOW_LOCAL_TRACK'; + readonly pid: Pid; + readonly trackIndex: TrackIndex; + } + | { + readonly type: 'ISOLATE_LOCAL_TRACK'; + readonly pid: Pid; + readonly hiddenGlobalTracks: Set; + readonly hiddenLocalTracks: Set; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'SET_CONTEXT_MENU_VISIBILITY'; + readonly isVisible: boolean; + } + | { + readonly type: 'INCREMENT_PANEL_LAYOUT_GENERATION'; + } + | { readonly type: 'HAS_ZOOMED_VIA_MOUSEWHEEL' } + | { readonly type: 'DISMISS_NEWLY_PUBLISHED' } + | { + readonly type: 'ENABLE_EVENT_DELAY_TRACKS'; + readonly localTracksByPid: Map; + readonly localTrackOrderByPid: Map; + } + | { + readonly type: 'ENABLE_EXPERIMENTAL_CPU_GRAPHS'; + } + | { + readonly type: 'ENABLE_EXPERIMENTAL_PROCESS_CPU_TRACKS'; + readonly localTracksByPid: Map; + readonly localTrackOrderByPid: Map; + } + | { + readonly type: 'UPDATE_BOTTOM_BOX'; + readonly libIndex: IndexIntoLibs | null; + readonly sourceFile: string | null; + readonly nativeSymbol: NativeSymbolInfo | null; + readonly allNativeSymbolsForInitiatingCallNode: NativeSymbolInfo[]; + readonly currentTab: TabSlug; + readonly shouldOpenBottomBox: boolean; + readonly shouldOpenAssemblyView: boolean; + } + | { + readonly type: 'OPEN_ASSEMBLY_VIEW'; + } + | { + readonly type: 'CLOSE_ASSEMBLY_VIEW'; + } + | { + readonly type: 'CLOSE_BOTTOM_BOX_FOR_TAB'; + readonly tab: TabSlug; + }; + +type ReceiveProfileAction = + | { + readonly type: 'BULK_SYMBOLICATION'; + readonly symbolicatedThreads: RawThread[]; + readonly oldFuncToNewFuncsMaps: Map; + } + | { + readonly type: 'DONE_SYMBOLICATING'; + } + | { + readonly type: 'TEMPORARY_ERROR'; + readonly error: TemporaryError; + } + | { + readonly type: 'FATAL_ERROR'; + readonly error: Error; + } + | { + readonly type: 'PROFILE_LOADED'; + readonly profile: Profile; + readonly pathInZipFile: string | null; + readonly implementationFilter: ImplementationFilter | null; + readonly transformStacks: TransformStacksPerThread | null; + } + | { + readonly type: 'VIEW_FULL_PROFILE'; + readonly selectedThreadIndexes: Set; + readonly globalTracks: GlobalTrack[]; + readonly globalTrackOrder: TrackIndex[]; + readonly hiddenGlobalTracks: Set; + readonly localTracksByPid: Map; + readonly hiddenLocalTracksByPid: Map>; + readonly localTrackOrderByPid: Map; + readonly timelineType: TimelineType | null; + readonly selectedTab: TabSlug; + } + | { + readonly type: 'DATA_RELOAD'; + } + | { readonly type: 'RECEIVE_ZIP_FILE'; readonly zip: JSZip } + | { + readonly type: 'PROCESS_PROFILE_FROM_ZIP_FILE'; + readonly pathInZipFile: string; + } + | { + readonly type: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE'; + readonly error: any; + } + | { readonly type: 'DISMISS_PROCESS_PROFILE_FROM_ZIP_ERROR' } + | { readonly type: 'RETURN_TO_ZIP_FILE_LIST' } + | { + readonly type: 'FILE_NOT_FOUND_IN_ZIP_FILE'; + readonly pathInZipFile: string; + } + | { + readonly type: 'REQUESTING_SYMBOL_TABLE'; + readonly requestedLib: RequestedLib; + } + | { + readonly type: 'RECEIVED_SYMBOL_TABLE_REPLY'; + readonly requestedLib: RequestedLib; + } + | { readonly type: 'START_SYMBOLICATING' } + | { readonly type: 'WAITING_FOR_PROFILE_FROM_BROWSER' } + | { readonly type: 'WAITING_FOR_PROFILE_FROM_STORE' } + | { + readonly type: 'WAITING_FOR_PROFILE_FROM_URL'; + readonly profileUrl: string | null; + } + | { readonly type: 'TRIGGER_LOADING_FROM_URL'; readonly profileUrl: string } + | { + readonly type: 'UPDATE_PAGES'; + readonly newPages: PageList; + }; + +type UrlEnhancerAction = + | { readonly type: 'START_FETCHING_PROFILES' } + | { readonly type: 'URL_SETUP_DONE' } + | { + readonly type: 'UPDATE_URL_STATE'; + readonly newUrlState: UrlState | null; + }; + +type UrlStateAction = + | { readonly type: 'WAITING_FOR_PROFILE_FROM_FILE' } + | { + readonly type: 'PROFILE_PUBLISHED'; + readonly hash: string; + readonly profileName: string; + readonly prePublishedState: State | null; + } + | { readonly type: 'CHANGE_SELECTED_TAB'; readonly selectedTab: TabSlug } + | { + readonly type: 'COMMIT_RANGE'; + readonly start: number; + readonly end: number; + } + | { + readonly type: 'POP_COMMITTED_RANGES'; + readonly firstPoppedFilterIndex: number; + readonly committedRange: StartEndRange | false; + } + | { + readonly type: 'CHANGE_SELECTED_THREAD'; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'SELECT_TRACK'; + readonly lastNonShiftClickInformation: LastNonShiftClickInformation | null; + readonly selectedThreadIndexes: Set; + readonly selectedTab: TabSlug; + } + | { + readonly type: 'CHANGE_RIGHT_CLICKED_TRACK'; + readonly trackReference: TrackReference | null; + } + | { + readonly type: 'CHANGE_CALL_TREE_SEARCH_STRING'; + readonly searchString: string; + } + | { + readonly type: 'ADD_TRANSFORM_TO_STACK'; + readonly threadsKey: ThreadsKey; + readonly transform: Transform; + readonly transformedThread: Thread; + readonly callNodeInfo: CallNodeInfo; + } + | { + readonly type: 'POP_TRANSFORMS_FROM_STACK'; + readonly threadsKey: ThreadsKey; + readonly firstPoppedFilterIndex: number; + } + | { + readonly type: 'CHANGE_TIMELINE_TYPE'; + readonly timelineType: TimelineType; + } + | { + readonly type: 'CHANGE_IMPLEMENTATION_FILTER'; + readonly threadsKey: ThreadsKey; + readonly transformedThread: Thread; + readonly previousImplementation: ImplementationFilter; + readonly implementation: ImplementationFilter; + } + | { + readonly type: 'CHANGE_CALL_TREE_SUMMARY_STRATEGY'; + readonly callTreeSummaryStrategy: CallTreeSummaryStrategy; + } + | { + readonly type: 'CHANGE_INVERT_CALLSTACK'; + readonly invertCallstack: boolean; + readonly newSelectedCallNodePath: CallNodePath; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'CHANGE_SHOW_USER_TIMINGS'; + readonly showUserTimings: boolean; + } + | { + readonly type: 'CHANGE_STACK_CHART_SAME_WIDTHS'; + readonly stackChartSameWidths: boolean; + } + | { + readonly type: 'CHANGE_SHOW_JS_TRACER_SUMMARY'; + readonly showSummary: boolean; + } + | { + readonly type: 'CHANGE_MARKER_SEARCH_STRING'; + readonly searchString: string; + } + | { + readonly type: 'CHANGE_NETWORK_SEARCH_STRING'; + readonly searchString: string; + } + | { readonly type: 'CHANGE_PROFILES_TO_COMPARE'; readonly profiles: string[] } + | { + readonly type: 'CHANGE_PROFILE_NAME'; + readonly profileName: string | null; + } + | { + readonly type: 'SANITIZED_PROFILE_PUBLISHED'; + readonly hash: string; + readonly committedRanges: StartEndRange[] | null; + readonly oldThreadIndexToNew: Map | null; + readonly profileName: string; + readonly prePublishedState: State | null; + } + | { + readonly type: 'SET_DATA_SOURCE'; + readonly dataSource: DataSource; + } + | { + readonly type: 'CHANGE_MOUSE_TIME_POSITION'; + readonly mouseTimePosition: Milliseconds | null; + } + | { + readonly type: 'CHANGE_TABLE_VIEW_OPTIONS'; + readonly tab: TabSlug; + readonly tableViewOptions: TableViewOptions; + } + | { + readonly type: 'TOGGLE_RESOURCES_PANEL'; + readonly selectedThreadIndexes: Set; + } + | { + readonly type: 'PROFILE_REMOTELY_DELETED'; + } + | { + readonly type: 'TOGGLE_SIDEBAR_OPEN_CATEGORY'; + readonly kind: string; + readonly category: IndexIntoCategoryList; + } + | { + readonly type: 'CHANGE_TAB_FILTER'; + readonly tabID: TabID | null; + readonly selectedThreadIndexes: Set; + readonly globalTracks: GlobalTrack[]; + readonly globalTrackOrder: TrackIndex[]; + readonly hiddenGlobalTracks: Set; + readonly localTracksByPid: Map; + readonly hiddenLocalTracksByPid: Map>; + readonly localTrackOrderByPid: Map; + readonly selectedTab: TabSlug; + }; + +export type IconWithClassName = { + readonly icon: string; + readonly className: string; +}; +type IconsAction = + | { + readonly type: 'ICON_HAS_LOADED'; + readonly iconWithClassName: IconWithClassName; + } + | { readonly type: 'ICON_IN_ERROR'; readonly icon: string } + | { readonly type: 'ICON_BATCH_ADD'; icons: IconWithClassName[] }; + +type SidebarAction = { + readonly type: 'CHANGE_SIDEBAR_OPEN_STATE'; + readonly tab: TabSlug; + readonly isOpen: boolean; +}; + +type PublishAction = + | { + readonly type: 'TOGGLE_CHECKED_SHARING_OPTION'; + readonly slug: keyof CheckedSharingOptions; + } + | { + readonly type: 'UPLOAD_STARTED'; + } + | { + readonly type: 'UPDATE_UPLOAD_PROGRESS'; + readonly uploadProgress: number; + } + | { + readonly type: 'UPLOAD_FAILED'; + readonly error: unknown; + } + | { + readonly type: 'UPLOAD_ABORTED'; + } + | { + readonly type: 'UPLOAD_RESET'; + } + | { + readonly type: 'UPLOAD_COMPRESSION_STARTED'; + readonly abortFunction: () => void; + } + | { + readonly type: 'CHANGE_UPLOAD_STATE'; + readonly changes: Partial; + } + | { + readonly type: 'REVERT_TO_PRE_PUBLISHED_STATE'; + readonly prePublishedState: State; + } + | { readonly type: 'HIDE_STALE_PROFILE' }; + +type DragAndDropAction = + | { + readonly type: 'START_DRAGGING'; + } + | { + readonly type: 'STOP_DRAGGING'; + } + | { + readonly type: 'REGISTER_DRAG_AND_DROP_OVERLAY'; + } + | { + readonly type: 'UNREGISTER_DRAG_AND_DROP_OVERLAY'; + }; + +type CurrentProfileUploadedInformationAction = { + readonly type: 'SET_CURRENT_PROFILE_UPLOADED_INFORMATION'; + readonly uploadedProfileInformation: UploadedProfileInformation | null; +}; + +type SourcesAction = + | { + readonly type: 'SOURCE_CODE_LOADING_BEGIN_URL'; + readonly file: string; + readonly url: string; + } + | { + readonly type: 'SOURCE_CODE_LOADING_BEGIN_BROWSER_CONNECTION'; + readonly file: string; + } + | { + readonly type: 'SOURCE_CODE_LOADING_SUCCESS'; + readonly file: string; + readonly code: string; + } + | { + readonly type: 'SOURCE_CODE_LOADING_ERROR'; + readonly file: string; + readonly errors: SourceCodeLoadingError[]; + }; + +// nativeSymbolKey == `${lib.debugName}/${lib.breakpadID}/${nativeSymbolInfo.address.toString(16)}` + +type AssemblyAction = + | { + readonly type: 'ASSEMBLY_CODE_LOADING_BEGIN_URL'; + readonly nativeSymbolKey: string; + readonly url: string; + } + | { + readonly type: 'ASSEMBLY_CODE_LOADING_BEGIN_BROWSER_CONNECTION'; + readonly nativeSymbolKey: string; + } + | { + readonly type: 'ASSEMBLY_CODE_LOADING_SUCCESS'; + readonly nativeSymbolKey: string; + readonly instructions: DecodedInstruction[]; + } + | { + readonly type: 'ASSEMBLY_CODE_LOADING_ERROR'; + readonly nativeSymbolKey: string; + readonly errors: ApiQueryError[]; + }; + +type AppAction = { + readonly type: 'UPDATE_BROWSER_CONNECTION_STATUS'; + readonly browserConnectionStatus: BrowserConnectionStatus; +}; + +export type Action = + | ProfileAction + | ReceiveProfileAction + | SidebarAction + | UrlEnhancerAction + | UrlStateAction + | IconsAction + | PublishAction + | DragAndDropAction + | CurrentProfileUploadedInformationAction + | SourcesAction + | AssemblyAction + | AppAction; diff --git a/src/types/globals/ClipboardEvent.js b/src/types/globals/ClipboardEvent.js deleted file mode 100644 index da555da0cd..0000000000 --- a/src/types/globals/ClipboardEvent.js +++ /dev/null @@ -1,8 +0,0 @@ -/* 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/. */ -// @flow - -declare class ClipboardEvent extends Event { - clipboardData: DataTransfer; -} diff --git a/src/types/globals/Image.js b/src/types/globals/Image.js deleted file mode 100644 index 322cc74b7c..0000000000 --- a/src/types/globals/Image.js +++ /dev/null @@ -1,9 +0,0 @@ -/* 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/. */ -// @flow - -declare class Image extends HTMLImageElement { - constructor(width?: number, height?: number): void; - referrerPolicy: ReferrerPolicyType; -} diff --git a/src/types/globals/WheelEvent.js b/src/types/globals/WheelEvent.js deleted file mode 100644 index dbe1331682..0000000000 --- a/src/types/globals/WheelEvent.js +++ /dev/null @@ -1,14 +0,0 @@ -/* 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/. */ -// @flow - -declare class WheelEvent extends MouseEvent { - deltaX: number; // readonly - deltaY: number; // readonly - deltaZ: number; // readonly - deltaMode: 0x00 | 0x01 | 0x02; // readonly - DOM_DELTA_PIXEL: 0x00; // readonly - DOM_DELTA_PAGE: 0x01; // readonly - DOM_DELTA_LINE: 0x02; // readonly -} diff --git a/src/types/globals/Window.d.ts b/src/types/globals/Window.d.ts new file mode 100644 index 0000000000..ee055eaac7 --- /dev/null +++ b/src/types/globals/Window.d.ts @@ -0,0 +1,41 @@ +/* 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/. */ + +import type { SymbolTableAsTuple } from '../../profile-logic/symbol-store-db'; +import type { GoogleAnalytics } from '../../utils/analytics'; +import type FetchMockJest from '@fetch-mock/jest'; + +declare global { + // Because this type isn't an existing Global type, but still it's useful to + // have it available, we define it with a $ as prfix. + interface $GeckoProfiler { + getProfile: () => unknown; + getSymbolTable: ( + debugName: string, + breakpadId: string + ) => Promise; + } + + interface WebChannelEvent { + detail: { + id: string; + message: unknown; + }; + } + + interface Window { + // Google Analytics + ga?: GoogleAnalytics; + // profiler.firefox.com and globals injected via frame scripts. + geckoProfilerPromise: Promise<$GeckoProfiler>; + connectToGeckoProfiler: (profiler: $GeckoProfiler) => void; + + // For debugging purposes, allow tooltips to persist. This aids in inspecting + // the DOM structure. + persistTooltips?: boolean; + + // Test-only + fetchMock: typeof FetchMockJest; + } +} diff --git a/src/types/globals/Window.js b/src/types/globals/Window.js deleted file mode 100644 index 5498e1077f..0000000000 --- a/src/types/globals/Window.js +++ /dev/null @@ -1,98 +0,0 @@ -/* 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/. */ -// @flow - -import type { IDBFactory, IDBKeyRange } from '../indexeddb'; -import type { SymbolTableAsTuple } from '../../profile-logic/symbol-store-db'; -import type { GoogleAnalytics } from '../../utils/analytics'; -import type { MixedObject } from '../utils'; -import type { FetchMockJest } from '@fetch-mock/jest'; - -// Because this type isn't an existing Global type, but still it's useful to -// have it available, we define it with a $ as prfix. -declare type $GeckoProfiler = { - getProfile: () => MixedObject, - getSymbolTable: ( - debugName: string, - breakpadId: string - ) => Promise, -}; - -declare class WebChannelEvent { - detail: { - id: string, - message: mixed, - }; -} - -declare class Window { - // Google Analytics - ga?: GoogleAnalytics; - // profiler.firefox.com and globals injected via frame scripts. - geckoProfilerPromise: Promise<$GeckoProfiler>; - connectToGeckoProfiler: ($GeckoProfiler) => void; - - // For debugging purposes, allow tooltips to persist. This aids in inspecting - // the DOM structure. - persistTooltips?: boolean; - - // WebChannel events. - // https://searchfox.org/mozilla-central/source/toolkit/modules/WebChannel.sys.mjs - addEventListener: $PropertyType & - (( - 'WebChannelMessageToContent', - (event: WebChannelEvent) => void, - true - ) => void) & - (('message', (event: MessageEvent) => void) => void); - - removeEventListener: $PropertyType & - (( - 'WebChannelMessageToContent', - (event: WebChannelEvent) => void, - true - ) => void) & - (('message', (event: MessageEvent) => void) => void); - - // Built-ins. - dispatchEvent: $PropertyType; - getComputedStyle: ( - element: HTMLElement, - pseudoEl: ?string - ) => CSSStyleDeclaration; - TextDecoder: typeof TextDecoder; - setTimeout: typeof setTimeout; - crypto: { - // This is a definition of only the methods we use. - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest - subtle: { - digest: (string, Uint8Array) => Promise, - }, - }; - fetch: typeof fetch; - fetchMock: FetchMockJest /* only used in tests */; - requestIdleCallback: typeof requestIdleCallback; - requestAnimationFrame: typeof requestAnimationFrame; - devicePixelRatio: number; - // The indexedDB is marked as optional, as we should handle the test environment - // where this is not available. It can lead to hard to debug promise failure - // messages. - indexedDB?: IDBFactory; - IDBKeyRange: IDBKeyRange<>; - innerWidth: number; - innerHeight: number; - location: Location; - open: (url: string, windowName: string, windowFeatures: ?string) => Window; - history: History; - Worker: typeof Worker; - WheelEvent: WheelEvent; - navigator: { - userAgent: string, - platform: string, - }; - postMessage: (message: any, targetOrigin: string) => void; - matchMedia: (matchMedia: string) => MediaQueryList; -} - -declare var window: Window; diff --git a/src/types/globals/l10n.js b/src/types/globals/l10n.js deleted file mode 100644 index 7d617146f0..0000000000 --- a/src/types/globals/l10n.js +++ /dev/null @@ -1,8 +0,0 @@ -/* 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/. */ -// @flow - -// This is a variable set by Webpack when we run it with L10N=1, especially in -// the l10n branch. -declare var AVAILABLE_STAGING_LOCALES: Array | void; diff --git a/src/types/index.js b/src/types/index.ts similarity index 93% rename from src/types/index.js rename to src/types/index.ts index c22520d151..970de63619 100644 --- a/src/types/index.js +++ b/src/types/index.ts @@ -2,10 +2,8 @@ * 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/. */ -// @flow export * from './actions'; export * from './gecko-profile'; -export * from './indexeddb'; export * from './markers'; export * from './profile-derived'; export * from './profile'; diff --git a/src/types/profile-derived.js b/src/types/profile-derived.ts similarity index 77% rename from src/types/profile-derived.js rename to src/types/profile-derived.ts index 10b635e831..d83a9a1e9d 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.ts @@ -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/. */ -// @flow import type { Milliseconds, StartEndRange, Address, Bytes } from './units'; import type { MarkerPayload, MarkerSchema } from './markers'; import type { @@ -54,93 +53,93 @@ export type IndexIntoCallNodeTable = number; * The fields that differ from RawThread are collected at the end of this type * definition. */ -export type Thread = {| - processType: ProcessType, - processStartupTime: Milliseconds, - processShutdownTime: Milliseconds | null, - registerTime: Milliseconds, - unregisterTime: Milliseconds | null, - pausedRanges: PausedRange[], - showMarkersInTimeline?: boolean, - name: string, - isMainThread: boolean, +export type Thread = { + processType: ProcessType; + processStartupTime: Milliseconds; + processShutdownTime: Milliseconds | null; + registerTime: Milliseconds; + unregisterTime: Milliseconds | null; + pausedRanges: PausedRange[]; + showMarkersInTimeline?: boolean; + name: string; + isMainThread: boolean; // The eTLD+1 of the isolated content process if provided by the back-end. // It will be undefined if: // - Fission is not enabled. // - It's not an isolated content process. // - It's a sanitized profile. // - It's a profile from an older Firefox which doesn't include this field (introduced in Firefox 80). - 'eTLD+1'?: string, - processName?: string, - isJsTracer?: boolean, - pid: Pid, - tid: Tid, - jsAllocations?: JsAllocationsTable, - nativeAllocations?: NativeAllocationsTable, - markers: RawMarkerTable, - stackTable: StackTable, - frameTable: FrameTable, - funcTable: FuncTable, - resourceTable: ResourceTable, - nativeSymbols: NativeSymbolTable, - jsTracer?: JsTracerTable, + 'eTLD+1'?: string; + processName?: string; + isJsTracer?: boolean; + pid: Pid; + tid: Tid; + jsAllocations?: JsAllocationsTable; + nativeAllocations?: NativeAllocationsTable; + markers: RawMarkerTable; + stackTable: StackTable; + frameTable: FrameTable; + funcTable: FuncTable; + resourceTable: ResourceTable; + nativeSymbols: NativeSymbolTable; + jsTracer?: JsTracerTable; // If present and true, this thread was launched for a private browsing session only. // When false, it can still contain private browsing data if the profile was // captured in a non-fission browser. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. - isPrivateBrowsing?: boolean, + isPrivateBrowsing?: boolean; // If present and non-0, the number represents the container this thread was loaded in. // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. - userContextId?: number, + userContextId?: number; // The fields below this comment are derived data, and not present on the RawThread // in the same form. // Strings for profiles are collected into a single table, and are referred to by // their index by other tables. - stringTable: StringTable, + stringTable: StringTable; // The stack samples collected for this thread. This field is different from // RawThread in that the `time` column is always present. - samples: SamplesTable, -|}; + samples: SamplesTable; +}; /** * The derived samples table. */ -export type SamplesTable = {| +export type SamplesTable = { // Responsiveness is the older version of eventDelay. It injects events every 16ms. // This is optional because newer profiles don't have that field anymore. - responsiveness?: Array, + responsiveness?: Array; // Event delay is the newer version of responsiveness. It allow us to get a finer-grained // view of jank by inferring what would be the delay of a hypothetical input event at // any point in time. It requires a pre-processing to be able to visualize properly. // This is optional because older profiles didn't have that field. - eventDelay?: Array, - stack: Array, - time: Milliseconds[], + eventDelay?: Array; + stack: Array; + time: Milliseconds[]; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. - weight: null | number[], - weightType: WeightType, + weight: null | number[]; + weightType: WeightType; // The CPU ratio, between 0 and 1, over the time between the previous sample // and this sample. - threadCPURatio?: Float64Array, + threadCPURatio?: Float64Array | undefined; // This property isn't present in normal threads. However it's present for // merged threads, so that we know the origin thread for these samples. - threadId?: Tid[], - length: number, -|}; + threadId?: Tid[]; + length: number; +}; type SamplesLikeTableShape = { - stack: Array, - time: Milliseconds[], + stack: Array; + time: Milliseconds[]; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. - weight: null | number[], - weightType: WeightType, - length: number, + weight: null | number[]; + weightType: WeightType; + length: number; }; export type SamplesLikeTable = @@ -149,25 +148,25 @@ export type SamplesLikeTable = | NativeAllocationsTable | JsAllocationsTable; -export type CounterSamplesTable = {| - time: Milliseconds[], +export type CounterSamplesTable = { + time: Milliseconds[]; // The number of times the Counter's "number" was changed since the previous sample. // This property was mandatory until the format version 42, it was made optional in 43. - number?: number[], + number?: number[]; // The count of the data, for instance for memory this would be bytes. - count: number[], - length: number, -|}; - -export type Counter = {| - name: string, - category: string, - description: string, - color?: GraphColor, - pid: Pid, - mainThreadIndex: ThreadIndex, - samples: CounterSamplesTable, -|}; + count: number[]; + length: number; +}; + +export type Counter = { + name: string; + category: string; + description: string; + color?: GraphColor; + pid: Pid; + mainThreadIndex: ThreadIndex; + samples: CounterSamplesTable; +}; /** * The `StackTable` type of the derived thread. @@ -197,16 +196,16 @@ export type Counter = {| * category would be lost if it wasn't inherited into the * nsAttrAndChildArray::InsertChildAt stack before transforms are applied. */ -export type StackTable = {| +export type StackTable = { // Same as in RawStackTable - frame: IndexIntoFrameTable[], - prefix: Array, - length: number, + frame: IndexIntoFrameTable[]; + prefix: Array; + length: number; // Derived from RawStackTable + FrameTable - category: IndexIntoCategoryList[], - subcategory: IndexIntoSubcategoryListForCategory[], -|}; + category: IndexIntoCategoryList[]; + subcategory: IndexIntoSubcategoryListForCategory[]; +}; /** * Similar to the StackTable, but based on functions rather than on frames. @@ -280,10 +279,10 @@ export type StackTable = {| */ export type CallNodeTable = { // The index of the parent call node, or -1 for root nodes. - prefix: Int32Array, // IndexIntoCallNodeTable -> IndexIntoCallNodeTable | -1 + prefix: Int32Array; // IndexIntoCallNodeTable -> IndexIntoCallNodeTable | -1 // The index of this node's next sibling, or -1 if this node is the last child / last root. - nextSibling: Int32Array, // IndexIntoCallNodeTable -> IndexIntoCallNodeTable | -1 + nextSibling: Int32Array; // IndexIntoCallNodeTable -> IndexIntoCallNodeTable | -1 // The index after this node's last descendant. If this node has a next sibling, // subtreeRangeEnd is equal to nextSibling. Otherwise, this is the index @@ -291,22 +290,22 @@ export type CallNodeTable = { // The last node has subtreeRangeEnd set to callNodeTable.length. // // The nodes in the range range [A, subtreeRangeEnd[A]) form A's subtree. - subtreeRangeEnd: Uint32Array, // IndexIntoCallNodeTable -> IndexIntoCallNodeTable + subtreeRangeEnd: Uint32Array; // IndexIntoCallNodeTable -> IndexIntoCallNodeTable - func: Int32Array, // IndexIntoCallNodeTable -> IndexIntoFuncTable - category: Int32Array, // IndexIntoCallNodeTable -> IndexIntoCategoryList - subcategory: Int32Array, // IndexIntoCallNodeTable -> IndexIntoSubcategoryListForCategory - innerWindowID: Float64Array, // IndexIntoCallNodeTable -> InnerWindowID + func: Int32Array; // IndexIntoCallNodeTable -> IndexIntoFuncTable + category: Int32Array; // IndexIntoCallNodeTable -> IndexIntoCategoryList + subcategory: Int32Array; // IndexIntoCallNodeTable -> IndexIntoSubcategoryListForCategory + innerWindowID: Float64Array; // IndexIntoCallNodeTable -> InnerWindowID // IndexIntoNativeSymbolTable: all frames that collapsed into this call node inlined into the same native symbol // -1: divergent: not all frames that collapsed into this call node were inlined, or they are from different symbols // -2: no inlining - sourceFramesInlinedIntoSymbol: Int32Array, + sourceFramesInlinedIntoSymbol: Int32Array; // The depth of the call node. Roots have depth 0. - depth: Int32Array, + depth: Int32Array; // The maximum value in the depth column, or -1 if this table is empty. - maxDepth: number, + maxDepth: number; // The number of call nodes. All columns in this table have this length. - length: number, + length: number; }; // A bitset which indicates something per call node. Use `checkBit` from @@ -332,28 +331,28 @@ export type LineNumber = number; // For stacks which are only used as prefix stack nodes, selfLine and // stackLine may be null. This is fine because their values are not accessed // during the LineTimings computation. -export type StackLineInfo = {| +export type StackLineInfo = { // An array that contains, for each "self" stack, the line number that this stack // spends its self time in, in this file, or null if the self time of the // stack is in a different file or if the line number is not known. // For non-"self" stacks, i.e. stacks which are only used as prefix stacks and // never referred to from a SamplesLikeTable, the value may be null. - selfLine: Array, + selfLine: Array; // An array that contains, for each "self" stack, all the lines that the frames in // this stack hit in this file, or null if this stack does not hit any line // in the given file. // For non-"self" stacks, i.e. stacks which are only used as prefix stacks and // never referred to from a SamplesLikeTable, the value may be null. - stackLines: Array | null>, -|}; + stackLines: Array | null>; +}; // Stores, for all lines of one specific file, how many times each line is hit // by samples in a thread. The maps only contain non-zero values. // So map.get(line) === undefined should be treated as zero. -export type LineTimings = {| - totalLineHits: Map, - selfLineHits: Map, -|}; +export type LineTimings = { + totalLineHits: Map; + selfLineHits: Map; +}; // Stores the addresses which are hit by each stack, for addresses belonging to // one specific native symbol. @@ -372,28 +371,28 @@ export type LineTimings = {| // For stacks which are only used as prefix stack nodes, selfAddress and // stackAddress may be null. This is fine because their values are not accessed // during the AddressTimings computation. -export type StackAddressInfo = {| +export type StackAddressInfo = { // An array that contains, for each "self" stack, the address that this stack // spends its self time in, in this native symbol, or null if the self time of // the stack is in a different native symbol or if the address is not known. // For non-"self" stacks, i.e. stacks which are only used as prefix stacks and // never referred to from a SamplesLikeTable, the value may be null. - selfAddress: Array
, + selfAddress: Array
; // An array that contains, for each "self" stack, all the addresses that the // frames in this stack hit in this native symbol, or null if this stack does // not hit any address in the given native symbol. // For non-"self" stacks, i.e. stacks which are only used as prefix stacks and // never referred to from a SamplesLikeTable, the value may be null. - stackAddresses: Array | null>, -|}; + stackAddresses: Array | null>; +}; // Stores, for all addresses of one specific library, how many times each // address is hit by samples in a thread. The maps only contain non-zero values. // So map.get(address) === undefined should be treated as zero. -export type AddressTimings = {| - totalAddressHits: Map, - selfAddressHits: Map, -|}; +export type AddressTimings = { + totalAddressHits: Map; + selfAddressHits: Map; +}; // Stores the information that's needed to prove to the symbolication API that // we are authorized to request the source code for a specific file. @@ -418,16 +417,16 @@ export type AddressTimings = {| // will usually put a randomized token into the URL in order to make it even // harder to guess the right URL. The address proof check is an extra layer // of protection on top of that, in case the secret URL somehow leaks. -export type AddressProof = {| +export type AddressProof = { // The debugName of a library whose symbol information refers to the requested // file. - debugName: string, + debugName: string; // The breakpadId of that library. - breakpadId: string, + breakpadId: string; // The address in that library for which the symbolicated frames refer to the // requested file. - address: Address, -|}; + address: Address; +}; /** * When working with call trees, individual nodes in the tree are not stable across @@ -447,23 +446,23 @@ export type CallNodePath = IndexIntoFuncTable[]; * This type contains the first derived `Marker[]` information, plus an IndexedArray * to get back to the RawMarkerTable. */ -export type DerivedMarkerInfo = {| - markers: Marker[], +export type DerivedMarkerInfo = { + markers: Marker[]; markerIndexToRawMarkerIndexes: IndexedArray< MarkerIndex, - IndexIntoRawMarkerTable[], - >, -|}; - -export type Marker = {| - start: Milliseconds, - end: Milliseconds | null, - name: string, - category: IndexIntoCategoryList, - threadId: Tid | null, - data: MarkerPayload | null, - incomplete?: boolean, -|}; + IndexIntoRawMarkerTable[] + >; +}; + +export type Marker = { + start: Milliseconds; + end: Milliseconds | null; + name: string; + category: IndexIntoCategoryList; + threadId: Tid | null; + data: MarkerPayload | null; + incomplete?: boolean; +}; /** * A value with this type uniquely identifies a marker. This is the index of a @@ -477,64 +476,62 @@ export type Marker = {| export type MarkerIndex = number; export type CallNodeData = { - funcName: string, - total: number, - totalRelative: number, - self: number, - selfRelative: number, + funcName: string; + total: number; + totalRelative: number; + self: number; + selfRelative: number; }; -export type ExtraBadgeInfo = {| - name: string, - localizationId: string, - vars: mixed, - titleFallback: string, - contentFallback: string, -|}; - -export type CallNodeDisplayData = $Exact< - $ReadOnly<{ - total: string, - totalWithUnit: string, - totalPercent: string, - self: string, - selfWithUnit: string, - name: string, - lib: string, - isFrameLabel: boolean, - categoryName: string, - categoryColor: string, - iconSrc: string | null, - badge?: ExtraBadgeInfo, - icon: string | null, - ariaLabel: string, - }>, ->; - -export type ThreadWithReservedFunctions = {| - thread: Thread, +export type ExtraBadgeInfo = { + name: string; + localizationId: string; + vars: unknown; + titleFallback: string; + contentFallback: string; +}; + +export type CallNodeDisplayData = Readonly<{ + total: string; + totalWithUnit: string; + totalPercent: string; + self: string; + selfWithUnit: string; + name: string; + lib: string; + isFrameLabel: boolean; + categoryName: string; + categoryColor: string; + iconSrc: string | null; + badge?: ExtraBadgeInfo; + icon: string | null; + ariaLabel: string; +}>; + +export type ThreadWithReservedFunctions = { + thread: Thread; reservedFunctionsForResources: Map< IndexIntoResourceTable, - IndexIntoFuncTable, - >, -|}; + IndexIntoFuncTable + >; +}; /** * The marker timing contains the necessary information to draw markers very quickly * in the marker chart. It represents a single row of markers in the chart. */ -export type MarkerTiming = {| +export type MarkerTiming = { // Start time in milliseconds. - start: number[], + start: number[]; // End time in milliseconds. It will equals start for instant markers. - end: number[], - index: MarkerIndex[], - name: string, - bucket: string, + end: number[]; + index: MarkerIndex[]; + name: string; + bucket: string; // True if this marker timing contains only instant markers. - instantOnly: boolean, - length: number, -|}; + instantOnly: boolean; + length: number; +}; export type MarkerTimingRows = Array; @@ -559,68 +556,76 @@ export type MarkerTimingAndBuckets = Array; export type JsTracerTiming = { // Start time in milliseconds. - start: number[], + start: number[]; // End time in milliseconds. - end: number[], - index: IndexIntoJsTracerEvents[], - label: string[], - name: string, - func: Array, - length: number, + end: number[]; + index: IndexIntoJsTracerEvents[]; + label: string[]; + name: string; + func: Array; + length: number; }; /** * The memory counter contains relative offsets of memory. This type provides a data * structure that can be used to see the total range of change over all the samples. */ -export type AccumulatedCounterSamples = {| - +minCount: number, - +maxCount: number, - +countRange: number, +export type AccumulatedCounterSamples = { + readonly minCount: number; + readonly maxCount: number; + readonly countRange: number; // This value holds the accumulation of all the previous counts in the Counter samples. // For a memory counter, this gives the relative offset of bytes in that range // selection. The array will share the indexes of the range filtered counter samples. - +accumulatedCounts: number[], -|}; + readonly accumulatedCounts: number[]; +}; /** * A collection of the data for all configured lines for a given marker */ -export type CollectedCustomMarkerSamples = {| - +minNumber: number, - +maxNumber: number, +export type CollectedCustomMarkerSamples = { + readonly minNumber: number; + readonly maxNumber: number; // This value holds the number per configured line // selection. The array will share the indexes of the range filtered marker samples. - +numbersPerLine: number[][], - +markerIndexes: MarkerIndex[], -|}; + readonly numbersPerLine: number[][]; + readonly markerIndexes: MarkerIndex[]; +}; export type StackType = 'js' | 'native' | 'unsymbolicated'; export type GlobalTrack = // mainThreadIndex is null when this is a fake global process added to contain // real threads. - | {| +type: 'process', +pid: Pid, +mainThreadIndex: ThreadIndex | null |} - | {| +type: 'screenshots', +id: string, +threadIndex: ThreadIndex |} - | {| +type: 'visual-progress' |} - | {| +type: 'perceptual-visual-progress' |} - | {| +type: 'contentful-visual-progress' |}; + | { + readonly type: 'process'; + readonly pid: Pid; + readonly mainThreadIndex: ThreadIndex | null; + } + | { + readonly type: 'screenshots'; + readonly id: string; + readonly threadIndex: ThreadIndex; + } + | { readonly type: 'visual-progress' } + | { readonly type: 'perceptual-visual-progress' } + | { readonly type: 'contentful-visual-progress' }; export type LocalTrack = - | {| +type: 'thread', +threadIndex: ThreadIndex |} - | {| +type: 'network', +threadIndex: ThreadIndex |} - | {| +type: 'memory', +counterIndex: CounterIndex |} - | {| +type: 'bandwidth', +counterIndex: CounterIndex |} - | {| +type: 'ipc', +threadIndex: ThreadIndex |} - | {| +type: 'event-delay', +threadIndex: ThreadIndex |} - | {| +type: 'process-cpu', +counterIndex: CounterIndex |} - | {| +type: 'power', +counterIndex: CounterIndex |} - | {| - +type: 'marker', - +threadIndex: ThreadIndex, - +markerSchema: MarkerSchema, - +markerName: IndexIntoStringTable, - |}; + | { readonly type: 'thread'; readonly threadIndex: ThreadIndex } + | { readonly type: 'network'; readonly threadIndex: ThreadIndex } + | { readonly type: 'memory'; readonly counterIndex: CounterIndex } + | { readonly type: 'bandwidth'; readonly counterIndex: CounterIndex } + | { readonly type: 'ipc'; readonly threadIndex: ThreadIndex } + | { readonly type: 'event-delay'; readonly threadIndex: ThreadIndex } + | { readonly type: 'process-cpu'; readonly counterIndex: CounterIndex } + | { readonly type: 'power'; readonly counterIndex: CounterIndex } + | { + readonly type: 'marker'; + readonly threadIndex: ThreadIndex; + readonly markerSchema: MarkerSchema; + readonly markerName: IndexIntoStringTable; + }; export type Track = GlobalTrack | LocalTrack; @@ -634,24 +639,24 @@ export type TrackIndex = number; * Type that holds the values of personally identifiable information that user * wants to remove. */ -export type RemoveProfileInformation = {| +export type RemoveProfileInformation = { // Remove the given hidden threads if they are provided. - +shouldRemoveThreads: Set, + readonly shouldRemoveThreads: Set; // Remove the given counters if they are provided. - +shouldRemoveCounters: Set, + readonly shouldRemoveCounters: Set; // Remove the screenshots if they are provided. - +shouldRemoveThreadsWithScreenshots: Set, + readonly shouldRemoveThreadsWithScreenshots: Set; // Remove the full time range if StartEndRange is provided. - +shouldFilterToCommittedRange: StartEndRange | null, + readonly shouldFilterToCommittedRange: StartEndRange | null; // Remove all the URLs if it's true. - +shouldRemoveUrls: boolean, + readonly shouldRemoveUrls: boolean; // Remove the extension list if it's true. - +shouldRemoveExtensions: boolean, + readonly shouldRemoveExtensions: boolean; // Remove the preference values if it's true. - +shouldRemovePreferenceValues: boolean, + readonly shouldRemovePreferenceValues: boolean; // Remove the private browsing data if it's true. - +shouldRemovePrivateBrowsingData: boolean, -|}; + readonly shouldRemovePrivateBrowsingData: boolean; +}; /** * This type is used to decide how to highlight and stripe areas in the @@ -680,30 +685,30 @@ export type InitialSelectedTrackReference = HTMLElement; /** * Page data for ProfileFilterNavigator component. */ -export type ProfileFilterPageData = {| - origin: string, - hostname: string, - favicon: string, -|}; +export type ProfileFilterPageData = { + origin: string; + hostname: string; + favicon: string; +}; /** * Information about the Tab selector state that is sorted by their tab activity * scores. */ -export type SortedTabPageData = Array<{| - tabID: TabID, - tabScore: number, - pageData: ProfileFilterPageData, -|}>; +export type SortedTabPageData = Array<{ + tabID: TabID; + tabScore: number; + pageData: ProfileFilterPageData; +}>; -export type CallNodeSelfAndSummary = {| +export type CallNodeSelfAndSummary = { // This property stores the amount of unit (time, bytes, count, etc.) spent in // this call node and not in any of its descendant nodes. - callNodeSelf: Float64Array, + callNodeSelf: Float64Array; // The sum of absolute values in callNodeSelf. // This is used for computing the percentages displayed in the call tree. - rootTotalSummary: number, -|}; + rootTotalSummary: number; +}; /** * The self and total time, usually for a single call node. @@ -713,7 +718,7 @@ export type CallNodeSelfAndSummary = {| * - Otherwise, the values are in the same unit as the sample weight type. For * example, they could be sample counts, weights, or bytes. */ -export type SelfAndTotal = {| self: number, total: number |}; +export type SelfAndTotal = { self: number; total: number }; /* * Event delay table that holds the pre-processed event delay values and other @@ -722,12 +727,12 @@ export type SelfAndTotal = {| self: number, total: number |}; * to make a calculation to find out their real values. Also see: * https://searchfox.org/mozilla-central/rev/3811b11b5773c1dccfe8228bfc7143b10a9a2a99/tools/profiler/core/platform.cpp#3000-3186 */ -export type EventDelayInfo = {| - +eventDelays: Float32Array, - +minDelay: Milliseconds, - +maxDelay: Milliseconds, - +delayRange: Milliseconds, -|}; +export type EventDelayInfo = { + readonly eventDelays: Float32Array; + readonly minDelay: Milliseconds; + readonly maxDelay: Milliseconds; + readonly delayRange: Milliseconds; +}; /** * This is a unique key that can be used in an object cache that represents either @@ -744,30 +749,30 @@ export type ThreadsKey = string | number; * would only be meaningful within a thread. * This can be removed if the native symbol table ever becomes global. */ -export type NativeSymbolInfo = {| - name: string, - address: Address, +export type NativeSymbolInfo = { + name: string; + address: Address; // The number of bytes belonging to this function, starting at the symbol address. // If functionSizeIsKnown is false, then this is a minimum size. - functionSize: Bytes, - functionSizeIsKnown: boolean, - libIndex: IndexIntoLibs, -|}; + functionSize: Bytes; + functionSizeIsKnown: boolean; + libIndex: IndexIntoLibs; +}; /** * Information about the initiating call node when the bottom box (source view + * assembly view) is updated. */ -export type BottomBoxInfo = {| - libIndex: IndexIntoLibs | null, - sourceFile: string | null, - nativeSymbols: NativeSymbolInfo[], -|}; +export type BottomBoxInfo = { + libIndex: IndexIntoLibs | null; + sourceFile: string | null; + nativeSymbols: NativeSymbolInfo[]; +}; /** * Favicon data that is retrieved from the browser connection. */ -export type FaviconData = {| - +data: ArrayBuffer, - +mimeType: string, -|}; +export type FaviconData = { + readonly data: ArrayBuffer; + readonly mimeType: string; +}; diff --git a/src/types/state.js b/src/types/state.js deleted file mode 100644 index a2a1248633..0000000000 --- a/src/types/state.js +++ /dev/null @@ -1,393 +0,0 @@ -/* 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/. */ - -// @flow - -import type { - Action, - DataSource, - PreviewSelection, - ImplementationFilter, - CallTreeSummaryStrategy, - RequestedLib, - TrackReference, - TimelineType, - CheckedSharingOptions, - Localization, - LastNonShiftClickInformation, -} from './actions'; -import type { TabSlug } from '../app-logic/tabs-handling'; -import type { StartEndRange, CssPixels, Milliseconds, Address } from './units'; -import type { - Profile, - ThreadIndex, - Pid, - TabID, - IndexIntoLibs, -} from './profile'; - -import type { - CallNodePath, - GlobalTrack, - LocalTrack, - TrackIndex, - MarkerIndex, - ThreadsKey, - NativeSymbolInfo, -} from './profile-derived'; -import type { Attempt } from '../utils/errors'; -import type { TransformStacksPerThread } from './transforms'; -import type JSZip from 'jszip'; -import type { IndexIntoZipFileTable } from '../profile-logic/zip-files'; -import type { PathSet } from '../utils/path'; -import type { UploadedProfileInformation as ImportedUploadedProfileInformation } from 'firefox-profiler/app-logic/uploaded-profiles-db'; -import type { BrowserConnectionStatus } from 'firefox-profiler/app-logic/browser-connection'; - -export type Reducer = (T | void, Action) => T; - -// This type is defined in uploaded-profiles-db.js because it is very tied to -// the data stored in our local IndexedDB, and we don't want to change it -// lightly, without changing the DB code. -// We reexport this type here mostly for easier access. -export type UploadedProfileInformation = ImportedUploadedProfileInformation; - -export type SymbolicationStatus = 'DONE' | 'SYMBOLICATING'; -export type ThreadViewOptions = {| - +selectedNonInvertedCallNodePath: CallNodePath, - +selectedInvertedCallNodePath: CallNodePath, - +expandedNonInvertedCallNodePaths: PathSet, - +expandedInvertedCallNodePaths: PathSet, - +selectedMarker: MarkerIndex | null, - +selectedNetworkMarker: MarkerIndex | null, -|}; - -export type ThreadViewOptionsPerThreads = { [ThreadsKey]: ThreadViewOptions }; - -export type TableViewOptions = {| - +fixedColumnWidths: Array | null, -|}; - -export type TableViewOptionsPerTab = { [TabSlug]: TableViewOptions }; - -export type RightClickedCallNode = {| - +threadsKey: ThreadsKey, - +callNodePath: CallNodePath, -|}; - -export type MarkerReference = {| - +threadsKey: ThreadsKey, - +markerIndex: MarkerIndex, -|}; - -/** - * Profile view state - */ -export type ProfileViewState = { - +viewOptions: {| - perThread: ThreadViewOptionsPerThreads, - symbolicationStatus: SymbolicationStatus, - waitingForLibs: Set, - previewSelection: PreviewSelection, - scrollToSelectionGeneration: number, - focusCallTreeGeneration: number, - rootRange: StartEndRange, - lastNonShiftClick: LastNonShiftClickInformation | null, - rightClickedTrack: TrackReference | null, - rightClickedCallNode: RightClickedCallNode | null, - rightClickedMarker: MarkerReference | null, - hoveredMarker: MarkerReference | null, - mouseTimePosition: Milliseconds | null, - perTab: TableViewOptionsPerTab, - |}, - +profile: Profile | null, - globalTracks: GlobalTrack[], - localTracksByPid: Map, -}; - -export type AppViewState = - | {| +phase: 'ROUTE_NOT_FOUND' |} - | {| +phase: 'TRANSITIONING_FROM_STALE_PROFILE' |} - | {| +phase: 'PROFILE_LOADED' |} - | {| +phase: 'DATA_LOADED' |} - | {| +phase: 'DATA_RELOAD' |} - | {| +phase: 'FATAL_ERROR', +error: Error |} - | {| - +phase: 'INITIALIZING', - +additionalData?: {| +attempt: Attempt | null, +message: string |}, - |}; - -export type Phase = $PropertyType; - -/** - * This represents the finite state machine for loading zip files. The phase represents - * where the state is now. - */ -export type ZipFileState = - | {| - +phase: 'NO_ZIP_FILE', - +zip: null, - +pathInZipFile: null, - |} - | {| - +phase: 'LIST_FILES_IN_ZIP_FILE', - +zip: JSZip, - +pathInZipFile: null, - |} - | {| - +phase: 'PROCESS_PROFILE_FROM_ZIP_FILE', - +zip: JSZip, - +pathInZipFile: string, - |} - | {| - +phase: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE', - +zip: JSZip, - +pathInZipFile: string, - |} - | {| - +phase: 'FILE_NOT_FOUND_IN_ZIP_FILE', - +zip: JSZip, - +pathInZipFile: string, - |} - | {| - +phase: 'VIEW_PROFILE_IN_ZIP_FILE', - +zip: JSZip, - +pathInZipFile: string, - |}; - -export type IsOpenPerPanelState = { [TabSlug]: boolean }; - -export type UrlSetupPhase = 'initial-load' | 'loading-profile' | 'done'; - -/* - * Experimental features that are mostly disabled by default. You need to enable - * them from the DevTools console with `experimental.enable()`, - * e.g. `experimental.enableEventDelayTracks()`. - */ -export type ExperimentalFlags = {| - +eventDelayTracks: boolean, - +cpuGraphs: boolean, - +processCPUTracks: boolean, -|}; - -export type AppState = {| - +view: AppViewState, - +urlSetupPhase: UrlSetupPhase, - +hasZoomedViaMousewheel: boolean, - +isSidebarOpenPerPanel: IsOpenPerPanelState, - +sidebarOpenCategories: Map>, - +panelLayoutGeneration: number, - +lastVisibleThreadTabSlug: TabSlug, - +trackThreadHeights: { - [key: ThreadsKey]: CssPixels, - }, - +isNewlyPublished: boolean, - +isDragAndDropDragging: boolean, - +isDragAndDropOverlayRegistered: boolean, - +experimental: ExperimentalFlags, - +currentProfileUploadedInformation: UploadedProfileInformation | null, - +browserConnectionStatus: BrowserConnectionStatus, -|}; - -export type UploadPhase = - | 'local' - | 'compressing' - | 'uploading' - | 'uploaded' - | 'error'; - -export type UploadState = {| - phase: UploadPhase, - uploadProgress: number, - error: Error | mixed, - abortFunction: () => void, - generation: number, -|}; - -export type PublishState = {| - +checkedSharingOptions: CheckedSharingOptions, - +upload: UploadState, - +isHidingStaleProfile: boolean, - +hasSanitizedProfile: boolean, - +prePublishedState: State | null, -|}; - -export type ZippedProfilesState = { - zipFile: ZipFileState, - error: Error | null, - selectedZipFileIndex: IndexIntoZipFileTable | null, - // In practice this should never contain null, but needs to support the - // TreeView interface. - expandedZipFileIndexes: Array, -}; - -export type SourceViewState = {| - scrollGeneration: number, - // Non-null if this source file was opened for a function from native code. - // In theory, multiple different libraries can have source files with the same - // path but different content. - // Null if the source file is not for native code or if the lib is not known, - // for example if the source view was opened via the URL (the source URL param - // currently discards the libIndex). - libIndex: IndexIntoLibs | null, - // The path to the source file. Null if a function without a file path was - // double clicked. - sourceFile: string | null, -|}; - -export type AssemblyViewState = {| - // Whether the assembly view panel is open within the bottom box. This can be - // true even if the bottom box itself is closed. - isOpen: boolean, - // When this is incremented, the assembly view scrolls to the "hotspot" line. - scrollGeneration: number, - // The native symbol for which the assembly code is being shown at the moment. - // Null if the initiating call node did not have a native symbol. - nativeSymbol: NativeSymbolInfo | null, - // The set of native symbols which contributed samples to the initiating call - // node. Often, this will just be one element (the same as `nativeSymbol`), - // but it can also be multiple elements, for example when double-clicking a - // function like `Vec::push` in an inverted call tree, if that function has - // been inlined into multiple different callers. - allNativeSymbolsForInitiatingCallNode: NativeSymbolInfo[], -|}; - -export type DecodedInstruction = {| - address: Address, - decodedString: string, -|}; - -export type SourceCodeStatus = - | {| type: 'LOADING', source: CodeLoadingSource |} - | {| type: 'ERROR', errors: SourceCodeLoadingError[] |} - | {| type: 'AVAILABLE', code: string |}; - -export type AssemblyCodeStatus = - | {| type: 'LOADING', source: CodeLoadingSource |} - | {| type: 'ERROR', errors: ApiQueryError[] |} - | {| type: 'AVAILABLE', instructions: DecodedInstruction[] |}; - -export type CodeLoadingSource = - | {| type: 'URL', url: string |} - | {| type: 'BROWSER_CONNECTION' |}; - -export type ApiQueryError = - | {| - type: 'NETWORK_ERROR', - url: string, - networkErrorMessage: string, - |} - // Used when the symbol server reported an error, for example because our - // request was bad. - | {| - type: 'SYMBOL_SERVER_API_ERROR', - apiErrorMessage: string, - |} - // Used when the symbol server's response was bad. - | {| - type: 'SYMBOL_SERVER_API_MALFORMED_RESPONSE', - errorMessage: string, - |} - // Used when the browser API reported an error, for example because our - // request was bad. - | {| - type: 'BROWSER_CONNECTION_ERROR', - browserConnectionErrorMessage: string, - |} - // Used when the browser's response was bad. - | {| - type: 'BROWSER_API_ERROR', - apiErrorMessage: string, - |} - | {| - type: 'BROWSER_API_MALFORMED_RESPONSE', - errorMessage: string, - |}; - -export type SourceCodeLoadingError = - | ApiQueryError - | {| type: 'NO_KNOWN_CORS_URL' |} - | {| - type: 'NOT_PRESENT_IN_ARCHIVE', - url: string, - pathInArchive: string, - |} - | {| - type: 'ARCHIVE_PARSING_ERROR', - url: string, - parsingErrorMessage: string, - |}; - -export type ProfileSpecificUrlState = {| - selectedThreads: Set | null, - implementation: ImplementationFilter, - lastSelectedCallTreeSummaryStrategy: CallTreeSummaryStrategy, - invertCallstack: boolean, - showUserTimings: boolean, - stackChartSameWidths: boolean, - committedRanges: StartEndRange[], - callTreeSearchString: string, - markersSearchString: string, - networkSearchString: string, - transforms: TransformStacksPerThread, - timelineType: TimelineType, - sourceView: SourceViewState, - assemblyView: AssemblyViewState, - isBottomBoxOpenPerPanel: IsOpenPerPanelState, - globalTrackOrder: TrackIndex[], - hiddenGlobalTracks: Set, - hiddenLocalTracksByPid: Map>, - localTrackOrderByPid: Map, - localTrackOrderChangedPids: Set, - showJsTracerSummary: boolean, - tabFilter: TabID | null, - legacyThreadOrder: ThreadIndex[] | null, - legacyHiddenThreads: ThreadIndex[] | null, -|}; - -export type UrlState = {| - +dataSource: DataSource, - // This is used for the "public" dataSource". - +hash: string, - // This is used for the "from-url" dataSource. - +profileUrl: string, - // This is used for the "compare" dataSource, to compare 2 profiles. - +profilesToCompare: string[] | null, - +selectedTab: TabSlug, - +pathInZipFile: string | null, - +profileName: string | null, - +profileSpecific: ProfileSpecificUrlState, - +symbolServerUrl: string | null, -|}; - -/** - * Localization State - */ -export type PseudoStrategy = null | 'bidi' | 'accented'; -export type L10nState = {| - +requestedLocales: string[] | null, - +pseudoStrategy: PseudoStrategy, - +localization: Localization, - +primaryLocale: string | null, - +direction: 'ltr' | 'rtl', -|}; - -/** - * Map of icons to their class names - */ -export type IconsWithClassNames = Map; - -export type CodeState = {| - +sourceCodeCache: Map, - +assemblyCodeCache: Map, -|}; - -export type State = {| - +app: AppState, - +profileView: ProfileViewState, - +urlState: UrlState, - +icons: IconsWithClassNames, - +zippedProfiles: ZippedProfilesState, - +publish: PublishState, - +code: CodeState, -|}; diff --git a/src/types/state.ts b/src/types/state.ts new file mode 100644 index 0000000000..e41079e2c3 --- /dev/null +++ b/src/types/state.ts @@ -0,0 +1,386 @@ +/* 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/. */ + +import type { + Action, + DataSource, + PreviewSelection, + ImplementationFilter, + CallTreeSummaryStrategy, + RequestedLib, + TrackReference, + TimelineType, + CheckedSharingOptions, + LastNonShiftClickInformation, +} from './actions'; +import type { TabSlug } from '../app-logic/tabs-handling'; +import type { StartEndRange, CssPixels, Milliseconds, Address } from './units'; +import type { + Profile, + ThreadIndex, + Pid, + TabID, + IndexIntoLibs, +} from './profile'; + +import type { + CallNodePath, + GlobalTrack, + LocalTrack, + TrackIndex, + MarkerIndex, + ThreadsKey, + NativeSymbolInfo, +} from './profile-derived'; +import type { Attempt } from '../utils/errors'; +import type { TransformStacksPerThread } from './transforms'; +import type JSZip from 'jszip'; +import type { IndexIntoZipFileTable } from '../profile-logic/zip-files'; +import type { PathSet } from '../utils/path'; +import type { UploadedProfileInformation as ImportedUploadedProfileInformation } from '../app-logic/uploaded-profiles-db'; +import type { BrowserConnectionStatus } from '../app-logic/browser-connection'; + +export type Reducer = (state: T | undefined, action: Action) => T; + +// This type is defined in uploaded-profiles-db.js because it is very tied to +// the data stored in our local IndexedDB, and we don't want to change it +// lightly, without changing the DB code. +// We reexport this type here mostly for easier access. +export type UploadedProfileInformation = ImportedUploadedProfileInformation; + +export type SymbolicationStatus = 'DONE' | 'SYMBOLICATING'; +export type ThreadViewOptions = { + readonly selectedNonInvertedCallNodePath: CallNodePath; + readonly selectedInvertedCallNodePath: CallNodePath; + readonly expandedNonInvertedCallNodePaths: PathSet; + readonly expandedInvertedCallNodePaths: PathSet; + readonly selectedMarker: MarkerIndex | null; + readonly selectedNetworkMarker: MarkerIndex | null; +}; + +export type ThreadViewOptionsPerThreads = { + [K in ThreadsKey]: ThreadViewOptions; +}; + +export type TableViewOptions = { + readonly fixedColumnWidths: Array | null; +}; + +export type TableViewOptionsPerTab = { [K in TabSlug]: TableViewOptions }; + +export type RightClickedCallNode = { + readonly threadsKey: ThreadsKey; + readonly callNodePath: CallNodePath; +}; + +export type MarkerReference = { + readonly threadsKey: ThreadsKey; + readonly markerIndex: MarkerIndex; +}; + +/** + * Profile view state + */ +export type ProfileViewState = { + readonly viewOptions: { + perThread: ThreadViewOptionsPerThreads; + symbolicationStatus: SymbolicationStatus; + waitingForLibs: Set; + previewSelection: PreviewSelection; + scrollToSelectionGeneration: number; + focusCallTreeGeneration: number; + rootRange: StartEndRange; + lastNonShiftClick: LastNonShiftClickInformation | null; + rightClickedTrack: TrackReference | null; + rightClickedCallNode: RightClickedCallNode | null; + rightClickedMarker: MarkerReference | null; + hoveredMarker: MarkerReference | null; + mouseTimePosition: Milliseconds | null; + perTab: TableViewOptionsPerTab; + }; + readonly profile: Profile | null; + globalTracks: GlobalTrack[]; + localTracksByPid: Map; +}; + +export type AppViewState = + | { readonly phase: 'ROUTE_NOT_FOUND' } + | { readonly phase: 'TRANSITIONING_FROM_STALE_PROFILE' } + | { readonly phase: 'PROFILE_LOADED' } + | { readonly phase: 'DATA_LOADED' } + | { readonly phase: 'DATA_RELOAD' } + | { readonly phase: 'FATAL_ERROR'; readonly error: Error } + | { + readonly phase: 'INITIALIZING'; + readonly additionalData?: { + readonly attempt: Attempt | null; + readonly message: string; + }; + }; + +export type Phase = AppViewState['phase']; + +/** + * This represents the finite state machine for loading zip files. The phase represents + * where the state is now. + */ +export type ZipFileState = + | { + readonly phase: 'NO_ZIP_FILE'; + readonly zip: null; + readonly pathInZipFile: null; + } + | { + readonly phase: 'LIST_FILES_IN_ZIP_FILE'; + readonly zip: JSZip; + readonly pathInZipFile: null; + } + | { + readonly phase: 'PROCESS_PROFILE_FROM_ZIP_FILE'; + readonly zip: JSZip; + readonly pathInZipFile: string; + } + | { + readonly phase: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE'; + readonly zip: JSZip; + readonly pathInZipFile: string; + } + | { + readonly phase: 'FILE_NOT_FOUND_IN_ZIP_FILE'; + readonly zip: JSZip; + readonly pathInZipFile: string; + } + | { + readonly phase: 'VIEW_PROFILE_IN_ZIP_FILE'; + readonly zip: JSZip; + readonly pathInZipFile: string; + }; + +export type IsOpenPerPanelState = { [K in TabSlug]: boolean }; + +export type UrlSetupPhase = 'initial-load' | 'loading-profile' | 'done'; + +/* + * Experimental features that are mostly disabled by default. You need to enable + * them from the DevTools console with `experimental.enable()`, + * e.g. `experimental.enableEventDelayTracks()`. + */ +export type ExperimentalFlags = { + readonly eventDelayTracks: boolean; + readonly cpuGraphs: boolean; + readonly processCPUTracks: boolean; +}; + +export type AppState = { + readonly view: AppViewState; + readonly urlSetupPhase: UrlSetupPhase; + readonly hasZoomedViaMousewheel: boolean; + readonly isSidebarOpenPerPanel: IsOpenPerPanelState; + readonly sidebarOpenCategories: Map>; + readonly panelLayoutGeneration: number; + readonly lastVisibleThreadTabSlug: TabSlug; + readonly trackThreadHeights: Record; + readonly isNewlyPublished: boolean; + readonly isDragAndDropDragging: boolean; + readonly isDragAndDropOverlayRegistered: boolean; + readonly experimental: ExperimentalFlags; + readonly currentProfileUploadedInformation: UploadedProfileInformation | null; + readonly browserConnectionStatus: BrowserConnectionStatus; +}; + +export type UploadPhase = + | 'local' + | 'compressing' + | 'uploading' + | 'uploaded' + | 'error'; + +export type UploadState = { + phase: UploadPhase; + uploadProgress: number; + error: Error | unknown; + abortFunction: () => void; + generation: number; +}; + +export type PublishState = { + readonly checkedSharingOptions: CheckedSharingOptions; + readonly upload: UploadState; + readonly isHidingStaleProfile: boolean; + readonly hasSanitizedProfile: boolean; + readonly prePublishedState: State | null; +}; + +export type ZippedProfilesState = { + zipFile: ZipFileState; + error: Error | null; + selectedZipFileIndex: IndexIntoZipFileTable | null; + // In practice this should never contain null, but needs to support the + // TreeView interface. + expandedZipFileIndexes: Array; +}; + +export type SourceViewState = { + scrollGeneration: number; + // Non-null if this source file was opened for a function from native code. + // In theory, multiple different libraries can have source files with the same + // path but different content. + // Null if the source file is not for native code or if the lib is not known, + // for example if the source view was opened via the URL (the source URL param + // currently discards the libIndex). + libIndex: IndexIntoLibs | null; + // The path to the source file. Null if a function without a file path was + // double clicked. + sourceFile: string | null; +}; + +export type AssemblyViewState = { + // Whether the assembly view panel is open within the bottom box. This can be + // true even if the bottom box itself is closed. + isOpen: boolean; + // When this is incremented, the assembly view scrolls to the "hotspot" line. + scrollGeneration: number; + // The native symbol for which the assembly code is being shown at the moment. + // Null if the initiating call node did not have a native symbol. + nativeSymbol: NativeSymbolInfo | null; + // The set of native symbols which contributed samples to the initiating call + // node. Often, this will just be one element (the same as `nativeSymbol`), + // but it can also be multiple elements, for example when double-clicking a + // function like `Vec::push` in an inverted call tree, if that function has + // been inlined into multiple different callers. + allNativeSymbolsForInitiatingCallNode: NativeSymbolInfo[]; +}; + +export type DecodedInstruction = { + address: Address; + decodedString: string; +}; + +export type SourceCodeStatus = + | { type: 'LOADING'; source: CodeLoadingSource } + | { type: 'ERROR'; errors: SourceCodeLoadingError[] } + | { type: 'AVAILABLE'; code: string }; + +export type AssemblyCodeStatus = + | { type: 'LOADING'; source: CodeLoadingSource } + | { type: 'ERROR'; errors: ApiQueryError[] } + | { type: 'AVAILABLE'; instructions: DecodedInstruction[] }; + +export type CodeLoadingSource = + | { type: 'URL'; url: string } + | { type: 'BROWSER_CONNECTION' }; + +export type ApiQueryError = + | { + type: 'NETWORK_ERROR'; + url: string; + networkErrorMessage: string; + } + // Used when the symbol server reported an error, for example because our + // request was bad. + | { + type: 'SYMBOL_SERVER_API_ERROR'; + apiErrorMessage: string; + } + // Used when the symbol server's response was bad. + | { + type: 'SYMBOL_SERVER_API_MALFORMED_RESPONSE'; + errorMessage: string; + } + // Used when the browser API reported an error, for example because our + // request was bad. + | { + type: 'BROWSER_CONNECTION_ERROR'; + browserConnectionErrorMessage: string; + } + // Used when the browser's response was bad. + | { + type: 'BROWSER_API_ERROR'; + apiErrorMessage: string; + } + | { + type: 'BROWSER_API_MALFORMED_RESPONSE'; + errorMessage: string; + }; + +export type SourceCodeLoadingError = + | ApiQueryError + | { type: 'NO_KNOWN_CORS_URL' } + | { + type: 'NOT_PRESENT_IN_ARCHIVE'; + url: string; + pathInArchive: string; + } + | { + type: 'ARCHIVE_PARSING_ERROR'; + url: string; + parsingErrorMessage: string; + }; + +export type ProfileSpecificUrlState = { + selectedThreads: Set | null; + implementation: ImplementationFilter; + lastSelectedCallTreeSummaryStrategy: CallTreeSummaryStrategy; + invertCallstack: boolean; + showUserTimings: boolean; + stackChartSameWidths: boolean; + committedRanges: StartEndRange[]; + callTreeSearchString: string; + markersSearchString: string; + networkSearchString: string; + transforms: TransformStacksPerThread; + timelineType: TimelineType; + sourceView: SourceViewState; + assemblyView: AssemblyViewState; + isBottomBoxOpenPerPanel: IsOpenPerPanelState; + globalTrackOrder: TrackIndex[]; + hiddenGlobalTracks: Set; + hiddenLocalTracksByPid: Map>; + localTrackOrderByPid: Map; + localTrackOrderChangedPids: Set; + showJsTracerSummary: boolean; + tabFilter: TabID | null; + legacyThreadOrder: ThreadIndex[] | null; + legacyHiddenThreads: ThreadIndex[] | null; +}; + +export type UrlState = { + readonly dataSource: DataSource; + // This is used for the "public" dataSource". + readonly hash: string; + // This is used for the "from-url" dataSource. + readonly profileUrl: string; + // This is used for the "compare" dataSource, to compare 2 profiles. + readonly profilesToCompare: string[] | null; + readonly selectedTab: TabSlug; + readonly pathInZipFile: string | null; + readonly profileName: string | null; + readonly profileSpecific: ProfileSpecificUrlState; + readonly symbolServerUrl: string | null; +}; + +/** + * Localization State + */ +export type PseudoStrategy = null | 'bidi' | 'accented'; + +/** + * Map of icons to their class names + */ +export type IconsWithClassNames = Map; + +export type CodeState = { + readonly sourceCodeCache: Map; + readonly assemblyCodeCache: Map; +}; + +export type State = { + readonly app: AppState; + readonly profileView: ProfileViewState; + readonly urlState: UrlState; + readonly icons: IconsWithClassNames; + readonly zippedProfiles: ZippedProfilesState; + readonly publish: PublishState; + readonly code: CodeState; +}; diff --git a/src/types/store.js b/src/types/store.ts similarity index 82% rename from src/types/store.js rename to src/types/store.ts index a58b7cfee9..c2bccdb594 100644 --- a/src/types/store.js +++ b/src/types/store.ts @@ -1,11 +1,11 @@ /* 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/. */ -// @flow import type { Store as ReduxStore } from 'redux'; -import type { Action as ActionsRef } from './actions'; -import type { State as StateRef } from './state'; +import type { ThunkDispatch } from 'redux-thunk'; +import type { Action } from './actions'; +import type { State } from './state'; /** * This file contains type definitions for the Redux store. Unlike the definitions @@ -13,11 +13,6 @@ import type { State as StateRef } from './state'; * specific union of Actions and the store's specific State type definition. */ -// Re-export these here so they are easily available from wherever and avoids -// circular dependencies. -export type Action = ActionsRef; -export type State = StateRef; - /** * The selector type enforces the selector pattern, and should be used when * defining selectors. These selectors can be simple functions, or created using @@ -26,7 +21,7 @@ export type State = StateRef; * * See the type below for additional considerations. */ -export type Selector = (State) => T; +export type Selector = (state: State) => T; /** * Selectors generally come in two different varieties: selectors that trivially access @@ -54,30 +49,31 @@ export type Selector = (State) => T; * See: https://github.com/reduxjs/reselect/blob/master/README.md#q-how-do-i-create-a-selector-that-takes-an-argument */ export type DangerousSelectorWithArguments = ( - State, - A1, - A2, - A3 + state: State, + arg1: A1, + arg2: A2, + arg3: A3 ) => T; -type ThunkDispatch = (action: ThunkAction) => Returns; -type PlainDispatch = (action: Action) => Action; export type GetState = () => State; /** * A thunk action */ -export type ThunkAction = (dispatch: Dispatch, GetState) => Returns; +export type ThunkAction = ( + dispatch: Dispatch, + getState: GetState +) => Returns; /** * The `dispatch` function can accept either a plain action or a thunk action. * This is similar to a type `(action: Action | ThunkAction) => any` except this * allows to type the return value as well. */ -export type Dispatch = PlainDispatch & ThunkDispatch; +export type Dispatch = ThunkDispatch; /** * Export a store that is opinionated about our State definition, and the union * of all Actions, as well as specific Dispatch behavior. */ -export type Store = ReduxStore; +export type Store = ReduxStore & { dispatch: Dispatch }; diff --git a/src/types/transforms.js b/src/types/transforms.ts similarity index 88% rename from src/types/transforms.js rename to src/types/transforms.ts index 54d267fd95..4487a0c4ab 100644 --- a/src/types/transforms.js +++ b/src/types/transforms.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - /** * Transforms are the minimal representation some kind of transformation to the data * that is used to transform the sample and stack information of a profile. They are @@ -32,7 +30,7 @@ import type { ImplementationFilter } from './actions'; export type FilterSamplesType = 'marker-search'; /* - * Define all of the transforms on an object to conveniently access $ObjMap and do + * Define all of the transforms on an object to conveniently access mapped types and do * nice things like iterate over every transform type. There is no way to create a * union from a tuple in flow. * @@ -93,12 +91,12 @@ export type TransformDefinitions = { * ↓ ↓ * A:1,0 X:1,1 */ - 'focus-subtree': {| - +type: 'focus-subtree', - +callNodePath: CallNodePath, - +implementation: ImplementationFilter, - +inverted: boolean, - |}, + 'focus-subtree': { + readonly type: 'focus-subtree'; + callNodePath: CallNodePath; + readonly implementation: ImplementationFilter; + readonly inverted: boolean; + }; /** * This is the same operation as the FocusSubtree, but it is performed on each usage @@ -124,10 +122,10 @@ export type TransformDefinitions = { * v * D:2,2 */ - 'focus-function': {| - +type: 'focus-function', - +funcIndex: IndexIntoFuncTable, - |}, + 'focus-function': { + readonly type: 'focus-function'; + readonly funcIndex: IndexIntoFuncTable; + }; /** * The MergeCallNode transform represents merging a CallNode into the parent CallNode. The @@ -172,11 +170,11 @@ export type TransformDefinitions = { * This same operation is not applied to an inverted call stack as it has been deemed * not particularly useful, and prone to not give the expected results. */ - 'merge-call-node': {| - +type: 'merge-call-node', - +callNodePath: CallNodePath, - +implementation: ImplementationFilter, - |}, + 'merge-call-node': { + readonly type: 'merge-call-node'; + callNodePath: CallNodePath; + readonly implementation: ImplementationFilter; + }; /** * The MergeFunctions transform is similar to the MergeCallNode, except it merges a single @@ -197,10 +195,10 @@ export type TransformDefinitions = { * v v * E:1,1 G:1,1 */ - 'merge-function': {| - +type: 'merge-function', - +funcIndex: IndexIntoFuncTable, - |}, + 'merge-function': { + readonly type: 'merge-function'; + readonly funcIndex: IndexIntoFuncTable; + }; /** * The DropFunction transform removes samples from the thread that have a function @@ -217,10 +215,10 @@ export type TransformDefinitions = { * v * D:1,1 */ - 'drop-function': {| - +type: 'drop-function', - +funcIndex: IndexIntoFuncTable, - |}, + 'drop-function': { + readonly type: 'drop-function'; + readonly funcIndex: IndexIntoFuncTable; + }; /** * Collapse resource takes CallNodes that are of a consecutive library, and collapses @@ -238,13 +236,13 @@ export type TransformDefinitions = { * v * D */ - 'collapse-resource': {| - +type: 'collapse-resource', - +resourceIndex: IndexIntoResourceTable, + 'collapse-resource': { + readonly type: 'collapse-resource'; + readonly resourceIndex: IndexIntoResourceTable; // This is the index of the newly created function that represents the collapsed stack. - +collapsedFuncIndex: IndexIntoFuncTable, - +implementation: ImplementationFilter, - |}, + readonly collapsedFuncIndex: IndexIntoFuncTable; + readonly implementation: ImplementationFilter; + }; /** * Collapse direct recursion takes a function that calls itself recursively and collapses @@ -262,11 +260,11 @@ export type TransformDefinitions = { * ↓ * C */ - 'collapse-direct-recursion': {| - +type: 'collapse-direct-recursion', - +funcIndex: IndexIntoFuncTable, - +implementation: ImplementationFilter, - |}, + 'collapse-direct-recursion': { + readonly type: 'collapse-direct-recursion'; + readonly funcIndex: IndexIntoFuncTable; + readonly implementation: ImplementationFilter; + }; /** * Collapse recursion takes a function that calls itself recursively (directly @@ -284,10 +282,10 @@ export type TransformDefinitions = { * ↓ * D */ - 'collapse-recursion': {| - +type: 'collapse-recursion', - +funcIndex: IndexIntoFuncTable, - |}, + 'collapse-recursion': { + readonly type: 'collapse-recursion'; + readonly funcIndex: IndexIntoFuncTable; + }; /** * Collapse the subtree of a function into that function across the entire tree. @@ -306,10 +304,10 @@ export type TransformDefinitions = { * v v v v * E:1,1 G:1,1 I:1,1 J:1,1 */ - 'collapse-function-subtree': {| - +type: 'collapse-function-subtree', - +funcIndex: IndexIntoFuncTable, - |}, + 'collapse-function-subtree': { + readonly type: 'collapse-function-subtree'; + readonly funcIndex: IndexIntoFuncTable; + }; /** * Focus on the functions that belong to the same category as the current function. * A example of this is given below with 'function:category' as the node name. @@ -328,31 +326,28 @@ export type TransformDefinitions = { * v v * F:JS A:JS */ - 'focus-category': {| - +type: 'focus-category', - +category: IndexIntoCategoryList, - |}, + 'focus-category': { + readonly type: 'focus-category'; + readonly category: IndexIntoCategoryList; + }; /** * Filter the samples in the thread by the filter. * Currently it only supports filtering by the marker name but can be extended * to support more filters in the future. */ - 'filter-samples': {| - +type: 'filter-samples', + 'filter-samples': { + readonly type: 'filter-samples'; // Expand this type when you need to support more than just the marker. - +filterType: FilterSamplesType, - +filter: string, - |}, + readonly filterType: FilterSamplesType; + readonly filter: string; + }; }; -// Extract the transforms into a union. -export type Transform = $Values; - -// This pulls the string value out of { type } for a transform. -type ExtractType = (transform: S) => T; - -export type TransformType = $Values<$ObjMap>; +// Union of transforms +export type Transform = TransformDefinitions[keyof TransformDefinitions]; +// Union of transform types +export type TransformType = Transform['type']; export type TransformStack = Transform[]; export type TransformStacksPerThread = { [key: ThreadsKey]: TransformStack }; diff --git a/src/utils/flow.js b/src/utils/flow.ts similarity index 83% rename from src/utils/flow.js rename to src/utils/flow.ts index 1f6e834b3c..9786b86b93 100644 --- a/src/utils/flow.js +++ b/src/utils/flow.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import type { TabSlug } from '../app-logic/tabs-handling'; import type { TransformType } from 'firefox-profiler/types'; @@ -20,7 +19,7 @@ import type { TransformType } from 'firefox-profiler/types'; * more readable. */ export function assertExhaustiveCheck( - notValid: empty, + notValid: never, errorMessage: string = `There was an unhandled case for the value: "${notValid}"` ): void { throw new Error(errorMessage); @@ -40,7 +39,7 @@ export function immutableUpdate(object: T, ...rest: any[]): T { * throw an error so that any arbitrary string can be converted, e.g. from a URL. */ export function toValidTabSlug(tabSlug: any): TabSlug | null { - const coercedTabSlug = (tabSlug: TabSlug); + const coercedTabSlug = tabSlug as TabSlug; switch (coercedTabSlug) { case 'calltree': case 'stack-chart': @@ -53,7 +52,6 @@ export function toValidTabSlug(tabSlug: any): TabSlug | null { default: { // The coerced type SHOULD be empty here. If in reality we get // here, then it's not a valid transform type, so return null. - (coercedTabSlug: empty); return null; } } @@ -79,7 +77,7 @@ export function ensureIsValidTabSlug(type: string): TabSlug { */ export function convertToTransformType(type: string): TransformType | null { // Coerce this into a TransformType even if it's not one. - const coercedType = ((type: any): TransformType); + const coercedType = type as TransformType; switch (coercedType) { // Exhaustively check each TransformType. The default arm will assert that // we have been exhaustive. @@ -98,7 +96,6 @@ export function convertToTransformType(type: string): TransformType | null { default: { // The coerced type SHOULD be empty here. If in reality we get // here, then it's not a valid transform type, so return null. - (coercedType: empty); return null; } } @@ -109,54 +106,54 @@ export function convertToTransformType(type: string): TransformType | null { * This is equivalent to: (((value: A): any): B) */ export function coerce(item: A): B { - return (item: any); + return item as any; } /** * It can be helpful to coerce one type that matches the shape of another. */ -export function coerceMatchingShape(item: $Shape): T { - return (item: any); +export function coerceMatchingShape(item: Partial): T { + return item as any; } /** * This is a type-friendly version of Object.values that assumes the object has * a Map-like structure. */ -export function objectValues( +export function objectValues>( object: Obj ): Value[] { - return (Object.values: any)(object); + return Object.values(object) as any; } /** * This is a type-friendly version of Object.entries that assumes the object has * a Map-like structure. */ -export function objectEntries(object: { - [Key]: Value, +export function objectEntries(object: { + [K in Key]: Value; }): Array<[Key, Value]> { - return (Object.entries: any)(object); + return Object.entries(object) as any; } /** * This is a type-friendly version of Object.entries that assumes the object has * a Map-like structure. */ -export function objectMap( - object: { [Key]: Value }, - fn: (Value, Key) => Return -): { [Key]: Return } { - const result: { [Key]: Return } = {}; +export function objectMap( + object: { [K in Key]: Value }, + fn: (value: Value, key: Key) => Return +): { [K in Key]: Return } { + const result: { [K in Key]: Return } = {} as any; for (const [key, value] of objectEntries(object)) { result[key] = fn(value, key); } return result; } -// Generic bounds with an Object is a false positive. -// eslint-disable-next-line flowtype/no-weak-types -export function getObjectValuesAsUnion(obj: T): Array<$Values> { +export function getObjectValuesAsUnion>( + obj: T +): Array { return Object.values(obj); } @@ -174,7 +171,10 @@ export function ensureIsTransformType(type: string): TransformType { return assertedType; } -export function ensureExists(item: ?T, message: ?string): T { +export function ensureExists( + item: T | null | undefined, + message?: string +): T { if (item === null) { throw new Error(message || 'Expected an item to exist, and it was null.'); } @@ -189,6 +189,6 @@ export function ensureExists(item: ?T, message: ?string): T { /** * Returns the first item from Set in a type friendly manner. */ -export function getFirstItemFromSet(set: Set): T | void { +export function getFirstItemFromSet(set: Set): T | undefined { return set.values().next().value; } diff --git a/src/utils/format-numbers.js b/src/utils/format-numbers.ts similarity index 99% rename from src/utils/format-numbers.js rename to src/utils/format-numbers.ts index a9ecd2e392..bff89a78b7 100644 --- a/src/utils/format-numbers.js +++ b/src/utils/format-numbers.ts @@ -2,8 +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/. */ -// @flow - import memoize from 'memoize-immutable'; import NamedTupleMap from 'namedtuplemap'; @@ -12,7 +10,7 @@ import type { Milliseconds, Nanoseconds, WeightType, -} from 'firefox-profiler/types'; +} from '../types'; import { assertExhaustiveCheck } from './flow'; // Calling `toLocalestring` repeatedly in a tight loop can be a performance @@ -24,9 +22,9 @@ function _getNumberFormat({ places, style, }: { - places: number, - style: 'decimal' | 'percent', -}) { + places: number; + style: 'decimal' | 'percent'; +}): Intl.NumberFormat { return new Intl.NumberFormat(undefined, { minimumFractionDigits: places, maximumFractionDigits: places, @@ -516,7 +514,7 @@ export function formatTimestamp( export function formatValueTotal( a: number, b: number, - formatNum: (number) => string = String, + formatNum: (num: number) => string = String, includePercent: boolean = true ) { const value_total = formatNum(a) + ' / ' + formatNum(b); diff --git a/src/utils/index.js b/src/utils/index.ts similarity index 91% rename from src/utils/index.js rename to src/utils/index.ts index db19e3501b..175bfb6e79 100644 --- a/src/utils/index.js +++ b/src/utils/index.ts @@ -1,14 +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/. */ -// @flow import type { - KeyboardModifiers, Marker, Milliseconds, StartEndRange, + KeyboardModifiers, } from 'firefox-profiler/types'; +import type { + MouseEvent as SyntheticMouseEvent, + KeyboardEvent as SyntheticKeyboardEvent, +} from 'react'; /** * Firefox has issues switching quickly between fill style colors, as the CSS color @@ -36,18 +39,15 @@ export class FastFillStyle { * Perform a simple shallow object equality check. */ export function objectShallowEquals< - // False positive, Objects are fine as generic trait bounds. - // eslint-disable-next-line flowtype/no-weak-types - A: Object, - // eslint-disable-next-line flowtype/no-weak-types - B: Object, + A extends Record, + B extends Record, >(a: A, b: B): boolean { let aLength = 0; let bLength = 0; for (const key in a) { if (Object.prototype.hasOwnProperty.call(a, key)) { aLength++; - if (a[key] !== b[key]) { + if (a[key] !== (b as any)[key]) { return false; } } @@ -102,8 +102,8 @@ export function getTrackSelectionModifiers( event: | MouseEvent | KeyboardEvent - | SyntheticMouseEvent<> - | SyntheticKeyboardEvent<> + | SyntheticMouseEvent + | SyntheticKeyboardEvent ): KeyboardModifiers { return { ctrlOrMeta: (event.ctrlKey || event.metaKey) && !event.altKey, @@ -127,7 +127,7 @@ export function countPositiveValues(arr: Array): number { * If multiple entries with the highest value exist, it returns the key of the * first encountered highest value. */ -export function mapGetKeyWithMaxValue(map: Map): K | void { +export function mapGetKeyWithMaxValue(map: Map): K | undefined { let maxValue = -Infinity; let keyForMaxValue; for (const [key, value] of map) { diff --git a/src/utils/path.js b/src/utils/path.ts similarity index 81% rename from src/utils/path.js rename to src/utils/path.ts index 814d741858..cf2c44fe8c 100644 --- a/src/utils/path.js +++ b/src/utils/path.ts @@ -2,8 +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/. */ -// @flow - import type { CallNodePath, IndexIntoFuncTable } from 'firefox-profiler/types'; export function arePathsEqual(a: CallNodePath, b: CallNodePath): boolean { @@ -95,7 +93,10 @@ export class PathSet implements Iterable { } } - forEach(func: (CallNodePath, CallNodePath, PathSet) => void, thisArg?: any) { + forEach( + func: (value: CallNodePath, value2: CallNodePath, set: PathSet) => void, + thisArg?: any + ) { for (const entry of this) { func.call(thisArg, entry, entry, this); } @@ -109,21 +110,7 @@ export class PathSet implements Iterable { return this._table.size; } - // Because Flow doesn't understand Symbols and well-known symbols yet, we need - // to resort to this hack to make it possible to implement the iterator. - // See https://github.com/facebook/flow/issues/3258 for more information - // and https://stackoverflow.com/questions/48491307/iterable-class-in-flow for - // the solution used here. - - // $FlowFixMe ignore Flow error about computed properties in a class *[Symbol.iterator]() { yield* this._table.values(); } - - /*:: - @@iterator(): * { - // $FlowFixMe ignore Flow error about Symbol support - return this[Symbol.iterator]() - } - */ } diff --git a/src/utils/query-api.js b/src/utils/query-api.ts similarity index 91% rename from src/utils/query-api.js rename to src/utils/query-api.ts index 8d146172ec..313f9d881f 100644 --- a/src/utils/query-api.js +++ b/src/utils/query-api.ts @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import type { ApiQueryError } from 'firefox-profiler/types'; +import type { MixedObject, ApiQueryError } from 'firefox-profiler/types'; import type { BrowserConnection } from 'firefox-profiler/app-logic/browser-connection'; /** @@ -17,7 +15,7 @@ import type { BrowserConnection } from 'firefox-profiler/app-logic/browser-conne export interface ExternalCommunicationDelegate { // Fetch a cross-origin URL and return its Response. If postData is specified, // the method should be POST. - fetchUrlResponse(url: string, postData?: MixedObject): Promise; + fetchUrlResponse(url: string, postData?: string): Promise; // Query the symbolication API of the browser, if a connection to the browser // is available. @@ -28,8 +26,8 @@ export interface ExternalCommunicationDelegate { } export type ApiQueryResult = - | { type: 'SUCCESS', convertedResponse: T } - | { type: 'ERROR', errors: ApiQueryError[] }; + | { type: 'SUCCESS'; convertedResponse: T } + | { type: 'ERROR'; errors: ApiQueryError[] }; /** * Sends a JSON query to the browser symbolication API and, if supplied, to a @@ -40,7 +38,7 @@ export async function queryApiWithFallback( requestJson: string, symbolServerUrlForFallback: string | null, delegate: ExternalCommunicationDelegate, - convertJsonResponse: (MixedObject) => T + convertJsonResponse: (responseJson: MixedObject) => T ): Promise> { const errors: ApiQueryError[] = []; @@ -134,12 +132,12 @@ export class RegularExternalCommunicationDelegate this._callbacks = callbacks; } - async fetchUrlResponse(url: string, postData?: MixedObject) { + async fetchUrlResponse(url: string, postData?: string) { this._callbacks.onBeginUrlRequest(url); - const requestInit = + const requestInit: RequestInit = postData !== undefined ? { - body: postData, + body: JSON.stringify(postData), method: 'POST', mode: 'cors', credentials: 'omit', diff --git a/src/utils/range-set.js b/src/utils/range-set.ts similarity index 99% rename from src/utils/range-set.js rename to src/utils/range-set.ts index a94f1ae4fe..114372cdd7 100644 --- a/src/utils/range-set.js +++ b/src/utils/range-set.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import type { StartEndRange } from 'firefox-profiler/types'; diff --git a/src/utils/shorten-url.js b/src/utils/shorten-url.ts similarity index 99% rename from src/utils/shorten-url.js rename to src/utils/shorten-url.ts index 73fc31bf87..b2a436618c 100644 --- a/src/utils/shorten-url.js +++ b/src/utils/shorten-url.ts @@ -2,8 +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/. */ -// @flow - import { PROFILER_SERVER_ORIGIN } from 'firefox-profiler/app-logic/constants'; const ACCEPT_HEADER_VALUE = 'application/vnd.firefox-profiler+json;version=1.0'; diff --git a/src/utils/special-paths.js b/src/utils/special-paths.ts similarity index 93% rename from src/utils/special-paths.js rename to src/utils/special-paths.ts index 13f35eb0b6..2808df90de 100644 --- a/src/utils/special-paths.js +++ b/src/utils/special-paths.ts @@ -2,34 +2,32 @@ * 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/. */ -// @flow - import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; export type ParsedFileNameFromSymbolication = - | {| - type: 'normal', - path: string, - |} - | {| - type: 'hg' | 'git', - repo: string, - path: string, - rev: string, - |} - | {| - type: 's3', - bucket: string, - digest: string, - path: string, - |} - | {| - type: 'cargo', - registry: string, - crate: string, - version: string, - path: string, - |}; + | { + type: 'normal'; + path: string; + } + | { + type: 'hg' | 'git'; + repo: string; + path: string; + rev: string; + } + | { + type: 's3'; + bucket: string; + digest: string; + path: string; + } + | { + type: 'cargo'; + registry: string; + crate: string; + version: string; + path: string; + }; // Describes how to obtain a source file from the web. // In the simplest case, the source code is served as a cross-origin accessible @@ -45,8 +43,8 @@ export type ParsedFileNameFromSymbolication = // SourceFileDownloadRecipe; SourceFileDownloadRecipe only covers sources that // can be downloaded from the web. export type SourceFileDownloadRecipe = - | { type: 'CORS_ENABLED_SINGLE_FILE', url: string } - | { type: 'CORS_ENABLED_ARCHIVE', archiveUrl: string, pathInArchive: string } + | { type: 'CORS_ENABLED_SINGLE_FILE'; url: string } + | { type: 'CORS_ENABLED_ARCHIVE'; archiveUrl: string; pathInArchive: string } | { type: 'NO_KNOWN_CORS_URL' }; // For native code, the symbolication API returns special filenames that allow @@ -213,6 +211,6 @@ export function getDownloadRecipeForSourceFile( return { type: 'NO_KNOWN_CORS_URL' }; } default: - throw assertExhaustiveCheck(parsedFile.type, 'unhandled ParsedFile type'); + throw assertExhaustiveCheck(parsedFile, 'unhandled ParsedFile type'); } } diff --git a/src/utils/string-table.js b/src/utils/string-table.ts similarity index 97% rename from src/utils/string-table.js rename to src/utils/string-table.ts index 67b9c088be..457fabd526 100644 --- a/src/utils/string-table.js +++ b/src/utils/string-table.ts @@ -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/. */ -// @flow import type { IndexIntoStringTable } from 'firefox-profiler/types'; const _cachedTables: WeakMap = new WeakMap(); @@ -61,7 +60,7 @@ export class StringTable { return table; } - getString(index: IndexIntoStringTable, els: ?string): string { + getString(index: IndexIntoStringTable, els?: string | null): string { if (!this.hasIndex(index)) { if (els) { console.warn(`index ${index} not in StringTable`); From 066d54876e101c7be99c4cbdd3b42c1e17fcb184 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:40:07 -0400 Subject: [PATCH 050/124] Convert src/reducers. --- src/reducers/{app.js => app.ts} | 7 +++---- src/reducers/{code.js => code.ts} | 1 - src/reducers/{icons.js => icons.ts} | 1 - src/reducers/{index.js => index.ts} | 1 - src/reducers/{profile-view.js => profile-view.ts} | 9 ++++----- src/reducers/{publish.js => publish.ts} | 3 +-- src/reducers/{url-state.js => url-state.ts} | 13 ++++++------- .../{zipped-profiles.js => zipped-profiles.ts} | 7 +++---- 8 files changed, 17 insertions(+), 25 deletions(-) rename src/reducers/{app.js => app.ts} (98%) rename src/reducers/{code.js => code.ts} (99%) rename src/reducers/{icons.js => icons.ts} (98%) rename src/reducers/{index.js => index.ts} (99%) rename src/reducers/{profile-view.js => profile-view.ts} (99%) rename src/reducers/{publish.js => publish.ts} (98%) rename src/reducers/{url-state.js => url-state.ts} (98%) rename src/reducers/{zipped-profiles.js => zipped-profiles.ts} (97%) diff --git a/src/reducers/app.js b/src/reducers/app.ts similarity index 98% rename from src/reducers/app.js rename to src/reducers/app.ts index 57751dc5d8..d3cff8785e 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.ts @@ -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/. */ -// @flow import { combineReducers } from 'redux'; import { tabSlugs } from '../app-logic/tabs-handling'; @@ -83,8 +82,8 @@ const hasZoomedViaMousewheel: Reducer = (state = false, action) => { } }; -function _getSidebarInitialState() { - const state = {}; +function _getSidebarInitialState(): IsOpenPerPanelState { + const state = {} as IsOpenPerPanelState; tabSlugs.forEach((tabSlug) => (state[tabSlug] = false)); state.calltree = true; state['marker-table'] = true; @@ -295,7 +294,7 @@ const processCPUTracks: Reducer = (state = false, action) => { * its uploaded information in the IndexedDB. */ const currentProfileUploadedInformation: Reducer< - UploadedProfileInformation | null, + UploadedProfileInformation | null > = (state = null, action) => { switch (action.type) { case 'SET_CURRENT_PROFILE_UPLOADED_INFORMATION': diff --git a/src/reducers/code.js b/src/reducers/code.ts similarity index 99% rename from src/reducers/code.js rename to src/reducers/code.ts index 2bf31b39f7..d9f8054800 100644 --- a/src/reducers/code.js +++ b/src/reducers/code.ts @@ -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/. */ -// @flow import type { Reducer, SourceCodeStatus, diff --git a/src/reducers/icons.js b/src/reducers/icons.ts similarity index 98% rename from src/reducers/icons.js rename to src/reducers/icons.ts index 1c77ae3c69..accaffb80f 100644 --- a/src/reducers/icons.js +++ b/src/reducers/icons.ts @@ -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/. */ -// @flow import type { Reducer, IconsWithClassNames } from 'firefox-profiler/types'; const favicons: Reducer = (state = new Map(), action) => { diff --git a/src/reducers/index.js b/src/reducers/index.ts similarity index 99% rename from src/reducers/index.js rename to src/reducers/index.ts index b6691c19cd..285a54c7f3 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.ts @@ -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/. */ -// @flow import profileView from './profile-view'; import app from './app'; import urlState from './url-state'; diff --git a/src/reducers/profile-view.js b/src/reducers/profile-view.ts similarity index 99% rename from src/reducers/profile-view.js rename to src/reducers/profile-view.ts index b8dacaa8be..52520440f5 100644 --- a/src/reducers/profile-view.js +++ b/src/reducers/profile-view.ts @@ -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/. */ -// @flow import { combineReducers } from 'redux'; import * as Transforms from '../profile-logic/transforms'; import * as ProfileData from '../profile-logic/profile-data'; @@ -156,7 +155,7 @@ function _getThreadViewOptions( function _updateThreadViewOptions( state: ThreadViewOptionsPerThreads, threadsKey: ThreadsKey, - updates: $Shape + updates: Partial ): ThreadViewOptionsPerThreads { const newState = { ...state }; newState[threadsKey] = { @@ -167,7 +166,7 @@ function _updateThreadViewOptions( } const viewOptionsPerThread: Reducer = ( - state = ({}: ThreadViewOptionsPerThreads), + state = {} as ThreadViewOptionsPerThreads, action ): ThreadViewOptionsPerThreads => { switch (action.type) { @@ -484,7 +483,7 @@ export const defaultTableViewOptions: TableViewOptions = { function _updateTableViewOptions( state: TableViewOptionsPerTab, tab: TabSlug, - updates: $Shape + updates: Partial ): TableViewOptionsPerTab { const newState = { ...state }; newState[tab] = { @@ -495,7 +494,7 @@ function _updateTableViewOptions( } const tableViewOptionsPerTab: Reducer = ( - state = ({}: TableViewOptionsPerTab), + state = {} as TableViewOptionsPerTab, action ): TableViewOptionsPerTab => { switch (action.type) { diff --git a/src/reducers/publish.js b/src/reducers/publish.ts similarity index 98% rename from src/reducers/publish.js rename to src/reducers/publish.ts index 955061918f..25dd14b25f 100644 --- a/src/reducers/publish.js +++ b/src/reducers/publish.ts @@ -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/. */ -// @flow import { combineReducers } from 'redux'; import { getShouldSanitizeByDefault } from '../profile-logic/sanitize'; @@ -114,7 +113,7 @@ const uploadProgress: Reducer = (state = 0, action) => { } }; -const error: Reducer = (state = null, action) => { +const error: Reducer = (state = null, action) => { switch (action.type) { case 'UPLOAD_FAILED': return action.error; diff --git a/src/reducers/url-state.js b/src/reducers/url-state.ts similarity index 98% rename from src/reducers/url-state.js rename to src/reducers/url-state.ts index 86d65c2a5c..c67b060b92 100644 --- a/src/reducers/url-state.js +++ b/src/reducers/url-state.ts @@ -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/. */ -// @flow import { combineReducers } from 'redux'; import { oneLine } from 'common-tags'; import { objectEntries } from '../utils/flow'; @@ -124,7 +123,7 @@ const committedRanges: Reducer = (state = [], action) => { const selectedThreads: Reducer | null> = ( state = null, action -) => { +): Set | null => { switch (action.type) { case 'CHANGE_SELECTED_THREAD': case 'SELECT_TRACK': @@ -138,14 +137,14 @@ const selectedThreads: Reducer | null> = ( case 'TOGGLE_RESOURCES_PANEL': case 'CHANGE_TAB_FILTER': // Only switch to non-null selected threads. - return (action.selectedThreadIndexes: Set); + return action.selectedThreadIndexes; case 'SANITIZED_PROFILE_PUBLISHED': { const { oldThreadIndexToNew } = action; if (state === null || !oldThreadIndexToNew) { // Either there was no selected thread, or the thread indexes were not modified. return state; } - const newSelectedThreads = new Set(); + const newSelectedThreads = new Set(); for (const oldThreadIndex of state) { const newThreadIndex = oldThreadIndexToNew.get(oldThreadIndex); if (newThreadIndex === undefined) { @@ -216,7 +215,7 @@ const transforms: Reducer = (state = {}, action) => { return state; } // This may no longer be valid because of PII sanitization. - const newTransforms = {}; + const newTransforms = {} as TransformStacksPerThread; for (const [threadsKey, transformStack] of objectEntries(state)) { const newThreadIndex = oldThreadIndexToNew.get(Number(threadsKey)); if (newThreadIndex !== undefined) { @@ -614,8 +613,8 @@ const assemblyView: Reducer = ( } }; -function _getBottomBoxInitialState() { - const state = {}; +function _getBottomBoxInitialState(): IsOpenPerPanelState { + const state = {} as IsOpenPerPanelState; tabSlugs.forEach((tabSlug) => (state[tabSlug] = false)); return state; } diff --git a/src/reducers/zipped-profiles.js b/src/reducers/zipped-profiles.ts similarity index 97% rename from src/reducers/zipped-profiles.js rename to src/reducers/zipped-profiles.ts index 33b2ea2fa2..97a0ddb712 100644 --- a/src/reducers/zipped-profiles.js +++ b/src/reducers/zipped-profiles.ts @@ -2,11 +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/. */ -// @flow import { combineReducers } from 'redux'; import { oneLine } from 'common-tags'; import { ensureExists } from '../utils/flow'; -import * as ZipFiles from '../profile-logic/zip-files'; +import type * as ZipFiles from '../profile-logic/zip-files'; import type { ZipFileState, @@ -169,7 +168,7 @@ function _validateStateTransition( expectedNextPhases = ['LIST_FILES_IN_ZIP_FILE']; break; default: - throw new Error(`Unhandled ZipFileState “${(prevPhase: empty)}”`); + throw new Error(`Unhandled ZipFileState "${prevPhase}"`); } if (!expectedNextPhases.includes(next.phase)) { console.error('Previous ZipFileState:', prev); @@ -198,7 +197,7 @@ const selectedZipFileIndex: Reducer = ( }; const expandedZipFileIndexes: Reducer< - Array, + Array > = ( // In practice this should never contain null, but needs to support the // TreeView interface. From 9b82bcdf58d166da973aef44d724e916e84b2d42 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:41:08 -0400 Subject: [PATCH 051/124] Convert src/selectors. --- src/selectors/{app.js => app.tsx} | 6 +- src/selectors/{code.js => code.tsx} | 2 - src/selectors/{cpu.js => cpu.tsx} | 2 - src/selectors/{icons.js => icons.tsx} | 4 +- src/selectors/{index.js => index.ts} | 2 - .../per-thread/{composed.js => composed.ts} | 19 +-- .../per-thread/{index.js => index.ts} | 50 +++--- .../per-thread/{markers.js => markers.ts} | 148 +++++++++--------- .../{stack-sample.js => stack-sample.ts} | 67 ++++---- .../per-thread/{thread.js => thread.tsx} | 35 ++--- src/selectors/{profile.js => profile.ts} | 77 +++++---- src/selectors/{publish.js => publish.ts} | 51 +++--- ...ll-node.js => right-clicked-call-node.tsx} | 10 +- ...ked-marker.js => right-clicked-marker.tsx} | 2 - src/selectors/{url-state.js => url-state.ts} | 41 ++--- ...zipped-profiles.js => zipped-profiles.tsx} | 4 +- 16 files changed, 259 insertions(+), 261 deletions(-) rename src/selectors/{app.js => app.tsx} (99%) rename src/selectors/{code.js => code.tsx} (99%) rename src/selectors/{cpu.js => cpu.tsx} (99%) rename src/selectors/{icons.js => icons.tsx} (97%) rename src/selectors/{index.js => index.ts} (99%) rename src/selectors/per-thread/{composed.js => composed.ts} (88%) rename src/selectors/per-thread/{index.js => index.ts} (91%) rename src/selectors/per-thread/{markers.js => markers.ts} (89%) rename src/selectors/per-thread/{stack-sample.js => stack-sample.ts} (89%) rename src/selectors/per-thread/{thread.js => thread.tsx} (96%) rename src/selectors/{profile.js => profile.ts} (94%) rename src/selectors/{publish.js => publish.ts} (86%) rename src/selectors/{right-clicked-call-node.js => right-clicked-call-node.tsx} (83%) rename src/selectors/{right-clicked-marker.js => right-clicked-marker.tsx} (98%) rename src/selectors/{url-state.js => url-state.ts} (94%) rename src/selectors/{zipped-profiles.js => zipped-profiles.tsx} (98%) diff --git a/src/selectors/app.js b/src/selectors/app.tsx similarity index 99% rename from src/selectors/app.js rename to src/selectors/app.tsx index 6b92cf1f2f..e88ac4f8f7 100644 --- a/src/selectors/app.js +++ b/src/selectors/app.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { @@ -61,7 +59,7 @@ export const getPanelLayoutGeneration: Selector = (state) => export const getLastVisibleThreadTabSlug: Selector = (state) => getApp(state).lastVisibleThreadTabSlug; export const getTrackThreadHeights: Selector<{ - [key: ThreadsKey]: CssPixels, + [key: ThreadsKey]: CssPixels; }> = (state) => getApp(state).trackThreadHeights; export const getIsNewlyPublished: Selector = (state) => getApp(state).isNewlyPublished; @@ -88,7 +86,7 @@ export const getIsDragAndDropOverlayRegistered: Selector = (state) => getApp(state).isDragAndDropOverlayRegistered; export const getCurrentProfileUploadedInformation: Selector< - UploadedProfileInformation | null, + UploadedProfileInformation | null > = (state) => getApp(state).currentProfileUploadedInformation; /** diff --git a/src/selectors/code.js b/src/selectors/code.tsx similarity index 99% rename from src/selectors/code.js rename to src/selectors/code.tsx index 70a3a2d6d1..3a91c75223 100644 --- a/src/selectors/code.js +++ b/src/selectors/code.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import type { AssemblyCodeStatus, diff --git a/src/selectors/cpu.js b/src/selectors/cpu.tsx similarity index 99% rename from src/selectors/cpu.js rename to src/selectors/cpu.tsx index ebc040bc4e..fa9e0ba724 100644 --- a/src/selectors/cpu.js +++ b/src/selectors/cpu.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { createSelector } from 'reselect'; import { getThreads, getSampleUnits, getMeta, getCounters } from './profile'; diff --git a/src/selectors/icons.js b/src/selectors/icons.tsx similarity index 97% rename from src/selectors/icons.js rename to src/selectors/icons.tsx index 05ffdf75a7..16ccdc89fb 100644 --- a/src/selectors/icons.js +++ b/src/selectors/icons.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import type { IconsWithClassNames, Selector, @@ -24,7 +22,7 @@ export const getIconsWithClassNames: Selector = (state) => */ export const getIconClassName: DangerousSelectorWithArguments< string, - string | null, + string | null > = (state, icon) => { if (icon === null) { return ''; diff --git a/src/selectors/index.js b/src/selectors/index.ts similarity index 99% rename from src/selectors/index.js rename to src/selectors/index.ts index 86abf71453..69b552d23e 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow export * from './app'; export * from './per-thread'; export * from './profile'; diff --git a/src/selectors/per-thread/composed.js b/src/selectors/per-thread/composed.ts similarity index 88% rename from src/selectors/per-thread/composed.js rename to src/selectors/per-thread/composed.ts index 6e9fdeffd4..4816f5f56a 100644 --- a/src/selectors/per-thread/composed.js +++ b/src/selectors/per-thread/composed.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { @@ -15,7 +13,6 @@ import { getRawProfileSharedData } from '../profile'; import type { Selector, - $ReturnType, RawThread, JsTracerTable, MarkerTimingRows, @@ -35,8 +32,8 @@ import { hasUsefulSamples } from '../../profile-logic/profile-data'; * is done that so that the local type definition with `Selector` is the canonical * definition for the type of the selector. */ -export type ComposedSelectorsPerThread = $ReturnType< - typeof getComposedSelectorsPerThread, +export type ComposedSelectorsPerThread = ReturnType< + typeof getComposedSelectorsPerThread >; /** @@ -45,11 +42,11 @@ export type ComposedSelectorsPerThread = $ReturnType< * elements that we don't use here, and that's OK. */ type NeededThreadSelectors = { - getRawThread: Selector, - getIsNetworkChartEmptyInFullRange: Selector, - getJsTracerTable: Selector, - getUserTimingMarkerTiming: Selector, - getStackTimingByDepth: Selector, + getRawThread: Selector; + getIsNetworkChartEmptyInFullRange: Selector; + getJsTracerTable: Selector; + getUserTimingMarkerTiming: Selector; + getStackTimingByDepth: Selector; }; /** @@ -63,7 +60,7 @@ export function getComposedSelectorsPerThread( * effort is made to not show a tab when there is no data available for it or * when it's absurd. */ - const getUsefulTabs: Selector<$ReadOnlyArray> = createSelector( + const getUsefulTabs: Selector> = createSelector( getRawProfileSharedData, threadSelectors.getRawThread, threadSelectors.getIsNetworkChartEmptyInFullRange, diff --git a/src/selectors/per-thread/index.js b/src/selectors/per-thread/index.ts similarity index 91% rename from src/selectors/per-thread/index.js rename to src/selectors/per-thread/index.ts index ab7adfd58e..382f18c49a 100644 --- a/src/selectors/per-thread/index.js +++ b/src/selectors/per-thread/index.ts @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import { createSelector } from 'reselect'; import memoize from 'memoize-immutable'; import * as UrlState from '../url-state'; @@ -43,6 +43,7 @@ import type { LineTimings, StackAddressInfo, AddressTimings, + State, } from 'firefox-profiler/types'; import type { TimingsForPath } from '../../profile-logic/profile-data'; @@ -54,17 +55,15 @@ import type { TimingsForPath } from '../../profile-logic/profile-data'; * across a single render call. Instead for ThreadSelectors, duplicate the selector * functions once per thread in the profile, so each memoizes separately. */ -export type ThreadSelectors = {| - ...ThreadSelectorsPerThread, - ...MarkerSelectorsPerThread, - ...StackAndSampleSelectorsPerThread, - ...ComposedSelectorsPerThread, -|}; +export type ThreadSelectors = ThreadSelectorsPerThread & + MarkerSelectorsPerThread & + StackAndSampleSelectorsPerThread & + ComposedSelectorsPerThread; /** * This is the static object store that holds the selector functions. */ -const _threadSelectorsCache: { [number]: ThreadSelectors } = {}; +const _threadSelectorsCache: { [key: number]: ThreadSelectors } = {}; const _mergedThreadSelectorsMemoized = memoize( (threadsKey: ThreadsKey) => { // We don't pass this set inside this memoization function since we create @@ -156,7 +155,10 @@ function _buildThreadSelectors( // We define the thread selectors in 5 steps to ensure clarity in the // separate files. // 1. The basic thread selectors. - let selectors = getBasicThreadSelectorsPerThread(threadIndexes, threadsKey); + let selectors: any = getBasicThreadSelectorsPerThread( + threadIndexes, + threadsKey + ); // 2. The marker selectors. selectors = { ...selectors, @@ -195,25 +197,27 @@ function _buildThreadSelectors( */ export const selectedThreadSelectors: ThreadSelectors = (() => { const anyThreadSelectors: ThreadSelectors = getThreadSelectors(0); - const result: $Shape = {}; + const result: any = {}; for (const key in anyThreadSelectors) { - result[key] = (state) => - getThreadSelectors(UrlState.getSelectedThreadIndexes(state))[key](state); + result[key] = (state: State) => + (getThreadSelectors(UrlState.getSelectedThreadIndexes(state)) as any)[ + key + ](state); } - const result2: ThreadSelectors = (result: any); + const result2: ThreadSelectors = result as any; return result2; })(); -export type NodeSelectors = {| - +getName: Selector, - +getIsJS: Selector, - +getLib: Selector, - +getTimingsForSidebar: Selector, - +getSourceViewStackLineInfo: Selector, - +getSourceViewLineTimings: Selector, - +getAssemblyViewStackAddressInfo: Selector, - +getAssemblyViewAddressTimings: Selector, -|}; +export type NodeSelectors = { + readonly getName: Selector; + readonly getIsJS: Selector; + readonly getLib: Selector; + readonly getTimingsForSidebar: Selector; + readonly getSourceViewStackLineInfo: Selector; + readonly getSourceViewLineTimings: Selector; + readonly getAssemblyViewStackAddressInfo: Selector; + readonly getAssemblyViewAddressTimings: Selector; +}; export const selectedNodeSelectors: NodeSelectors = (() => { const getName: Selector = createSelector( diff --git a/src/selectors/per-thread/markers.js b/src/selectors/per-thread/markers.ts similarity index 89% rename from src/selectors/per-thread/markers.js rename to src/selectors/per-thread/markers.ts index 17b0112d59..59a9ab4d7a 100644 --- a/src/selectors/per-thread/markers.js +++ b/src/selectors/per-thread/markers.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { stripIndent } from 'common-tags'; @@ -27,12 +25,12 @@ import type { IndexedArray, IndexIntoRawMarkerTable, Selector, - $ReturnType, ThreadsKey, Tid, CollectedCustomMarkerSamples, IndexIntoSamplesTable, IndexIntoStringTable, + State, } from 'firefox-profiler/types'; /** @@ -40,8 +38,8 @@ import type { * is done that so that the local type definition with `Selector` is the canonical * definition for the type of the selector. */ -export type MarkerSelectorsPerThread = $ReturnType< - typeof getMarkerSelectorsPerThread, +export type MarkerSelectorsPerThread = ReturnType< + typeof getMarkerSelectorsPerThread >; /** @@ -49,7 +47,7 @@ export type MarkerSelectorsPerThread = $ReturnType< */ export function getMarkerSelectorsPerThread( threadSelectors: BasicThreadSelectorsPerThread, - threadIndexes: Set, + _threadIndexes: Set, threadsKey: ThreadsKey ) { const _getRawMarkerTable: Selector = (state) => @@ -89,7 +87,7 @@ export function getMarkerSelectorsPerThread( * very start of our marker pipeline. */ const getDerivedMarkerInfo: Selector = createSelector( _getRawMarkerTable, - (state) => ProfileSelectors.getProfile(state).shared.stringArray, + (state: State) => ProfileSelectors.getProfile(state).shared.stringArray, _getThreadId, threadSelectors.getThreadRange, ProfileSelectors.getIPCMarkerCorrelations, @@ -102,7 +100,7 @@ export function getMarkerSelectorsPerThread( ); const getMarkerIndexToRawMarkerIndexes: Selector< - IndexedArray, + IndexedArray > = createSelector( getDerivedMarkerInfo, ({ markerIndexToRawMarkerIndexes }) => markerIndexToRawMarkerIndexes @@ -148,20 +146,21 @@ export function getMarkerSelectorsPerThread( * encapsulated and handles the case where a marker object isn't found (which * means the marker index is incorrect). */ - const getMarkerGetter: Selector<(MarkerIndex) => Marker> = createSelector( - getFullMarkerList, - (markerList) => - (markerIndex: MarkerIndex): Marker => { - const marker = markerList[markerIndex]; - if (!marker) { - throw new Error(stripIndent` + const getMarkerGetter: Selector<(actionOrActionList: MarkerIndex) => Marker> = + createSelector( + getFullMarkerList, + (markerList) => + (markerIndex: MarkerIndex): Marker => { + const marker = markerList[markerIndex]; + if (!marker) { + throw new Error(stripIndent` Tried to get marker index ${markerIndex} but it's not in the full list. This is a programming error. `); + } + return marker; } - return marker; - } - ); + ); /** * This returns the list of all marker indexes. This is simply a sequence @@ -190,9 +189,9 @@ export function getMarkerSelectorsPerThread( * ); */ const filterMarkerIndexesCreator = - (filterFunc: (Marker) => boolean) => + (filterFunc: (param: Marker) => boolean) => ( - getMarker: (MarkerIndex) => Marker, + getMarker: (param: MarkerIndex) => Marker, markerIndexes: MarkerIndex[] ): MarkerIndex[] => MarkerData.filterMarkerIndexes(getMarker, markerIndexes, filterFunc); @@ -227,7 +226,7 @@ export function getMarkerSelectorsPerThread( getCommittedRangeFilteredMarkerIndexes, ProfileSelectors.getMarkerSchema, ProfileSelectors.getMarkerSchemaByName, - () => 'timeline-overview', + () => 'timeline-overview' as const, MarkerData.filterMarkerByDisplayLocation ); @@ -388,60 +387,64 @@ export function getMarkerSelectorsPerThread( /** * This getter uses the marker schema to decide on the labels for tooltips. */ - const getMarkerTooltipLabelGetter: Selector<(MarkerIndex) => string> = - createSelector( - getMarkerGetter, - ProfileSelectors.getMarkerSchema, - ProfileSelectors.getMarkerSchemaByName, - ProfileSelectors.getCategories, - ProfileSelectors.getStringTable, - () => 'tooltipLabel', - getLabelGetter - ); + const getMarkerTooltipLabelGetter: Selector< + (actionOrActionList: MarkerIndex) => string + > = createSelector( + getMarkerGetter, + ProfileSelectors.getMarkerSchema, + ProfileSelectors.getMarkerSchemaByName, + ProfileSelectors.getCategories, + ProfileSelectors.getStringTable, + () => 'tooltipLabel' as const, + getLabelGetter + ); /** * This getter uses the marker schema to decide on the labels for the marker table. */ - const getMarkerTableLabelGetter: Selector<(MarkerIndex) => string> = - createSelector( - getMarkerGetter, - ProfileSelectors.getMarkerSchema, - ProfileSelectors.getMarkerSchemaByName, - ProfileSelectors.getCategories, - ProfileSelectors.getStringTable, - () => 'tableLabel', - getLabelGetter - ); + const getMarkerTableLabelGetter: Selector< + (actionOrActionList: MarkerIndex) => string + > = createSelector( + getMarkerGetter, + ProfileSelectors.getMarkerSchema, + ProfileSelectors.getMarkerSchemaByName, + ProfileSelectors.getCategories, + ProfileSelectors.getStringTable, + () => 'tableLabel' as const, + getLabelGetter + ); /** * This getter uses the marker schema to decide on the labels for the marker chart. */ - const getMarkerChartLabelGetter: Selector<(MarkerIndex) => string> = - createSelector( - getMarkerGetter, - ProfileSelectors.getMarkerSchema, - ProfileSelectors.getMarkerSchemaByName, - ProfileSelectors.getCategories, - ProfileSelectors.getStringTable, - () => 'chartLabel', - getLabelGetter - ); + const getMarkerChartLabelGetter: Selector< + (actionOrActionList: MarkerIndex) => string + > = createSelector( + getMarkerGetter, + ProfileSelectors.getMarkerSchema, + ProfileSelectors.getMarkerSchemaByName, + ProfileSelectors.getCategories, + ProfileSelectors.getStringTable, + () => 'chartLabel' as const, + getLabelGetter + ); /** * This selector is used by the generic marker context menu to decide what to copy. * Currently we want to copy the same thing that is displayed as a description * in the marker table. */ - const getMarkerLabelToCopyGetter: Selector<(MarkerIndex) => string> = - createSelector( - getMarkerGetter, - ProfileSelectors.getMarkerSchema, - ProfileSelectors.getMarkerSchemaByName, - ProfileSelectors.getCategories, - ProfileSelectors.getStringTable, - () => 'copyLabel', - getLabelGetter - ); + const getMarkerLabelToCopyGetter: Selector< + (actionOrActionList: MarkerIndex) => string + > = createSelector( + getMarkerGetter, + ProfileSelectors.getMarkerSchema, + ProfileSelectors.getMarkerSchemaByName, + ProfileSelectors.getCategories, + ProfileSelectors.getStringTable, + () => 'copyLabel' as const, + getLabelGetter + ); /** * This organizes the result of the previous selector in rows to be nicely @@ -466,7 +469,7 @@ export function getMarkerSelectorsPerThread( getCommittedRangeFilteredMarkerIndexes, ProfileSelectors.getMarkerSchema, ProfileSelectors.getMarkerSchemaByName, - () => 'timeline-fileio', + () => 'timeline-fileio' as const, // Custom filtering in addition to the schema logic: () => MarkerData.isOnThreadFileIoMarker, MarkerData.filterMarkerByDisplayLocation @@ -481,7 +484,7 @@ export function getMarkerSelectorsPerThread( getCommittedRangeFilteredMarkerIndexes, ProfileSelectors.getMarkerSchema, ProfileSelectors.getMarkerSchemaByName, - () => 'timeline-memory', + () => 'timeline-memory' as const, MarkerData.filterMarkerByDisplayLocation ); @@ -493,7 +496,7 @@ export function getMarkerSelectorsPerThread( getCommittedRangeFilteredMarkerIndexes, ProfileSelectors.getMarkerSchema, ProfileSelectors.getMarkerSchemaByName, - () => 'timeline-ipc', + () => 'timeline-ipc' as const, MarkerData.filterMarkerByDisplayLocation ); @@ -504,6 +507,7 @@ export function getMarkerSelectorsPerThread( const getNetworkTrackTiming: Selector = createSelector( getMarkerGetter, getNetworkMarkerIndexes, + () => null, MarkerTimingLogic.getMarkerTiming ); @@ -514,6 +518,7 @@ export function getMarkerSelectorsPerThread( const getUserTimingMarkerTiming: Selector = createSelector( getMarkerGetter, getUserTimingMarkerIndexes, + () => null, MarkerTimingLogic.getMarkerTiming ); @@ -593,8 +598,11 @@ export function getMarkerSelectorsPerThread( } ); - type MarkerTrackSelectors = $ReturnType; - const _markerTrackSelectors = {}; + type MarkerTrackSelectors = ReturnType; + const _markerTrackSelectors: Record< + string, + Record + > = {}; const getMarkerTrackSelectors = ( markerSchema: MarkerSchema, markerName: IndexIntoStringTable @@ -631,10 +639,10 @@ export function getMarkerSelectorsPerThread( `No graphs for marker ${markerName}. This shouldn't happen.` ); } - const markerIndexes = []; + const markerIndexes: MarkerIndex[] = []; let minNumber = Infinity; let maxNumber = -Infinity; - const numbersPerLine = []; + const numbersPerLine: number[][] = []; const { graphs, name: schemaName } = markerSchema; const keys = graphs.map((graph) => { numbersPerLine.push([]); @@ -651,7 +659,7 @@ export function getMarkerSelectorsPerThread( ) { markerIndexes.push(index); for (let i = 0; i < keys.length; ++i) { - const val = data[keys[i]]; + const val = (data as any)[keys[i]]; numbersPerLine[i].push(val); if (val < minNumber) { minNumber = val; @@ -673,7 +681,7 @@ export function getMarkerSelectorsPerThread( ); const getCommittedRangeMarkerSampleRange: Selector< - [IndexIntoSamplesTable, IndexIntoSamplesTable], + [IndexIntoSamplesTable, IndexIntoSamplesTable] > = createSelector( getCollectedCustomMarkerSamples, ProfileSelectors.getCommittedRange, diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.ts similarity index 89% rename from src/selectors/per-thread/stack-sample.js rename to src/selectors/per-thread/stack-sample.ts index 4bf55eda12..de4e844965 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector, createSelectorCreator, @@ -13,7 +11,7 @@ import * as ProfileData from '../../profile-logic/profile-data'; import * as StackTiming from '../../profile-logic/stack-timing'; import * as FlameGraph from '../../profile-logic/flame-graph'; import * as CallTree from '../../profile-logic/call-tree'; -import { PathSet } from '../../utils/path'; +import type { PathSet } from '../../utils/path'; import * as ProfileSelectors from '../profile'; import { getRightClickedCallNodeInfo } from '../right-clicked-call-node'; import { @@ -40,11 +38,11 @@ import type { SelectedState, StartEndRange, Selector, - $ReturnType, ThreadsKey, SelfAndTotal, CallNodeTable, CallNodeSelfAndSummary, + State, CallNodeTableBitSet, } from 'firefox-profiler/types'; import type { @@ -60,14 +58,12 @@ import type { MarkerSelectorsPerThread } from './markers'; * is done that so that the local type definition with `Selector` is the canonical * definition for the type of the selector. */ -export type StackAndSampleSelectorsPerThread = $ReturnType< - typeof getStackAndSampleSelectorsPerThread, +export type StackAndSampleSelectorsPerThread = ReturnType< + typeof getStackAndSampleSelectorsPerThread >; -type ThreadAndMarkerSelectorsPerThread = {| - ...ThreadSelectorsPerThread, - ...MarkerSelectorsPerThread, -|}; +type ThreadAndMarkerSelectorsPerThread = ThreadSelectorsPerThread & + MarkerSelectorsPerThread; // A variant of createSelector which caches the value for two most recent keys, // not just for the single most recent key. @@ -112,8 +108,8 @@ export function getStackAndSampleSelectorsPerThread( // function and the whole profile. const _getNonInvertedCallNodeInfo: Selector = createSelectorWithTwoCacheSlots( - (state) => threadSelectors.getFilteredThread(state).stackTable, - (state) => threadSelectors.getFilteredThread(state).frameTable, + (state: State) => threadSelectors.getFilteredThread(state).stackTable, + (state: State) => threadSelectors.getFilteredThread(state).frameTable, ProfileSelectors.getDefaultCategory, ProfileData.getCallNodeInfo ); @@ -122,7 +118,8 @@ export function getStackAndSampleSelectorsPerThread( createSelectorWithTwoCacheSlots( _getNonInvertedCallNodeInfo, ProfileSelectors.getDefaultCategory, - (state) => threadSelectors.getFilteredThread(state).funcTable.length, + (state: State) => + threadSelectors.getFilteredThread(state).funcTable.length, ProfileData.getInvertedCallNodeInfo ); @@ -227,7 +224,7 @@ export function getStackAndSampleSelectorsPerThread( ); const getExpandedCallNodeIndexes: Selector< - Array, + Array > = createSelector( getCallNodeInfo, getExpandedCallNodePaths, @@ -238,31 +235,35 @@ export function getStackAndSampleSelectorsPerThread( ); const _getSampleIndexToNonInvertedCallNodeIndexForPreviewFilteredCtssThread: Selector< - Array, + Array > = createSelector( - (state) => threadSelectors.getPreviewFilteredCtssSamples(state).stack, - (state) => getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), + (state: State) => + threadSelectors.getPreviewFilteredCtssSamples(state).stack, + (state: State) => + getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), ProfileData.getSampleIndexToCallNodeIndex ); const _getSampleIndexToNonInvertedCallNodeIndexForFilteredCtssThread: Selector< - Array, + Array > = createSelector( - (state) => threadSelectors.getFilteredCtssSamples(state).stack, - (state) => getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), + (state: State) => threadSelectors.getFilteredCtssSamples(state).stack, + (state: State) => + getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), ProfileData.getSampleIndexToCallNodeIndex ); const getSampleIndexToNonInvertedCallNodeIndexForFilteredThread: Selector< - Array, + Array > = createSelector( - (state) => threadSelectors.getFilteredThread(state).samples.stack, - (state) => getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), + (state: State) => threadSelectors.getFilteredThread(state).samples.stack, + (state: State) => + getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), ProfileData.getSampleIndexToCallNodeIndex ); const getSamplesSelectedStatesInFilteredThread: Selector< - null | SelectedState[], + null | SelectedState[] > = createSelector( getSampleIndexToNonInvertedCallNodeIndexForFilteredThread, getCallNodeInfo, @@ -277,7 +278,10 @@ export function getStackAndSampleSelectorsPerThread( ); const getTreeOrderComparatorInFilteredThread: Selector< - (IndexIntoSamplesTable, IndexIntoSamplesTable) => number, + ( + sampleIndexA: IndexIntoSamplesTable, + sampleIndexB: IndexIntoSamplesTable + ) => number > = createSelector( getSampleIndexToNonInvertedCallNodeIndexForFilteredThread, getCallNodeInfo, @@ -334,7 +338,8 @@ export function getStackAndSampleSelectorsPerThread( _getCallNodeTable, _getCallNodeFuncIsDuplicate, getCallNodeSelfAndSummary, - (state) => threadSelectors.getFilteredThread(state).funcTable.length, + (state: State) => + threadSelectors.getFilteredThread(state).funcTable.length, CallTree.computeFunctionListTimings ); @@ -436,21 +441,21 @@ export function getStackAndSampleSelectorsPerThread( state ) => _getStackTimingByDepthWithMap(state).timings; const getSameWidthsIndexToTimestampMap: Selector< - StackTiming.SameWidthsIndexToTimestampMap, + StackTiming.SameWidthsIndexToTimestampMap > = (state) => _getStackTimingByDepthWithMap(state).sameWidthsIndexToTimestampMap; const getFlameGraphRows: Selector = createSelector( - (state) => getCallNodeInfo(state).getCallNodeTable(), - (state) => threadSelectors.getFilteredThread(state).funcTable, - (state) => threadSelectors.getFilteredThread(state).stringTable, + (state: State) => getCallNodeInfo(state).getCallNodeTable(), + (state: State) => threadSelectors.getFilteredThread(state).funcTable, + (state: State) => threadSelectors.getFilteredThread(state).stringTable, FlameGraph.computeFlameGraphRows ); const getFlameGraphTiming: Selector = createSelector( getFlameGraphRows, - (state) => getCallNodeInfo(state).getCallNodeTable(), + (state: State) => getCallNodeInfo(state).getCallNodeTable(), getCallTreeTimingsNonInverted, FlameGraph.getFlameGraphTiming ); diff --git a/src/selectors/per-thread/thread.js b/src/selectors/per-thread/thread.tsx similarity index 96% rename from src/selectors/per-thread/thread.js rename to src/selectors/per-thread/thread.tsx index e0093b4003..141f01ac52 100644 --- a/src/selectors/per-thread/thread.js +++ b/src/selectors/per-thread/thread.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import memoize from 'memoize-immutable'; import MixedTupleMap from 'mixedtuplemap'; @@ -36,7 +34,6 @@ import type { ThreadViewOptions, TransformStack, JsTracerTiming, - $ReturnType, StartEndRange, WeightType, EventDelayInfo, @@ -45,6 +42,7 @@ import type { ThreadWithReservedFunctions, IndexIntoResourceTable, IndexIntoFuncTable, + State, } from 'firefox-profiler/types'; import type { TransformLabeL10nIds } from 'firefox-profiler/profile-logic/transforms'; @@ -59,13 +57,11 @@ import { defaultThreadViewOptions } from '../../reducers/profile-view'; * the local type definition with `Selector` is the canonical definition for * the type of the selector. */ -export type BasicThreadSelectorsPerThread = $ReturnType< - typeof getBasicThreadSelectorsPerThread, +export type BasicThreadSelectorsPerThread = ReturnType< + typeof getBasicThreadSelectorsPerThread >; -export type ThreadSelectorsPerThread = {| - ...BasicThreadSelectorsPerThread, - ...$ReturnType, -|}; +export type ThreadSelectorsPerThread = BasicThreadSelectorsPerThread & + ReturnType; /** * Create the selectors for a thread that have to do with an entire thread. This includes @@ -117,8 +113,8 @@ export function getBasicThreadSelectorsPerThread( ProfileSelectors.getProfileInterval(state) ); const getStackTable: Selector = createSelector( - (state) => getRawThread(state).stackTable, - (state) => getRawThread(state).frameTable, + (state: State) => getRawThread(state).stackTable, + (state: State) => getRawThread(state).frameTable, ProfileSelectors.getDefaultCategory, ProfileData.computeStackTableFromRawStackTable ); @@ -160,7 +156,7 @@ export function getBasicThreadSelectorsPerThread( getThreadWithReservedFunctions(state).thread; const getReservedFunctionsForResources: Selector< - Map, + Map > = (state) => getThreadWithReservedFunctions(state).reservedFunctionsForResources; @@ -259,7 +255,7 @@ export function getBasicThreadSelectorsPerThread( * samples. */ const getFilteredSampleIndexOffset: Selector = createSelector( - (state) => getSamplesTable(state), + getSamplesTable, ProfileSelectors.getCommittedRange, (samples, { start, end }) => { const [beginSampleIndex] = ProfileData.getSampleIndexRangeForSelection( @@ -408,10 +404,8 @@ export function getBasicThreadSelectorsPerThread( }; } -type BasicThreadAndMarkerSelectorsPerThread = {| - ...BasicThreadSelectorsPerThread, - ...MarkerSelectorsPerThread, -|}; +type BasicThreadAndMarkerSelectorsPerThread = BasicThreadSelectorsPerThread & + MarkerSelectorsPerThread; export function getThreadSelectorsWithMarkersPerThread( threadSelectors: BasicThreadAndMarkerSelectorsPerThread, @@ -550,9 +544,8 @@ export function getThreadSelectorsWithMarkersPerThread( Transforms.getTransformLabelL10nIds ); - const getLocalizedTransformLabels: Selector = createSelector( - getTransformLabelL10nIds, - (transformL10nIds) => + const getLocalizedTransformLabels: Selector = + createSelector(getTransformLabelL10nIds, (transformL10nIds) => transformL10nIds.map((transform) => ( )) - ); + ); return { getTransformStack, diff --git a/src/selectors/profile.js b/src/selectors/profile.ts similarity index 94% rename from src/selectors/profile.js rename to src/selectors/profile.ts index 699100766a..d4d6c4876d 100644 --- a/src/selectors/profile.js +++ b/src/selectors/profile.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import * as Tracks from '../profile-logic/tracks'; import * as CPU from '../profile-logic/cpu'; @@ -17,10 +15,8 @@ import { getInclusiveSampleIndexRangeForSelection, computeTabToThreadIndexesMap, } from '../profile-logic/profile-data'; -import { - IPCMarkerCorrelations, - correlateIPCMarkers, -} from '../profile-logic/marker-data'; +import type { IPCMarkerCorrelations } from '../profile-logic/marker-data'; +import { correlateIPCMarkers } from '../profile-logic/marker-data'; import { markerSchemaFrontEndOnly } from '../profile-logic/marker-schema'; import { getDefaultCategories } from 'firefox-profiler/profile-logic/data-structures'; import * as CommittedRanges from '../profile-logic/committed-ranges'; @@ -66,7 +62,6 @@ import type { State, ProfileViewState, SymbolicationStatus, - $ReturnType, MarkerSchema, MarkerSchemaByName, SampleUnits, @@ -87,7 +82,7 @@ export const getProfileView: Selector = (state) => * Profile View Options */ export const getProfileViewOptions: Selector< - $PropertyType, + ProfileViewState['viewOptions'] > = (state) => getProfileView(state).viewOptions; export const getCurrentTableViewOptions: Selector = (state) => getProfileViewOptions(state).perTab[UrlState.getSelectedTab(state)] || @@ -135,11 +130,12 @@ export const getCommittedRangeLabels: Selector = createSelector( export const getMouseTimePosition: Selector = (state) => getProfileViewOptions(state).mouseTimePosition; -export const getTableViewOptionSelectors: (TabSlug) => Selector = - (tab) => (state) => { - const options = getProfileViewOptions(state).perTab[tab]; - return options || defaultTableViewOptions; - }; +export const getTableViewOptionSelectors: ( + tab: TabSlug +) => Selector = (tab) => (state) => { + const options = getProfileViewOptions(state).perTab[tab]; + return options || defaultTableViewOptions; +}; export const getPreviewSelection: Selector = (state) => getProfileViewOptions(state).previewSelection; @@ -186,7 +182,7 @@ export const getThreads: Selector = (state) => export const getThreadNames: Selector = (state) => getProfile(state).threads.map((t) => t.name); export const getLastNonShiftClick: Selector< - LastNonShiftClickInformation | null, + LastNonShiftClickInformation | null > = (state) => getProfileViewOptions(state).lastNonShiftClick; export const getRightClickedTrack: Selector = (state) => getProfileViewOptions(state).rightClickedTrack; @@ -204,14 +200,14 @@ export const getVisualProgress: Selector = ( state ) => getVisualMetrics(state).VisualProgress; export const getPerceptualSpeedIndexProgress: Selector< - ProgressGraphData[] | null, + ProgressGraphData[] | null > = (state) => getVisualMetrics(state).PerceptualSpeedIndexProgress ?? null; export const getContentfulSpeedIndexProgress: Selector< - ProgressGraphData[] | null, + ProgressGraphData[] | null > = (state) => getVisualMetrics(state).ContentfulSpeedIndexProgress ?? null; -export const getProfilerConfiguration: Selector = ( - state -) => getMeta(state).configuration; +export const getProfilerConfiguration: Selector< + ProfilerConfiguration | undefined +> = (state) => getMeta(state).configuration; // Get the marker schema that comes from the Gecko profile. const getMarkerSchemaGecko: Selector = (state) => @@ -219,12 +215,13 @@ const getMarkerSchemaGecko: Selector = (state) => // Get the samples table units. They can be different depending on their platform. // See SampleUnits type definition for more information. -export const getSampleUnits: Selector = (state) => +export const getSampleUnits: Selector = (state) => getMeta(state).sampleUnits; // Get all extensions in the profile metadata. -export const getExtensionTable: Selector = (state) => - getMeta(state).extensions; +export const getExtensionTable: Selector = ( + state +) => getMeta(state).extensions; /** * Firefox profiles will always have categories. However, imported profiles may not @@ -239,8 +236,8 @@ export const getCategories: Selector = createSelector( ); export const getStringTable: Selector = createSelector( - (state) => getRawProfileSharedData(state).stringArray, - (stringArray) => StringTable.withBackingArray(stringArray) + (state: State) => getRawProfileSharedData(state).stringArray, + (stringArray) => StringTable.withBackingArray(stringArray as string[]) ); // Combine the marker schema from Gecko and the front-end. This allows the front-end @@ -268,9 +265,9 @@ export const getMarkerSchemaByName: Selector = return result; }); -type CounterSelectors = $ReturnType; +type CounterSelectors = ReturnType; -const _counterSelectors = {}; +const _counterSelectors: { [key: number]: CounterSelectors } = {}; export const getCounterSelectors = (index: CounterIndex): CounterSelectors => { let selectors = _counterSelectors[index]; if (!selectors) { @@ -301,7 +298,7 @@ function _createCounterSelectors(counterIndex: CounterIndex) { const getPid: Selector = (state) => getCounter(state).pid; const getCommittedRangeCounterSampleRange: Selector< - [IndexIntoSamplesTable, IndexIntoSamplesTable], + [IndexIntoSamplesTable, IndexIntoSamplesTable] > = createSelector(getCounter, getCommittedRange, (counter, range) => getInclusiveSampleIndexRangeForSelection( counter.samples, @@ -357,7 +354,7 @@ export const getIPCMarkerCorrelations: Selector = */ export const getInnerWindowIDToPageMap: Selector | null> = createSelector(getPageList, (pages) => { if (!pages) { // Return null if there are no pages. @@ -378,7 +375,7 @@ export const getInnerWindowIDToPageMap: Selector | null> = createSelector(getPageList, (pages) => { if (!pages) { // Return null if there are no pages. @@ -447,7 +444,7 @@ export const getHasPreferenceMarkers: Selector = createSelector( */ export const getGlobalTrackFromReference: DangerousSelectorWithArguments< GlobalTrack, - GlobalTrackReference, + GlobalTrackReference > = (state, trackReference) => getGlobalTracks(state)[trackReference.trackIndex]; @@ -458,8 +455,8 @@ export const getGlobalTrackFromReference: DangerousSelectorWithArguments< * properly work with a PureComponent. */ export const getGlobalTrackAndIndexByPid: DangerousSelectorWithArguments< - {| +globalTrackIndex: TrackIndex, +globalTrack: GlobalTrack |}, - Pid, + { readonly globalTrackIndex: TrackIndex; readonly globalTrack: GlobalTrack }, + Pid > = (state, pid) => { const globalTracks = getGlobalTracks(state); const globalTrackIndex = globalTracks.findIndex( @@ -488,7 +485,7 @@ export const getLocalTracksByPid: Selector> = (state) => */ export const getLocalTracks: DangerousSelectorWithArguments< LocalTrack[], - Pid, + Pid > = (state, pid) => ensureExists( getProfileView(state).localTracksByPid.get(pid), @@ -501,7 +498,7 @@ export const getLocalTracks: DangerousSelectorWithArguments< */ export const getLocalTrackFromReference: DangerousSelectorWithArguments< LocalTrack, - LocalTrackReference, + LocalTrackReference > = (state, trackReference) => getLocalTracks(state, trackReference.pid)[trackReference.trackIndex]; @@ -512,7 +509,7 @@ export const getLocalTrackFromReference: DangerousSelectorWithArguments< export const getProcessesWithMemoryTrack: Selector> = createSelector( getLocalTracksByPid, (localTracksByPid) => { - const processesWithMemoryTrack = new Set(); + const processesWithMemoryTrack = new Set(); for (const [pid, localTracks] of localTracksByPid.entries()) { if (localTracks.some((track) => track.type === 'memory')) { processesWithMemoryTrack.add(pid); @@ -557,7 +554,7 @@ export const getGlobalTrackNames: Selector = createSelector( export const getGlobalTrackName: DangerousSelectorWithArguments< string, - TrackIndex, + TrackIndex > = (state, trackIndex) => getGlobalTrackNames(state)[trackIndex]; export const getLocalTrackNamesByPid: Selector> = @@ -695,7 +692,7 @@ export const getPagesMap: Selector | null> = createSelector( for (const page of pageList) { // If this is an iframe, we recursively visit its parent. - const getTopMostParent = (item) => { + const getTopMostParent = (item: any) => { if (item.embedderInnerWindowID === 0) { return item; } @@ -734,7 +731,7 @@ export const getPagesMap: Selector | null> = createSelector( */ export const getInnerWindowIDSetByTabID: Selector, + Set > | null> = createSelector(getPagesMap, (pagesMap) => { if (pagesMap === null || pagesMap.size === 0) { // There is no data, return null @@ -773,7 +770,7 @@ export const getExtensionIdToNameMap: Selector | null> = * returns an empty Map if we don't have information about pages (in older profiles). */ export const getProfileFilterPageDataByTabID: Selector< - Map, + Map > = createSelector( getPagesMap, getExtensionIdToNameMap, @@ -880,7 +877,7 @@ export const getContainsPrivateBrowsingInformation: Selector = export const getProfiledThreadIds: Selector> = createSelector( getThreads, (threads) => { - const profiledThreadIds = new Set(); + const profiledThreadIds = new Set(); for (const { tid } of threads) { profiledThreadIds.add(tid); } diff --git a/src/selectors/publish.js b/src/selectors/publish.ts similarity index 86% rename from src/selectors/publish.js rename to src/selectors/publish.ts index d7661d2b63..1129d8cc0a 100644 --- a/src/selectors/publish.js +++ b/src/selectors/publish.ts @@ -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/. */ -// @flow import { createSelector } from 'reselect'; import clamp from 'clamp'; @@ -37,6 +36,8 @@ import type { CheckedSharingOptions, RemoveProfileInformation, DerivedMarkerInfo, + ThreadIndex, + CounterIndex, } from 'firefox-profiler/types'; import { getThreadSelectors } from './per-thread'; @@ -53,7 +54,7 @@ export const getFilenameString: Selector = createSelector( const { startTime, product } = profile.meta; // Pad single digit numbers with a 0. - const pad = (x) => (x < 10 ? `0${x}` : `${x}`); + const pad = (x: number) => (x < 10 ? `0${x}` : `${x}`); // Compute the date string. const date = new Date(startTime + rootRange.start); @@ -92,7 +93,7 @@ export const getRemoveProfileInformation: Selector { let isIncludingEverything = true; - for (const prop in checkedSharingOptions) { + for (const [prop, value] of Object.entries(checkedSharingOptions)) { // Do not include preference values or private browsing checkboxes if // they're hidden. Even though `includePreferenceValues` is not taken // into account, it is false, if the profile updateChannel is not @@ -106,8 +107,7 @@ export const getRemoveProfileInformation: Selector(); + const shouldRemoveCounters = new Set(); if (!checkedSharingOptions.includeHiddenThreads) { for (const globalTrackIndex of hiddenGlobalTracks) { const globalTrack = globalTracks[globalTrackIndex]; @@ -149,7 +149,10 @@ export const getRemoveProfileInformation: Selector( checkedSharingOptions.includeScreenshots ? [] - : profile.threads.map((_, threadIndex) => threadIndex) + : profile.threads.map( + (_: any, threadIndex: ThreadIndex) => threadIndex + ) ), shouldRemoveThreads, shouldRemoveCounters, @@ -184,14 +189,15 @@ export const getRemoveProfileInformation: Selector - getThreadSelectors(threadIndex).getDerivedMarkerInfo(state) + _derivedMarkerInfo = getThreads(state).map( + (_: any, threadIndex: ThreadIndex) => + getThreadSelectors(threadIndex).getDerivedMarkerInfo(state) ); } return _derivedMarkerInfo; @@ -220,13 +226,14 @@ export const getSanitizedProfile: Selector = * Due to this memoization strategy, one copy of the data is retained in memory and * never freed. */ -export const getSanitizedProfileData: Selector> = - createSelector(getSanitizedProfile, ({ profile }) => - // We use a Promise.resolve() call first so that the calls to compress and - // serializeProfile are out of React's rendering pipeline. We avoid crashes - // due to memory issues thanks to that. - Promise.resolve().then(() => compress(serializeProfile(profile))) - ); +export const getSanitizedProfileData: Selector< + Promise> +> = createSelector(getSanitizedProfile, ({ profile }) => + // We use a Promise.resolve() call first so that the calls to compress and + // serializeProfile are out of React's rendering pipeline. We avoid crashes + // due to memory issues thanks to that. + Promise.resolve().then(() => compress(serializeProfile(profile))) +); export const getUploadState: Selector = (state) => getPublishState(state).upload; @@ -247,7 +254,7 @@ export const getUploadProgress: Selector = createSelector( clamp(uploadProgress, 0.1, 0.95) ); -export const getUploadError: Selector = (state) => +export const getUploadError: Selector = (state) => getUploadState(state).error; export const getUploadProgressString: Selector = createSelector( diff --git a/src/selectors/right-clicked-call-node.js b/src/selectors/right-clicked-call-node.tsx similarity index 83% rename from src/selectors/right-clicked-call-node.js rename to src/selectors/right-clicked-call-node.tsx index 7dabb2501b..a72dc73d35 100644 --- a/src/selectors/right-clicked-call-node.js +++ b/src/selectors/right-clicked-call-node.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { getProfileViewOptions } from './profile'; @@ -13,10 +11,10 @@ import type { Selector, } from 'firefox-profiler/types'; -export type RightClickedCallNodeInfo = {| - +threadsKey: ThreadsKey, - +callNodePath: CallNodePath, -|}; +export type RightClickedCallNodeInfo = { + readonly threadsKey: ThreadsKey; + readonly callNodePath: CallNodePath; +}; export const getRightClickedCallNodeInfo: Selector = createSelector( diff --git a/src/selectors/right-clicked-marker.js b/src/selectors/right-clicked-marker.tsx similarity index 98% rename from src/selectors/right-clicked-marker.js rename to src/selectors/right-clicked-marker.tsx index ee2bc389fd..38c9632e08 100644 --- a/src/selectors/right-clicked-marker.js +++ b/src/selectors/right-clicked-marker.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { getProfileViewOptions } from './profile'; diff --git a/src/selectors/url-state.js b/src/selectors/url-state.ts similarity index 94% rename from src/selectors/url-state.js rename to src/selectors/url-state.ts index 13849e12f4..a933b18903 100644 --- a/src/selectors/url-state.js +++ b/src/selectors/url-state.ts @@ -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/. */ -// @flow import { createSelector } from 'reselect'; import { ensureExists, getFirstItemFromSet } from '../utils/flow'; import { urlFromState } from '../app-logic/url-handling'; @@ -63,7 +62,7 @@ export const getImplementationFilter: Selector = ( state ) => getProfileSpecificState(state).implementation; export const getLastSelectedCallTreeSummaryStrategy: Selector< - CallTreeSummaryStrategy, + CallTreeSummaryStrategy > = (state) => getProfileSpecificState(state).lastSelectedCallTreeSummaryStrategy; export const getShowUserTimings: Selector = (state) => @@ -100,7 +99,7 @@ export const getInvertCallstack: Selector = (state) => getProfileSpecificState(state).invertCallstack; export const getSelectedThreadIndexesOrNull: Selector< - Set | null, + Set | null > = (state) => getProfileSpecificState(state).selectedThreads; export const getSelectedThreadIndexes: Selector> = (state) => ensureExists( @@ -150,7 +149,7 @@ export const hasTabFilter: Selector = (state) => */ export const getHiddenLocalTracks: DangerousSelectorWithArguments< Set, - Pid, + Pid > = (state, pid) => ensureExists( getHiddenLocalTracksByPid(state).get(pid), @@ -163,7 +162,7 @@ export const getHiddenLocalTracks: DangerousSelectorWithArguments< */ export const getLocalTrackOrder: DangerousSelectorWithArguments< TrackIndex[], - Pid, + Pid > = (state, pid) => ensureExists( getLocalTrackOrderByPid(state).get(pid), @@ -199,11 +198,11 @@ export const getNetworkSearchStringsAsRegExp: Selector = createSelector(getNetworkSearchStrings, stringsToMarkerRegExps); // Pre-allocate an array to help with strict equality tests in the selectors. -const EMPTY_TRANSFORM_STACK = []; +const EMPTY_TRANSFORM_STACK: TransformStack = []; export const getTransformStack: DangerousSelectorWithArguments< TransformStack, - ThreadsKey, + ThreadsKey > = (state, threadsKey) => { return ( getProfileSpecificState(state).transforms[threadsKey] || @@ -220,17 +219,21 @@ export const getIsBottomBoxOpen: Selector = (state) => { * The URL predictor is used to generate a link for an uploaded profile, to predict * what the URL will be. */ -export const getUrlPredictor: Selector<(Action | Action[]) => string> = - createSelector( - getUrlState, - (oldUrlState: UrlState) => (actionOrActionList: Action | Action[]) => { - const actionList: Action[] = Array.isArray(actionOrActionList) - ? actionOrActionList - : [actionOrActionList]; - const newUrlState = actionList.reduce(urlStateReducer, oldUrlState); - return urlFromState(newUrlState); - } - ); +export const getUrlPredictor: Selector< + (actionOrActionList: Action | Action[]) => string +> = createSelector( + getUrlState, + (oldUrlState: UrlState) => (actionOrActionList: Action | Action[]) => { + const actionList: Action[] = Array.isArray(actionOrActionList) + ? actionOrActionList + : [actionOrActionList]; + const newUrlState = actionList.reduce( + urlStateReducer, + oldUrlState + ); + return urlFromState(newUrlState); + } +); /** * Get the current path for a zip file that is being used. @@ -313,7 +316,7 @@ export const getProfileNameForStorage: Selector = createSelector( } ); -function _shouldAllowSymbolServerUrl(symbolServerUrl) { +function _shouldAllowSymbolServerUrl(symbolServerUrl: string) { try { const url = new URL(symbolServerUrl); if (isLocalURL(url)) { diff --git a/src/selectors/zipped-profiles.js b/src/selectors/zipped-profiles.tsx similarity index 98% rename from src/selectors/zipped-profiles.js rename to src/selectors/zipped-profiles.tsx index 883de97084..2a81418774 100644 --- a/src/selectors/zipped-profiles.js +++ b/src/selectors/zipped-profiles.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { createSelector } from 'reselect'; import { getProfileUrl } from './url-state'; import { ensureExists } from '../utils/flow'; @@ -26,7 +24,7 @@ export const getSelectedZipFileIndex: Selector = ( state ) => getZippedProfilesState(state).selectedZipFileIndex; export const getExpandedZipFileIndexes: Selector< - Array, + Array > = (state) => getZippedProfilesState(state).expandedZipFileIndexes; export const getZipFileState: Selector = (state) => getZippedProfilesState(state).zipFile; From 24fe397c47d5b6682fac602c3dd57a909951572c Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:40:41 -0400 Subject: [PATCH 052/124] Convert src/actions. --- src/actions/{app.js => app.ts} | 30 ++--- src/actions/{code.js => code.ts} | 2 - src/actions/{errors.js => errors.ts} | 2 - src/actions/{icons.js => icons.tsx} | 27 +++-- src/actions/{index.js => index.ts} | 2 - .../{profile-view.js => profile-view.ts} | 84 ++++++++------ src/actions/{publish.js => publish.ts} | 14 +-- ...{receive-profile.js => receive-profile.ts} | 107 ++++++++++-------- ...{zipped-profiles.js => zipped-profiles.ts} | 17 +-- 9 files changed, 155 insertions(+), 130 deletions(-) rename src/actions/{app.js => app.ts} (94%) rename src/actions/{code.js => code.ts} (99%) rename src/actions/{errors.js => errors.ts} (97%) rename src/actions/{icons.js => icons.tsx} (84%) rename src/actions/{index.js => index.ts} (98%) rename src/actions/{profile-view.js => profile-view.ts} (97%) rename src/actions/{publish.js => publish.ts} (97%) rename src/actions/{receive-profile.js => receive-profile.ts} (95%) rename src/actions/{zipped-profiles.js => zipped-profiles.ts} (89%) diff --git a/src/actions/app.js b/src/actions/app.ts similarity index 94% rename from src/actions/app.js rename to src/actions/app.ts index 9f7a385df6..6302233259 100644 --- a/src/actions/app.js +++ b/src/actions/app.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { oneLine } from 'common-tags'; import { getSelectedTab, @@ -19,6 +17,7 @@ import { getLocalTracksByPid, getThreads, getCounters, + getProfile, } from 'firefox-profiler/selectors/profile'; import { sendAnalytics } from 'firefox-profiler/utils/analytics'; import { @@ -109,7 +108,7 @@ export function changeSidebarOpenState(tab: TabSlug, isOpen: boolean): Action { } export function invalidatePanelLayout(): Action { - return { type: 'INCREMENT_PANEL_LAYOUT_GENERATION' }; + return { type: 'INCREMENT_PANEL_LAYOUT_GENERATION' as const }; } /** @@ -117,7 +116,7 @@ export function invalidatePanelLayout(): Action { * time a user does this, the hint goes away. */ export function setHasZoomedViaMousewheel() { - return { type: 'HAS_ZOOMED_VIA_MOUSEWHEEL' }; + return { type: 'HAS_ZOOMED_VIA_MOUSEWHEEL' as const }; } /** @@ -204,7 +203,7 @@ export function reportTrackThreadHeight( * uploads a profile. We only want to remember this when we fist open the profile. */ export function dismissNewlyPublished(): Action { - return { type: 'DISMISS_NEWLY_PUBLISHED' }; + return { type: 'DISMISS_NEWLY_PUBLISHED' as const }; } /** @@ -212,14 +211,14 @@ export function dismissNewlyPublished(): Action { * profiles with the drag and drop component. */ export function startDragging(): Action { - return { type: 'START_DRAGGING' }; + return { type: 'START_DRAGGING' as const }; } /** * Called when a user has stopped dragging a file. */ export function stopDragging(): Action { - return { type: 'STOP_DRAGGING' }; + return { type: 'STOP_DRAGGING' as const }; } /** @@ -227,14 +226,14 @@ export function stopDragging(): Action { * the app know that we shouldn't create a default overlay. */ export function registerDragAndDropOverlay(): Action { - return { type: 'REGISTER_DRAG_AND_DROP_OVERLAY' }; + return { type: 'REGISTER_DRAG_AND_DROP_OVERLAY' as const }; } /** * Called when a custom drag and drop overlay is unmounted. */ export function unregisterDragAndDropOverlay(): Action { - return { type: 'UNREGISTER_DRAG_AND_DROP_OVERLAY' }; + return { type: 'UNREGISTER_DRAG_AND_DROP_OVERLAY' as const }; } /* @@ -273,7 +272,8 @@ export function enableEventDelayTracks(): ThunkAction { const localTrackOrderByPid = initializeLocalTrackOrderByPid( getLocalTrackOrderByPid(getState()), localTracksByPid, - null + null, + getProfile(getState()) ); dispatch({ type: 'ENABLE_EVENT_DELAY_TRACKS', @@ -355,7 +355,8 @@ export function enableExperimentalProcessCPUTracks(): ThunkAction { const localTrackOrderByPid = initializeLocalTrackOrderByPid( getLocalTrackOrderByPid(getState()), localTracksByPid, - null + null, + getProfile(getState()) ); dispatch({ @@ -383,13 +384,16 @@ export function setCurrentProfileUploadedInformation( export function profileRemotelyDeleted(): Action { // Ideally we should store the current profile data in a local indexeddb, and // set the URL to /local/. - return { type: 'PROFILE_REMOTELY_DELETED' }; + return { type: 'PROFILE_REMOTELY_DELETED' as const }; } export function updateBrowserConnectionStatus( browserConnectionStatus: BrowserConnectionStatus ): Action { - return { type: 'UPDATE_BROWSER_CONNECTION_STATUS', browserConnectionStatus }; + return { + type: 'UPDATE_BROWSER_CONNECTION_STATUS', + browserConnectionStatus, + }; } export function toggleOpenCategoryInSidebar( diff --git a/src/actions/code.js b/src/actions/code.ts similarity index 99% rename from src/actions/code.js rename to src/actions/code.ts index 2afa84c49b..870d4590ce 100644 --- a/src/actions/code.js +++ b/src/actions/code.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import type { Action, SourceCodeLoadingError, diff --git a/src/actions/errors.js b/src/actions/errors.ts similarity index 97% rename from src/actions/errors.js rename to src/actions/errors.ts index 7b0d2e444a..8e55e4ad9c 100644 --- a/src/actions/errors.js +++ b/src/actions/errors.ts @@ -2,8 +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/. */ -// @flow - // This file contains actions related to error handling. import type { Action } from 'firefox-profiler/types'; diff --git a/src/actions/icons.js b/src/actions/icons.tsx similarity index 84% rename from src/actions/icons.js rename to src/actions/icons.tsx index f22730980e..ac4c454abf 100644 --- a/src/actions/icons.js +++ b/src/actions/icons.tsx @@ -1,18 +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/. */ - -// @flow import type { Action, ThunkAction, IconWithClassName, } from 'firefox-profiler/types'; +import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; -export function iconHasLoaded(iconWithClassName: {| - +icon: string, - +className: string, -|}): Action { +export function iconHasLoaded(iconWithClassName: { + readonly icon: string; + readonly className: string; +}): Action { return { type: 'ICON_HAS_LOADED', iconWithClassName, @@ -26,15 +25,15 @@ export function iconIsInError(icon: string): Action { }; } -const icons: Set = new Set(); +const icons: Set = new Set(); let iconCounter = 0; type IconRequestResult = - | {| type: 'error' | 'cached' |} - | {| - type: 'loaded', - iconWithClassName: IconWithClassName, - |}; + | { type: 'error' | 'cached' } + | { + type: 'loaded'; + iconWithClassName: IconWithClassName; + }; async function _getIcon(icon: string): Promise { if (icons.has(icon)) { @@ -47,7 +46,7 @@ async function _getIcon(icon: string): Promise { // just increment the icon counter and return that string. const className = `favicon-${++iconCounter}`; - const result = new Promise((resolve) => { + const result = new Promise((resolve) => { const image = new Image(); image.src = icon; image.referrerPolicy = 'no-referrer'; @@ -76,7 +75,7 @@ export function iconStartLoading(icon: string): ThunkAction> { // nothing to do break; default: - throw new Error(`Unknown icon load result ${result.type}`); + throw assertExhaustiveCheck(result, 'Unknown icon load result'); } }); }; diff --git a/src/actions/index.js b/src/actions/index.ts similarity index 98% rename from src/actions/index.js rename to src/actions/index.ts index 93f821b063..909875bc05 100644 --- a/src/actions/index.js +++ b/src/actions/index.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as app from './app'; import * as icons from './icons'; import * as profileView from './profile-view'; diff --git a/src/actions/profile-view.js b/src/actions/profile-view.ts similarity index 97% rename from src/actions/profile-view.js rename to src/actions/profile-view.ts index bc69596aa9..ffaed61e43 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { oneLine } from 'common-tags'; import { getLastVisibleThreadTabSlug } from 'firefox-profiler/selectors/app'; import { @@ -138,7 +136,7 @@ export function changeSelectedCallNode( export function changeRightClickedCallNode( threadsKey: ThreadsKey, callNodePath: CallNodePath | null -) { +): Action { return { type: 'CHANGE_RIGHT_CLICKED_CALL_NODE', threadsKey, @@ -223,27 +221,27 @@ export function changeSelectedThreads( // This structure contains information needed to find the selected track from a // track reference. -type TrackInformation = {| - type: 'global' | 'local', +type TrackInformation = { + type: 'global' | 'local'; // This is the thread index for this specific track reference. This is null if // this track isn't a thread track. - threadIndex: null | ThreadIndex, + threadIndex: null | ThreadIndex; // This is the thread index for the thread related to this track. - relatedThreadIndex: ThreadIndex, + relatedThreadIndex: ThreadIndex; // This is the track index for the global track where this track is located. - globalTrackIndex: TrackIndex, + globalTrackIndex: TrackIndex; // This is the PID for the process that this track belongs to. - pid: Pid, + pid: Pid; // This is the track index of the local track in its process group. This is // null for global tracks. - localTrackIndex: null | TrackIndex, + localTrackIndex: null | TrackIndex; // This is the tab that should be selected from this track. `null` if this // track doesn't have a prefered tab. - relatedTab: null | TabSlug, + relatedTab: null | TabSlug; // This is the track reference that was passed to // getInformationFromTrackReference to generate this structure. - trackReference: TrackReference, -|}; + trackReference: TrackReference; +}; /** * This function collects some information about a track by requesting @@ -305,7 +303,7 @@ function getInformationFromTrackReference( trackReference.pid ); const commonLocalProperties = { - type: 'local', + type: 'local' as const, trackReference, pid: trackReference.pid, globalTrackIndex, @@ -433,7 +431,7 @@ function toggleOneTrack( * 0 => if trackA and trackB represent the same track */ function compareTrackOrder( - state, + state: State, trackA: TrackInformation, trackB: TrackInformation ): number { @@ -442,7 +440,8 @@ function compareTrackOrder( // Then we need to look at their local order // If one is a global track, its localTrackIndex is null, and therefore the // indexOf operation will return -1, which is exactly what we want. - const localTrackOrder = getLocalTrackOrder(state, trackA.pid); + const localTrackOrder: ReadonlyArray = + getLocalTrackOrder(state, trackA.pid); const orderA = localTrackOrder.indexOf(trackA.localTrackIndex); const orderB = localTrackOrder.indexOf(trackB.localTrackIndex); return orderA - orderB; @@ -505,9 +504,10 @@ function findThreadsBetweenTracks( // all tracks. if (fromTrack.type === 'local') { shouldAddStartGlobalTrack = false; - localTrackOrderStart = localTrackOrder.indexOf( - fromTrack.localTrackIndex - ); + localTrackOrderStart = + fromTrack.localTrackIndex === null + ? -1 + : localTrackOrder.indexOf(fromTrack.localTrackIndex); } } @@ -518,7 +518,10 @@ function findThreadsBetweenTracks( // No local track should be added localTrackOrderEnd = -1; } else { - localTrackOrderEnd = localTrackOrder.indexOf(toTrack.localTrackIndex); + localTrackOrderEnd = + toTrack.localTrackIndex === null + ? -1 + : localTrackOrder.indexOf(toTrack.localTrackIndex); } } @@ -624,7 +627,7 @@ function selectRangeOfTracks( */ export function selectTrackWithModifiers( trackReference: TrackReference, - modifiers: $Shape = {} + modifiers: Partial = {} ): ThunkAction { return (dispatch, getState) => { // These get assigned based on the track type. @@ -698,7 +701,10 @@ export function selectTrackFromTid(tid: Tid): ThunkAction { break; } default: - throw assertExhaustiveCheck(trackReference.type); + throw assertExhaustiveCheck( + trackReference, + 'Unhandled TrackReference type.' + ); } dispatch(selectTrackWithModifiers(trackReference)); @@ -880,7 +886,11 @@ export function showProvidedTracks( // their children are going to be made visible. const globalTracks = getGlobalTracks(getState()); for (const [globalTrackIndex, globalTrack] of globalTracks.entries()) { - if (globalTrack.pid && localTracksByPidToShow.has(globalTrack.pid)) { + if ( + globalTrack.type === 'process' && + globalTrack.pid && + localTracksByPidToShow.has(globalTrack.pid) + ) { globalTracksToShow.add(globalTrackIndex); } } @@ -962,7 +972,10 @@ export function hideProvidedTracks( pid ); - if (globalTrack.mainThreadIndex === null) { + if ( + globalTrack.type === 'process' && + globalTrack.mainThreadIndex === null + ) { // Since the process has no main thread, the entire process should be hidden. dispatch(hideGlobalTrack(globalTrackIndex)); } @@ -1054,7 +1067,7 @@ export function isolateProcess( const localTracks = getLocalTracks(getState(), globalTrack.pid); // Carry over the old selected thread indexes to the new ones. - const newSelectedThreadIndexes = new Set(); + const newSelectedThreadIndexes = new Set(); { // Consider the global track if ( @@ -1066,6 +1079,7 @@ export function isolateProcess( // Now look at all of the local tracks for (const localTrack of localTracks) { if ( + localTrack.type === 'thread' && localTrack.threadIndex !== undefined && oldSelectedThreadIndexes.has(localTrack.threadIndex) ) { @@ -1104,7 +1118,7 @@ export function isolateProcess( dispatch({ type: 'ISOLATE_PROCESS', - hiddenGlobalTracks: new Set( + hiddenGlobalTracks: new Set( trackIndexes.filter((i) => i !== isolatedTrackIndex) ), isolatedTrackIndex, @@ -1131,7 +1145,9 @@ export function isolateScreenshot( // Make sure that a thread really exists. return; } - const hiddenGlobalTracks = new Set(getHiddenGlobalTracks(getState())); + const hiddenGlobalTracks = new Set( + getHiddenGlobalTracks(getState()) + ); for (let i = 0; i < globalTracks.length; i++) { const track = globalTracks[i]; if (track.type === 'screenshots' && i !== isolatedTrackIndex) { @@ -1318,7 +1334,10 @@ export function hideLocalTrack( // local tracks. // 2.) There is no main thread for the process, attempt to hide the // processes' global track. - if (globalTrack.mainThreadIndex === null) { + if ( + globalTrack.type === 'process' && + globalTrack.mainThreadIndex === null + ) { // Since the process has no main thread, the entire process should be hidden. dispatch(hideGlobalTrack(globalTrackIndex)); return; @@ -1351,6 +1370,7 @@ export function hideLocalTrack( if ( newSelectedThreadIndexes.size === 0 && + globalTrack.type === 'process' && globalTrack.mainThreadIndex !== null && globalTrack.mainThreadIndex !== undefined ) { @@ -1437,7 +1457,7 @@ export function isolateLocalTrack( const localTrackIndexes = getLocalTrackOrder(getState(), pid); // Try to find a selected thread index. - const selectedThreadIndexes = new Set(); + const selectedThreadIndexes = new Set(); if (localTrackToIsolate.type === 'thread') { selectedThreadIndexes.add(localTrackToIsolate.threadIndex); } else if ( @@ -1462,10 +1482,10 @@ export function isolateLocalTrack( dispatch({ type: 'ISOLATE_LOCAL_TRACK', pid, - hiddenGlobalTracks: new Set( + hiddenGlobalTracks: new Set( globalTrackIndexes.filter((i) => i !== globalTrackIndex) ), - hiddenLocalTracks: new Set( + hiddenLocalTracks: new Set( localTrackIndexes.filter((i) => i !== isolatedTrackIndex) ), selectedThreadIndexes, @@ -1934,7 +1954,7 @@ export function closeBottomBox(): ThunkAction { } export function handleCallNodeTransformShortcut( - event: SyntheticKeyboardEvent<>, + event: React.KeyboardEvent, threadsKey: ThreadsKey, callNodeIndex: IndexIntoCallNodeTable ): ThunkAction { diff --git a/src/actions/publish.js b/src/actions/publish.ts similarity index 97% rename from src/actions/publish.js rename to src/actions/publish.ts index eed6dd12fe..6dcff8d57e 100644 --- a/src/actions/publish.js +++ b/src/actions/publish.ts @@ -1,12 +1,11 @@ /* 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/. */ - -// @flow import { stripIndent } from 'common-tags'; import { uploadBinaryProfileData } from 'firefox-profiler/profile-logic/profile-store'; import { sendAnalytics } from 'firefox-profiler/utils/analytics'; +import type { SanitizeProfileResult } from 'firefox-profiler/profile-logic/sanitize'; import { getUploadGeneration, getSanitizedProfile, @@ -40,7 +39,7 @@ import type { } from 'firefox-profiler/types'; export function toggleCheckedSharingOptions( - slug: $Keys + slug: keyof CheckedSharingOptions ): Action { return { type: 'TOGGLE_CHECKED_SHARING_OPTION', @@ -78,7 +77,7 @@ export function updateUploadProgress(uploadProgress: number): Action { /** * A profile upload failed. */ -export function uploadFailed(error: mixed): Action { +export function uploadFailed(error: unknown): Action { return { type: 'UPLOAD_FAILED', error }; } @@ -91,7 +90,7 @@ export function uploadFailed(error: mixed): Action { async function persistJustUploadedProfileInformationToDb( profileToken: string, jwtToken: string | null, - sanitizedInformation, + sanitizedInformation: SanitizeProfileResult, prepublishedState: State ): Promise { if (process.env.NODE_ENV === 'test' && !window.indexedDB) { @@ -103,7 +102,7 @@ async function persistJustUploadedProfileInformationToDb( } const zeroAt = getZeroAt(prepublishedState); - const adjustRange = (range) => ({ + const adjustRange = (range: StartEndRange) => ({ start: range.start - zeroAt, end: range.end - zeroAt, }); @@ -243,8 +242,7 @@ export function attemptToPublish(): ThunkAction> { dispatch(uploadCompressionStarted(abortfunction)); const sanitizedInformation = getSanitizedProfile(prePublishedState); - const gzipData: Uint8Array = - await getSanitizedProfileData(prePublishedState); + const gzipData = await getSanitizedProfileData(prePublishedState); // The previous line was async, check to make sure that this request is still valid. // The upload could have been aborted while we were compressing the data. diff --git a/src/actions/receive-profile.js b/src/actions/receive-profile.ts similarity index 95% rename from src/actions/receive-profile.js rename to src/actions/receive-profile.ts index 87d722e7be..2c61e5daef 100644 --- a/src/actions/receive-profile.js +++ b/src/actions/receive-profile.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { oneLine } from 'common-tags'; import queryString from 'query-string'; import JSZip from 'jszip'; @@ -83,6 +81,7 @@ import type { ThreadIndex, TabID, PageList, + MixedObject, } from 'firefox-profiler/types'; import type { @@ -121,13 +120,13 @@ export function waitingForProfileFromBrowser(): Action { */ export function loadProfile( profile: Profile, - config: $Shape<{| - pathInZipFile: string, - implementationFilter: ImplementationFilter, - transformStacks: TransformStacksPerThread, - browserConnection: BrowserConnection | null, - skipSymbolication: boolean, // Please use this in tests only. - |}> = {}, + config: Partial<{ + pathInZipFile: string; + implementationFilter: ImplementationFilter; + transformStacks: TransformStacksPerThread; + browserConnection: BrowserConnection | null; + skipSymbolication: boolean; // Please use this in tests only. + }> = {}, initialLoad: boolean = false ): ThunkAction> { return async (dispatch) => { @@ -153,9 +152,9 @@ export function loadProfile( dispatch({ type: 'PROFILE_LOADED', profile, - pathInZipFile: config.pathInZipFile, - implementationFilter: config.implementationFilter, - transformStacks: config.transformStacks, + pathInZipFile: config.pathInZipFile ?? null, + implementationFilter: config.implementationFilter ?? null, + transformStacks: config.transformStacks ?? null, }); // During initial load, we are upgrading the URL and generating the UrlState @@ -400,13 +399,13 @@ export function resymbolicateProfile(): ThunkAction> { // `loadProfile`) and wait until symbolication finishes. export function viewProfile( profile: Profile, - config: $Shape<{| - pathInZipFile: string, - implementationFilter: ImplementationFilter, - transformStacks: TransformStacksPerThread, - skipSymbolication: boolean, - browserConnection: BrowserConnection | null, - |}> = {} + config: Partial<{ + pathInZipFile: string; + implementationFilter: ImplementationFilter; + transformStacks: TransformStacksPerThread; + skipSymbolication: boolean; + browserConnection: BrowserConnection | null; + }> = {} ): ThunkAction> { return async (dispatch) => { await dispatch(loadProfile(profile, config, false)); @@ -476,7 +475,7 @@ export function bulkProcessSymbolicationSteps( let requestIdleCallbackPolyfill: ( callback: () => void, _opts?: { timeout: number } -) => mixed; +) => void; if (typeof window === 'object' && window.requestIdleCallback) { requestIdleCallbackPolyfill = window.requestIdleCallback; @@ -500,7 +499,7 @@ class SymbolicationStepQueue { this._requestedUpdate = false; } - _scheduleUpdate(dispatch) { + _scheduleUpdate(dispatch: Dispatch) { // Only request an update if one hasn't already been scheduled. if (!this._requestedUpdate) { requestIdleCallbackPolyfill(() => this._dispatchUpdate(dispatch), { @@ -510,7 +509,7 @@ class SymbolicationStepQueue { } } - _dispatchUpdate(dispatch) { + _dispatchUpdate(dispatch: Dispatch) { const updates = this._updates; const observers = this._updateObservers; this._updates = new Map(); @@ -549,12 +548,12 @@ const _symbolicationStepQueueSingleton = new SymbolicationStepQueue(); */ async function _unpackGeckoProfileFromBrowser( profile: ArrayBuffer | MixedObject -): MixedObject { +): Promise { // Note: the following check will work for array buffers coming from another // global. This happens especially with tests but could happen in the future // in Firefox too. if (Object.prototype.toString.call(profile) === '[object ArrayBuffer]') { - return _extractJsonFromArrayBuffer(profile); + return _extractJsonFromArrayBuffer(profile as ArrayBuffer); } return profile; } @@ -573,7 +572,7 @@ function getSymbolStore( async function requestSymbolsWithCallback( symbolSupplierName: string, requests: LibSymbolicationRequest[], - callback: (path: string, requestJson: string) => Promise + callback: (path: string, requestJson: string) => Promise ) { for (const { lib } of requests) { dispatch(requestingSymbolTable(lib)); @@ -667,7 +666,7 @@ export async function doSymbolicateProfile( ) { dispatch(startSymbolicating()); - const completionPromises = []; + const completionPromises: Promise[] = []; await symbolicateProfile( profile, @@ -682,7 +681,7 @@ export async function doSymbolicateProfile( dispatch, threadIndex, symbolicationStepInfo, - resolve + () => resolve(undefined) ); }) ); @@ -767,7 +766,7 @@ export function unwrapBrowserConnection( case 'TIMED_OUT': throw new Error('Timed out when waiting for reply to WebChannel message'); default: - throw assertExhaustiveCheck(browserConnectionStatus.status); + throw assertExhaustiveCheck(browserConnectionStatus as never); } // Now we know that browserConnectionStatus.status === 'ESTABLISHED'. @@ -803,7 +802,7 @@ export function retrieveProfileFromBrowser( }); const unpackedProfile = await _unpackGeckoProfileFromBrowser(rawGeckoProfile); - const meta = unpackedProfile.meta; + const meta = (unpackedProfile as any).meta; if (meta.configuration && meta.configuration.features.includes('power')) { try { await Promise.all([ @@ -813,7 +812,10 @@ export function retrieveProfileFromBrowser( meta.startTime + meta.profilingEndTime ) .then((tracks) => - insertExternalPowerCountersIntoProfile(tracks, unpackedProfile) + insertExternalPowerCountersIntoProfile( + tracks as any, + unpackedProfile as any + ) ), browserConnection .getExternalMarkers( @@ -821,7 +823,10 @@ export function retrieveProfileFromBrowser( meta.startTime + meta.profilingEndTime ) .then((markers) => - insertExternalMarkersIntoProfile(markers, unpackedProfile) + insertExternalMarkersIntoProfile( + markers, + unpackedProfile as any + ) ), ]); } catch (error) { @@ -829,7 +834,7 @@ export function retrieveProfileFromBrowser( console.error(error); } } - const profile = processGeckoProfile(unpackedProfile); + const profile = processGeckoProfile(unpackedProfile as any); await dispatch(loadProfile(profile, { browserConnection }, initialLoad)); } catch (error) { dispatch(fatalError(error)); @@ -847,7 +852,7 @@ export function waitingForProfileFromStore(): Action { export function waitingForProfileFromUrl(profileUrl?: string): Action { return { type: 'WAITING_FOR_PROFILE_FROM_URL', - profileUrl, + profileUrl: profileUrl ?? null, }; } @@ -887,19 +892,19 @@ function _loadProbablyFailedDueToSafariLocalhostHTTPRestriction( } class SafariLocalhostHTTPLoadError extends Error { - name = 'SafariLocalhostHTTPLoadError'; + override name = 'SafariLocalhostHTTPLoadError'; } type FetchProfileArgs = { - url: string, - onTemporaryError: (TemporaryError) => void, + url: string; + onTemporaryError: (param: TemporaryError) => void; // Allow tests to capture the reported error, but normally use console.error. - reportError?: (...data: Array) => void, + reportError?: (...data: Array) => void; }; type ProfileOrZip = - | {| responseType: 'PROFILE', profile: mixed |} - | {| responseType: 'ZIP', zip: JSZip |}; + | { responseType: 'PROFILE'; profile: unknown } + | { responseType: 'ZIP'; zip: JSZip }; /** * Tries to fetch a profile on `url`. If the profile is not found, @@ -1059,8 +1064,8 @@ async function _extractZipFromResponse( */ async function _extractJsonFromArrayBuffer( arrayBuffer: ArrayBuffer -): Promise { - let profileBytes = new Uint8Array(arrayBuffer); +): Promise { + let profileBytes: Uint8Array = new Uint8Array(arrayBuffer); // Check for the gzip magic number in the header. if (isGzip(profileBytes)) { profileBytes = await decompress(profileBytes); @@ -1077,7 +1082,7 @@ async function _extractJsonFromResponse( response: Response, reportError: (...data: Array) => void, fileType: 'application/json' | null -): Promise { +): Promise { let arrayBuffer: ArrayBuffer | null = null; try { // await before returning so that we can catch JSON parse errors. @@ -1171,7 +1176,7 @@ export function retrieveProfileOrZipFromUrl( } default: throw assertExhaustiveCheck( - response.responseType, + response as never, 'Expected to receive an archive or profile from _fetchProfile.' ); } @@ -1194,19 +1199,19 @@ function _fileReader(input: File) { // reader.result very well, as its definition is . // Here we ensure type safety by returning the proper Promise type from the // methods below. - reader.onload = () => resolve((reader.result: any)); + reader.onload = () => resolve(reader.result as any); reader.onerror = () => reject(reader.error); }); return { asText(): Promise { reader.readAsText(input); - return promise; + return promise as Promise; }, asArrayBuffer(): Promise { reader.readAsArrayBuffer(input); - return promise; + return promise as Promise; }, }; } @@ -1424,7 +1429,12 @@ export function retrieveProfileForRawUrl( arrayFormat: 'bracket', // This uses parameters with brackets for arrays. }); if (Array.isArray(query.profiles)) { - await dispatch(retrieveProfilesToCompare(query.profiles, true)); + await dispatch( + retrieveProfilesToCompare( + query.profiles.filter((p): p is string => p !== null), + true + ) + ); } break; } @@ -1450,7 +1460,7 @@ export function retrieveProfileForRawUrl( 'Responding via postMessage that the profiler is ready.' ); const otherWindow = event.source ?? window; - otherWindow.postMessage({ name: 'ready:response' }, '*'); + (otherWindow as any).postMessage({ name: 'ready:response' }, '*'); break; } default: @@ -1462,7 +1472,6 @@ export function retrieveProfileForRawUrl( } case 'uploaded-recordings': case 'none': - case 'from-file': case 'local': case 'unpublished': // There is no profile to download for these datasources. diff --git a/src/actions/zipped-profiles.js b/src/actions/zipped-profiles.ts similarity index 89% rename from src/actions/zipped-profiles.js rename to src/actions/zipped-profiles.ts index b8d94bcf75..e51d660ae8 100644 --- a/src/actions/zipped-profiles.js +++ b/src/actions/zipped-profiles.ts @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { getZipFileTable, getZipFileState, @@ -17,7 +15,7 @@ export function changeSelectedZipFile( selectedZipFileIndex: IndexIntoZipFileTable ): Action { return { - type: 'CHANGE_SELECTED_ZIP_FILE', + type: 'CHANGE_SELECTED_ZIP_FILE' as const, selectedZipFileIndex, }; } @@ -26,7 +24,7 @@ export function changeExpandedZipFile( expandedZipFileIndexes: Array ): Action { return { - type: 'CHANGE_EXPANDED_ZIP_FILES', + type: 'CHANGE_EXPANDED_ZIP_FILES' as const, expandedZipFileIndexes, }; } @@ -54,7 +52,7 @@ export function viewProfileFromZip( ); } - dispatch({ type: 'PROCESS_PROFILE_FROM_ZIP_FILE', pathInZipFile }); + dispatch({ type: 'PROCESS_PROFILE_FROM_ZIP_FILE' as const, pathInZipFile }); try { // Attempt to unserialize the profile. @@ -78,7 +76,10 @@ export function viewProfileFromZip( 'Failed to process the profile in the archive with the following error:' ); console.error(error); - dispatch({ type: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE', error }); + dispatch({ + type: 'FAILED_TO_PROCESS_PROFILE_FROM_ZIP_FILE' as const, + error, + }); } }; } @@ -102,9 +103,9 @@ export function viewProfileFromPathInZipFile( } export function returnToZipFileList() { - return { type: 'RETURN_TO_ZIP_FILE_LIST' }; + return { type: 'RETURN_TO_ZIP_FILE_LIST' as const }; } export function showErrorForNoFileInZip(pathInZipFile: string) { - return { type: 'FILE_NOT_FOUND_IN_ZIP_FILE', pathInZipFile }; + return { type: 'FILE_NOT_FOUND_IN_ZIP_FILE' as const, pathInZipFile }; } From e52c41025d3ef9d7c57654ccfc90cbcdb3b11db8 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:19:40 -0400 Subject: [PATCH 053/124] Convert app-logic/create-store.js. --- src/app-logic/create-store.js | 36 ----------------------------------- src/app-logic/create-store.ts | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 src/app-logic/create-store.js create mode 100644 src/app-logic/create-store.ts diff --git a/src/app-logic/create-store.js b/src/app-logic/create-store.js deleted file mode 100644 index 16c9266c3c..0000000000 --- a/src/app-logic/create-store.js +++ /dev/null @@ -1,36 +0,0 @@ -/* 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/. */ - -// @flow - -import { createStore, applyMiddleware } from 'redux'; -import { thunk } from 'redux-thunk'; -import { createLogger } from 'redux-logger'; -import reducers from 'firefox-profiler/reducers'; -import type { Store } from 'firefox-profiler/types'; - -/** - * Isolate the store creation into a function, so that it can be used outside of the - * app's execution context, e.g. for testing. - * @return {object} Redux store. - */ -export default function initializeStore(): Store { - const middlewares = [thunk]; - - if (process.env.NODE_ENV === 'development') { - middlewares.push( - createLogger({ - collapsed: true, - titleFormatter: (action, time, duration) => - `[action] ${action.type} (in ${duration.toFixed(2)} ms)`, - logErrors: false, - duration: true, - }) - ); - } - - const store = createStore(reducers, applyMiddleware(...middlewares)); - - return store; -} diff --git a/src/app-logic/create-store.ts b/src/app-logic/create-store.ts new file mode 100644 index 0000000000..25251020db --- /dev/null +++ b/src/app-logic/create-store.ts @@ -0,0 +1,33 @@ +/* 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/. */ + +import { createStore, applyMiddleware, type Middleware } from 'redux'; +import { thunk, type ThunkMiddleware } from 'redux-thunk'; +import { createLogger } from 'redux-logger'; +import reducers from 'firefox-profiler/reducers'; +import type { Action, State, Store } from 'firefox-profiler/types'; + +/** + * Isolate the store creation into a function, so that it can be used outside of the + * app's execution context, e.g. for testing. + * @return {object} Redux store. + */ +export default function initializeStore(): Store { + let loggerMiddleware: Middleware | null = null; + if (process.env.NODE_ENV === 'development') { + loggerMiddleware = createLogger({ + collapsed: true, + titleFormatter: (action, time, duration) => + `[action] ${action.type} (in ${duration.toFixed(2)} ms)`, + logErrors: false, + duration: true, + }); + } + + const thunkMiddleware: ThunkMiddleware = thunk; + const enhancer = loggerMiddleware + ? applyMiddleware(thunkMiddleware, loggerMiddleware) + : applyMiddleware(thunkMiddleware); + return createStore(reducers, enhancer); +} From b109476379b06fa12f6fbbc0114b4a01adc8ae78 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:48:41 -0400 Subject: [PATCH 054/124] Convert the rest of src/utils except for window-console.js. --- ...emirror-shared.js => codemirror-shared.ts} | 21 +-- src/utils/connect.js | 165 ------------------ src/utils/connect.ts | 137 +++++++++++++++ .../{fetch-assembly.js => fetch-assembly.ts} | 13 +- .../{fetch-source.js => fetch-source.ts} | 12 +- 5 files changed, 156 insertions(+), 192 deletions(-) rename src/utils/{codemirror-shared.js => codemirror-shared.ts} (92%) delete mode 100644 src/utils/connect.js create mode 100644 src/utils/connect.ts rename src/utils/{fetch-assembly.js => fetch-assembly.ts} (92%) rename src/utils/{fetch-source.js => fetch-source.ts} (95%) diff --git a/src/utils/codemirror-shared.js b/src/utils/codemirror-shared.ts similarity index 92% rename from src/utils/codemirror-shared.js rename to src/utils/codemirror-shared.ts index 4dda68bd17..16c04454e8 100644 --- a/src/utils/codemirror-shared.js +++ b/src/utils/codemirror-shared.ts @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { EditorView, @@ -10,12 +9,8 @@ import { gutter, gutterLineClass, } from '@codemirror/view'; -import { - EditorState, - StateField, - StateEffect, - RangeSet, -} from '@codemirror/state'; +import type { EditorState } from '@codemirror/state'; +import { StateField, StateEffect, RangeSet } from '@codemirror/state'; import type { LineTimings } from 'firefox-profiler/types'; @@ -23,7 +18,7 @@ import { emptyLineTimings } from 'firefox-profiler/profile-logic/line-timings'; // This gutter marker applies the "cm-nonZeroLine" class to gutter elements. const nonZeroLineGutterMarker = new (class extends GutterMarker { - elementClass = 'cm-nonZeroLine'; + override elementClass = 'cm-nonZeroLine'; })(); // This "decoration" applies the "cm-nonZeroLine" class to the line of assembly @@ -58,7 +53,7 @@ const timingsField = StateField.define({ // Then they are sorted, because our caller wants to have a sorted list. function getSortedStartPositionsOfNonZeroLines(state: EditorState): number[] { const timings = state.field(timingsField); - const nonZeroLines = new Set(); + const nonZeroLines = new Set(); for (const lineNumber of timings.totalLineHits.keys()) { nonZeroLines.add(lineNumber); } @@ -66,7 +61,7 @@ function getSortedStartPositionsOfNonZeroLines(state: EditorState): number[] { nonZeroLines.add(lineNumber); } const lineCount = state.doc.lines; - const positions = [...nonZeroLines] + const positions = Array.from(nonZeroLines) .filter((l) => l >= 1 && l <= lineCount) .map((lineNumber) => state.doc.line(lineNumber).from); positions.sort((a, b) => a - b); @@ -111,7 +106,7 @@ export class StringMarker extends GutterMarker { this._s = s; } - toDOM() { + override toDOM() { return document.createTextNode(this._s); } } @@ -125,7 +120,7 @@ const totalTimingsGutter = gutter({ const lineNumber = view.state.doc.lineAt(line.from).number; const timings = view.state.field(timingsField); const totalTime = timings.totalLineHits.get(lineNumber); - return totalTime !== undefined ? new StringMarker(totalTime) : null; + return totalTime !== undefined ? new StringMarker(String(totalTime)) : null; }, lineMarkerChange(update) { // Return true if the update affects the total timings in the gutter. @@ -144,7 +139,7 @@ const selfTimingsGutter = gutter({ const lineNumber = view.state.doc.lineAt(line.from).number; const timings = view.state.field(timingsField); const selfTime = timings.selfLineHits.get(lineNumber); - return selfTime !== undefined ? new StringMarker(selfTime) : null; + return selfTime !== undefined ? new StringMarker(String(selfTime)) : null; }, lineMarkerChange(update) { // Return true if the update affects the self timings in the gutter. diff --git a/src/utils/connect.js b/src/utils/connect.js deleted file mode 100644 index bbf83c1478..0000000000 --- a/src/utils/connect.js +++ /dev/null @@ -1,165 +0,0 @@ -/* 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/. */ -// @flow - -// Ignore for this file, which uses extensive use of generic type bounds, which -// triggers a false positive with this rule. -/* eslint-disable flowtype/no-weak-types */ - -// At this time, it's not worth migrating away from this existential type. It's -// probably possible to move to using the built-in react-redux types. -/* eslint-disable flowtype/no-existential-type */ - -import * as React from 'react'; -import { connect } from 'react-redux'; -import type { - Dispatch, - State, - ThunkAction, - Action, -} from 'firefox-profiler/types'; - -type MapStateToProps = ( - state: State, - ownProps: OwnProps -) => StateProps; - -type MapDispatchToProps = - | ((dispatch: Dispatch, ownProps: OwnProps) => DispatchProps) - | DispatchProps; - -type MergeProps< - StateProps, - DispatchProps: Object, - OwnProps: Object, - Props: Object, -> = ( - stateProps: StateProps, - dispatchProps: DispatchProps, - ownProps: OwnProps -) => Props; - -type ConnectOptions = { - pure?: boolean, - areStatesEqual?: boolean, - areOwnPropsEqual?: boolean, - areStatePropsEqual?: boolean, - areMergedPropsEqual?: boolean, - storeKey?: boolean, - forwardRef?: boolean, -}; - -/** - * This function type describes the operation of taking a simple action creator, and - * just returning it. - */ -type WrapActionCreator = ( - // Take as input an action creator. - (...Args) => Action - // If this function matches the above signature, do not modify it. -) => (...Args) => Action; - -/** - * This function type describes the operation of removing the (Dispatch, GetState) from - * a thunk action creator. - * For instance: - * (...Args) => (Dispatch, GetState) => Returns - * - * Gets transformed into: - * (...Args) => Returns - */ -type WrapThunkActionCreator = ( - // Take as input a ThunkAction. - (...Args) => ThunkAction - // Return the wrapped action. -) => (...Args) => Returns; - -/** - * This type takes a Props object and wraps each function in Redux's connect function. - * It is primarily exported for testing as explicitConnect should do this for us - * automatically. It leaves normal action creators alone, but with ThunkActions it - * removes the (Dispatch, GetState) part of a ThunkAction. - */ -export type WrapDispatchProps = $ObjMap< - DispatchProps, - WrapActionCreator<*> & WrapThunkActionCreator<*, *>, ->; - -/** - * This type takes a single action creator, and returns the type as if the dispatch - * function was wrapped around it. It leaves normal action creators alone, but with - * ThunkActions it removes the (Dispatch, GetState) part of a ThunkAction. - */ -export type WrapFunctionInDispatch = $Call< - WrapActionCreator<*> & WrapThunkActionCreator<*, *>, - Fn, ->; - -type ExplicitConnectOptions< - OwnProps: Object, - StateProps: Object, - DispatchProps: Object, -> = {| - mapStateToProps?: MapStateToProps, - mapDispatchToProps?: MapDispatchToProps, - mergeProps?: MergeProps< - StateProps, - DispatchProps, - OwnProps, - ConnectedProps, - >, - options?: ConnectOptions, - component: React.ComponentType< - ConnectedProps, - >, -|}; - -export type ConnectedProps< - OwnProps: Object, - StateProps: Object, - DispatchProps: Object, -> = $ReadOnly<{| - ...OwnProps, - ...StateProps, - ...DispatchProps, -|}>; - -export type ConnectedComponent< - OwnProps: Object, - StateProps: Object, - DispatchProps: Object, -> = - | React.ComponentType> - | React.StatelessFunctionalComponent< - ConnectedProps, - >; - -/** - * react-redux's connect function is too polymorphic and problematic. This function - * is a wrapper to simplify the typing of connect and make it more explicit, and - * less magical. - */ -export default function explicitConnect< - OwnProps: Object, - StateProps: Object, - DispatchProps: Object, ->( - connectOptions: ExplicitConnectOptions -): React.ComponentType { - const { - mapStateToProps, - mapDispatchToProps, - mergeProps, - options, - component, - } = connectOptions; - - // Opt out of the flow-typed definition of react-redux's connect, and use our own. - return (connect: any)( - mapStateToProps, - mapDispatchToProps, - mergeProps, - options - )(component); -} diff --git a/src/utils/connect.ts b/src/utils/connect.ts new file mode 100644 index 0000000000..b08c067d99 --- /dev/null +++ b/src/utils/connect.ts @@ -0,0 +1,137 @@ +/* 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/. */ + +import type * as React from 'react'; +import { connect } from 'react-redux'; +import type { + Dispatch, + State, + ThunkAction, + Action, +} from 'firefox-profiler/types'; + +type MapStateToProps = ( + state: State, + ownProps: OwnProps +) => StateProps; + +type MapDispatchToProps = + | ((dispatch: Dispatch, ownProps: OwnProps) => DispatchProps) + | DispatchProps; + +type MergeProps = ( + stateProps: StateProps, + dispatchProps: DispatchProps, + ownProps: OwnProps +) => Props; + +type ConnectOptions = { + pure?: boolean; + areStatesEqual?: boolean; + areOwnPropsEqual?: boolean; + areStatePropsEqual?: boolean; + areMergedPropsEqual?: boolean; + storeKey?: boolean; + forwardRef?: boolean; +}; + +/** + * This type takes a Props object and wraps each function in Redux's connect function. + * It is primarily exported for testing as explicitConnect should do this for us + * automatically. It leaves normal action creators alone, but with ThunkActions it + * removes the (Dispatch, GetState) part of a ThunkAction. + */ +export type WrapDispatchProps = { + [K in keyof DispatchProps]: WrapFunctionInDispatch; +}; + +/** + * This type takes a single action creator, and returns the type as if the dispatch + * function was wrapped around it. It leaves normal action creators alone, but with + * ThunkActions it removes the (Dispatch, GetState) part of a ThunkAction. + */ +export type WrapFunctionInDispatch = Fn extends ( + ...args: infer Args +) => ThunkAction + ? (...args: Args) => Returns + : Fn extends (...args: infer Args) => Action + ? (...args: Args) => Action + : Fn; + +type ExplicitConnectOptions = { + mapStateToProps?: MapStateToProps; + mapDispatchToProps?: MapDispatchToProps; + mergeProps?: MergeProps< + StateProps, + DispatchProps, + OwnProps, + ConnectedProps + >; + options?: ConnectOptions; + component: React.ComponentType< + ConnectedProps + >; +}; + +export type ConnectedProps = Readonly< + OwnProps & StateProps & WrapDispatchProps +>; + +export type ConnectedComponent = + | React.ComponentType> + | React.FunctionComponent< + ConnectedProps + >; + +/** + * react-redux's connect function is too polymorphic and problematic. This function + * is a wrapper to simplify the typing of connect and make it more explicit, and + * less magical. + */ +export default function explicitConnect( + connectOptions: ExplicitConnectOptions +): React.ComponentType { + const { + mapStateToProps, + mapDispatchToProps, + mergeProps, + options, + component, + } = connectOptions; + + // Opt out of the TypeScript definition of react-redux's connect. + // If want to figure out how to align their types with ours, feel free + // but don't get your hopes up. + return (connect as any)( + mapStateToProps, + mapDispatchToProps, + mergeProps, + options + )(component); +} + +export function explicitConnectWithForwardRef< + OwnProps, + StateProps, + DispatchProps, + RefInterface, +>( + connectOptions: ExplicitConnectOptions +): React.ComponentType }> { + const { + mapStateToProps, + mapDispatchToProps, + mergeProps, + options, + component, + } = connectOptions; + + // Opt out of the flow-typed definition of react-redux's connect, and use our own. + return (connect as any)( + mapStateToProps, + mapDispatchToProps, + mergeProps, + options + )(component); +} diff --git a/src/utils/fetch-assembly.js b/src/utils/fetch-assembly.ts similarity index 92% rename from src/utils/fetch-assembly.js rename to src/utils/fetch-assembly.ts index e06a9132e4..426dd9d3ac 100644 --- a/src/utils/fetch-assembly.js +++ b/src/utils/fetch-assembly.ts @@ -2,22 +2,21 @@ * 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/. */ -// @flow - import { assertExhaustiveCheck } from './flow'; import type { ApiQueryError, DecodedInstruction, NativeSymbolInfo, Lib, + MixedObject, } from 'firefox-profiler/types'; import { queryApiWithFallback } from './query-api'; import type { ExternalCommunicationDelegate } from './query-api'; import { isLocalURL } from './url'; export type FetchAssemblyResult = - | { type: 'SUCCESS', instructions: DecodedInstruction[] } - | { type: 'ERROR', errors: ApiQueryError[] }; + | { type: 'SUCCESS'; instructions: DecodedInstruction[] } + | { type: 'ERROR'; errors: ApiQueryError[] }; /** * Fetch a native function's assembly instructions, using the symbolication @@ -66,7 +65,7 @@ export async function fetchAssembly( return { type: 'ERROR', errors: queryResult.errors }; } default: - throw assertExhaustiveCheck(queryResult.type); + throw assertExhaustiveCheck(queryResult, 'queryResult.type'); } } @@ -98,11 +97,11 @@ function convertJsonInstructions( throw new Error('The instructions field in asm response is not an array'); } const { startAddress, instructions } = responseJSON; - const startAddressNum = parseInt(startAddress, 16); + const startAddressNum = parseInt(startAddress as string, 16); if (isNaN(startAddressNum)) { throw new Error('Invalid startAddress value in asm response'); } - return instructions.map((instructionData) => { + return instructions.map((instructionData: unknown) => { if (!Array.isArray(instructionData)) { throw new Error('Invalid instruction data (not an array)'); } diff --git a/src/utils/fetch-source.js b/src/utils/fetch-source.ts similarity index 95% rename from src/utils/fetch-source.js rename to src/utils/fetch-source.ts index f30ad6bc62..ff3fa07f14 100644 --- a/src/utils/fetch-source.js +++ b/src/utils/fetch-source.ts @@ -2,8 +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/. */ -// @flow - import { assertExhaustiveCheck } from './flow'; import { getDownloadRecipeForSourceFile, @@ -20,8 +18,8 @@ import type { } from 'firefox-profiler/types'; export type FetchSourceResult = - | { type: 'SUCCESS', source: string } - | { type: 'ERROR', errors: SourceCodeLoadingError[] }; + | { type: 'SUCCESS'; source: string } + | { type: 'ERROR'; errors: SourceCodeLoadingError[] }; /** * Fetch the source code for a file path from the web. @@ -83,7 +81,7 @@ export async function fetchSource( break; } default: - throw assertExhaustiveCheck(queryResult.type); + throw assertExhaustiveCheck(queryResult); } } @@ -171,12 +169,12 @@ export async function fetchSource( } default: - throw assertExhaustiveCheck(downloadRecipe.type); + throw assertExhaustiveCheck(downloadRecipe); } return { type: 'ERROR', errors }; } -function convertResponseJsonToSourceCode(responseJson: MixedObject): string { +function convertResponseJsonToSourceCode(responseJson: any): string { if (!('source' in responseJson) || typeof responseJson.source !== 'string') { throw new Error('No string "source" property on API response'); } From 900b289a617c57be9a8ca39198a6301a28c73c2e Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:51:22 -0400 Subject: [PATCH 055/124] Convert components/shared and components/tooltip. --- ...emirror.js => AssemblyView-codemirror.tsx} | 6 +- .../{AssemblyView.js => AssemblyView.tsx} | 24 +- .../shared/{Backtrace.js => Backtrace.tsx} | 17 +- .../{BlobUrlLink.js => BlobUrlLink.tsx} | 33 +- .../{ArrowPanel.js => ArrowPanel.tsx} | 46 +-- .../ButtonWithPanel/{index.js => index.tsx} | 44 ++- ...ContextMenu.js => CallNodeContextMenu.tsx} | 134 ++++---- ...Setting.js => CallTreeStrategySetting.tsx} | 38 +-- .../{ContextMenu.js => ContextMenu.tsx} | 19 +- ...nter.js => ContextMenuNoHidingOnEnter.tsx} | 1 - ...tMenuTrigger.js => ContextMenuTrigger.tsx} | 13 +- .../shared/{Draggable.js => Draggable.tsx} | 53 ++- .../{EmptyReasons.js => EmptyReasons.tsx} | 15 +- src/components/shared/FilterNavigatorBar.js | 120 ------- src/components/shared/FilterNavigatorBar.tsx | 122 +++++++ src/components/shared/{Icon.js => Icon.tsx} | 41 ++- ...IdleSearchField.js => IdleSearchField.tsx} | 40 ++- ...igationLink.js => InnerNavigationLink.tsx} | 45 ++- ...erContextMenu.js => MarkerContextMenu.tsx} | 95 +++--- ...xtMenu.js => MarkerFiltersContextMenu.tsx} | 30 +- .../{MarkerSettings.js => MarkerSettings.tsx} | 36 +- ...NetworkSettings.js => NetworkSettings.tsx} | 36 +- .../{PanelSearch.js => PanelSearch.tsx} | 22 +- ...oSummary.js => ProfileMetaInfoSummary.tsx} | 22 +- .../{Reorderable.js => Reorderable.tsx} | 84 ++--- ...pContents.js => SampleTooltipContents.tsx} | 40 +-- ...codemirror.js => SourceView-codemirror.ts} | 2 - .../shared/{SourceView.js => SourceView.tsx} | 24 +- ...ting.js => StackImplementationSetting.tsx} | 28 +- .../{StackSettings.js => StackSettings.tsx} | 56 ++-- .../shared/{StyleDef.js => StyleDef.tsx} | 30 +- ...TabSelectorMenu.js => TabSelectorMenu.tsx} | 47 ++- ...ackSearchField.js => TrackSearchField.tsx} | 22 +- ...ormNavigator.js => TransformNavigator.tsx} | 18 +- .../shared/{TreeView.js => TreeView.tsx} | 316 +++++++++--------- .../{VirtualList.js => VirtualList.tsx} | 150 +++++---- .../shared/{Warning.js => Warning.tsx} | 27 +- .../shared/{WithSize.js => WithSize.tsx} | 37 +- .../shared/chart/{Canvas.js => Canvas.tsx} | 102 +++--- .../chart/{Viewport.js => Viewport.tsx} | 184 +++++----- .../{ActivityGraph.js => ActivityGraph.tsx} | 81 +++-- ...GraphCanvas.js => ActivityGraphCanvas.tsx} | 72 ++-- ...tyGraphFills.js => ActivityGraphFills.tsx} | 140 ++++---- .../thread/{CPUGraph.js => CPUGraph.tsx} | 36 +- .../{HeightGraph.js => HeightGraph.tsx} | 64 ++-- .../{SampleGraph.js => SampleGraph.tsx} | 111 +++--- .../thread/{StackGraph.js => StackGraph.tsx} | 38 +-- .../tooltip/{CallNode.js => CallNode.tsx} | 66 ++-- .../{DivWithTooltip.js => DivWithTooltip.tsx} | 23 +- .../tooltip/{GCMarker.js => GCMarker.tsx} | 17 +- .../tooltip/{Marker.js => Marker.tsx} | 63 ++-- .../{NetworkMarker.js => NetworkMarker.tsx} | 36 +- .../tooltip/{Tooltip.js => Tooltip.tsx} | 45 +-- .../{TooltipDetails.js => TooltipDetails.tsx} | 20 +- .../tooltip/{TrackPower.js => TrackPower.tsx} | 50 ++- 55 files changed, 1518 insertions(+), 1563 deletions(-) rename src/components/shared/{AssemblyView-codemirror.js => AssemblyView-codemirror.tsx} (99%) rename src/components/shared/{AssemblyView.js => AssemblyView.tsx} (93%) rename src/components/shared/{Backtrace.js => Backtrace.tsx} (90%) rename src/components/shared/{BlobUrlLink.js => BlobUrlLink.tsx} (71%) rename src/components/shared/ButtonWithPanel/{ArrowPanel.js => ArrowPanel.tsx} (80%) rename src/components/shared/ButtonWithPanel/{index.js => index.tsx} (87%) rename src/components/shared/{CallNodeContextMenu.js => CallNodeContextMenu.tsx} (91%) rename src/components/shared/{CallTreeStrategySetting.js => CallTreeStrategySetting.tsx} (87%) rename src/components/shared/{ContextMenu.js => ContextMenu.tsx} (81%) mode change 100755 => 100644 rename src/components/shared/{ContextMenuNoHidingOnEnter.js => ContextMenuNoHidingOnEnter.tsx} (99%) rename src/components/shared/{ContextMenuTrigger.js => ContextMenuTrigger.tsx} (69%) rename src/components/shared/{Draggable.js => Draggable.tsx} (77%) rename src/components/shared/{EmptyReasons.js => EmptyReasons.tsx} (79%) delete mode 100644 src/components/shared/FilterNavigatorBar.js create mode 100644 src/components/shared/FilterNavigatorBar.tsx rename src/components/shared/{Icon.js => Icon.tsx} (73%) rename src/components/shared/{IdleSearchField.js => IdleSearchField.tsx} (80%) rename src/components/shared/{InnerNavigationLink.js => InnerNavigationLink.tsx} (55%) rename src/components/shared/{MarkerContextMenu.js => MarkerContextMenu.tsx} (90%) rename src/components/shared/{MarkerFiltersContextMenu.js => MarkerFiltersContextMenu.tsx} (86%) rename src/components/shared/{MarkerSettings.js => MarkerSettings.tsx} (88%) rename src/components/shared/{NetworkSettings.js => NetworkSettings.tsx} (71%) rename src/components/shared/{PanelSearch.js => PanelSearch.tsx} (86%) rename src/components/shared/{ProfileMetaInfoSummary.js => ProfileMetaInfoSummary.tsx} (84%) rename src/components/shared/{Reorderable.js => Reorderable.tsx} (85%) rename src/components/shared/{SampleTooltipContents.js => SampleTooltipContents.tsx} (88%) rename src/components/shared/{SourceView-codemirror.js => SourceView-codemirror.ts} (99%) rename src/components/shared/{SourceView.js => SourceView.tsx} (94%) rename src/components/shared/{StackImplementationSetting.js => StackImplementationSetting.tsx} (88%) rename src/components/shared/{StackSettings.js => StackSettings.tsx} (85%) rename src/components/shared/{StyleDef.js => StyleDef.tsx} (78%) rename src/components/shared/{TabSelectorMenu.js => TabSelectorMenu.tsx} (78%) rename src/components/shared/{TrackSearchField.js => TrackSearchField.tsx} (82%) rename src/components/shared/{TransformNavigator.js => TransformNavigator.tsx} (79%) rename src/components/shared/{TreeView.js => TreeView.tsx} (80%) rename src/components/shared/{VirtualList.js => VirtualList.tsx} (85%) rename src/components/shared/{Warning.js => Warning.tsx} (81%) rename src/components/shared/{WithSize.js => WithSize.tsx} (72%) rename src/components/shared/chart/{Canvas.js => Canvas.tsx} (87%) rename src/components/shared/chart/{Viewport.js => Viewport.tsx} (89%) rename src/components/shared/thread/{ActivityGraph.js => ActivityGraph.tsx} (78%) rename src/components/shared/thread/{ActivityGraphCanvas.js => ActivityGraphCanvas.tsx} (84%) rename src/components/shared/thread/{ActivityGraphFills.js => ActivityGraphFills.tsx} (92%) rename src/components/shared/thread/{CPUGraph.js => CPUGraph.tsx} (78%) rename src/components/shared/thread/{HeightGraph.js => HeightGraph.tsx} (85%) rename src/components/shared/thread/{SampleGraph.js => SampleGraph.tsx} (83%) rename src/components/shared/thread/{StackGraph.js => StackGraph.tsx} (76%) rename src/components/tooltip/{CallNode.js => CallNode.tsx} (94%) rename src/components/tooltip/{DivWithTooltip.js => DivWithTooltip.tsx} (87%) rename src/components/tooltip/{GCMarker.js => GCMarker.tsx} (98%) rename src/components/tooltip/{Marker.js => Marker.tsx} (93%) rename src/components/tooltip/{NetworkMarker.js => NetworkMarker.tsx} (94%) rename src/components/tooltip/{Tooltip.js => Tooltip.tsx} (86%) rename src/components/tooltip/{TooltipDetails.js => TooltipDetails.tsx} (83%) rename src/components/tooltip/{TrackPower.js => TrackPower.tsx} (89%) diff --git a/src/components/shared/AssemblyView-codemirror.js b/src/components/shared/AssemblyView-codemirror.tsx similarity index 99% rename from src/components/shared/AssemblyView-codemirror.js rename to src/components/shared/AssemblyView-codemirror.tsx index e53cc956a6..e0f4a330d0 100644 --- a/src/components/shared/AssemblyView-codemirror.js +++ b/src/components/shared/AssemblyView-codemirror.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - /** * This module wraps all the interaction with the CodeMirror API into a * AssemblyViewEditor class. @@ -50,7 +48,7 @@ const updateAddressToLineMapEffect = StateEffect.define(); // instructionAddressGutter to map line numbers to addresses. const addressToLineMapField = StateField.define({ create() { - return []; + return new AddressToLineMap([]); }, update(instructionAddresses, transaction) { // Get the new value from an effect in the transaction. @@ -105,7 +103,7 @@ class AddressToLineMap { // works. _instructionAddresses: Address[]; - constructor(instructionAddresses) { + constructor(instructionAddresses: Address[]) { this._instructionAddresses = instructionAddresses; } diff --git a/src/components/shared/AssemblyView.js b/src/components/shared/AssemblyView.tsx similarity index 93% rename from src/components/shared/AssemblyView.js rename to src/components/shared/AssemblyView.tsx index 5018671217..50d4a7cea5 100644 --- a/src/components/shared/AssemblyView.js +++ b/src/components/shared/AssemblyView.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import * as React from 'react'; import { ensureExists } from 'firefox-profiler/utils/flow'; @@ -43,14 +41,14 @@ for understanding where time was actually spent in a program." ); }; -type AssemblyViewProps = {| - +timings: AddressTimings, - +assemblyCode: DecodedInstruction[], - +disableOverscan: boolean, - +nativeSymbol: NativeSymbolInfo | null, - +scrollToHotSpotGeneration: number, - +hotSpotTimings: AddressTimings, -|}; +type AssemblyViewProps = { + readonly timings: AddressTimings; + readonly assemblyCode: DecodedInstruction[]; + readonly disableOverscan: boolean; + readonly nativeSymbol: NativeSymbolInfo | null; + readonly scrollToHotSpotGeneration: number; + readonly hotSpotTimings: AddressTimings; +}; let editorModulePromise: Promise | null = null; @@ -126,7 +124,7 @@ export class AssemblyView extends React.PureComponent { })); } - render() { + override render() { return (
@@ -135,7 +133,7 @@ export class AssemblyView extends React.PureComponent { ); } - componentDidMount() { + override componentDidMount() { // Load the module with all the @codemirror imports asynchronously, so that // it can be split into a separate bundle chunk. if (editorModulePromise === null) { @@ -162,7 +160,7 @@ export class AssemblyView extends React.PureComponent { // CodeMirror's API is not based on React. When our props change, we need to // translate those changes into CodeMirror API calls manually. - componentDidUpdate(prevProps: AssemblyViewProps) { + override componentDidUpdate(prevProps: AssemblyViewProps) { if (!this._editor) { return; } diff --git a/src/components/shared/Backtrace.js b/src/components/shared/Backtrace.tsx similarity index 90% rename from src/components/shared/Backtrace.js rename to src/components/shared/Backtrace.tsx index 86d8f76b92..0bdc4bf884 100644 --- a/src/components/shared/Backtrace.js +++ b/src/components/shared/Backtrace.tsx @@ -2,9 +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/. */ -// @flow - -import React from 'react'; import classNames from 'classnames'; import { getBacktraceItemsForStack } from 'firefox-profiler/profile-logic/transforms'; @@ -17,15 +14,15 @@ import type { import './Backtrace.css'; -type Props = {| - +thread: Thread, +type Props = { + readonly thread: Thread; // Tooltips will want to only show a certain number of stacks, while the sidebars // can show all of the stacks. - +maxStacks: number, - +stackIndex: IndexIntoStackTable, - +implementationFilter: ImplementationFilter, - +categories: CategoryList, -|}; + readonly maxStacks: number; + readonly stackIndex: IndexIntoStackTable; + readonly implementationFilter: ImplementationFilter; + readonly categories: CategoryList; +}; export function Backtrace(props: Props) { const { stackIndex, thread, implementationFilter, maxStacks, categories } = diff --git a/src/components/shared/BlobUrlLink.js b/src/components/shared/BlobUrlLink.tsx similarity index 71% rename from src/components/shared/BlobUrlLink.js rename to src/components/shared/BlobUrlLink.tsx index 5884e168ae..0b8f769982 100644 --- a/src/components/shared/BlobUrlLink.js +++ b/src/components/shared/BlobUrlLink.tsx @@ -2,20 +2,17 @@ * 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/. */ -// @flow - import * as React from 'react'; type Props = { - // Do not make these props exact, the extra props are passed to the anchor element. - +blob: Blob, - +children: React.Node, + readonly blob: Blob; + readonly children: React.ReactNode; }; -type State = {| - url: string, - prevBlob: Blob | null, -|}; +type State = { + url: string; + prevBlob: Blob | null; +}; /** * This component is responsible for converting a Blob into an @@ -23,8 +20,11 @@ type State = {| * does the proper thing of cleaning up after itself as the component * is mounted, updated, and unmounted. */ -export class BlobUrlLink extends React.PureComponent { - state = { +export class BlobUrlLink extends React.PureComponent< + Props & React.AnchorHTMLAttributes, + State +> { + override state: State = { url: '', prevBlob: null, }; @@ -42,17 +42,12 @@ export class BlobUrlLink extends React.PureComponent { }; } - componentWillUnmount() { + override componentWillUnmount() { URL.revokeObjectURL(this.state.url); } - render() { - const { - // eslint-disable-next-line no-unused-vars - blob, - children, - ...rest - } = this.props; + override render() { + const { blob, children, ...rest } = this.props; // This component must be an rather than a - ) : ( - {children} - )} - - ); - } -} - -type Props = {| - +className: string, - +items: $ReadOnlyArray, - +onPop: (number) => mixed, - +selectedItem: number, - +uncommittedItem?: string, -|}; - -export class FilterNavigatorBar extends React.PureComponent { - render() { - const { className, items, selectedItem, uncommittedItem, onPop } = - this.props; - return ( - - {items.map((item, i) => ( - - - {item} - - - ))} - {uncommittedItem ? ( - - - {uncommittedItem} - - - ) : null} - - ); - } -} diff --git a/src/components/shared/FilterNavigatorBar.tsx b/src/components/shared/FilterNavigatorBar.tsx new file mode 100644 index 0000000000..5fb371e2a0 --- /dev/null +++ b/src/components/shared/FilterNavigatorBar.tsx @@ -0,0 +1,122 @@ +/* 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/. */ + +import * as React from 'react'; +import classNames from 'classnames'; +import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import './FilterNavigatorBar.css'; + +type FilterNavigatorBarListItemProps = { + readonly onClick?: null | ((index: number) => unknown); + readonly index: number; + readonly isFirstItem: boolean; + readonly isLastItem: boolean; + readonly isSelectedItem: boolean; + readonly title?: string; + readonly additionalClassName?: string; + readonly children: React.ReactNode; +}; + +class FilterNavigatorBarListItem extends React.PureComponent { + _onClick = () => { + const { index, onClick } = this.props; + if (onClick) { + onClick(index); + } + }; + + override render() { + const { + isFirstItem, + isLastItem, + isSelectedItem, + children, + additionalClassName, + onClick, + title, + } = this.props; + return ( +
  • + {onClick ? ( + + ) : ( + {children} + )} +
  • + ); + } +} + +type Props = { + readonly className: string; + readonly items: ReadonlyArray; + readonly onPop: (param: number) => void; + readonly selectedItem: number; + readonly uncommittedItem?: string; +}; + +export class FilterNavigatorBar extends React.PureComponent { + override render() { + const { className, items, selectedItem, uncommittedItem, onPop } = + this.props; + + const transitions = items.map((item, i) => ( + + + {item} + + + )); + + if (uncommittedItem) { + transitions.push( + + + {uncommittedItem} + + + ); + } + + return ( + + {transitions} + + ); + } +} diff --git a/src/components/shared/Icon.js b/src/components/shared/Icon.tsx similarity index 73% rename from src/components/shared/Icon.js rename to src/components/shared/Icon.tsx index 66ad12f520..87fb6abac0 100644 --- a/src/components/shared/Icon.js +++ b/src/components/shared/Icon.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import explicitConnect from 'firefox-profiler/utils/connect'; import { getIconClassName } from 'firefox-profiler/selectors/icons'; import { iconStartLoading } from 'firefox-profiler/actions/icons'; @@ -15,23 +13,23 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './Icon.css'; type OwnProps = - | {| + | { // This prop is used by call tree. - +displayData: CallNodeDisplayData, - |} - | {| + readonly displayData: CallNodeDisplayData; + } + | { // This prop is for other parts of the profiler. - +iconUrl: string | null, - |}; + readonly iconUrl: string | null; + }; -type StateProps = {| - +className: string, - +icon: string | null, -|}; +type StateProps = { + readonly className: string; + readonly icon: string | null; +}; -type DispatchProps = {| - +iconStartLoading: typeof iconStartLoading, -|}; +type DispatchProps = { + readonly iconStartLoading: typeof iconStartLoading; +}; type Props = ConnectedProps; @@ -43,22 +41,23 @@ class IconImpl extends PureComponent { } } - componentDidUpdate() { + override componentDidUpdate() { if (this.props.icon) { this.props.iconStartLoading(this.props.icon); } } - render() { + override render() { return
    ; } } export const Icon = explicitConnect({ mapStateToProps: (state, ownProps) => { - const icon = ownProps.displayData - ? ownProps.displayData.iconSrc - : ownProps.iconUrl; + const icon = + 'displayData' in ownProps + ? ownProps.displayData.iconSrc + : ownProps.iconUrl; return { className: getIconClassName(state, icon), diff --git a/src/components/shared/IdleSearchField.js b/src/components/shared/IdleSearchField.tsx similarity index 80% rename from src/components/shared/IdleSearchField.js rename to src/components/shared/IdleSearchField.tsx index 398e5ddac8..ef722582fc 100644 --- a/src/components/shared/IdleSearchField.js +++ b/src/components/shared/IdleSearchField.tsx @@ -1,31 +1,29 @@ /* 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/. */ - -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import classNames from 'classnames'; import { Localized } from '@fluent/react'; import './IdleSearchField.css'; -type Props = {| - +onIdleAfterChange: (string) => void, - +onFocus?: () => void, - +onBlur?: (Element | null) => void, - +idlePeriod: number, - +defaultValue: ?string, - +className: ?string, - +title: ?string, -|}; +type Props = { + readonly onIdleAfterChange: (param: string) => void; + readonly onFocus?: () => void; + readonly onBlur?: (param: Element | null) => void; + readonly idlePeriod: number; + readonly defaultValue: string | null; + readonly className: string | null; + readonly title: string | null; +}; type State = { - value: string, - previousDefaultValue: string, + value: string; + previousDefaultValue: string; }; export class IdleSearchField extends PureComponent { - _timeout: TimeoutID | null = null; + _timeout: NodeJS.Timeout | null = null; _previouslyNotifiedValue: string; _input: HTMLInputElement | null = null; _takeInputRef = (input: HTMLInputElement | null) => (this._input = input); @@ -39,7 +37,7 @@ export class IdleSearchField extends PureComponent { this._previouslyNotifiedValue = this.state.value; } - _onSearchFieldFocus = (e: SyntheticFocusEvent) => { + _onSearchFieldFocus = (e: React.FocusEvent) => { e.currentTarget.select(); if (this.props.onFocus) { @@ -53,7 +51,7 @@ export class IdleSearchField extends PureComponent { } }; - _onSearchFieldChange = (e: SyntheticEvent) => { + _onSearchFieldChange = (e: React.ChangeEvent) => { this.setState({ value: e.currentTarget.value, }); @@ -90,7 +88,7 @@ export class IdleSearchField extends PureComponent { this._notifyIfChanged(''); }; - _onFormSubmit(e: SyntheticEvent) { + _onFormSubmit(e: React.FormEvent) { e.preventDefault(); } static getDerivedStateFromProps(props: Props, state: State) { @@ -103,7 +101,7 @@ export class IdleSearchField extends PureComponent { return null; } - render() { + override render() { const { className, title } = this.props; return (
    { name="search" placeholder="Enter filter terms" className="idleSearchFieldInput photon-input" - required="required" - title={title} + required={true} + title={title ?? undefined} value={this.state.value} onChange={this._onSearchFieldChange} onFocus={this._onSearchFieldFocus} diff --git a/src/components/shared/InnerNavigationLink.js b/src/components/shared/InnerNavigationLink.tsx similarity index 55% rename from src/components/shared/InnerNavigationLink.js rename to src/components/shared/InnerNavigationLink.tsx index 36d6448b83..4d104978e8 100644 --- a/src/components/shared/InnerNavigationLink.js +++ b/src/components/shared/InnerNavigationLink.tsx @@ -2,32 +2,29 @@ * 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/. */ -// @flow - import * as React from 'react'; import { setDataSource } from 'firefox-profiler/actions/profile-view'; -import explicitConnect, { - type ConnectedProps, -} from 'firefox-profiler/utils/connect'; +import type { ConnectedProps } from 'firefox-profiler/utils/connect'; +import explicitConnect from 'firefox-profiler/utils/connect'; -import type { DataSource } from 'firefox-profiler/types'; +import type { DataSource } from 'firefox-profiler/types/actions'; -type OwnProps = {| - +className?: string, - +dataSource: DataSource, - +children: React.Node, -|}; +type OwnProps = { + readonly className?: string; + readonly dataSource: DataSource; + readonly children: React.ReactNode; +}; -type DispatchProps = {| - +setDataSource: typeof setDataSource, -|}; +type DispatchProps = { + readonly setDataSource: typeof setDataSource; +}; -type Props = ConnectedProps; +type Props = ConnectedProps; class InnerNavigationLinkImpl extends React.PureComponent { - onClick = (e: SyntheticMouseEvent<>) => { + onClick = (e: React.MouseEvent) => { const { setDataSource, dataSource } = this.props; if (e.ctrlKey || e.metaKey) { // The user clearly wanted to open this link in a new tab. @@ -39,7 +36,7 @@ class InnerNavigationLinkImpl extends React.PureComponent { setDataSource(dataSource); }; - render() { + override render() { const { className, children, dataSource } = this.props; const href = dataSource === 'none' ? '/' : `/${dataSource}/`; @@ -51,11 +48,9 @@ class InnerNavigationLinkImpl extends React.PureComponent { } } -export const InnerNavigationLink = explicitConnect< - OwnProps, - {||}, - DispatchProps, ->({ - mapDispatchToProps: { setDataSource }, - component: InnerNavigationLinkImpl, -}); +export const InnerNavigationLink = explicitConnect( + { + mapDispatchToProps: { setDataSource }, + component: InnerNavigationLinkImpl, + } +); diff --git a/src/components/shared/MarkerContextMenu.js b/src/components/shared/MarkerContextMenu.tsx similarity index 90% rename from src/components/shared/MarkerContextMenu.js rename to src/components/shared/MarkerContextMenu.tsx index 8915eeecfd..a325d0e759 100644 --- a/src/components/shared/MarkerContextMenu.js +++ b/src/components/shared/MarkerContextMenu.tsx @@ -1,9 +1,7 @@ /* 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/. */ - -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { MenuItem } from '@firefox-devtools/react-contextmenu'; import { Localized } from '@fluent/react'; @@ -47,27 +45,27 @@ import { getThreadSelectorsFromThreadsKey } from 'firefox-profiler/selectors/per import './MarkerContextMenu.css'; -type OwnProps = {| - +rightClickedMarkerInfo: MarkerReference, -|}; - -type StateProps = {| - +marker: Marker, - +markerIndex: MarkerIndex, - +previewSelection: PreviewSelection, - +committedRange: StartEndRange, - +thread: Thread | null, - +implementationFilter: ImplementationFilter, - +getMarkerLabelToCopy: (MarkerIndex) => string, - +profiledThreadIds: Set, - innerWindowIDToPageMap: Map | null, -|}; - -type DispatchProps = {| - +updatePreviewSelection: typeof updatePreviewSelection, - +setContextMenuVisibility: typeof setContextMenuVisibility, - +selectTrackFromTid: typeof selectTrackFromTid, -|}; +type OwnProps = { + readonly rightClickedMarkerInfo: MarkerReference; +}; + +type StateProps = { + readonly marker: Marker; + readonly markerIndex: MarkerIndex; + readonly previewSelection: PreviewSelection; + readonly committedRange: StartEndRange; + readonly thread: Thread | null; + readonly implementationFilter: ImplementationFilter; + readonly getMarkerLabelToCopy: (param: MarkerIndex) => string; + readonly profiledThreadIds: Set; + innerWindowIDToPageMap: Map | null; +}; + +type DispatchProps = { + readonly updatePreviewSelection: typeof updatePreviewSelection; + readonly setContextMenuVisibility: typeof setContextMenuVisibility; + readonly selectTrackFromTid: typeof selectTrackFromTid; +}; type Props = ConnectedProps; @@ -146,7 +144,7 @@ class MarkerContextMenuImpl extends PureComponent { }); }; - _isZeroDurationMarker(marker: ?Marker): boolean { + _isZeroDurationMarker(marker: Marker | null): boolean { return !marker || marker.end === null; } @@ -180,7 +178,7 @@ class MarkerContextMenuImpl extends PureComponent { copyMarkerCause = () => { const { marker } = this.props; - if (marker.data && marker.data.cause) { + if (marker.data && 'cause' in marker.data && marker.data.cause) { const stack = this._convertStackToString(marker.data.cause.stack); if (stack) { copy(stack); @@ -204,7 +202,12 @@ class MarkerContextMenuImpl extends PureComponent { const { marker, innerWindowIDToPageMap } = this.props; const { data } = marker; - if (!data || !data.innerWindowID || !innerWindowIDToPageMap) { + if ( + !data || + !('innerWindowID' in data) || + !data.innerWindowID || + !innerWindowIDToPageMap + ) { // Marker doesn't contain any page information. Do not do anything. return; } @@ -259,12 +262,12 @@ class MarkerContextMenuImpl extends PureComponent { menuItemTextElement = ( }} > <> - Select the receiver thread “{data.recvThreadName} - ” + Select the receiver thread “ + {data.recvThreadName ?? ''} ); @@ -283,11 +286,12 @@ class MarkerContextMenuImpl extends PureComponent { menuItemTextElement = ( }} > <> - Select the sender thread “{data.sendThreadName}” + Select the sender thread “ + {data.sendThreadName ?? ''} ); @@ -311,7 +315,12 @@ class MarkerContextMenuImpl extends PureComponent { const { marker, innerWindowIDToPageMap } = this.props; const { data } = marker; - if (!data || !data.innerWindowID || !innerWindowIDToPageMap) { + if ( + !data || + !('innerWindowID' in data) || + !data.innerWindowID || + !innerWindowIDToPageMap + ) { // Marker doesn't contain any page information. Do not render anything. return null; } @@ -356,7 +365,7 @@ class MarkerContextMenuImpl extends PureComponent { // value which was set earlier when handling the "mousedown" event. // To avoid this problem we use this `setTimeout` call to delay the reset // just a bit, just in case we get a `_onShow` call right after that. - _hidingTimeout: TimeoutID | null = null; + _hidingTimeout: NodeJS.Timeout | null = null; _onHide = () => { this._hidingTimeout = setTimeout(() => { @@ -366,11 +375,13 @@ class MarkerContextMenuImpl extends PureComponent { }; _onShow = () => { - clearTimeout(this._hidingTimeout); + if (this._hidingTimeout) { + clearTimeout(this._hidingTimeout); + } this.props.setContextMenuVisibility(true); }; - render() { + override render() { const { marker, previewSelection, committedRange } = this.props; const { data } = marker; @@ -481,7 +492,7 @@ class MarkerContextMenuImpl extends PureComponent { Copy description - {data && data.cause ? ( + {data && 'cause' in data && data.cause ? ( @@ -535,16 +546,16 @@ const MarkerContextMenu = explicitConnect({ component: MarkerContextMenuImpl, }); -type MaybeProps = {| - +rightClickedMarkerInfo: MarkerReference | null, -|}; +type MaybeProps = { + readonly rightClickedMarkerInfo: MarkerReference | null; +}; /** * This component only renders the context menu if there is a right clicked marker. * It is the component that is actually exported here. */ class MaybeMarkerContextMenuImpl extends PureComponent { - render() { + override render() { const { rightClickedMarkerInfo } = this.props; if (rightClickedMarkerInfo === null) { @@ -557,7 +568,7 @@ class MaybeMarkerContextMenuImpl extends PureComponent { } } -export const MaybeMarkerContextMenu = explicitConnect<{||}, MaybeProps, {||}>({ +export const MaybeMarkerContextMenu = explicitConnect<{}, MaybeProps, {}>({ mapStateToProps: (state) => ({ rightClickedMarkerInfo: getRightClickedMarkerInfo(state), }), diff --git a/src/components/shared/MarkerFiltersContextMenu.js b/src/components/shared/MarkerFiltersContextMenu.tsx similarity index 86% rename from src/components/shared/MarkerFiltersContextMenu.js rename to src/components/shared/MarkerFiltersContextMenu.tsx index ba3d5cf80d..e69fd613ed 100644 --- a/src/components/shared/MarkerFiltersContextMenu.js +++ b/src/components/shared/MarkerFiltersContextMenu.tsx @@ -1,9 +1,7 @@ /* 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/. */ - -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { MenuItem } from '@firefox-devtools/react-contextmenu'; import { Localized } from '@fluent/react'; @@ -18,19 +16,19 @@ import { addTransformToStack } from 'firefox-profiler/actions/profile-view'; import type { ThreadsKey } from 'firefox-profiler/types'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; -type OwnProps = {| - +onShow: () => void, - +onHide: () => void, -|}; +type OwnProps = { + readonly onShow: () => void; + readonly onHide: () => void; +}; -type StateProps = {| - +searchString: string, - +threadsKey: ThreadsKey, -|}; +type StateProps = { + readonly searchString: string; + readonly threadsKey: ThreadsKey; +}; -type DispatchProps = {| - +addTransformToStack: typeof addTransformToStack, -|}; +type DispatchProps = { + readonly addTransformToStack: typeof addTransformToStack; +}; type Props = ConnectedProps; @@ -44,7 +42,7 @@ class MarkerFiltersContextMenuImpl extends PureComponent { }); }; - render() { + override render() { const { searchString, onShow, onHide } = this.props; return ( { export const MarkerFiltersContextMenu = explicitConnect< OwnProps, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ searchString: getMarkersSearchString(state), diff --git a/src/components/shared/MarkerSettings.js b/src/components/shared/MarkerSettings.tsx similarity index 88% rename from src/components/shared/MarkerSettings.js rename to src/components/shared/MarkerSettings.tsx index 3a52050206..d7f8383e2e 100644 --- a/src/components/shared/MarkerSettings.js +++ b/src/components/shared/MarkerSettings.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import classNames from 'classnames'; import { showMenu } from '@firefox-devtools/react-contextmenu'; @@ -22,29 +20,29 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import 'firefox-profiler/components/shared/PanelSettingsList.css'; import './MarkerSettings.css'; -type StateProps = {| - +searchString: string, - +allowSwitchingStackType: boolean, -|}; +type StateProps = { + readonly searchString: string; + readonly allowSwitchingStackType: boolean; +}; -type DispatchProps = {| - +changeMarkersSearchString: typeof changeMarkersSearchString, -|}; +type DispatchProps = { + readonly changeMarkersSearchString: typeof changeMarkersSearchString; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; -type State = {| - +isMarkerFiltersMenuVisible: boolean, +type State = { + readonly isMarkerFiltersMenuVisible: boolean; // react-contextmenu library automatically hides the menu on mousedown even // if it's already visible. That's why we need to handle the mousedown event // as well and check if the menu is visible or not before it hides it. // Otherwise, if we check this in onClick event, the state will always be // `false` since the library already hid it on mousedown. - +isFilterMenuVisibleOnMouseDown: boolean, -|}; + readonly isFilterMenuVisibleOnMouseDown: boolean; +}; class MarkerSettingsImpl extends PureComponent { - state = { + override state = { isMarkerFiltersMenuVisible: false, isFilterMenuVisibleOnMouseDown: false, }; @@ -53,7 +51,7 @@ class MarkerSettingsImpl extends PureComponent { this.props.changeMarkersSearchString(value); }; - _onClickToggleFilterButton = (event: SyntheticMouseEvent) => { + _onClickToggleFilterButton = (event: React.MouseEvent) => { const { isFilterMenuVisibleOnMouseDown } = this.state; if (isFilterMenuVisibleOnMouseDown) { // Do nothing as we would like to hide the menu if the menu was already visible on mouse down. @@ -88,7 +86,7 @@ class MarkerSettingsImpl extends PureComponent { })); }; - render() { + override render() { const { searchString, allowSwitchingStackType } = this.props; const { isMarkerFiltersMenuVisible } = this.state; @@ -139,7 +137,7 @@ class MarkerSettingsImpl extends PureComponent { } } -export const MarkerSettings = explicitConnect<{||}, StateProps, DispatchProps>({ +export const MarkerSettings = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ searchString: getMarkersSearchString(state), allowSwitchingStackType: getProfileUsesMultipleStackTypes(state), diff --git a/src/components/shared/NetworkSettings.js b/src/components/shared/NetworkSettings.tsx similarity index 71% rename from src/components/shared/NetworkSettings.js rename to src/components/shared/NetworkSettings.tsx index bab0008885..fa29fe8ad1 100644 --- a/src/components/shared/NetworkSettings.js +++ b/src/components/shared/NetworkSettings.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import explicitConnect from 'firefox-profiler/utils/connect'; @@ -16,22 +14,22 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './NetworkSettings.css'; -type StateProps = {| - +searchString: string, -|}; +type StateProps = { + readonly searchString: string; +}; -type DispatchProps = {| - +changeNetworkSearchString: typeof changeNetworkSearchString, -|}; +type DispatchProps = { + readonly changeNetworkSearchString: typeof changeNetworkSearchString; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class NetworkSettingsImpl extends PureComponent { _onSearch = (value: string) => { this.props.changeNetworkSearchString(value); }; - render() { + override render() { const { searchString } = this.props; return (
    @@ -53,12 +51,10 @@ class NetworkSettingsImpl extends PureComponent { } } -export const NetworkSettings = explicitConnect<{||}, StateProps, DispatchProps>( - { - mapStateToProps: (state) => ({ - searchString: getNetworkSearchString(state), - }), - mapDispatchToProps: { changeNetworkSearchString }, - component: NetworkSettingsImpl, - } -); +export const NetworkSettings = explicitConnect<{}, StateProps, DispatchProps>({ + mapStateToProps: (state) => ({ + searchString: getNetworkSearchString(state), + }), + mapDispatchToProps: { changeNetworkSearchString }, + component: NetworkSettingsImpl, +}); diff --git a/src/components/shared/PanelSearch.js b/src/components/shared/PanelSearch.tsx similarity index 86% rename from src/components/shared/PanelSearch.js rename to src/components/shared/PanelSearch.tsx index 7b36063ea2..be4be2aef7 100644 --- a/src/components/shared/PanelSearch.js +++ b/src/components/shared/PanelSearch.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import classNames from 'classnames'; import { IdleSearchField } from './IdleSearchField'; @@ -10,18 +8,18 @@ import { IdleSearchField } from './IdleSearchField'; import './PanelSearch.css'; import { Localized } from '@fluent/react'; -type Props = {| - +className: string, - +label: string, - +title: string, - +currentSearchString: string, - +onSearch: (string) => void, -|}; +type Props = { + readonly className: string; + readonly label: string; + readonly title: string; + readonly currentSearchString: string; + readonly onSearch: (param: string) => void; +}; -type State = {| searchFieldFocused: boolean |}; +type State = { searchFieldFocused: boolean }; export class PanelSearch extends React.PureComponent { - state = { searchFieldFocused: false }; + override state = { searchFieldFocused: false }; _onSearchFieldIdleAfterChange = (value: string) => { this.props.onSearch(value); }; @@ -34,7 +32,7 @@ export class PanelSearch extends React.PureComponent { this.setState(() => ({ searchFieldFocused: false })); }; - render() { + override render() { const { label, title, currentSearchString, className } = this.props; const { searchFieldFocused } = this.state; const showIntroduction = diff --git a/src/components/shared/ProfileMetaInfoSummary.js b/src/components/shared/ProfileMetaInfoSummary.tsx similarity index 84% rename from src/components/shared/ProfileMetaInfoSummary.js rename to src/components/shared/ProfileMetaInfoSummary.tsx index cd2f54703f..12e2910f5c 100644 --- a/src/components/shared/ProfileMetaInfoSummary.js +++ b/src/components/shared/ProfileMetaInfoSummary.tsx @@ -2,9 +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/. */ -// @flow -import React from 'react'; - import { formatProductAndVersion, formatPlatform, @@ -12,19 +9,18 @@ import { import './ProfileMetaInfoSummary.css'; -type Props = {| +type Props = { // We don't use ProfileMeta directly, because this is used also by the stored // data in the local IndexedDB, which doesn't use ProfileMeta. Therefore we // specify only the properties we use here. - +meta: { - +product: string, - +misc?: string, - +platform?: string, - +oscpu?: string, - +toolkit?: string, - ... - }, -|}; + readonly meta: { + readonly product: string; + readonly misc?: string; + readonly platform?: string; + readonly oscpu?: string; + readonly toolkit?: string; + }; +}; export function ProfileMetaInfoSummary({ meta }: Props) { const productAndVersion = formatProductAndVersion(meta); diff --git a/src/components/shared/Reorderable.js b/src/components/shared/Reorderable.tsx similarity index 85% rename from src/components/shared/Reorderable.js rename to src/components/shared/Reorderable.tsx index 309a2acbd2..ad287fca42 100644 --- a/src/components/shared/Reorderable.js +++ b/src/components/shared/Reorderable.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import clamp from 'clamp'; import arrayMove from 'array-move'; @@ -14,45 +12,45 @@ import { } from 'firefox-profiler/utils/css-geometry-tools'; import { bisectionRight } from 'firefox-profiler/utils/bisect'; -type Props = {| - orient: 'horizontal' | 'vertical', - tagName: string, - className: string, - order: number[], - onChangeOrder: (number[]) => mixed, +type Props = { + orient: 'horizontal' | 'vertical'; + tagName: React.ElementType; + className: string; + order: number[]; + onChangeOrder: (param: number[]) => void; // Reorderable elements should set a class name to match against. This allows // nested reorderable elements to set different matching class names. - grippyClassName: string, + grippyClassName: string; // This forces the children to be an array of React Elements. // See https://flow.org/en/docs/react/children/ for more information. // Be careful: children need to handle a `style` property. - children: React.ChildrenArray>, + children: React.ReactElement[]; // If present, this will be attached to the container added for these // children. As a reminder, the container will use the tagName defined above. - innerElementRef?: React.Ref, -|}; + innerElementRef?: React.Ref; +}; -type State = {| - phase: 'RESTING' | 'FINISHING' | 'MANIPULATING', - manipulatingIndex: number, - destinationIndex: number, - manipulationDelta: number, - adjustPrecedingBy: number, - adjustSucceedingBy: number, - finalOffset: number, -|}; +type State = { + phase: 'RESTING' | 'FINISHING' | 'MANIPULATING'; + manipulatingIndex: number; + destinationIndex: number; + manipulationDelta: number; + adjustPrecedingBy: number; + adjustSucceedingBy: number; + finalOffset: number; +}; -type XY = {| - pageXY: 'pageX' | 'pageY', - translateXY: 'translateX' | 'translateY', - lefttop: 'left' | 'top', - rightbottom: 'right' | 'bottom', -|}; +type XY = { + pageXY: 'pageX' | 'pageY'; + translateXY: 'translateX' | 'translateY'; + lefttop: 'left' | 'top'; + rightbottom: 'right' | 'bottom'; +}; -type EventWithPageProperties = { pageX: number, pageY: number }; +type EventWithPageProperties = { pageX: number; pageY: number }; export class Reorderable extends React.PureComponent { - _xy: {| horizontal: XY, vertical: XY |} = { + _xy: { horizontal: XY; vertical: XY } = { horizontal: { pageXY: 'pageX', translateXY: 'translateX', @@ -67,8 +65,8 @@ export class Reorderable extends React.PureComponent { }, }; - state = { - phase: 'RESTING', + override state = { + phase: 'RESTING' as const, manipulatingIndex: -1, destinationIndex: -1, manipulationDelta: 0, @@ -78,7 +76,7 @@ export class Reorderable extends React.PureComponent { }; _onMouseDown = ( - event: { target: EventTarget } & SyntheticMouseEvent + event: { target: EventTarget } & React.MouseEvent ) => { const container = event.currentTarget; @@ -93,7 +91,7 @@ export class Reorderable extends React.PureComponent { // Flow: Coerce the event target into an HTMLElement in combination with the above // `instanceof` statement. - let element = (event.target: HTMLElement); + let element = event.target as HTMLElement; const { grippyClassName } = this.props; if (!element.matches(`.${grippyClassName}, .${grippyClassName} *`)) { // Don't handle this event. Only clicking inside a matching grippy class @@ -102,7 +100,7 @@ export class Reorderable extends React.PureComponent { } while (element instanceof HTMLElement && element.parentNode !== container) { - element = element.parentNode; + element = element.parentNode as HTMLElement; } if (!(element instanceof HTMLElement)) { @@ -147,7 +145,7 @@ export class Reorderable extends React.PureComponent { isBefore = false; return 0; } - const childRect = getMarginRect(child); + const childRect = getMarginRect(child as HTMLElement); return isBefore ? extractDomRectValue(childRect, xy.lefttop) - extractDomRectValue(elementRect, xy.lefttop) @@ -172,7 +170,7 @@ export class Reorderable extends React.PureComponent { elementIndex === children.length - 1 ? extractDomRectValue(containerRect, xy.rightbottom) : extractDomRectValue( - getMarginRect(children[elementIndex + 1]), + getMarginRect(children[elementIndex + 1] as HTMLElement), xy.lefttop ); @@ -180,7 +178,7 @@ export class Reorderable extends React.PureComponent { elementIndex === 0 ? extractDomRectValue(containerRect, xy.lefttop) : extractDomRectValue( - getMarginRect(children[elementIndex - 1]), + getMarginRect(children[elementIndex - 1] as HTMLElement), xy.rightbottom ); @@ -242,7 +240,7 @@ export class Reorderable extends React.PureComponent { window.addEventListener('mouseup', mouseUpListener, true); } - render() { + override render() { const { className, order, innerElementRef } = this.props; const children = React.Children.toArray(this.props.children); const orderedChildren = order.map((childIndex) => children[childIndex]); @@ -272,15 +270,15 @@ export class Reorderable extends React.PureComponent { return ( {orderedChildren.map((child, childIndex) => { - const style = { + const style: React.CSSProperties = { transition: '200ms ease-in-out transform', willChange: 'transform', position: 'relative', - zIndex: '1', + zIndex: 1, transform: '', }; if (childIndex === manipulatingIndex) { - style.zIndex = '2'; + style.zIndex = 2; if (phase === 'MANIPULATING') { delete style.transition; style.transform = `${xy.translateXY}(${this.state.manipulationDelta}px)`; @@ -300,7 +298,9 @@ export class Reorderable extends React.PureComponent { } // Note: the child element needs to handle this `style` property. - return React.cloneElement(child, { style }); + return React.cloneElement(child as React.ReactElement, { + style, + }); })} ); diff --git a/src/components/shared/SampleTooltipContents.js b/src/components/shared/SampleTooltipContents.tsx similarity index 88% rename from src/components/shared/SampleTooltipContents.js rename to src/components/shared/SampleTooltipContents.tsx index 39d2cd24b0..f3d20415e4 100644 --- a/src/components/shared/SampleTooltipContents.js +++ b/src/components/shared/SampleTooltipContents.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import * as React from 'react'; import { Backtrace } from './Backtrace'; @@ -28,28 +26,30 @@ import type { CpuRatioInTimeRange } from './thread/ActivityGraphFills'; type CPUProps = CpuRatioInTimeRange; -type RestProps = {| - +sampleIndex: IndexIntoSamplesTable, - +categories: CategoryList, - +rangeFilteredThread: Thread, - +implementationFilter: ImplementationFilter, -|}; - -type Props = {| - ...RestProps, - +cpuRatioInTimeRange: CPUProps | null, - +sampleIndex: IndexIntoSamplesTable | null, - +zeroAt: Milliseconds, - +profileTimelineUnit: string, - +interval: Milliseconds, -|}; +type RestProps = { + readonly sampleIndex: IndexIntoSamplesTable; + readonly categories: CategoryList; + readonly rangeFilteredThread: Thread; + readonly implementationFilter: ImplementationFilter; +}; + +type Props = { + readonly cpuRatioInTimeRange: CPUProps | null; + readonly categories: CategoryList; + readonly rangeFilteredThread: Thread; + readonly implementationFilter: ImplementationFilter; + readonly sampleIndex: IndexIntoSamplesTable | null; + readonly zeroAt: Milliseconds; + readonly profileTimelineUnit: string; + readonly interval: Milliseconds; +}; /** * Render thread CPU usage if it's present in the profile. * This is split to reduce the rerender of the SampleTooltipRestContents component. */ class SampleTooltipCPUContents extends React.PureComponent { - render() { + override render() { const { cpuRatio, timeRange } = this.props; const percentageText = formatPercent(cpuRatio); @@ -70,7 +70,7 @@ class SampleTooltipCPUContents extends React.PureComponent { * Render the non-CPU related parts of the SampleTooltipContents. */ class SampleTooltipRestContents extends React.PureComponent { - render() { + override render() { const { sampleIndex, rangeFilteredThread, @@ -117,7 +117,7 @@ class SampleTooltipRestContents extends React.PureComponent { * will want to know what the function is, and its category. */ export class SampleTooltipContents extends React.PureComponent { - render() { + override render() { const { cpuRatioInTimeRange, sampleIndex, diff --git a/src/components/shared/SourceView-codemirror.js b/src/components/shared/SourceView-codemirror.ts similarity index 99% rename from src/components/shared/SourceView-codemirror.js rename to src/components/shared/SourceView-codemirror.ts index fe7a2bf47d..446d78b745 100644 --- a/src/components/shared/SourceView-codemirror.js +++ b/src/components/shared/SourceView-codemirror.ts @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - /** * This module wraps all the interaction with the CodeMirror API into a * SourceViewEditor class. diff --git a/src/components/shared/SourceView.js b/src/components/shared/SourceView.tsx similarity index 94% rename from src/components/shared/SourceView.js rename to src/components/shared/SourceView.tsx index 297578b3b6..f6ac91dcf2 100644 --- a/src/components/shared/SourceView.js +++ b/src/components/shared/SourceView.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import * as React from 'react'; import { ensureExists } from 'firefox-profiler/utils/flow'; @@ -39,14 +37,14 @@ for understanding where time was actually spent in a program." ); }; -type SourceViewProps = {| - +timings: LineTimings, - +sourceCode: string, - +disableOverscan: boolean, - +filePath: string | null, - +scrollToHotSpotGeneration: number, - +hotSpotTimings: LineTimings, -|}; +type SourceViewProps = { + readonly timings: LineTimings; + readonly sourceCode: string; + readonly disableOverscan: boolean; + readonly filePath: string | null; + readonly scrollToHotSpotGeneration: number; + readonly hotSpotTimings: LineTimings; +}; let editorModulePromise: Promise | null = null; @@ -115,7 +113,7 @@ export class SourceView extends React.PureComponent { return '\n'.repeat(this._getMaxLineNumber()); } - render() { + override render() { return (
    @@ -124,7 +122,7 @@ export class SourceView extends React.PureComponent { ); } - componentDidMount() { + override componentDidMount() { // Load the module with all the @codemirror imports asynchronously, so that // it can be split into a separate bundle chunk. if (editorModulePromise === null) { @@ -151,7 +149,7 @@ export class SourceView extends React.PureComponent { // CodeMirror's API is not based on React. When our props change, we need to // translate those changes into CodeMirror API calls manually. - componentDidUpdate(prevProps: SourceViewProps) { + override componentDidUpdate(prevProps: SourceViewProps) { if (!this._editor) { return; } diff --git a/src/components/shared/StackImplementationSetting.js b/src/components/shared/StackImplementationSetting.tsx similarity index 88% rename from src/components/shared/StackImplementationSetting.js rename to src/components/shared/StackImplementationSetting.tsx index 5f428f62c8..8c71753ab0 100644 --- a/src/components/shared/StackImplementationSetting.js +++ b/src/components/shared/StackImplementationSetting.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import { changeImplementationFilter } from 'firefox-profiler/actions/profile-view'; @@ -20,22 +18,22 @@ import './StackImplementationSetting.css'; import type { ImplementationFilter } from 'firefox-profiler/types'; -type OwnProps = {| - labelL10nId?: string, -|}; +type OwnProps = { + labelL10nId?: string; +}; -type StateProps = {| - +implementationFilter: ImplementationFilter, -|}; +type StateProps = { + readonly implementationFilter: ImplementationFilter; +}; -type DispatchProps = {| - +changeImplementationFilter: typeof changeImplementationFilter, -|}; +type DispatchProps = { + readonly changeImplementationFilter: typeof changeImplementationFilter; +}; type Props = ConnectedProps; class StackImplementationSettingImpl extends PureComponent { - _onImplementationFilterChange = (e: SyntheticEvent) => { + _onImplementationFilterChange = (e: React.ChangeEvent) => { this.props.changeImplementationFilter( // This function is here to satisfy Flow that we are getting a valid // implementation filter. @@ -70,7 +68,7 @@ class StackImplementationSettingImpl extends PureComponent { ); } - render() { + override render() { const { labelL10nId } = this.props; return ( @@ -100,7 +98,7 @@ class StackImplementationSettingImpl extends PureComponent { export const StackImplementationSetting = explicitConnect< OwnProps, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ implementationFilter: getImplementationFilter(state), diff --git a/src/components/shared/StackSettings.js b/src/components/shared/StackSettings.tsx similarity index 85% rename from src/components/shared/StackSettings.js rename to src/components/shared/StackSettings.tsx index 6842261779..2a502293c9 100644 --- a/src/components/shared/StackSettings.js +++ b/src/components/shared/StackSettings.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import { @@ -33,40 +31,40 @@ import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread'; import './PanelSettingsList.css'; import './StackSettings.css'; -type OwnProps = {| - +hideInvertCallstack?: true, -|}; - -type StateProps = {| - +selectedTab: string, - +allowSwitchingStackType: boolean, - +invertCallstack: boolean, - +showUserTimings: boolean, - +stackChartSameWidths: boolean, - +currentSearchString: string, - +hasUsefulJsAllocations: boolean, - +hasUsefulNativeAllocations: boolean, -|}; - -type DispatchProps = {| - +changeInvertCallstack: typeof changeInvertCallstack, - +changeShowUserTimings: typeof changeShowUserTimings, - +changeCallTreeSearchString: typeof changeCallTreeSearchString, - +changeStackChartSameWidths: typeof changeStackChartSameWidths, -|}; +type OwnProps = { + readonly hideInvertCallstack?: true; +}; + +type StateProps = { + readonly selectedTab: string; + readonly allowSwitchingStackType: boolean; + readonly invertCallstack: boolean; + readonly showUserTimings: boolean; + readonly stackChartSameWidths: boolean; + readonly currentSearchString: string; + readonly hasUsefulJsAllocations: boolean; + readonly hasUsefulNativeAllocations: boolean; +}; + +type DispatchProps = { + readonly changeInvertCallstack: typeof changeInvertCallstack; + readonly changeShowUserTimings: typeof changeShowUserTimings; + readonly changeCallTreeSearchString: typeof changeCallTreeSearchString; + readonly changeStackChartSameWidths: typeof changeStackChartSameWidths; +}; type Props = ConnectedProps; class StackSettingsImpl extends PureComponent { - _onInvertCallstackClick = (e: SyntheticEvent) => { + _onInvertCallstackClick = (e: React.ChangeEvent) => { this.props.changeInvertCallstack(e.currentTarget.checked); }; - _onShowUserTimingsClick = (e: SyntheticEvent) => { + _onShowUserTimingsClick = (e: React.ChangeEvent) => { this.props.changeShowUserTimings(e.currentTarget.checked); }; - _onUseStackChartSameWidths = (e: SyntheticEvent) => { + _onUseStackChartSameWidths = (e: React.ChangeEvent) => { this.props.changeStackChartSameWidths(e.currentTarget.checked); }; @@ -74,7 +72,7 @@ class StackSettingsImpl extends PureComponent { this.props.changeCallTreeSearchString(value); }; - render() { + override render() { const { allowSwitchingStackType, invertCallstack, @@ -169,7 +167,7 @@ class StackSettingsImpl extends PureComponent { export const StackSettings = explicitConnect< OwnProps, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ allowSwitchingStackType: getProfileUsesMultipleStackTypes(state), diff --git a/src/components/shared/StyleDef.js b/src/components/shared/StyleDef.tsx similarity index 78% rename from src/components/shared/StyleDef.js rename to src/components/shared/StyleDef.tsx index 033804828d..a25b760b24 100644 --- a/src/components/shared/StyleDef.js +++ b/src/components/shared/StyleDef.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow // inspired from https://gist.github.com/jviereck/9a71734afcfe848ddbe2 -- simplified // // Because JSX isn't nice with CSS content because of the braces, we use a @@ -12,16 +10,16 @@ // needed with some simple logic than having a complex code to detect // duplication. -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; -type StyleDefProps = {| - +content: string, -|}; +type StyleDefProps = { + readonly content: string; +}; export class StyleDef extends PureComponent { - _dom: ?HTMLStyleElement; + _dom: HTMLStyleElement | null = null; - componentDidMount() { + override componentDidMount() { const dom = document.createElement('style'); dom.textContent = this.props.content; const documentHead = document.head; @@ -31,14 +29,14 @@ export class StyleDef extends PureComponent { } } - componentDidUpdate(prevProps: StyleDefProps) { + override componentDidUpdate(prevProps: StyleDefProps) { const dom = this._dom; if (prevProps.content !== this.props.content && dom) { dom.textContent = this.props.content; } } - componentWillUnmount() { + override componentWillUnmount() { const dom = this._dom; if (dom) { dom.remove(); @@ -46,19 +44,19 @@ export class StyleDef extends PureComponent { } } - render() { + override render(): null { // The itself should not appear in the DOM. return null; } } -type BackgroundImageStyleDefProps = {| - +className: string, - +url: string, -|}; +type BackgroundImageStyleDefProps = { + readonly className: string; + readonly url: string; +}; export class BackgroundImageStyleDef extends PureComponent { - render() { + override render(): React.ReactElement { const content = ` .${this.props.className} { background-image: url(${this.props.url}); diff --git a/src/components/shared/TabSelectorMenu.js b/src/components/shared/TabSelectorMenu.tsx similarity index 78% rename from src/components/shared/TabSelectorMenu.js rename to src/components/shared/TabSelectorMenu.tsx index 9d4bfb01b7..59101c23a3 100644 --- a/src/components/shared/TabSelectorMenu.js +++ b/src/components/shared/TabSelectorMenu.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import { MenuItem } from '@firefox-devtools/react-contextmenu'; import { Localized } from '@fluent/react'; @@ -18,21 +16,24 @@ import { Icon } from 'firefox-profiler/components/shared/Icon'; import type { TabID, SortedTabPageData } from 'firefox-profiler/types'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; -type StateProps = {| - +tabFilter: TabID | null, - +sortedPageData: SortedTabPageData | null, -|}; +type StateProps = { + readonly tabFilter: TabID | null; + readonly sortedPageData: SortedTabPageData | null; +}; -type DispatchProps = {| - +changeTabFilter: typeof changeTabFilter, -|}; +type DispatchProps = { + readonly changeTabFilter: typeof changeTabFilter; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; import './TabSelectorMenu.css'; class TabSelectorMenuImpl extends React.PureComponent { - _handleClick = (_event: SyntheticEvent<>, data: {| id: TabID |}): void => { + _handleClick = ( + _event: React.ChangeEvent, + data: { id: TabID } + ): void => { this.props.changeTabFilter(data.id); }; @@ -80,7 +81,7 @@ class TabSelectorMenuImpl extends React.PureComponent { ); } - render() { + override render() { return ( {this.renderTabSelectorMenuContents()} @@ -89,15 +90,13 @@ class TabSelectorMenuImpl extends React.PureComponent { } } -export const TabSelectorMenu = explicitConnect<{||}, StateProps, DispatchProps>( - { - mapStateToProps: (state) => ({ - tabFilter: getTabFilter(state), - sortedPageData: getProfileFilterSortedPageData(state), - }), - mapDispatchToProps: { - changeTabFilter, - }, - component: TabSelectorMenuImpl, - } -); +export const TabSelectorMenu = explicitConnect<{}, StateProps, DispatchProps>({ + mapStateToProps: (state) => ({ + tabFilter: getTabFilter(state), + sortedPageData: getProfileFilterSortedPageData(state), + }), + mapDispatchToProps: { + changeTabFilter, + }, + component: TabSelectorMenuImpl, +}); diff --git a/src/components/shared/TrackSearchField.js b/src/components/shared/TrackSearchField.tsx similarity index 82% rename from src/components/shared/TrackSearchField.js rename to src/components/shared/TrackSearchField.tsx index 211b008cc9..1519c5e6f4 100644 --- a/src/components/shared/TrackSearchField.js +++ b/src/components/shared/TrackSearchField.tsx @@ -1,23 +1,21 @@ /* 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/. */ - -// @flow import * as React from 'react'; import classNames from 'classnames'; import { Localized } from '@fluent/react'; import './TrackSearchField.css'; -type Props = {| - +className: string, - +currentSearchString: string, - +onSearch: (string) => void, -|}; +type Props = { + readonly className: string; + readonly currentSearchString: string; + readonly onSearch: (param: string) => void; +}; export class TrackSearchField extends React.PureComponent { - searchFieldInput: {| current: HTMLInputElement | null |} = React.createRef(); - _onSearchFieldChange = (e: SyntheticEvent) => { + searchFieldInput: { current: HTMLInputElement | null } = React.createRef(); + _onSearchFieldChange = (e: React.ChangeEvent) => { this.props.onSearch(e.currentTarget.value); }; @@ -29,7 +27,7 @@ export class TrackSearchField extends React.PureComponent { } }; - _onFormSubmit(e: SyntheticEvent) { + _onFormSubmit(e: React.FormEvent) { e.preventDefault(); } @@ -41,7 +39,7 @@ export class TrackSearchField extends React.PureComponent { this.props.onSearch(''); }; - render() { + override render() { const { currentSearchString, className } = this.props; return ( { name="search" placeholder="Enter filter terms" className="trackSearchFieldInput photon-input" - required="required" + required={true} title="Only display tracks that match a certain text" value={currentSearchString} onChange={this._onSearchFieldChange} diff --git a/src/components/shared/TransformNavigator.js b/src/components/shared/TransformNavigator.tsx similarity index 79% rename from src/components/shared/TransformNavigator.js rename to src/components/shared/TransformNavigator.tsx index b5c4141a7b..bc2145bd10 100644 --- a/src/components/shared/TransformNavigator.js +++ b/src/components/shared/TransformNavigator.tsx @@ -2,28 +2,26 @@ * 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/. */ -// @flow - import explicitConnect from 'firefox-profiler/utils/connect'; import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread'; import { FilterNavigatorBar } from './FilterNavigatorBar'; import { popTransformsFromStack } from 'firefox-profiler/actions/profile-view'; import type { State } from 'firefox-profiler/types'; -import type { ElementProps } from 'react'; +import type { ComponentProps } from 'react'; import './TransformNavigator.css'; -type Props = ElementProps; -type DispatchProps = {| - +onPop: $PropertyType, -|}; -type StateProps = $Diff; +type Props = ComponentProps; +type DispatchProps = { + readonly onPop: Props['onPop']; +}; +type StateProps = Omit; export const TransformNavigator = explicitConnect< - {||}, + {}, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state: State) => { const items = selectedThreadSelectors.getLocalizedTransformLabels(state); diff --git a/src/components/shared/TreeView.js b/src/components/shared/TreeView.tsx similarity index 80% rename from src/components/shared/TreeView.js rename to src/components/shared/TreeView.tsx index 2abeef12ff..a6eb6e374d 100644 --- a/src/components/shared/TreeView.js +++ b/src/components/shared/TreeView.tsx @@ -1,11 +1,6 @@ /* 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/. */ -// @flow - -// This file uses extensive use of Object generic trait bounds, which is a false -// positive for this rule. -/* eslint-disable flowtype/no-weak-types */ import * as React from 'react'; import classNames from 'classnames'; @@ -35,68 +30,68 @@ const PAGE_KEYS_DELTA = 15; * `Localized` throws a warning if the `id` field is empty or null. This is made * to silence those warnings by directy rendering the children for that case instead. */ -function PermissiveLocalized(props: React.ElementConfig) { +function PermissiveLocalized(props: React.ComponentProps) { const { children, id } = props; return id ? {children} : children; } // This is used for the result of RegExp.prototype.exec because Flow doesn't do it. // See https://github.com/facebook/flow/issues/4099 -type RegExpResult = null | ({ index: number, input: string } & string[]); +type RegExpResult = null | ({ index: number; input: string } & string[]); type NodeIndex = number; -type TableViewOptionsWithDefault = {| - fixedColumnWidths: Array, -|}; - -export type Column = {| - +propName: string, - +titleL10nId: string, - +component?: React.ComponentType<{| - displayData: DisplayData, - |}>, -|}; - -export type MaybeResizableColumn = {| - ...Column, - /** defaults to initialWidth */ - +minWidth?: CssPixels, - /** This is the initial width, this can be changed in resizable columns */ - +initialWidth: CssPixels, - /** found width + adjustment = width of header column */ - +headerWidthAdjustment?: CssPixels, - // false by default - +resizable?: boolean, - // is the divider after the column hidden? false by default - +hideDividerAfter?: boolean, -|}; - -type TreeViewHeaderProps = {| - +fixedColumns: MaybeResizableColumn[], - +mainColumn: Column, - +viewOptions: TableViewOptionsWithDefault, +type TableViewOptionsWithDefault = { + fixedColumnWidths: Array; +}; + +export type Column> = { + readonly propName: string; + readonly titleL10nId: string; + readonly component?: React.ComponentType<{ + displayData: DisplayData; + }>; +}; + +export type MaybeResizableColumn> = + Column & { + /** defaults to initialWidth */ + readonly minWidth?: CssPixels; + /** This is the initial width, this can be changed in resizable columns */ + readonly initialWidth: CssPixels; + /** found width + adjustment = width of header column */ + readonly headerWidthAdjustment?: CssPixels; + // false by default + readonly resizable?: boolean; + // is the divider after the column hidden? false by default + readonly hideDividerAfter?: boolean; + }; + +type TreeViewHeaderProps> = { + readonly fixedColumns: MaybeResizableColumn[]; + readonly mainColumn: Column; + readonly viewOptions: TableViewOptionsWithDefault; // called when the users moves the divider right of the column, // passes the column index and the start x coordinate - +onColumnWidthChangeStart: (number, CssPixels) => void, - +onColumnWidthReset: (number) => void, -|}; - -class TreeViewHeader extends React.PureComponent< - TreeViewHeaderProps, -> { - _onDividerMouseDown = (event: SyntheticMouseEvent) => { + readonly onColumnWidthChangeStart: (param: number, x: CssPixels) => void; + readonly onColumnWidthReset: (param: number) => void; +}; + +class TreeViewHeader< + DisplayData extends Record, +> extends React.PureComponent> { + _onDividerMouseDown = (event: React.MouseEvent) => { this.props.onColumnWidthChangeStart( Number(event.currentTarget.dataset.columnIndex), event.clientX ); }; - _onDividerDoubleClick = (event: SyntheticMouseEvent) => { + _onDividerDoubleClick = (event: React.MouseEvent) => { this.props.onColumnWidthReset( Number(event.currentTarget.dataset.columnIndex) ); }; - render() { + override render() { const { fixedColumns, mainColumn, viewOptions } = this.props; const columnWidths = viewOptions.fixedColumnWidths; if (fixedColumns.length === 0 && !mainColumn.titleL10nId) { @@ -178,28 +173,31 @@ function reactStringWithHighlightedSubstrings( return highlighted; } -type TreeViewRowFixedColumnsProps = {| - +displayData: DisplayData, - +nodeId: NodeIndex, - +columns: MaybeResizableColumn[], - +index: number, - +isSelected: boolean, - +isRightClicked: boolean, - +onClick: (NodeIndex, SyntheticMouseEvent<>) => mixed, - +highlightRegExp: RegExp | null, - +rowHeightStyle: { height: CssPixels, lineHeight: string }, - +viewOptions: TableViewOptionsWithDefault, -|}; - -class TreeViewRowFixedColumns extends React.PureComponent< - TreeViewRowFixedColumnsProps, -> { - _onClick = (event: SyntheticMouseEvent<>) => { +type TreeViewRowFixedColumnsProps> = { + readonly displayData: DisplayData; + readonly nodeId: NodeIndex; + readonly columns: MaybeResizableColumn[]; + readonly index: number; + readonly isSelected: boolean; + readonly isRightClicked: boolean; + readonly onClick: ( + param: NodeIndex, + event: React.MouseEvent + ) => void; + readonly highlightRegExp: RegExp | null; + readonly rowHeightStyle: { height: CssPixels; lineHeight: string }; + readonly viewOptions: TableViewOptionsWithDefault; +}; + +class TreeViewRowFixedColumns< + DisplayData extends Record, +> extends React.PureComponent> { + _onClick = (event: React.MouseEvent) => { const { nodeId, onClick } = this.props; onClick(nodeId, event); }; - render() { + override render() { const { displayData, columns, @@ -253,51 +251,55 @@ class TreeViewRowFixedColumns extends React.PureComponent< } } -type TreeViewRowScrolledColumnsProps = {| - +displayData: DisplayData, - +nodeId: NodeIndex, - +depth: number, - +mainColumn: Column, - +appendageColumn?: Column, - +index: number, - +canBeExpanded: boolean, - +isExpanded: boolean, - +isSelected: boolean, - +isRightClicked: boolean, - +onToggle: (NodeIndex, boolean, boolean) => mixed, - +onClick: (NodeIndex, SyntheticMouseEvent<>) => mixed, - +highlightRegExp: RegExp | null, - // React converts height into 'px' values, while lineHeight is valid in - // non-'px' units. - +rowHeightStyle: { height: CssPixels, lineHeight: string }, - +indentWidth: CssPixels, -|}; +type TreeViewRowScrolledColumnsProps> = + { + readonly displayData: DisplayData; + readonly nodeId: NodeIndex; + readonly depth: number; + readonly mainColumn: Column; + readonly appendageColumn?: Column; + readonly index: number; + readonly canBeExpanded: boolean; + readonly isExpanded: boolean; + readonly isSelected: boolean; + readonly isRightClicked: boolean; + readonly onToggle: ( + param: NodeIndex, + expanded: boolean, + alt: boolean + ) => void; + readonly onClick: ( + param: NodeIndex, + event: React.MouseEvent + ) => void; + readonly highlightRegExp: RegExp | null; + // React converts height into 'px' values, while lineHeight is valid in + // non-'px' units. + readonly rowHeightStyle: { height: CssPixels; lineHeight: string }; + readonly indentWidth: CssPixels; + }; // This is a false-positive, as it's used as a generic trait bounds. class TreeViewRowScrolledColumns< - DisplayData: Object, + DisplayData extends Record, > extends React.PureComponent> { /** * In this mousedown handler, we use event delegation so we have to use * `target` instead of `currentTarget`. */ - _onMouseDown = ( - event: { target: Element } & SyntheticMouseEvent - ) => { + _onMouseDown = (event: React.MouseEvent) => { const { nodeId, onClick } = this.props; - if (!event.target.classList.contains('treeRowToggleButton')) { + if (!(event.target as Element).classList.contains('treeRowToggleButton')) { onClick(nodeId, event); } }; - _onToggleClick = ( - event: { target: Element } & SyntheticMouseEvent - ) => { + _onToggleClick = (event: React.MouseEvent) => { const { nodeId, isExpanded, onToggle } = this.props; onToggle(nodeId, !isExpanded, event.altKey === true); }; - render() { + override render() { const { displayData, depth, @@ -345,7 +347,7 @@ class TreeViewRowScrolledColumns< style={rowHeightStyle} onMouseDown={this._onMouseDown} // The following attributes are important for accessibility. - aria-expanded={ariaExpanded} + aria-expanded={ariaExpanded ?? undefined} aria-level={depth + 1} aria-selected={isSelected} aria-label={displayData.ariaLabel} @@ -427,64 +429,63 @@ class TreeViewRowScrolledColumns< } } -interface Tree { - getDepth(NodeIndex): number; +interface Tree> { + getDepth(nodeIndex: NodeIndex): number; getRoots(): NodeIndex[]; - getDisplayData(NodeIndex): DisplayData; - getParent(NodeIndex): NodeIndex; - getChildren(NodeIndex): NodeIndex[]; - hasChildren(NodeIndex): boolean; - getAllDescendants(NodeIndex): Set; + getDisplayData(nodeIndex: NodeIndex): DisplayData; + getParent(nodeIndex: NodeIndex): NodeIndex; + getChildren(nodeIndex: NodeIndex): NodeIndex[]; + hasChildren(nodeIndex: NodeIndex): boolean; + getAllDescendants(nodeIndex: NodeIndex): Set; } -type TreeViewProps = {| - +fixedColumns: MaybeResizableColumn[], - +mainColumn: Column, - +tree: Tree, - +expandedNodeIds: Array, - +selectedNodeId: NodeIndex | null, - +rightClickedNodeId?: NodeIndex | null, - +onExpandedNodesChange: (Array) => mixed, - +highlightRegExp?: RegExp | null, - +appendageColumn?: Column, - +disableOverscan?: boolean, - +contextMenu?: React.Element, - +contextMenuId?: string, - +maxNodeDepth: number, - +onSelectionChange: ( - NodeIndex, - {| source: 'keyboard' | 'pointer' |} - ) => mixed, - +onRightClickSelection?: (NodeIndex) => mixed, - +onEnterKey?: (NodeIndex) => mixed, - +onDoubleClick?: (NodeIndex) => mixed, - +rowHeight: CssPixels, - +indentWidth: CssPixels, - +onKeyDown?: (SyntheticKeyboardEvent<>) => void, - +viewOptions: TableViewOptions, - +onViewOptionsChange?: (TableViewOptions) => void, -|}; - -type TreeViewState = {| - +fixedColumnWidths: Array | null, - +isResizingColumns: boolean, -|}; - -export class TreeView extends React.PureComponent< - TreeViewProps, - TreeViewState, -> { +type TreeViewProps> = { + readonly fixedColumns: MaybeResizableColumn[]; + readonly mainColumn: Column; + readonly tree: Tree; + readonly expandedNodeIds: Array; + readonly selectedNodeId: NodeIndex | null; + readonly rightClickedNodeId?: NodeIndex | null; + readonly onExpandedNodesChange: (param: Array) => void; + readonly highlightRegExp?: RegExp | null; + readonly appendageColumn?: Column; + readonly disableOverscan?: boolean; + readonly contextMenu?: React.ReactElement; + readonly contextMenuId?: string; + readonly maxNodeDepth: number; + readonly onSelectionChange: ( + param: NodeIndex, + detail: { source: 'keyboard' | 'pointer' } + ) => void; + readonly onRightClickSelection?: (param: NodeIndex) => void; + readonly onEnterKey?: (param: NodeIndex) => void; + readonly onDoubleClick?: (param: NodeIndex) => void; + readonly rowHeight: CssPixels; + readonly indentWidth: CssPixels; + readonly onKeyDown?: (param: React.KeyboardEvent) => void; + readonly viewOptions: TableViewOptions; + readonly onViewOptionsChange?: (param: TableViewOptions) => void; +}; + +type TreeViewState = { + readonly fixedColumnWidths: Array | null; + readonly isResizingColumns: boolean; +}; + +export class TreeView< + DisplayData extends Record, +> extends React.PureComponent, TreeViewState> { _list: VirtualList | null = null; _takeListRef = (list: VirtualList | null) => (this._list = list); // This contains the information about the current column resizing happening currently. - _currentMovedColumnState: {| - columnIndex: number, - startX: CssPixels, - initialWidth: CssPixels, - |} | null = null; + _currentMovedColumnState: { + columnIndex: number; + startX: CssPixels; + initialWidth: CssPixels; + } | null = null; - state = { + override state = { // This contains the current widths, while or after the user resizes them. fixedColumnWidths: null, @@ -502,7 +503,7 @@ export class TreeView extends React.PureComponent< _computeSpecialItemsMemoized = memoize( ( selectedNodeId: NodeIndex | null, - rightClickedNodeId: ?NodeIndex + rightClickedNodeId: NodeIndex | null ): [NodeIndex | void, NodeIndex | void] => [ selectedNodeId ?? undefined, rightClickedNodeId ?? undefined, @@ -585,7 +586,7 @@ export class TreeView extends React.PureComponent< this._propagateColumnWidthChange(this._getCurrentFixedColumnWidths()); }; - componentWillUnmount = () => { + override componentWillUnmount = () => { this._cleanUpMouseHandlers(); }; @@ -612,7 +613,12 @@ export class TreeView extends React.PureComponent< _computeAllVisibleRowsMemoized = memoize( (tree: Tree, expandedNodes: Set) => { - function _addVisibleRowsFromNode(tree, expandedNodes, arr, nodeId) { + function _addVisibleRowsFromNode( + tree: Tree, + expandedNodes: Set, + arr: NodeIndex[], + nodeId: NodeIndex + ) { arr.push(nodeId); if (!expandedNodes.has(nodeId)) { return; @@ -624,7 +630,7 @@ export class TreeView extends React.PureComponent< } const roots = tree.getRoots(); - const allRows = []; + const allRows: NodeIndex[] = []; for (let i = 0; i < roots.length; i++) { _addVisibleRowsFromNode(tree, expandedNodes, allRows, roots[i]); } @@ -722,7 +728,7 @@ export class TreeView extends React.PureComponent< const { selectedNodeId, rightClickedNodeId } = this.props; return this._computeSpecialItemsMemoized( selectedNodeId, - rightClickedNodeId + rightClickedNodeId ?? null ); } @@ -768,7 +774,7 @@ export class TreeView extends React.PureComponent< } } - _onRowClicked = (nodeId: NodeIndex, event: SyntheticMouseEvent<>) => { + _onRowClicked = (nodeId: NodeIndex, event: React.MouseEvent) => { if (event.button === 0) { this._selectWithMouse(nodeId); } else if (event.button === 2) { @@ -790,7 +796,7 @@ export class TreeView extends React.PureComponent< const { tree, selectedNodeId, mainColumn } = this.props; if (selectedNodeId) { const displayData = tree.getDisplayData(selectedNodeId); - const clipboardData: DataTransfer = (event: any).clipboardData; + const clipboardData: DataTransfer = event.clipboardData!; clipboardData.setData('text/plain', displayData[mainColumn.propName]); } }; @@ -799,7 +805,7 @@ export class TreeView extends React.PureComponent< this.props.onSelectionChange(nodeId, { source: 'keyboard' }); } - _onKeyDown = (event: SyntheticKeyboardEvent<>) => { + _onKeyDown = (event: React.KeyboardEvent) => { if (this.props.onKeyDown) { this.props.onKeyDown(event); } @@ -936,7 +942,7 @@ export class TreeView extends React.PureComponent< } } - render() { + override render() { const { fixedColumns, mainColumn, @@ -969,7 +975,9 @@ export class TreeView extends React.PureComponent< // This attribute exposes the current active child element, // while keeping focus on the parent (call tree). ariaActiveDescendant={ - selectedNodeId !== null ? `treeViewRow-${selectedNodeId}` : null + selectedNodeId !== null + ? `treeViewRow-${selectedNodeId}` + : undefined } items={this._getAllVisibleRows()} renderItem={this._renderRow} diff --git a/src/components/shared/VirtualList.js b/src/components/shared/VirtualList.tsx similarity index 85% rename from src/components/shared/VirtualList.js rename to src/components/shared/VirtualList.tsx index 9bd711f3b3..bd01924cf2 100644 --- a/src/components/shared/VirtualList.js +++ b/src/components/shared/VirtualList.tsx @@ -2,8 +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/. */ -// @flow - /** * VirtualList implements a virtualized component. This means it doesn't * render only the items that are currently displayed, and makes long list @@ -40,13 +38,17 @@ import { getResizeObserverWrapper } from 'firefox-profiler/utils/resize-observer import type { CssPixels } from 'firefox-profiler/types'; -type RenderItem = (Item, number, number) => React.Node; - -type VirtualListRowProps = {| - +renderItem: RenderItem, - +item: Item, - +index: number, - +columnIndex: number, +type RenderItem = ( + item: Item, + index: number, + columnIndex: number +) => React.ReactNode; + +type VirtualListRowProps = { + readonly renderItem: RenderItem; + readonly item: Item; + readonly index: number; + readonly columnIndex: number; // These properties are not used directly, but are needed for strict equality // checks so that the components update correctly. // * `forceRenderControl` is used when we want to update one row or a few rows only, @@ -54,44 +56,44 @@ type VirtualListRowProps = {| // selection need to be changed. // It needs to change whenever the row should be updated, so it should be // computed from the values that control these update. - +forceRenderItem: string, + readonly forceRenderItem: string; // * `items` contains the full items, so that we update the whole list // whenever the source changes. This is necessary because often `item` is a // native value (eg a number), and shallow checking only `item` won't always // give the expected behavior. - +items: $ReadOnlyArray, + readonly items: ReadonlyArray; // * `forceRender` is passed through directly from the main VirtualList // component to the row as a way to update the full list for reasons // unbeknownst to this component. This can be used for example in chart-like // panels where we'd want to redraw if some source value necessary to the // computation changes. - +forceRender?: number | string, -|}; + readonly forceRender?: number | string; +}; class VirtualListRow extends React.PureComponent< - VirtualListRowProps, + VirtualListRowProps > { - render() { + override render() { const { renderItem, item, index, columnIndex } = this.props; return renderItem(item, index, columnIndex); } } -type VirtualListInnerChunkProps = {| - +className: string, - +renderItem: RenderItem, - +items: $ReadOnlyArray, - +specialItems: $ReadOnlyArray, - +visibleRangeStart: number, - +visibleRangeEnd: number, - +columnIndex: number, - +forceRender?: number | string, -|}; +type VirtualListInnerChunkProps = { + readonly className: string; + readonly renderItem: RenderItem; + readonly items: ReadonlyArray; + readonly specialItems: ReadonlyArray; + readonly visibleRangeStart: number; + readonly visibleRangeEnd: number; + readonly columnIndex: number; + readonly forceRender?: number | string; +}; class VirtualListInnerChunk extends React.PureComponent< - VirtualListInnerChunkProps, + VirtualListInnerChunkProps > { - render() { + override render() { const { className, renderItem, @@ -108,7 +110,7 @@ class VirtualListInnerChunk extends React.PureComponent< {range( visibleRangeStart, Math.max(visibleRangeStart, visibleRangeEnd) - ).map((i) => { + ).map((i: number) => { const item = items[i]; // We compute forceRenderItem from the first position of item in the list, @@ -139,23 +141,23 @@ class VirtualListInnerChunk extends React.PureComponent< } } -type VirtualListInnerProps = {| - +itemHeight: CssPixels, - +className: string, - +renderItem: RenderItem, - +items: $ReadOnlyArray, - +specialItems: $ReadOnlyArray, - +visibleRangeStart: number, - +visibleRangeEnd: number, - +columnIndex: number, - +containerWidth: CssPixels, - +forceRender?: number | string, -|}; +type VirtualListInnerProps = { + readonly itemHeight: CssPixels; + readonly className: string; + readonly renderItem: RenderItem; + readonly items: ReadonlyArray; + readonly specialItems: ReadonlyArray; + readonly visibleRangeStart: number; + readonly visibleRangeEnd: number; + readonly columnIndex: number; + readonly containerWidth: CssPixels; + readonly forceRender?: number | string; +}; class VirtualListInner extends React.PureComponent< - VirtualListInnerProps, + VirtualListInnerProps > { - render() { + override render() { const { itemHeight, className, @@ -175,7 +177,7 @@ class VirtualListInner extends React.PureComponent< const chunks = range( startChunkIndex, Math.max(startChunkIndex, endChunkIndex) - ).map((c) => c * chunkSize); + ).map((c: number) => c * chunkSize); return (
    extends React.PureComponent< key={-1} style={{ height: Math.max(0, visibleRangeStart) * itemHeight + 'px' }} /> - {chunks.map((chunkStart) => { + {chunks.map((chunkStart: number) => { return ( extends React.PureComponent< } } -type VirtualListProps = {| - +itemHeight: CssPixels, - +className: string, - +renderItem: RenderItem, - +items: $ReadOnlyArray, - +focusable: boolean, - +specialItems: $ReadOnlyArray, - +onKeyDown?: (SyntheticKeyboardEvent<>) => void, - +onCopy?: (ClipboardEvent) => void, +type VirtualListProps = { + readonly itemHeight: CssPixels; + readonly className: string; + readonly renderItem: RenderItem; + readonly items: ReadonlyArray; + readonly focusable: boolean; + readonly specialItems: ReadonlyArray; + readonly onKeyDown?: (event: React.KeyboardEvent) => void; + readonly onCopy?: (param: ClipboardEvent) => void; // This is called when the mouse leaves the list as it is rendered. That is if // there isn't enough item to fill the component's height, and the user moves // the mouse below the items, this callback would be called. - +onMouseLeaveRenderedList?: () => void, + readonly onMouseLeaveRenderedList?: () => void; // Set `disableOverscan` to `true` when you expect a lot of updates in a short // time: this will render only the visible part, which makes each update faster. - +disableOverscan: boolean, - +columnCount: number, - +containerWidth: CssPixels, + readonly disableOverscan: boolean; + readonly columnCount: number; + readonly containerWidth: CssPixels; // `forceRender` is passed through directly from the main VirtualList // component to the row as a way to update the full list for reasons // unbeknownst to this component. This can be used for example in chart-like // panels where we'd want to redraw if some source value necessary to the // computation changes. - +forceRender?: number | string, + readonly forceRender?: number | string; // The next 3 props will be applied to the underlying DOM element. // They're important for accessibility (especially focus and navigation). - +ariaLabel?: string, - +ariaRole?: string, + readonly ariaLabel?: string; + readonly ariaRole?: string; // Aria-activedescendant specifies the children's "virtual" focus. - +ariaActiveDescendant?: null | string, -|}; + readonly ariaActiveDescendant?: string; +}; -type VirtualListState = {| +type VirtualListState = { // This value is updated from the scroll event. - scrollTop: CssPixels, + scrollTop: CssPixels; // This is updated from a resize observer. - containerHeight: CssPixels, -|}; + containerHeight: CssPixels; +}; export class VirtualList extends React.PureComponent< VirtualListProps, - VirtualListState, + VirtualListState > { - _container: {| current: HTMLDivElement | null |} = React.createRef(); - state = { scrollTop: 0, containerHeight: 0 }; + _container: { current: HTMLDivElement | null } = React.createRef(); + override state = { scrollTop: 0, containerHeight: 0 }; - componentDidMount() { + override componentDidMount() { document.addEventListener('copy', this._onCopy, false); const container = this._container.current; if (!container) { @@ -272,7 +274,7 @@ export class VirtualList extends React.PureComponent< getResizeObserverWrapper().subscribe(container, this._resizeListener); } - componentWillUnmount() { + override componentWillUnmount() { document.removeEventListener('copy', this._onCopy, false); const container = this._container.current; if (!container) { @@ -288,7 +290,7 @@ export class VirtualList extends React.PureComponent< this.setState({ containerHeight: contentRect.height }); }; - _onScroll = (event: SyntheticEvent) => { + _onScroll = (event: React.UIEvent) => { this.setState({ scrollTop: event.currentTarget.scrollTop, }); @@ -450,7 +452,7 @@ export class VirtualList extends React.PureComponent< } }; - render() { + override render() { const { itemHeight, className, @@ -482,7 +484,7 @@ export class VirtualList extends React.PureComponent< className={`${className}InnerWrapper`} onMouseLeave={this._onMouseLeaveInnerWrapper} > - {range(columnCount).map((columnIndex) => ( + {range(columnCount).map((columnIndex: number) => ( mixed, - +onClose?: () => mixed, -|}; +type Props = { + readonly message: string; + readonly actionText?: string; + readonly actionTitle?: string; + readonly actionOnClick?: () => unknown; + readonly onClose?: () => unknown; +}; -type State = {| - +isNoticeDisplayed: boolean, -|}; +type State = { + readonly isNoticeDisplayed: boolean; +}; export class Warning extends PureComponent { - state = { isNoticeDisplayed: true }; + override state = { isNoticeDisplayed: true }; _onHideClick = () => { this.setState({ @@ -31,7 +30,7 @@ export class Warning extends PureComponent { } }; - render() { + override render() { if (!this.state.isNoticeDisplayed) { return null; } diff --git a/src/components/shared/WithSize.js b/src/components/shared/WithSize.tsx similarity index 72% rename from src/components/shared/WithSize.js rename to src/components/shared/WithSize.tsx index f8f2fcbc39..596ddabba7 100644 --- a/src/components/shared/WithSize.js +++ b/src/components/shared/WithSize.tsx @@ -1,21 +1,19 @@ /* 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/. */ -// @flow - import * as React from 'react'; import { findDOMNode } from 'react-dom'; import type { CssPixels } from 'firefox-profiler/types'; import { getResizeObserverWrapper } from 'firefox-profiler/utils/resize-observer-wrapper'; -type State = {| - width: CssPixels, - height: CssPixels, -|}; +type State = { + width: CssPixels; + height: CssPixels; +}; -export type SizeProps = $ReadOnly; +export type SizeProps = Readonly; -export type PropsWithSize = {| ...Props, ...SizeProps |}; +export type PropsWithSize = Props & SizeProps; /** * Wraps a React component and makes 'width' and 'height' available in the @@ -30,15 +28,12 @@ export type PropsWithSize = {| ...Props, ...SizeProps |}; export function withSize( Wrapped: React.ComponentType> ): React.ComponentType { - return class WithSizeWrapper extends React.PureComponent< - Props, - State, - > { - state = { width: 0, height: 0 }; + return class WithSizeWrapper extends React.PureComponent { + override state = { width: 0, height: 0 }; _container: HTMLElement | null = null; - componentDidMount() { - const container = findDOMNode(this); // eslint-disable-line react/no-find-dom-node + override componentDidMount() { + const container = findDOMNode(this) as HTMLElement; // eslint-disable-line react/no-find-dom-node if (!container) { throw new Error('Unable to find the DOMNode'); } @@ -55,7 +50,7 @@ export function withSize( this._updateSize(container, contentRect); }; - componentWillUnmount() { + override componentWillUnmount() { const container = this._container; if (container) { getResizeObserverWrapper().unsubscribe(container, this._resizeListener); @@ -64,15 +59,19 @@ export function withSize( this._container = null; } - _updateSize(container: HTMLElement, contentRect: DOMRectReadOnly) { + _updateSize(_container: HTMLElement, contentRect: DOMRectReadOnly) { this.setState({ width: contentRect.width, height: contentRect.height, }); } - render() { - return ; + override render() { + const combinedProps: Props & SizeProps = { + ...this.props, + ...this.state, + }; + return ; } }; } diff --git a/src/components/shared/chart/Canvas.js b/src/components/shared/chart/Canvas.tsx similarity index 87% rename from src/components/shared/chart/Canvas.js rename to src/components/shared/chart/Canvas.tsx index f88ca7ce55..beac8505f3 100644 --- a/src/components/shared/chart/Canvas.js +++ b/src/components/shared/chart/Canvas.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import { timeCode } from 'firefox-profiler/utils/time-code'; import classNames from 'classnames'; @@ -10,53 +8,53 @@ import { Tooltip } from 'firefox-profiler/components/tooltip/Tooltip'; import type { CssPixels, DevicePixels } from 'firefox-profiler/types'; -type Props = {| - +containerWidth: CssPixels, - +containerHeight: CssPixels, - +className: string, - +onSelectItem?: (Item | null) => void, - +onRightClick?: (Item | null) => void, - +onDoubleClickItem: (Item | null) => void, - +getHoveredItemInfo: (Item) => React.Node, - +drawCanvas: ( - CanvasRenderingContext2D, +type Props = { + readonly containerWidth: CssPixels; + readonly containerHeight: CssPixels; + readonly className: string; + readonly onSelectItem?: (param: Item | null) => void; + readonly onRightClick?: (param: Item | null) => void; + readonly onDoubleClickItem: (param: Item | null) => void; + readonly getHoveredItemInfo: (param: Item) => React.ReactNode; + readonly drawCanvas: ( + ctx: CanvasRenderingContext2D, ChartCanvasScale: ChartCanvasScale, ChartCanvasHoverInfo: ChartCanvasHoverInfo - ) => void, - +isDragging: boolean, + ) => void; + readonly isDragging: boolean; // Applies ctx.scale() to the canvas to draw using CssPixels rather than DevicePixels. - +scaleCtxToCssPixels: boolean, - +hitTest: (x: CssPixels, y: CssPixels) => Item | null, + readonly scaleCtxToCssPixels: boolean; + readonly hitTest: (x: CssPixels, y: CssPixels) => Item | null; // Default to true. Set to false if the chart should be redrawn right away after // rerender. - +drawCanvasAfterRaf?: boolean, + readonly drawCanvasAfterRaf?: boolean; - +onMouseMove?: (e: { nativeEvent: MouseEvent }) => mixed, - +onMouseLeave?: (e: { nativeEvent: MouseEvent }) => mixed, + readonly onMouseMove?: (e: { nativeEvent: MouseEvent }) => unknown; + readonly onMouseLeave?: (e: { nativeEvent: MouseEvent }) => unknown; // Defaults to false. Set to true if the chart should persist the tooltips on click. - +stickyTooltips?: boolean, -|}; + readonly stickyTooltips?: boolean; +}; // The naming of the X and Y coordinates here correspond to the ones // found on the MouseEvent interface. type State = { - hoveredItem: Item | null, - selectedItem: Item | null, - pageX: CssPixels, - pageY: CssPixels, + hoveredItem: Item | null; + selectedItem: Item | null; + pageX: CssPixels; + pageY: CssPixels; }; export type ChartCanvasScale = { // Always equal to devicePixelRatio - cssToDeviceScale: number, + cssToDeviceScale: number; // 1 if scaleCtxToCssPixels is true, otherwise equal to cssToDeviceScale - cssToUserScale: number, + cssToUserScale: number; }; export type ChartCanvasHoverInfo = { - hoveredItem: Item | null, - prevHoveredItem: Item | null, - isHoveredOnlyDifferent: boolean, + hoveredItem: Item | null; + prevHoveredItem: Item | null; + isHoveredOnlyDifferent: boolean; }; import './Canvas.css'; @@ -76,7 +74,7 @@ const MOUSE_CLICK_MAX_MOVEMENT_DELTA: CssPixels = 5; // But we still conditionally update the canvas itself, see componentDidUpdate. export class ChartCanvas extends React.Component< Props, - State, + State > { _devicePixelRatio: number = 1; // The current mouse position. Needs to be stored for tooltip @@ -91,11 +89,11 @@ export class ChartCanvas extends React.Component< // Indicates if move threshold breached. Checked at mouse up event // to prevent it from being interpreted as a click. _mouseMovedWhileClicked: boolean = false; - _ctx: CanvasRenderingContext2D; + _ctx: CanvasRenderingContext2D | null = null; _canvas: HTMLCanvasElement | null = null; _isDrawScheduled: boolean = false; - state: State = { + override state: State = { hoveredItem: null, selectedItem: null, pageX: 0, @@ -123,18 +121,18 @@ export class ChartCanvas extends React.Component< }); } - _prepCanvas() { + _prepCanvas(): CanvasRenderingContext2D | null { const canvas = this._canvas; const { containerWidth, containerHeight, scaleCtxToCssPixels } = this.props; const { devicePixelRatio } = window; if (!canvas) { - return; + return null; } let ctx = this._ctx; if (!ctx) { - ctx = canvas.getContext('2d', { alpha: false }); + ctx = canvas.getContext('2d', { alpha: false })!; this._ctx = ctx; } @@ -173,6 +171,8 @@ export class ChartCanvas extends React.Component< } this._devicePixelRatio = devicePixelRatio; } + + return ctx; } _doDrawCanvas( @@ -181,12 +181,12 @@ export class ChartCanvas extends React.Component< ) { const { className, drawCanvas, scaleCtxToCssPixels } = this.props; const { hoveredItem } = this.state; - if (this._canvas) { - timeCode(`${className} render`, () => { - this._prepCanvas(); + timeCode(`${className} render`, () => { + const ctx = this._prepCanvas(); + if (ctx !== null) { const scale = this._devicePixelRatio; drawCanvas( - this._ctx, + ctx, { cssToDeviceScale: scale, cssToUserScale: scaleCtxToCssPixels ? 1 : scale, @@ -197,11 +197,13 @@ export class ChartCanvas extends React.Component< isHoveredOnlyDifferent, } ); - }); - } + } + }); } - _onMouseDown = (e: { nativeEvent: MouseEvent } & SyntheticMouseEvent<>) => { + _onMouseDown = ( + e: { nativeEvent: MouseEvent } & React.MouseEvent + ) => { if (e.button === 0) { // Remember where the mouse was positioned. Move too far and it // won't be registered as a selecting click on mouse up. @@ -219,7 +221,7 @@ export class ChartCanvas extends React.Component< } }; - _onClick = (e: SyntheticMouseEvent<>) => { + _onClick = (e: React.MouseEvent) => { if (this._mouseMovedWhileClicked) { return; } @@ -239,7 +241,7 @@ export class ChartCanvas extends React.Component< }; _onMouseLeave = ( - event: { nativeEvent: MouseEvent } & SyntheticMouseEvent<> + event: { nativeEvent: MouseEvent } & React.MouseEvent ) => { if (this.props.onMouseLeave) { this.props.onMouseLeave(event); @@ -247,7 +249,7 @@ export class ChartCanvas extends React.Component< }; _onMouseMove = ( - event: { nativeEvent: MouseEvent } & SyntheticMouseEvent<> + event: { nativeEvent: MouseEvent } & React.MouseEvent ) => { if (!this._canvas) { return; @@ -327,7 +329,7 @@ export class ChartCanvas extends React.Component< this.props.onDoubleClickItem(this.state.hoveredItem); }; - _getHoveredItemInfo = (): React.Node => { + _getHoveredItemInfo = (): React.ReactNode => { const { hoveredItem, selectedItem } = this.state; if (selectedItem !== null) { // If we have a selected item, persist that one instead of returning @@ -346,7 +348,7 @@ export class ChartCanvas extends React.Component< this._canvas = canvas; }; - UNSAFE_componentWillReceiveProps() { + override UNSAFE_componentWillReceiveProps() { // It is possible that the data backing the chart has been // changed, for instance after symbolication. Clear the // hoveredItem if the mouse no longer hovers over it. @@ -362,7 +364,7 @@ export class ChartCanvas extends React.Component< } } - componentDidUpdate(prevProps: Props, prevState: State) { + override componentDidUpdate(prevProps: Props, prevState: State) { if (prevProps !== this.props) { if ( this.state.selectedItem !== null && @@ -385,7 +387,7 @@ export class ChartCanvas extends React.Component< } } - render() { + override render() { const { isDragging } = this.props; const { hoveredItem, pageX, pageY } = this.state; diff --git a/src/components/shared/chart/Viewport.js b/src/components/shared/chart/Viewport.tsx similarity index 89% rename from src/components/shared/chart/Viewport.js rename to src/components/shared/chart/Viewport.tsx index 0b8856c056..99402fa8ed 100644 --- a/src/components/shared/chart/Viewport.js +++ b/src/components/shared/chart/Viewport.tsx @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import * as React from 'react'; import classNames from 'classnames'; @@ -96,7 +95,7 @@ type NavigationKey = 'zoomIn' | 'zoomOut' | 'up' | 'down' | 'left' | 'right'; /** * Mapping from keycode to navigation key when no modifiers are down. */ -const BARE_KEYMAP: { [string]: NavigationKey } = { +const BARE_KEYMAP: { [key: string]: NavigationKey } = { KeyQ: 'zoomIn', KeyY: 'zoomIn', KeyE: 'zoomOut', @@ -113,89 +112,88 @@ const BARE_KEYMAP: { [string]: NavigationKey } = { /** * Mapping from keycode to navigation key when the ctrl modifier is down. */ -const CTRL_KEYMAP: { [string]: NavigationKey } = { +const CTRL_KEYMAP: { [key: string]: NavigationKey } = { ArrowUp: 'zoomIn', ArrowDown: 'zoomOut', }; // These viewport values (most of which are computed dynamically by // the HOC) are passed into the props of the wrapped component. -export type Viewport = {| - +containerWidth: CssPixels, - +containerHeight: CssPixels, - +viewportLeft: UnitIntervalOfProfileRange, - +viewportRight: UnitIntervalOfProfileRange, - +viewportTop: CssPixels, - +viewportBottom: CssPixels, - +isDragging: boolean, - +moveViewport: (CssPixels, CssPixels) => void, - +isSizeSet: boolean, -|}; - -type ChartViewportImplStateProps = {| - +panelLayoutGeneration: number, - +hasZoomedViaMousewheel?: boolean, -|}; - -type ChartViewportImplDispatchProps = {| - +updatePreviewSelection: typeof updatePreviewSelection, - +setHasZoomedViaMousewheel?: typeof setHasZoomedViaMousewheel, -|}; - -type ViewportProps = {| +export type Viewport = { + readonly containerWidth: CssPixels; + readonly containerHeight: CssPixels; + readonly viewportLeft: UnitIntervalOfProfileRange; + readonly viewportRight: UnitIntervalOfProfileRange; + readonly viewportTop: CssPixels; + readonly viewportBottom: CssPixels; + readonly isDragging: boolean; + readonly moveViewport: (dx: CssPixels, dy: CssPixels) => void; + readonly isSizeSet: boolean; +}; + +type ChartViewportImplStateProps = { + readonly panelLayoutGeneration: number; + readonly hasZoomedViaMousewheel?: boolean; +}; + +type ChartViewportImplDispatchProps = { + readonly updatePreviewSelection: typeof updatePreviewSelection; + readonly setHasZoomedViaMousewheel?: typeof setHasZoomedViaMousewheel; +}; + +type ViewportProps = { // The "committed range", whose endpoints correspond to 0 and 1. - +timeRange: StartEndRange, + readonly timeRange: StartEndRange; // The preview selection, whose endpoints correspond to viewportLeft and viewportRight. - +previewSelection: PreviewSelection, + readonly previewSelection: PreviewSelection; // The left margin. Margins are outside the viewport but inside containerWidth. - +marginLeft: CssPixels, + readonly marginLeft: CssPixels; // The right margin. Margins are outside the viewport but inside containerWidth. - +marginRight: CssPixels, - - +maxViewportHeight: number, - +startsAtBottom?: boolean, - +maximumZoom: UnitIntervalOfProfileRange, - +disableHorizontalMovement?: boolean, - +className?: string, - +containerRef?: (HTMLDivElement | null) => void, - +viewportNeedsUpdate: ( + readonly marginRight: CssPixels; + + readonly maxViewportHeight: number; + readonly startsAtBottom?: boolean; + readonly maximumZoom: UnitIntervalOfProfileRange; + readonly disableHorizontalMovement?: boolean; + readonly className?: string; + readonly containerRef?: (container: HTMLDivElement | null) => void; + readonly viewportNeedsUpdate: ( prevProps: ChartProps, nextProps: ChartProps - ) => boolean, -|}; + ) => boolean; +}; // These are the props consumed by the ViewportImpl component. -type ChartViewportImplOwnProps = {| - +viewportProps: ViewportProps, - +chart: React.ComponentType>, - +chartProps: ChartProps, -|}; - -export type ChartPropsPlusViewport = {| - ...ChartProps, - +viewport: Viewport, -|}; - -type HorizontalViewport = {| +type ChartViewportImplOwnProps = { + readonly viewportProps: ViewportProps; + readonly chart: React.ComponentType>; + readonly chartProps: ChartProps; +}; + +export type ChartPropsPlusViewport = ChartProps & { + viewport: Viewport; +}; + +type HorizontalViewport = { // The position of the profile range that should be drawn at the left edge of // the chart's "inner box", i.e. after the marginLeft. - viewportLeft: UnitIntervalOfProfileRange, + viewportLeft: UnitIntervalOfProfileRange; // The position of the profile range that should be drawn at the right edge of // the chart's "inner box", i.e. to the left of marginRight. - viewportRight: UnitIntervalOfProfileRange, -|}; - -type State = {| - containerWidth: CssPixels, - containerHeight: CssPixels, - containerLeft: CssPixels, - viewportTop: CssPixels, - viewportBottom: CssPixels, - horizontalViewport: HorizontalViewport, - isDragging: boolean, - isScrollHintVisible: boolean, - isSizeSet: boolean, -|}; + viewportRight: UnitIntervalOfProfileRange; +}; + +type State = { + containerWidth: CssPixels; + containerHeight: CssPixels; + containerLeft: CssPixels; + viewportTop: CssPixels; + viewportBottom: CssPixels; + horizontalViewport: HorizontalViewport; + isDragging: boolean; + isScrollHintVisible: boolean; + isSizeSet: boolean; +}; import './Viewport.css'; @@ -207,31 +205,31 @@ const PINCH_ZOOM_FACTOR = 3; type ChartViewportImplProps = ConnectedProps< ChartViewportImplOwnProps, ChartViewportImplStateProps, - ChartViewportImplDispatchProps, + ChartViewportImplDispatchProps >; -export class ChartViewportImpl extends React.PureComponent< - ChartViewportImplProps, - State, +class ChartViewportImpl extends React.PureComponent< + ChartViewportImplProps, + State > { zoomScrollId: number = 0; _pendingPreviewSelectionUpdates: Array< - (HorizontalViewport) => PreviewSelection, + (horizontalViewport: HorizontalViewport) => PreviewSelection > = []; _container: HTMLDivElement | null = null; - _takeContainerRef = (container: HTMLDivElement | null) => { + _takeContainerRef = (container: HTMLDivElement) => { if (this.props.viewportProps.containerRef) { this.props.viewportProps.containerRef(container); } this._container = container; }; _lastKeyboardNavigationFrame: number = 0; - _keysDown: Set = new Set(); + _keysDown: Set = new Set(); _deltaToZoomFactor = (delta: number) => Math.pow(ZOOM_SPEED, delta); _dragX: number = 0; _dragY: number = 0; - constructor(props: ChartViewportImplProps) { + constructor(props: ChartViewportImplProps) { super(props); this.state = this.getDefaultState(props); } @@ -254,7 +252,7 @@ export class ChartViewportImpl extends React.PureComponent< }; } - getDefaultState(props: ChartViewportImplProps) { + getDefaultState(props: ChartViewportImplProps) { const { previewSelection, timeRange } = props.viewportProps; const horizontalViewport = this.getHorizontalViewport( previewSelection, @@ -294,8 +292,8 @@ export class ChartViewportImpl extends React.PureComponent< }, 1000); } - UNSAFE_componentWillReceiveProps( - newProps: ChartViewportImplProps + override UNSAFE_componentWillReceiveProps( + newProps: ChartViewportImplProps ) { if ( this.props.viewportProps.viewportNeedsUpdate( @@ -435,7 +433,7 @@ export class ChartViewportImpl extends React.PureComponent< * processes all queued updates from a requestAnimationFrame callback. */ _addBatchedPreviewSelectionUpdate( - callback: (HorizontalViewport) => PreviewSelection + callback: (param: HorizontalViewport) => PreviewSelection ) { if (this._pendingPreviewSelectionUpdates.length === 0) { requestAnimationFrame(() => this._flushPendingPreviewSelectionUpdates()); @@ -550,7 +548,7 @@ export class ChartViewportImpl extends React.PureComponent< ); }; - _mouseDownListener = (event: SyntheticMouseEvent<>) => { + _mouseDownListener = (event: React.MouseEvent) => { event.preventDefault(); if (this._container) { this._container.focus(); @@ -580,7 +578,7 @@ export class ChartViewportImpl extends React.PureComponent< }; _keyDownListener = ( - event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> + event: { nativeEvent: KeyboardEvent } & React.KeyboardEvent ) => { let navigationKey; if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { @@ -605,7 +603,7 @@ export class ChartViewportImpl extends React.PureComponent< }; _keyUpListener = ( - event: { nativeEvent: KeyboardEvent } & SyntheticKeyboardEvent<> + event: { nativeEvent: KeyboardEvent } & React.KeyboardEvent ) => { if (!event.ctrlKey) { // The ctrl modifier might have been released here. Try to @@ -763,7 +761,7 @@ export class ChartViewportImpl extends React.PureComponent< }); }; - componentDidMount() { + override componentDidMount() { // The first _setSize ensures that the screen does not blip when mounting // the component, while the second ensures that it lays out correctly if the DOM // is not fully layed out correctly yet. @@ -778,20 +776,18 @@ export class ChartViewportImpl extends React.PureComponent< } } - componentWillUnmount() { + override componentWillUnmount() { window.removeEventListener('resize', this._setSizeNextFrame, false); window.removeEventListener('mousemove', this._mouseMoveListener, true); window.removeEventListener('mouseup', this._mouseUpListener, true); const container = this._container; if (container) { getResizeObserverWrapper().unsubscribe(container, this._setSize); - container.removeEventListener('wheel', this._mouseWheelListener, { - passive: false, - }); + container.removeEventListener('wheel', this._mouseWheelListener); } } - render() { + override render() { const { chart, chartProps, @@ -861,10 +857,10 @@ export class ChartViewportImpl extends React.PureComponent< } } -export type ChartViewportProps = {| - +viewportProps: ViewportProps, - +chartProps: ChartProps, -|}; +export type ChartViewportProps = { + readonly viewportProps: ViewportProps; + readonly chartProps: ChartProps; +}; // const MyChartOuter = withChartViewport(MyChartInner); // @@ -880,7 +876,7 @@ export function withChartViewport( const ConnectedChartViewport = explicitConnect< ChartViewportImplOwnProps, ChartViewportImplStateProps, - ChartViewportImplDispatchProps, + ChartViewportImplDispatchProps >({ mapStateToProps: (state) => ({ panelLayoutGeneration: getPanelLayoutGeneration(state), @@ -904,7 +900,7 @@ export function withChartViewport( }; } -function clamp(min, max, value) { +function clamp(min: number, max: number, value: number) { return Math.max(min, Math.min(max, value)); } diff --git a/src/components/shared/thread/ActivityGraph.js b/src/components/shared/thread/ActivityGraph.tsx similarity index 78% rename from src/components/shared/thread/ActivityGraph.js rename to src/components/shared/thread/ActivityGraph.tsx index 465d00839b..526faaa727 100644 --- a/src/components/shared/thread/ActivityGraph.js +++ b/src/components/shared/thread/ActivityGraph.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import * as React from 'react'; import classNames from 'classnames'; @@ -33,48 +31,47 @@ import type { CpuRatioInTimeRange, } from './ActivityGraphFills'; -export type Props = {| - +className: string, - +trackName: string, - +fullThread: Thread, - +rangeFilteredThread: Thread, - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +sampleIndexOffset: number, - +onSampleClick: ( - event: SyntheticMouseEvent<>, +export type Props = { + readonly className: string; + readonly trackName: string; + readonly fullThread: Thread; + readonly rangeFilteredThread: Thread; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly sampleIndexOffset: number; + readonly onSampleClick: ( + event: React.MouseEvent, sampleIndex: IndexIntoSamplesTable | null - ) => void, - +categories: CategoryList, - +samplesSelectedStates: null | SelectedState[], - +treeOrderSampleComparator: ( - IndexIntoSamplesTable, - IndexIntoSamplesTable - ) => number, - +enableCPUUsage: boolean, - +implementationFilter: ImplementationFilter, - +timelineType: TimelineType, - +zeroAt: Milliseconds, - +profileTimelineUnit: string, - ...SizeProps, -|}; - -export type HoveredPixelState = {| - +sample: IndexIntoSamplesTable | null, - +cpuRatioInTimeRange: CpuRatioInTimeRange | null, -|}; + ) => void; + readonly categories: CategoryList; + readonly samplesSelectedStates: null | SelectedState[]; + readonly treeOrderSampleComparator: ( + a: IndexIntoSamplesTable, + b: IndexIntoSamplesTable + ) => number; + readonly enableCPUUsage: boolean; + readonly implementationFilter: ImplementationFilter; + readonly timelineType: TimelineType; + readonly zeroAt: Milliseconds; + readonly profileTimelineUnit: string; +} & SizeProps; + +export type HoveredPixelState = { + readonly sample: IndexIntoSamplesTable | null; + readonly cpuRatioInTimeRange: CpuRatioInTimeRange | null; +}; type State = { - hoveredPixelState: null | HoveredPixelState, - mouseX: CssPixels, - mouseY: CssPixels, + hoveredPixelState: null | HoveredPixelState; + mouseX: CssPixels; + mouseY: CssPixels; }; class ThreadActivityGraphImpl extends React.PureComponent { _fillsQuerier: null | ActivityFillGraphQuerier = null; - state = { + override state: State = { hoveredPixelState: null, mouseX: 0, mouseY: 0, @@ -84,7 +81,7 @@ class ThreadActivityGraphImpl extends React.PureComponent { this.setState({ hoveredPixelState: null }); }; - _onMouseMove = (event: SyntheticMouseEvent) => { + _onMouseMove = (event: React.MouseEvent) => { const canvas = event.currentTarget; if (!canvas) { return; @@ -105,7 +102,7 @@ class ThreadActivityGraphImpl extends React.PureComponent { }; _getSampleAtMouseEvent( - event: SyntheticMouseEvent + event: React.MouseEvent ): null | HoveredPixelState { const { width } = this.props; // Create local variables so that Flow can refine the following to be non-null. @@ -123,12 +120,12 @@ class ThreadActivityGraphImpl extends React.PureComponent { return fillsQuerier.getSampleAndCpuRatioAtClick(x, y, time); } - _onClick = (event: SyntheticMouseEvent) => { + _onClick = (event: React.MouseEvent) => { const sampleState = this._getSampleAtMouseEvent(event); this.props.onSampleClick(event, sampleState ? sampleState.sample : null); }; - render() { + override render() { const { fullThread, rangeFilteredThread, @@ -199,6 +196,4 @@ class ThreadActivityGraphImpl extends React.PureComponent { } } -export const ThreadActivityGraph = withSize<$Diff>( - ThreadActivityGraphImpl -); +export const ThreadActivityGraph = withSize(ThreadActivityGraphImpl); diff --git a/src/components/shared/thread/ActivityGraphCanvas.js b/src/components/shared/thread/ActivityGraphCanvas.tsx similarity index 84% rename from src/components/shared/thread/ActivityGraphCanvas.js rename to src/components/shared/thread/ActivityGraphCanvas.tsx index fc568ad49a..705045cc82 100644 --- a/src/components/shared/thread/ActivityGraphCanvas.js +++ b/src/components/shared/thread/ActivityGraphCanvas.tsx @@ -2,14 +2,13 @@ * 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/. * */ -// @flow - import * as React from 'react'; import { InView } from 'react-intersection-observer'; -import { +import type { ActivityFillGraphQuerier, - computeActivityGraphFills, + CategoryDrawStyles, } from './ActivityGraphFills'; +import { computeActivityGraphFills } from './ActivityGraphFills'; import { timeCode } from 'firefox-profiler/utils/time-code'; import { mapCategoryColorNameToStyles } from 'firefox-profiler/utils/colors'; @@ -22,33 +21,30 @@ import type { } from 'firefox-profiler/types'; import type { SizeProps } from 'firefox-profiler/components/shared/WithSize'; -import type { CategoryDrawStyles } from './ActivityGraphFills'; - -type CanvasProps = {| - +className: string, - +trackName: string, - +fullThread: Thread, - +rangeFilteredThread: Thread, - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +sampleIndexOffset: number, - +samplesSelectedStates: null | SelectedState[], - +treeOrderSampleComparator: ( - IndexIntoSamplesTable, - IndexIntoSamplesTable - ) => number, - +categories: CategoryList, - +passFillsQuerier: (ActivityFillGraphQuerier) => void, - +onClick: (SyntheticMouseEvent) => void, - +enableCPUUsage: boolean, - ...SizeProps, -|}; +type CanvasProps = { + readonly className: string; + readonly trackName: string; + readonly fullThread: Thread; + readonly rangeFilteredThread: Thread; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly sampleIndexOffset: number; + readonly samplesSelectedStates: null | SelectedState[]; + readonly treeOrderSampleComparator: ( + a: IndexIntoSamplesTable, + b: IndexIntoSamplesTable + ) => number; + readonly categories: CategoryList; + readonly passFillsQuerier: (param: ActivityFillGraphQuerier) => void; + readonly onClick: (param: React.MouseEvent) => void; + readonly enableCPUUsage: boolean; +} & SizeProps; export class ActivityGraphCanvas extends React.PureComponent { - _canvas: {| current: null | HTMLCanvasElement |} = React.createRef(); + _canvas: { current: null | HTMLCanvasElement } = React.createRef(); _categoryDrawStyles: null | CategoryDrawStyles = null; - _canvasState: {| renderScheduled: boolean, inView: boolean |} = { + _canvasState: { renderScheduled: boolean; inView: boolean } = { renderScheduled: false, inView: false, }; @@ -107,11 +103,11 @@ export class ActivityGraphCanvas extends React.PureComponent { this._renderCanvas(); }; - componentDidMount() { + override componentDidMount() { this._renderCanvas(); } - componentDidUpdate() { + override componentDidUpdate() { this._renderCanvas(); } @@ -131,7 +127,7 @@ export class ActivityGraphCanvas extends React.PureComponent { height, } = this.props; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d')!; const canvasPixelWidth = Math.round(width * window.devicePixelRatio); const canvasPixelHeight = Math.round(height * window.devicePixelRatio); canvas.width = canvasPixelWidth; @@ -151,7 +147,7 @@ export class ActivityGraphCanvas extends React.PureComponent { xPixelsPerMs: canvasPixelWidth / (rangeEnd - rangeStart), treeOrderSampleComparator, greyCategoryIndex: categories.findIndex((c) => c.color === 'grey') || 0, - categoryDrawStyles: this._getCategoryDrawStyles(ctx), + categoryDrawStyles: this._getCategoryDrawStyles(ctx!), }); // The value in fillsQuerier is needed in ActivityGraph but is computed in this method @@ -215,7 +211,7 @@ export class ActivityGraphCanvas extends React.PureComponent { } } - render() { + override render() { const { className, trackName, onClick } = this.props; return ( @@ -241,7 +237,7 @@ function _createDiagonalStripePattern( const dpr = Math.round(window.devicePixelRatio); patternCanvas.width = 4 * dpr; patternCanvas.height = 4 * dpr; - const patternContext = patternCanvas.getContext('2d'); + const patternContext = patternCanvas.getContext('2d')!; patternContext.scale(dpr, dpr); const linear = patternContext.createLinearGradient(0, 0, 4, 4); @@ -256,13 +252,17 @@ function _createDiagonalStripePattern( patternContext.fillStyle = linear; patternContext.fillRect(0, 0, 4, 4); - return chartCtx.createPattern(patternCanvas, 'repeat'); + return chartCtx.createPattern(patternCanvas, 'repeat')!; } /** * Search an array from a starting index to find where two arrays diverge. */ -function _findNextDifferentIndex(arr1, arr2, startIndex) { +function _findNextDifferentIndex( + arr1: Float32Array, + arr2: Float32Array, + startIndex: number +): number { for (let i = startIndex; i < arr1.length; i++) { if (arr1[i] !== arr2[i]) { return i; diff --git a/src/components/shared/thread/ActivityGraphFills.js b/src/components/shared/thread/ActivityGraphFills.tsx similarity index 92% rename from src/components/shared/thread/ActivityGraphFills.js rename to src/components/shared/thread/ActivityGraphFills.tsx index 9f2660bb1f..baa6fe7245 100644 --- a/src/components/shared/thread/ActivityGraphFills.js +++ b/src/components/shared/thread/ActivityGraphFills.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - import { bisectionRight } from 'firefox-profiler/utils/bisect'; import { ensureExists } from 'firefox-profiler/utils/flow'; @@ -29,70 +27,69 @@ import type { HoveredPixelState } from './ActivityGraph'; * immutable values. This object makes it easy to share these values between different * classes and functions. */ -type RenderedComponentSettings = {| - +canvasPixelWidth: DevicePixels, - +canvasPixelHeight: DevicePixels, - +fullThread: Thread, - +rangeFilteredThread: Thread, - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +sampleIndexOffset: number, - +xPixelsPerMs: number, - +enableCPUUsage: boolean, - +treeOrderSampleComparator: ?( - IndexIntoSamplesTable, - IndexIntoSamplesTable - ) => number, - +greyCategoryIndex: IndexIntoCategoryList, - +samplesSelectedStates: null | Array, - +categoryDrawStyles: CategoryDrawStyles, -|}; - -type SampleContributionToPixel = {| - +sample: IndexIntoSamplesTable, - +contribution: number, -|}; +type RenderedComponentSettings = { + readonly canvasPixelWidth: DevicePixels; + readonly canvasPixelHeight: DevicePixels; + readonly fullThread: Thread; + readonly rangeFilteredThread: Thread; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly sampleIndexOffset: number; + readonly xPixelsPerMs: number; + readonly enableCPUUsage: boolean; + readonly treeOrderSampleComparator: + | ((a: IndexIntoSamplesTable, b: IndexIntoSamplesTable) => number) + | null; + readonly greyCategoryIndex: IndexIntoCategoryList; + readonly samplesSelectedStates: null | Array; + readonly categoryDrawStyles: CategoryDrawStyles; +}; + +type SampleContributionToPixel = { + readonly sample: IndexIntoSamplesTable; + readonly contribution: number; +}; /** * The category fills are the computation that is ultimately returned for drawing * the categories to the canvas. During the computation step, this value is mutated * in place, but should be consumed immutably. */ -type CategoryFill = {| - +category: IndexIntoCategoryList, - +fillStyle: string | CanvasPattern, +type CategoryFill = { + readonly category: IndexIntoCategoryList; + readonly fillStyle: string | CanvasPattern; // The Float32Arrays are mutated in place during the computation step. - +perPixelContribution: Float32Array, - +accumulatedUpperEdge: Float32Array, -|}; - -export type CategoryDrawStyles = $ReadOnlyArray<{| - +category: number, - +gravity: number, - +selectedFillStyle: string, - +unselectedFillStyle: string, - +filteredOutByTransformFillStyle: CanvasPattern, - +selectedTextColor: string, -|}>; - -type SelectedPercentageAtPixelBuffers = {| + readonly perPixelContribution: Float32Array; + readonly accumulatedUpperEdge: Float32Array; +}; + +export type CategoryDrawStyles = ReadonlyArray<{ + readonly category: number; + readonly gravity: number; + readonly selectedFillStyle: string; + readonly unselectedFillStyle: string; + readonly filteredOutByTransformFillStyle: CanvasPattern; + readonly selectedTextColor: string; +}>; + +type SelectedPercentageAtPixelBuffers = { // These Float32Arrays are mutated in place during the computation step. - +beforeSelectedPercentageAtPixel: Float32Array, - +selectedPercentageAtPixel: Float32Array, - +afterSelectedPercentageAtPixel: Float32Array, - +filteredOutByTransformPercentageAtPixel: Float32Array, - +filteredOutByTabPercentageAtPixel: Float32Array, -|}; - -export type CpuRatioInTimeRange = {| - +cpuRatio: number, - +timeRange: Milliseconds, -|}; + readonly beforeSelectedPercentageAtPixel: Float32Array; + readonly selectedPercentageAtPixel: Float32Array; + readonly afterSelectedPercentageAtPixel: Float32Array; + readonly filteredOutByTransformPercentageAtPixel: Float32Array; + readonly filteredOutByTabPercentageAtPixel: Float32Array; +}; + +export type CpuRatioInTimeRange = { + readonly cpuRatio: number; + readonly timeRange: Milliseconds; +}; const BOX_BLUR_RADII = [3, 2, 2]; const SMOOTHING_RADIUS = 3 + 2 + 2; -const SMOOTHING_KERNEL: Float32Array = _getSmoothingKernel( +const SMOOTHING_KERNEL: Float32Array = _getSmoothingKernel( SMOOTHING_RADIUS, BOX_BLUR_RADII ); @@ -133,10 +130,10 @@ export function computeActivityGraphFills( * fills by mutating the selected pecentage buffers and the category fill values. */ export class ActivityGraphFillComputer { - +renderedComponentSettings: RenderedComponentSettings; + readonly renderedComponentSettings: RenderedComponentSettings; // The fills and percentages are mutated in place. - +mutablePercentageBuffers: SelectedPercentageAtPixelBuffers[]; - +mutableFills: CategoryFill[]; + readonly mutablePercentageBuffers: SelectedPercentageAtPixelBuffers[]; + readonly mutableFills: CategoryFill[]; constructor( renderedComponentSettings: RenderedComponentSettings, @@ -152,10 +149,10 @@ export class ActivityGraphFillComputer { * Run the computation to compute a list of the fills that need to be drawn for the * ThreadActivityGraph. */ - run(): {| - +averageCPUPerPixel: Float32Array, - +upperGraphEdge: Float32Array, - |} { + run(): { + readonly averageCPUPerPixel: Float32Array; + readonly upperGraphEdge: Float32Array; + } { // First go through each sample, and set the buffers that contain the percentage // that a category contributes to a given place in the X axis of the chart. this._accumulateSampleCategories(); @@ -495,7 +492,7 @@ export class ActivityFillGraphQuerier { */ _getCPURatioAtX( deviceX: DevicePixels, - samplesAtThisPixel: $ReadOnlyArray + samplesAtThisPixel: ReadonlyArray ): CpuRatioInTimeRange | null { const { rangeFilteredThread: { samples }, @@ -540,9 +537,9 @@ export class ActivityFillGraphQuerier { deviceX: DevicePixels, deviceY: DevicePixels ): null | { - category: IndexIntoCategoryList, - categoryLowerEdge: number, - yPercentage: number, + category: IndexIntoCategoryList; + categoryLowerEdge: number; + yPercentage: number; } { const { canvasPixelHeight } = this.renderedComponentSettings; @@ -599,7 +596,7 @@ export class ActivityFillGraphQuerier { */ _getSamplesAtTime( time: Milliseconds - ): $ReadOnlyArray { + ): ReadonlyArray { const { rangeStart, treeOrderSampleComparator, xPixelsPerMs } = this.renderedComponentSettings; @@ -740,7 +737,7 @@ export class ActivityFillGraphQuerier { function _getSmoothingKernel( smoothingRadius: number, boxBlurRadii: number[] -): Float32Array { +): Float32Array { const kernelWidth = smoothingRadius + 1 + smoothingRadius; const kernel = new Float32Array(kernelWidth); kernel[smoothingRadius] = 1; @@ -756,6 +753,9 @@ function _getSmoothingKernel( function _createSelectedPercentageAtPixelBuffers({ categoryDrawStyles, canvasPixelWidth, +}: { + categoryDrawStyles: CategoryDrawStyles; + canvasPixelWidth: number; }): SelectedPercentageAtPixelBuffers[] { return categoryDrawStyles.map(() => ({ beforeSelectedPercentageAtPixel: new Float32Array(canvasPixelWidth), @@ -831,7 +831,7 @@ function _getCategoryFills( ); // Flatten out the fills into a single array. - return [].concat(...nestedFills); + return ([] as CategoryFill[]).concat(...nestedFills); } /** @@ -966,7 +966,7 @@ function _boxBlur1D( * Apply a blur with a gaussian distribution to a destination array. */ function _applyGaussianBlur1D( - srcArray: Float32Array, + srcArray: Float32Array, boxBlurRadii: number[] ): void { let a = srcArray; diff --git a/src/components/shared/thread/CPUGraph.js b/src/components/shared/thread/CPUGraph.tsx similarity index 78% rename from src/components/shared/thread/CPUGraph.js rename to src/components/shared/thread/CPUGraph.tsx index 8fa969dd37..d730237a3d 100644 --- a/src/components/shared/thread/CPUGraph.js +++ b/src/components/shared/thread/CPUGraph.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { ThreadHeightGraph } from './HeightGraph'; import { ensureExists } from 'firefox-profiler/utils/flow'; @@ -17,23 +15,23 @@ import type { } from 'firefox-profiler/types'; import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; -type Props = {| - +className: string, - +thread: Thread, - +samplesSelectedStates: null | SelectedState[], - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +callNodeInfo: CallNodeInfo, - +categories: CategoryList, - +onSampleClick: ( - event: SyntheticMouseEvent<>, +type Props = { + readonly className: string; + readonly thread: Thread; + readonly samplesSelectedStates: null | SelectedState[]; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly callNodeInfo: CallNodeInfo; + readonly categories: CategoryList; + readonly onSampleClick: ( + event: React.MouseEvent, sampleIndex: IndexIntoSamplesTable - ) => void, + ) => void; // Decide which way the stacks grow up from the floor, or down from the ceiling. - +stacksGrowFromCeiling?: boolean, - +trackName: string, -|}; + readonly stacksGrowFromCeiling?: boolean; + readonly trackName: string; +}; export class ThreadCPUGraph extends PureComponent { _heightFunction = (sampleIndex: IndexIntoSamplesTable): number | null => { @@ -49,7 +47,7 @@ export class ThreadCPUGraph extends PureComponent { return ensureExists(samples.threadCPURatio)[sampleIndex + 1] || 0; }; - render() { + override render() { const { className, thread, diff --git a/src/components/shared/thread/HeightGraph.js b/src/components/shared/thread/HeightGraph.tsx similarity index 85% rename from src/components/shared/thread/HeightGraph.js rename to src/components/shared/thread/HeightGraph.tsx index fe097b784c..6b5a848cb4 100644 --- a/src/components/shared/thread/HeightGraph.js +++ b/src/components/shared/thread/HeightGraph.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import classNames from 'classnames'; import { ensureExists } from 'firefox-profiler/utils/flow'; import { timeCode } from 'firefox-profiler/utils/time-code'; @@ -20,24 +18,24 @@ import type { SelectedState, } from 'firefox-profiler/types'; -type Props = {| - +heightFunc: (IndexIntoSamplesTable) => number | null, - +maxValue: number, - +className: string, - +thread: Thread, - +samplesSelectedStates: null | SelectedState[], - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +categories: CategoryList, - +onSampleClick: ( - event: SyntheticMouseEvent<>, +type Props = { + readonly heightFunc: (param: IndexIntoSamplesTable) => number | null; + readonly maxValue: number; + readonly className: string; + readonly thread: Thread; + readonly samplesSelectedStates: null | SelectedState[]; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly categories: CategoryList; + readonly onSampleClick: ( + event: React.MouseEvent, sampleIndex: IndexIntoSamplesTable - ) => void, + ) => void; // Decide which way the stacks grow up from the floor, or down from the ceiling. - +stacksGrowFromCeiling?: boolean, - +trackName: string, -|}; + readonly stacksGrowFromCeiling?: boolean; + readonly trackName: string; +}; export class ThreadHeightGraph extends PureComponent { _canvas: null | HTMLCanvasElement = null; @@ -54,12 +52,12 @@ export class ThreadHeightGraph extends PureComponent { } } - componentDidMount() { + override componentDidMount() { window.addEventListener('resize', this._resizeListener); this.forceUpdate(); // for initial size } - componentWillUnmount() { + override componentWillUnmount() { window.removeEventListener('resize', this._resizeListener); } @@ -77,12 +75,12 @@ export class ThreadHeightGraph extends PureComponent { } = this.props; const devicePixelRatio = canvas.ownerDocument - ? canvas.ownerDocument.defaultView.devicePixelRatio + ? (canvas.ownerDocument.defaultView?.devicePixelRatio ?? 1) : 1; const rect = canvas.getBoundingClientRect(); canvas.width = Math.round(rect.width * devicePixelRatio); canvas.height = Math.round(rect.height * devicePixelRatio); - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d')!; const range = [rangeStart, rangeEnd]; const rangeLength = range[1] - range[0]; const xPixelsPerMs = canvas.width / rangeLength; @@ -109,12 +107,12 @@ export class ThreadHeightGraph extends PureComponent { // Do one pass over the samples array to gather the samples we want to draw. const regularSamples = { - height: [], - xPos: [], + height: [] as number[], + xPos: [] as number[], }; const idleSamples = { - height: [], - xPos: [], + height: [] as number[], + xPos: [] as number[], }; const highlightedSamples = { height: [], @@ -162,8 +160,8 @@ export class ThreadHeightGraph extends PureComponent { } type SamplesBucket = { - height: number[], - xPos: number[], + height: number[]; + xPos: number[]; }; function drawSamples(samplesBucket: SamplesBucket, color: string) { if (samplesBucket.xPos.length === 0) { @@ -174,7 +172,9 @@ export class ThreadHeightGraph extends PureComponent { const height = samplesBucket.height[i]; const startY = stacksGrowFromCeiling ? 0 : canvas.height - height; const xPos = samplesBucket.xPos[i]; - ctx.fillRect(xPos, startY, drawnIntervalWidth, height); + if (ctx) { + ctx.fillRect(xPos, startY, drawnIntervalWidth, height); + } } } @@ -186,7 +186,7 @@ export class ThreadHeightGraph extends PureComponent { drawSamples(idleSamples, lighterBlue); } - _onClick = (event: SyntheticMouseEvent<>) => { + _onClick = (event: React.MouseEvent) => { const canvas = this._canvas; if (canvas) { const { rangeStart, rangeEnd, thread, interval } = this.props; @@ -212,7 +212,7 @@ export class ThreadHeightGraph extends PureComponent { } }; - render() { + override render() { this._renderCanvas(); const { className, trackName } = this.props; return ( diff --git a/src/components/shared/thread/SampleGraph.js b/src/components/shared/thread/SampleGraph.tsx similarity index 83% rename from src/components/shared/thread/SampleGraph.js rename to src/components/shared/thread/SampleGraph.tsx index da7f530c7f..032e0bb35f 100644 --- a/src/components/shared/thread/SampleGraph.js +++ b/src/components/shared/thread/SampleGraph.tsx @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import React, { PureComponent } from 'react'; import classNames from 'classnames'; @@ -32,48 +31,46 @@ import type { import type { SizeProps } from 'firefox-profiler/components/shared/WithSize'; import type { CpuRatioInTimeRange } from './ActivityGraphFills'; -export type HoveredPixelState = {| - +sample: IndexIntoSamplesTable | null, - +cpuRatioInTimeRange: CpuRatioInTimeRange | null, -|}; - -type Props = {| - +className: string, - +thread: Thread, - +samplesSelectedStates: null | SelectedState[], - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +categories: CategoryList, - +onSampleClick: ( - event: SyntheticMouseEvent<>, +export type HoveredPixelState = { + readonly sample: IndexIntoSamplesTable | null; + readonly cpuRatioInTimeRange: CpuRatioInTimeRange | null; +}; + +type Props = { + readonly className: string; + readonly thread: Thread; + readonly samplesSelectedStates: null | SelectedState[]; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly categories: CategoryList; + readonly onSampleClick: ( + event: React.MouseEvent, sampleIndex: IndexIntoSamplesTable | null - ) => void, - +trackName: string, - +timelineType: TimelineType, - +implementationFilter: ImplementationFilter, - +zeroAt: Milliseconds, - +profileTimelineUnit: string, - ...SizeProps, -|}; + ) => void; + readonly trackName: string; + readonly timelineType: TimelineType; + readonly implementationFilter: ImplementationFilter; + readonly zeroAt: Milliseconds; + readonly profileTimelineUnit: string; +} & SizeProps; type State = { - hoveredPixelState: null | HoveredPixelState, - mouseX: CssPixels, - mouseY: CssPixels, + hoveredPixelState: null | HoveredPixelState; + mouseX: CssPixels; + mouseY: CssPixels; }; -type CanvasProps = {| - +className: string, - +thread: Thread, - +samplesSelectedStates: null | SelectedState[], - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +categories: CategoryList, - +trackName: string, - ...SizeProps, -|}; +type CanvasProps = { + readonly className: string; + readonly thread: Thread; + readonly samplesSelectedStates: null | SelectedState[]; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly categories: CategoryList; + readonly trackName: string; +} & SizeProps; /** * This component controls the rendering of the canvas. Every render call through @@ -84,7 +81,7 @@ class ThreadSampleGraphCanvas extends React.PureComponent { _canvas: null | HTMLCanvasElement = null; _takeCanvasRef = (canvas: HTMLCanvasElement | null) => (this._canvas = canvas); - _canvasState: {| renderScheduled: boolean, inView: boolean |} = { + _canvasState: { renderScheduled: boolean; inView: boolean } = { renderScheduled: false, inView: false, }; @@ -118,11 +115,11 @@ class ThreadSampleGraphCanvas extends React.PureComponent { this._renderCanvas(); }; - componentDidMount() { + override componentDidMount() { this._renderCanvas(); } - componentDidUpdate() { + override componentDidUpdate() { this._renderCanvas(); } @@ -139,11 +136,11 @@ class ThreadSampleGraphCanvas extends React.PureComponent { } = this.props; const devicePixelRatio = canvas.ownerDocument - ? canvas.ownerDocument.defaultView.devicePixelRatio + ? canvas.ownerDocument.defaultView?.devicePixelRatio || 1 : 1; canvas.width = Math.round(width * devicePixelRatio); canvas.height = Math.round(height * devicePixelRatio); - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d')!; const rangeLength = rangeEnd - rangeStart; const xPixelsPerMs = canvas.width / rangeLength; const trueIntervalPixelWidth = interval * xPixelsPerMs; @@ -168,9 +165,9 @@ class ThreadSampleGraphCanvas extends React.PureComponent { ); // Do one pass over the samples array to gather the samples we want to draw. - const regularSamples = []; - const idleSamples = []; - const highlightedSamples = []; + const regularSamples: number[] = []; + const idleSamples: number[] = []; + const highlightedSamples: number[] = []; // Enforce a minimum distance so that we don't draw more than 4 samples per // pixel. const minGapMs = 0.25 / xPixelsPerMs; @@ -225,7 +222,7 @@ class ThreadSampleGraphCanvas extends React.PureComponent { drawSamples(idleSamples, lighterBlue); } - render() { + override render() { const { trackName } = this.props; return ( @@ -246,13 +243,13 @@ class ThreadSampleGraphCanvas extends React.PureComponent { } export class ThreadSampleGraphImpl extends PureComponent { - state = { + override state = { hoveredPixelState: null, mouseX: 0, mouseY: 0, }; - _onClick = (event: SyntheticMouseEvent) => { + _onClick = (event: React.MouseEvent) => { const hoveredSample = this._getSampleAtMouseEvent(event); this.props.onSampleClick(event, hoveredSample?.sample ?? null); }; @@ -261,7 +258,7 @@ export class ThreadSampleGraphImpl extends PureComponent { this.setState({ hoveredPixelState: null }); }; - _onMouseMove = (event: SyntheticMouseEvent) => { + _onMouseMove = (event: React.MouseEvent) => { const canvas = event.currentTarget; if (!canvas) { return; @@ -277,9 +274,9 @@ export class ThreadSampleGraphImpl extends PureComponent { }; _getSampleAtMouseEvent( - event: SyntheticMouseEvent + event: React.MouseEvent ): null | HoveredPixelState { - const canvas = event.currentTarget; + const canvas = event.currentTarget as HTMLCanvasElement; if (!canvas) { return null; } @@ -331,7 +328,7 @@ export class ThreadSampleGraphImpl extends PureComponent { }; } - render() { + override render() { const { className, trackName, @@ -373,10 +370,10 @@ export class ThreadSampleGraphImpl extends PureComponent { {hoveredPixelState === null ? null : ( { } } -export const ThreadSampleGraph = withSize<$Diff>( - ThreadSampleGraphImpl -); +export const ThreadSampleGraph = withSize(ThreadSampleGraphImpl); diff --git a/src/components/shared/thread/StackGraph.js b/src/components/shared/thread/StackGraph.tsx similarity index 76% rename from src/components/shared/thread/StackGraph.js rename to src/components/shared/thread/StackGraph.tsx index 9d0df41da4..3a83052809 100644 --- a/src/components/shared/thread/StackGraph.js +++ b/src/components/shared/thread/StackGraph.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { ThreadHeightGraph } from './HeightGraph'; @@ -17,24 +15,24 @@ import type { } from 'firefox-profiler/types'; import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; -type Props = {| - +className: string, - +thread: Thread, - +samplesSelectedStates: null | SelectedState[], - +sampleNonInvertedCallNodes: Array, - +interval: Milliseconds, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +callNodeInfo: CallNodeInfo, - +categories: CategoryList, - +onSampleClick: ( - event: SyntheticMouseEvent<>, +type Props = { + readonly className: string; + readonly thread: Thread; + readonly samplesSelectedStates: null | SelectedState[]; + readonly sampleNonInvertedCallNodes: Array; + readonly interval: Milliseconds; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly callNodeInfo: CallNodeInfo; + readonly categories: CategoryList; + readonly onSampleClick: ( + event: React.MouseEvent, sampleIndex: IndexIntoSamplesTable - ) => void, + ) => void; // Decide which way the stacks grow up from the floor, or down from the ceiling. - +stacksGrowFromCeiling?: boolean, - +trackName: string, -|}; + readonly stacksGrowFromCeiling?: boolean; + readonly trackName: string; +}; export class ThreadStackGraph extends PureComponent { _heightFunction = (sampleIndex: IndexIntoSamplesTable): number | null => { @@ -48,7 +46,7 @@ export class ThreadStackGraph extends PureComponent { return callNodeTable.depth[nonInvertedCallNodeIndex]; }; - render() { + override render() { const { className, thread, diff --git a/src/components/tooltip/CallNode.js b/src/components/tooltip/CallNode.tsx similarity index 94% rename from src/components/tooltip/CallNode.js rename to src/components/tooltip/CallNode.tsx index 6e334ae7d2..b150ca7b49 100644 --- a/src/components/tooltip/CallNode.js +++ b/src/components/tooltip/CallNode.tsx @@ -1,7 +1,7 @@ /* 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/. */ -// @flow + import * as React from 'react'; import { getStackType } from 'firefox-profiler/profile-logic/transforms'; @@ -47,13 +47,13 @@ function TooltipCallNodeMeter({ value, color, ariaLabel, -}: {| - additionalClassName: string, - max: number, - value: number, - color?: string, - ariaLabel: string, -|}) { +}: { + additionalClassName: string; + max: number; + value: number; + color?: string; + ariaLabel: string; +}) { const widthPercent = (value / max) * 100 + '%'; const barColor = color ? `var(--category-color-${color})` : 'var(--blue-40)'; return ( @@ -82,14 +82,14 @@ function TooltipCallNodeTotalSelfMeters({ total, color, labelQualifier, -}: {| - isHeader: boolean, - max: number, - self: number, - total: number, - color?: string, - labelQualifier: string, -|}) { +}: { + isHeader: boolean; + max: number; + self: number; + total: number; + color?: string; + labelQualifier: string; +}) { return (
    | null, - +callNodeIndex: IndexIntoCallNodeTable, - +callNodeInfo: CallNodeInfo, - +categories: CategoryList, - +interval: Milliseconds, +type Props = { + readonly thread: Thread; + readonly weightType: WeightType; + readonly innerWindowIDToPageMap: Map | null; + readonly callNodeIndex: IndexIntoCallNodeTable; + readonly callNodeInfo: CallNodeInfo; + readonly categories: CategoryList; + readonly interval: Milliseconds; // Since this tooltip can be used in different context, provide some kind of duration // label, e.g. "100ms" or "33%". - +durationText: string, - +displayData?: CallNodeDisplayData, - +timings?: TimingsForPath, - +callTreeSummaryStrategy: CallTreeSummaryStrategy, - +displayStackType: boolean, -|}; + readonly durationText: string; + readonly displayData?: CallNodeDisplayData; + readonly timings?: TimingsForPath; + readonly callTreeSummaryStrategy: CallTreeSummaryStrategy; + readonly displayStackType: boolean; +}; /** * This class collects the tooltip rendering for anything that cares about call nodes. @@ -197,7 +197,7 @@ export class TooltipCallNode extends React.PureComponent { { selfTime, totalTime }: ItemTimings, category: IndexIntoCategoryList, isHighPrecision: boolean - ): React.Node { + ): React.ReactNode { if (totalTime.breakdownByCategory === null) { return null; } @@ -286,7 +286,7 @@ export class TooltipCallNode extends React.PureComponent { return rows; } - _renderCategoryTimings(maybeTimings: ?TimingsForPath) { + _renderCategoryTimings(maybeTimings: TimingsForPath | undefined) { if (!maybeTimings) { return null; } @@ -352,7 +352,7 @@ export class TooltipCallNode extends React.PureComponent { ); } - render() { + override render() { const { callNodeIndex, thread, diff --git a/src/components/tooltip/DivWithTooltip.js b/src/components/tooltip/DivWithTooltip.tsx similarity index 87% rename from src/components/tooltip/DivWithTooltip.js rename to src/components/tooltip/DivWithTooltip.tsx index f68d4e6ee9..bb430e9c79 100644 --- a/src/components/tooltip/DivWithTooltip.js +++ b/src/components/tooltip/DivWithTooltip.tsx @@ -1,37 +1,34 @@ /* 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/. */ - -// @flow import * as React from 'react'; import { Tooltip } from './Tooltip'; import type { CssPixels } from 'firefox-profiler/types'; // This isn't an exact object on purpose, because we'll pass all other props to // the underlying
    . -type Props = { - +tooltip: React.Node, - +children?: React.Node, +type Props = React.HTMLAttributes & { + readonly tooltip: React.ReactNode; }; -type State = {| - isMouseOver: boolean, - mouseX: CssPixels, - mouseY: CssPixels, -|}; +type State = { + isMouseOver: boolean; + mouseX: CssPixels; + mouseY: CssPixels; +}; /** * This component provides a way to automatically insert a tooltip when mousing over * a div. */ export class DivWithTooltip extends React.PureComponent { - state = { + override state = { isMouseOver: false, mouseX: 0, mouseY: 0, }; - componentWillUnmount() { + override componentWillUnmount() { document.removeEventListener('mousemove', this._onMouseMove, false); } @@ -56,7 +53,7 @@ export class DivWithTooltip extends React.PureComponent { }); }; - render() { + override render() { const { children, tooltip, ...containerProps } = this.props; const { mouseX, mouseY, isMouseOver } = this.state; const shouldShowTooltip = isMouseOver; diff --git a/src/components/tooltip/GCMarker.js b/src/components/tooltip/GCMarker.tsx similarity index 98% rename from src/components/tooltip/GCMarker.js rename to src/components/tooltip/GCMarker.tsx index 0d3a940e13..b9dd40603b 100644 --- a/src/components/tooltip/GCMarker.js +++ b/src/components/tooltip/GCMarker.tsx @@ -2,9 +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/. */ -// @flow - -import * as React from 'react'; import { formatPercent, formatBytes, @@ -334,7 +331,7 @@ export function getGCSliceDetails( return details; } -type PhaseTimeTuple = {| name: string, time: Microseconds |}; +type PhaseTimeTuple = { name: string; time: Microseconds }; function _markerDetailPhase(p: PhaseTimeTuple) { return ( @@ -390,11 +387,11 @@ function _makePhaseTimesArray( * (self-time) is 180ms". */ -type PhaseTreeNode = {| - value: PhaseTimeTuple, - leaf: boolean, - branches: Map, -|}; +type PhaseTreeNode = { + value: PhaseTimeTuple; + leaf: boolean; + branches: Map; +}; function _treeInsert( tree: Map, @@ -503,7 +500,7 @@ function _filterInterestingPhaseTimes( * Sort by the ordering of the original list, which is in an execution order * that we'd like to preserve. */ - const order = {}; + const order: Record = {}; let i = 0; for (const { name } of phaseTimes) { order[name] = i++; diff --git a/src/components/tooltip/Marker.js b/src/components/tooltip/Marker.tsx similarity index 93% rename from src/components/tooltip/Marker.js rename to src/components/tooltip/Marker.tsx index f6a4d7a1f7..aca6e2ff71 100644 --- a/src/components/tooltip/Marker.js +++ b/src/components/tooltip/Marker.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import classNames from 'classnames'; import { @@ -76,32 +74,32 @@ function _maybeFormatDuration( return 'unknown'; } -type OwnProps = {| - +markerIndex: MarkerIndex, - +marker: Marker, - +threadsKey: ThreadsKey, - +className?: string, +type OwnProps = { + readonly markerIndex: MarkerIndex; + readonly marker: Marker; + readonly threadsKey: ThreadsKey; + readonly className?: string; // In tooltips it can be awkward for really long and tall things to force // the layout to be huge. This option when set to true will restrict the // height of things like stacks, and the width of long things like URLs. - +restrictHeightWidth: boolean, -|}; - -type StateProps = {| - +threadName?: string, - +thread: Thread, - +implementationFilter: ImplementationFilter, - +pages: PageList | null, - +innerWindowIDToPageMap: Map | null, - +zeroAt: Milliseconds, - +threadIdToNameMap: Map, - +processIdToNameMap: Map, - +markerSchemaByName: MarkerSchemaByName, - +getMarkerLabel: (MarkerIndex) => string, - +categories: CategoryList, -|}; - -type Props = ConnectedProps; + readonly restrictHeightWidth: boolean; +}; + +type StateProps = { + readonly threadName?: string; + readonly thread: Thread; + readonly implementationFilter: ImplementationFilter; + readonly pages: PageList | null; + readonly innerWindowIDToPageMap: Map | null; + readonly zeroAt: Milliseconds; + readonly threadIdToNameMap: Map; + readonly processIdToNameMap: Map; + readonly markerSchemaByName: MarkerSchemaByName; + readonly getMarkerLabel: (param: MarkerIndex) => string; + readonly categories: CategoryList; +}; + +type Props = ConnectedProps; // Maximum image size of a tooltip field. const MAXIMUM_IMAGE_SIZE = 350; @@ -119,6 +117,7 @@ class MarkerTooltipContents extends React.PureComponent { pages && innerWindowIDToPageMap && marker.data && + 'innerWindowID' in marker.data && marker.data.innerWindowID ) ) { @@ -188,7 +187,7 @@ class MarkerTooltipContents extends React.PureComponent { ? threadIdToNameMap.get(marker.threadId) : this.props.threadName; - if (data && data.threadId !== undefined) { + if (data && 'threadId' in data && data.threadId !== undefined) { // This marker has some threadId data in its payload, which is about the // thread where this event happened. const threadId = data.threadId; @@ -260,7 +259,7 @@ class MarkerTooltipContents extends React.PureComponent { const { key, label, format } = field; - const value = data[key]; + const value = (data as any)[key]; if (value === undefined || value === null) { // This marker doesn't have a value for this field. Values are optional. continue; @@ -333,7 +332,11 @@ class MarkerTooltipContents extends React.PureComponent { break; } case 'CompositorScreenshot': { - if (data.url !== undefined) { + if ( + data.url !== undefined && + 'windowWidth' in data && + 'windowHeight' in data + ) { const { width, height } = computeScreenshotSize( data, MAXIMUM_IMAGE_SIZE @@ -494,7 +497,7 @@ class MarkerTooltipContents extends React.PureComponent { * ternaries everywhere. This leads to a style of render function that includes * a short list of rendering strategies, in the order they appear. */ - render() { + override render() { const { className } = this.props; return (
    @@ -516,7 +519,7 @@ class MarkerTooltipContents extends React.PureComponent { } } -export const TooltipMarker = explicitConnect({ +export const TooltipMarker = explicitConnect({ mapStateToProps: (state, props) => { const selectors = getThreadSelectorsFromThreadsKey(props.threadsKey); return { diff --git a/src/components/tooltip/NetworkMarker.js b/src/components/tooltip/NetworkMarker.tsx similarity index 94% rename from src/components/tooltip/NetworkMarker.js rename to src/components/tooltip/NetworkMarker.tsx index e470b9494d..dc986780c3 100644 --- a/src/components/tooltip/NetworkMarker.js +++ b/src/components/tooltip/NetworkMarker.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import classNames from 'classnames'; @@ -38,7 +36,7 @@ import type { import './NetworkMarker.css'; /* The labels are for the duration between _this_ label and the next label. */ -const HUMAN_LABEL_FOR_PHASE: { [NetworkPhaseName]: string } = { +const HUMAN_LABEL_FOR_PHASE: Record = { startTime: 'Waiting for socket thread', domainLookupStart: 'DNS request', domainLookupEnd: 'After DNS request', @@ -52,7 +50,7 @@ const HUMAN_LABEL_FOR_PHASE: { [NetworkPhaseName]: string } = { endTime: 'End', }; -const OPACITY_FOR_PHASE: { [NetworkPhaseName]: number } = { +const OPACITY_FOR_PHASE: Record = { startTime: 0, domainLookupStart: 0.5, domainLookupEnd: 0.5, @@ -66,15 +64,15 @@ const OPACITY_FOR_PHASE: { [NetworkPhaseName]: number } = { endTime: 0, }; -type NetworkPhaseProps = {| - +propertyName: NetworkPhaseName, - +dur: Milliseconds, - +startPosition: Milliseconds, - +phaseDuration: Milliseconds, -|}; +type NetworkPhaseProps = { + readonly propertyName: NetworkPhaseName; + readonly dur: Milliseconds; + readonly startPosition: Milliseconds; + readonly phaseDuration: Milliseconds; +}; class NetworkPhase extends React.PureComponent { - render() { + override render() { const { startPosition, dur, propertyName, phaseDuration } = this.props; const startPositionPercent = (startPosition / dur) * 100; const durationPercent = Math.max(0.3, (phaseDuration / dur) * 100); @@ -102,7 +100,7 @@ class NetworkPhase extends React.PureComponent { style={{ marginLeft: startPositionPercent + '%', marginRight: 100 - startPositionPercent - durationPercent + '%', - opacity: opacity === 0 ? null : opacity, + opacity: opacity === 0 ? undefined : opacity, }} /> @@ -110,17 +108,17 @@ class NetworkPhase extends React.PureComponent { } } -type Props = {| - +payload: NetworkPayload, - +zeroAt: Milliseconds, -|}; +type Props = { + readonly payload: NetworkPayload; + readonly zeroAt: Milliseconds; +}; export class TooltipNetworkMarkerPhases extends React.PureComponent { _renderPhases( properties: NetworkPhaseAndValue[], sectionDuration: Milliseconds, startTime: Milliseconds - ): Array> | null { + ): Array> | null { if (properties.length < 2) { console.error( 'Only 1 preconnect property has been found, this should not happen.' @@ -150,7 +148,7 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { return phases; } - _renderPreconnectPhases(): React.Node { + _renderPreconnectPhases(): React.ReactNode { const { payload, zeroAt } = this.props; const preconnectStart = payload.domainLookupStart; if (typeof preconnectStart !== 'number') { @@ -198,7 +196,7 @@ export class TooltipNetworkMarkerPhases extends React.PureComponent { ); } - render() { + override render() { const { payload } = this.props; const mimeType = payload.contentType || guessMimeTypeFromNetworkMarker(payload); diff --git a/src/components/tooltip/Tooltip.js b/src/components/tooltip/Tooltip.tsx similarity index 86% rename from src/components/tooltip/Tooltip.js rename to src/components/tooltip/Tooltip.tsx index ce41ca54d4..5a5937f479 100644 --- a/src/components/tooltip/Tooltip.js +++ b/src/components/tooltip/Tooltip.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; @@ -18,12 +16,12 @@ export const MOUSE_OFFSET = 11; // If changing this value, make sure and adjust the max-width in the .tooltip class. export const VISUAL_MARGIN: CssPixels = 8; -type Props = {| - +mouseX: CssPixels, - +mouseY: CssPixels, - +children: React.Node, - +className?: string, -|}; +type Props = { + readonly mouseX: CssPixels; + readonly mouseY: CssPixels; + readonly children: React.ReactNode; + readonly className?: string; +}; // These types represent the tooltip's position. They will be used when storing // the previous position as well as when defining the new one. @@ -31,15 +29,15 @@ type PositionFromMouse = 'before-mouse' | 'after-mouse'; type TooltipPosition = PositionFromMouse | 'window-edge'; export class Tooltip extends React.PureComponent { - _interiorElementRef: {| current: HTMLDivElement | null |} = React.createRef(); + _interiorElementRef: { current: HTMLDivElement | null } = React.createRef(); // This keeps the previous tooltip positioning relatively to the mouse cursor. // "after" / "after" is the prefered positioning, so it's our default. // "edge" means aligned to the window's left or top edge. _previousPosition: { - horizontal: TooltipPosition, - vertical: TooltipPosition, - } = { horizontal: 'after-mouse', vertical: 'after-mouse' }; + horizontal: TooltipPosition; + vertical: TooltipPosition; + } = { horizontal: 'after-mouse' as const, vertical: 'after-mouse' as const }; _overlayElement = ensureExists( document.querySelector('#root-overlay'), @@ -55,11 +53,11 @@ export class Tooltip extends React.PureComponent { windowSize, previousPosition, }: { - mousePosition: CssPixels, - elementSize: CssPixels, - windowSize: CssPixels, - previousPosition: TooltipPosition, - }): { position: TooltipPosition, style: CssPixels } { + mousePosition: CssPixels; + elementSize: CssPixels; + windowSize: CssPixels; + previousPosition: TooltipPosition; + }): { position: TooltipPosition; style: CssPixels } { // 1. Compute the possible tooltip positions depending on the mouse position, // the tooltip's size, as well as the available space in the window. const possiblePositions: Array = []; @@ -112,7 +110,10 @@ export class Tooltip extends React.PureComponent { cssStyle = VISUAL_MARGIN; break; default: - throw assertExhaustiveCheck(newPosition); + throw assertExhaustiveCheck( + newPosition as never, + 'Unknown position type' + ); } // 4. Return all the values, so that they can be applied and saved. @@ -153,21 +154,21 @@ export class Tooltip extends React.PureComponent { }; } - componentDidMount() { + override componentDidMount() { this.setPositioningStyle(); } - componentDidUpdate() { + override componentDidUpdate() { this.setPositioningStyle(); } - _mouseDownListener = (event: SyntheticMouseEvent<>) => { + _mouseDownListener = (event: React.MouseEvent) => { // Prevent the canvas element to handle the mouse down event. Otherwise // drag and drop events closes the tooltip. event.stopPropagation(); }; - render() { + override render() { return ReactDOM.createPortal(
    , -|}; + readonly children?: React.ReactNode; +}; export function TooltipDetail({ label, children }: DetailProps) { if (children === null || children === undefined || children === '') { @@ -48,13 +46,13 @@ export function TooltipDetailSeparator() { return
    ; } -export type TooltipDetailComponent = React.Element< - typeof TooltipDetail | typeof TooltipDetailSeparator, +export type TooltipDetailComponent = React.ReactElement< + typeof TooltipDetail | typeof TooltipDetailSeparator > | null; -type Props = {| +type Props = { // This component accepts only TooltipDetail children. - +children: React.ChildrenArray, -|}; + readonly children: React.ReactNode; +}; export function TooltipDetails({ children }: Props) { return
    {children}
    ; diff --git a/src/components/tooltip/TrackPower.js b/src/components/tooltip/TrackPower.tsx similarity index 89% rename from src/components/tooltip/TrackPower.js rename to src/components/tooltip/TrackPower.tsx index aa8e2a97bd..5f50ec9afd 100644 --- a/src/components/tooltip/TrackPower.js +++ b/src/components/tooltip/TrackPower.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import { Localized } from '@fluent/react'; import memoize from 'memoize-one'; @@ -19,32 +17,31 @@ import { getMeta, } from 'firefox-profiler/selectors/profile'; import { getSampleIndexRangeForSelection } from 'firefox-profiler/profile-logic/profile-data'; - import { TooltipDetails, TooltipDetail } from './TooltipDetails'; import type { + State, Counter, Milliseconds, PreviewSelection, StartEndRange, ProfileMeta, } from 'firefox-profiler/types'; - import type { ConnectedProps } from 'firefox-profiler/utils/connect'; -type OwnProps = {| - counter: Counter, - counterSampleIndex: number, -|}; +type OwnProps = { + counter: Counter; + counterSampleIndex: number; +}; -type StateProps = {| - interval: Milliseconds, - meta: ProfileMeta, - committedRange: StartEndRange, - previewSelection: PreviewSelection, -|}; +type StateProps = { + interval: Milliseconds; + meta: ProfileMeta; + committedRange: StartEndRange; + previewSelection: PreviewSelection; +}; -type Props = ConnectedProps; +type Props = ConnectedProps; class TooltipTrackPowerImpl extends React.PureComponent { // This compute the sum of the power in the range. This returns a value in Wh. @@ -83,11 +80,11 @@ class TooltipTrackPowerImpl extends React.PureComponent { _formatPowerValue( power: number, - l10nIdKiloUnit, - l10nIdUnit, - l10nIdMilliUnit, - l10nIdMicroUnit - ): Localized { + l10nIdKiloUnit: string, + l10nIdUnit: string, + l10nIdMilliUnit: string, + l10nIdMicroUnit?: string + ): React.ReactElement { let value, l10nId, carbonValue; const carbon = this._computeCO2eFromPower(power); if (power > 1000) { @@ -126,8 +123,8 @@ class TooltipTrackPowerImpl extends React.PureComponent { } maybeRenderForPreviewSelection( - previewSelection - ): React.ChildrenArray | null> | null { + previewSelection: PreviewSelection + ): React.ReactElement | null { if (!previewSelection.hasSelection) { return null; } @@ -159,13 +156,14 @@ class TooltipTrackPowerImpl extends React.PureComponent { selectionRange, 'TrackPower--tooltip-average-power-kilowatt', 'TrackPower--tooltip-average-power-watt', - 'TrackPower--tooltip-average-power-milliwatt' + 'TrackPower--tooltip-average-power-milliwatt', + 'TrackPower--tooltip-average-power-microwatt' )} ); } - render() { + override render() { const { counter, counterSampleIndex, @@ -209,8 +207,8 @@ class TooltipTrackPowerImpl extends React.PureComponent { } } -export const TooltipTrackPower = explicitConnect({ - mapStateToProps: (state) => ({ +export const TooltipTrackPower = explicitConnect({ + mapStateToProps: (state: State) => ({ interval: getProfileInterval(state), meta: getMeta(state), committedRange: getCommittedRange(state), From 782abcc68117522eaba5e7ad2715a09458c548e4 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:54:36 -0400 Subject: [PATCH 056/124] Convert more components. --- .../calltree/{CallTree.js => CallTree.tsx} | 79 +++++----- ...ptyReasons.js => CallTreeEmptyReasons.tsx} | 20 ++- ...allTreeView.js => ProfileCallTreeView.tsx} | 3 - .../flame-graph/{Canvas.js => Canvas.tsx} | 81 +++++----- .../{FlameGraph.js => FlameGraph.tsx} | 96 ++++++------ ...yReasons.js => FlameGraphEmptyReasons.tsx} | 20 ++- ...MaybeFlameGraph.js => MaybeFlameGraph.tsx} | 61 ++++---- .../flame-graph/{index.js => index.tsx} | 3 - .../js-tracer/{Canvas.js => Canvas.tsx} | 93 ++++++------ .../js-tracer/{Chart.js => Chart.tsx} | 82 +++++----- .../{EmptyReasons.js => EmptyReasons.tsx} | 20 ++- .../js-tracer/{Settings.js => Settings.tsx} | 26 ++-- .../js-tracer/{index.js => index.tsx} | 26 ++-- .../marker-chart/{Canvas.js => Canvas.tsx} | 99 +++++++------ ...Reasons.js => MarkerChartEmptyReasons.tsx} | 22 ++- .../marker-chart/{index.js => index.tsx} | 54 ++++--- ...Reasons.js => MarkerTableEmptyReasons.tsx} | 20 +-- .../marker-table/{index.js => index.tsx} | 103 +++++++------ ...easons.js => NetworkChartEmptyReasons.tsx} | 30 ++-- ...NetworkChartRow.js => NetworkChartRow.tsx} | 103 +++++++------ .../network-chart/{index.js => index.tsx} | 66 ++++----- ...CallTreeSidebar.js => CallTreeSidebar.tsx} | 90 +++++------ src/components/sidebar/CanSelectContent.js | 46 ------ src/components/sidebar/CanSelectContent.tsx | 43 ++++++ .../{MarkerSidebar.js => MarkerSidebar.tsx} | 18 +-- .../sidebar/{index.js => index.tsx} | 6 +- .../stack-chart/{Canvas.js => Canvas.tsx} | 140 ++++++++++-------- ...yReasons.js => StackChartEmptyReasons.tsx} | 20 ++- .../stack-chart/{index.js => index.tsx} | 80 +++++----- 29 files changed, 778 insertions(+), 772 deletions(-) rename src/components/calltree/{CallTree.js => CallTree.tsx} (87%) rename src/components/calltree/{CallTreeEmptyReasons.js => CallTreeEmptyReasons.tsx} (84%) rename src/components/calltree/{ProfileCallTreeView.js => ProfileCallTreeView.tsx} (94%) rename src/components/flame-graph/{Canvas.js => Canvas.tsx} (90%) rename src/components/flame-graph/{FlameGraph.js => FlameGraph.tsx} (87%) rename src/components/flame-graph/{FlameGraphEmptyReasons.js => FlameGraphEmptyReasons.tsx} (84%) rename src/components/flame-graph/{MaybeFlameGraph.js => MaybeFlameGraph.tsx} (64%) rename src/components/flame-graph/{index.js => index.tsx} (94%) rename src/components/js-tracer/{Canvas.js => Canvas.tsx} (93%) rename src/components/js-tracer/{Chart.js => Chart.tsx} (86%) rename src/components/js-tracer/{EmptyReasons.js => EmptyReasons.tsx} (70%) rename src/components/js-tracer/{Settings.js => Settings.tsx} (81%) rename src/components/js-tracer/{index.js => index.tsx} (82%) rename src/components/marker-chart/{Canvas.js => Canvas.tsx} (93%) rename src/components/marker-chart/{MarkerChartEmptyReasons.js => MarkerChartEmptyReasons.tsx} (73%) rename src/components/marker-chart/{index.js => index.tsx} (83%) rename src/components/marker-table/{MarkerTableEmptyReasons.js => MarkerTableEmptyReasons.tsx} (73%) rename src/components/marker-table/{index.js => index.tsx} (79%) rename src/components/network-chart/{NetworkChartEmptyReasons.js => NetworkChartEmptyReasons.tsx} (66%) rename src/components/network-chart/{NetworkChartRow.js => NetworkChartRow.tsx} (89%) rename src/components/network-chart/{index.js => index.tsx} (91%) rename src/components/sidebar/{CallTreeSidebar.js => CallTreeSidebar.tsx} (89%) delete mode 100644 src/components/sidebar/CanSelectContent.js create mode 100644 src/components/sidebar/CanSelectContent.tsx rename src/components/sidebar/{MarkerSidebar.js => MarkerSidebar.tsx} (86%) rename src/components/sidebar/{index.js => index.tsx} (89%) rename src/components/stack-chart/{Canvas.js => Canvas.tsx} (90%) rename src/components/stack-chart/{StackChartEmptyReasons.js => StackChartEmptyReasons.tsx} (84%) rename src/components/stack-chart/{index.js => index.tsx} (83%) diff --git a/src/components/calltree/CallTree.js b/src/components/calltree/CallTree.tsx similarity index 87% rename from src/components/calltree/CallTree.js rename to src/components/calltree/CallTree.tsx index 65bbf352b4..7187c44002 100644 --- a/src/components/calltree/CallTree.js +++ b/src/components/calltree/CallTree.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from 'firefox-profiler/utils/connect'; import { TreeView } from 'firefox-profiler/components/shared/TreeView'; @@ -56,36 +54,36 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './CallTree.css'; -type StateProps = {| - +threadsKey: ThreadsKey, - +scrollToSelectionGeneration: number, - +focusCallTreeGeneration: number, - +tree: CallTreeType, - +callNodeInfo: CallNodeInfo, - +categories: CategoryList, - +selectedCallNodeIndex: IndexIntoCallNodeTable | null, - +rightClickedCallNodeIndex: IndexIntoCallNodeTable | null, - +expandedCallNodeIndexes: Array, - +searchStringsRegExp: RegExp | null, - +disableOverscan: boolean, - +invertCallstack: boolean, - +implementationFilter: ImplementationFilter, - +callNodeMaxDepthPlusOne: number, - +weightType: WeightType, - +tableViewOptions: TableViewOptions, -|}; +type StateProps = { + readonly threadsKey: ThreadsKey; + readonly scrollToSelectionGeneration: number; + readonly focusCallTreeGeneration: number; + readonly tree: CallTreeType; + readonly callNodeInfo: CallNodeInfo; + readonly categories: CategoryList; + readonly selectedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly rightClickedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly expandedCallNodeIndexes: Array; + readonly searchStringsRegExp: RegExp | null; + readonly disableOverscan: boolean; + readonly invertCallstack: boolean; + readonly implementationFilter: ImplementationFilter; + readonly callNodeMaxDepthPlusOne: number; + readonly weightType: WeightType; + readonly tableViewOptions: TableViewOptions; +}; -type DispatchProps = {| - +changeSelectedCallNode: typeof changeSelectedCallNode, - +changeRightClickedCallNode: typeof changeRightClickedCallNode, - +changeExpandedCallNodes: typeof changeExpandedCallNodes, - +addTransformToStack: typeof addTransformToStack, - +handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut, - +updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen, - +onTableViewOptionsChange: (TableViewOptions) => any, -|}; +type DispatchProps = { + readonly changeSelectedCallNode: typeof changeSelectedCallNode; + readonly changeRightClickedCallNode: typeof changeRightClickedCallNode; + readonly changeExpandedCallNodes: typeof changeExpandedCallNodes; + readonly addTransformToStack: typeof addTransformToStack; + readonly handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut; + readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen; + readonly onTableViewOptionsChange: (param: TableViewOptions) => any; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class CallTreeImpl extends PureComponent { _mainColumn: Column = { @@ -97,7 +95,8 @@ class CallTreeImpl extends PureComponent { titleL10nId: '', }; _treeView: TreeView | null = null; - _takeTreeViewRef = (treeView) => (this._treeView = treeView); + _takeTreeViewRef = (treeView: TreeView | null) => + (this._treeView = treeView); /** * Call Trees can have different types of "weights" for the data. Choose the @@ -132,7 +131,7 @@ class CallTreeImpl extends PureComponent { { propName: 'icon', titleL10nId: '', - component: Icon, + component: Icon as any, initialWidth: 10, }, ]; @@ -162,7 +161,7 @@ class CallTreeImpl extends PureComponent { { propName: 'icon', titleL10nId: '', - component: Icon, + component: Icon as any, initialWidth: 10, }, ]; @@ -192,7 +191,7 @@ class CallTreeImpl extends PureComponent { { propName: 'icon', titleL10nId: '', - component: Icon, + component: Icon as any, initialWidth: 10, }, ]; @@ -204,7 +203,7 @@ class CallTreeImpl extends PureComponent { { cache: new Map() } ); - componentDidMount() { + override componentDidMount() { this.focus(); this.maybeProcureInterestingInitialSelection(); @@ -213,7 +212,7 @@ class CallTreeImpl extends PureComponent { } } - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: Props) { if ( this.props.focusCallTreeGeneration > prevProps.focusCallTreeGeneration ) { @@ -270,7 +269,7 @@ class CallTreeImpl extends PureComponent { ); }; - _onKeyDown = (event: SyntheticKeyboardEvent<>) => { + _onKeyDown = (event: React.KeyboardEvent) => { const { selectedCallNodeIndex, rightClickedCallNodeIndex, @@ -351,7 +350,7 @@ class CallTreeImpl extends PureComponent { } } - render() { + override render() { const { tree, selectedCallNodeIndex, @@ -396,7 +395,7 @@ class CallTreeImpl extends PureComponent { } } -export const CallTree = explicitConnect<{||}, StateProps, DispatchProps>({ +export const CallTree = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state: State) => ({ threadsKey: getSelectedThreadsKey(state), scrollToSelectionGeneration: getScrollToSelectionGeneration(state), diff --git a/src/components/calltree/CallTreeEmptyReasons.js b/src/components/calltree/CallTreeEmptyReasons.tsx similarity index 84% rename from src/components/calltree/CallTreeEmptyReasons.js rename to src/components/calltree/CallTreeEmptyReasons.tsx index 5516ec46fe..859225bf36 100644 --- a/src/components/calltree/CallTreeEmptyReasons.js +++ b/src/components/calltree/CallTreeEmptyReasons.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from '../shared/EmptyReasons'; import { selectedThreadSelectors } from '../../selectors/per-thread'; @@ -14,20 +12,20 @@ import explicitConnect, { import type { Thread, State } from 'firefox-profiler/types'; -type StateProps = {| - threadName: string, - rangeFilteredThread: Thread, - thread: Thread, -|}; +type StateProps = { + threadName: string; + rangeFilteredThread: Thread; + thread: Thread; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; /** * This component attempts to tell why exactly a call tree is empty with no samples * and display a friendly message to the end user. */ class CallTreeEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { thread, rangeFilteredThread, threadName } = this.props; let reason; @@ -52,7 +50,7 @@ class CallTreeEmptyReasonsImpl extends PureComponent { } } -export const CallTreeEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const CallTreeEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), thread: selectedThreadSelectors.getThread(state), diff --git a/src/components/calltree/ProfileCallTreeView.js b/src/components/calltree/ProfileCallTreeView.tsx similarity index 94% rename from src/components/calltree/ProfileCallTreeView.js rename to src/components/calltree/ProfileCallTreeView.tsx index 255289eefc..3f741d9e8d 100644 --- a/src/components/calltree/ProfileCallTreeView.js +++ b/src/components/calltree/ProfileCallTreeView.tsx @@ -2,9 +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/. */ -// @flow - -import React from 'react'; import { CallTree } from './CallTree'; import { StackSettings } from 'firefox-profiler/components/shared/StackSettings'; import { TransformNavigator } from 'firefox-profiler/components/shared/TransformNavigator'; diff --git a/src/components/flame-graph/Canvas.js b/src/components/flame-graph/Canvas.tsx similarity index 90% rename from src/components/flame-graph/Canvas.js rename to src/components/flame-graph/Canvas.tsx index 8bb280c57c..53ddeafc62 100644 --- a/src/components/flame-graph/Canvas.js +++ b/src/components/flame-graph/Canvas.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import memoize from 'memoize-immutable'; import { withChartViewport, type Viewport } from '../shared/chart/Viewport'; @@ -49,44 +47,43 @@ import type { CallTreeTimingsNonInverted, } from 'firefox-profiler/profile-logic/call-tree'; -export type OwnProps = {| - +thread: Thread, - +weightType: WeightType, - +innerWindowIDToPageMap: Map | null, - +unfilteredThread: Thread, - +ctssSampleIndexOffset: number, - +maxStackDepthPlusOne: number, - +flameGraphTiming: FlameGraphTiming, - +callNodeInfo: CallNodeInfo, - +callTree: CallTree, - +stackFrameHeight: CssPixels, - +selectedCallNodeIndex: IndexIntoCallNodeTable | null, - +rightClickedCallNodeIndex: IndexIntoCallNodeTable | null, - +onSelectionChange: (IndexIntoCallNodeTable | null) => void, - +onRightClick: (IndexIntoCallNodeTable | null) => void, - +onDoubleClick: (IndexIntoCallNodeTable | null) => void, - +shouldDisplayTooltips: () => boolean, - +scrollToSelectionGeneration: number, - +categories: CategoryList, - +interval: Milliseconds, - +isInverted: boolean, - +callTreeSummaryStrategy: CallTreeSummaryStrategy, - +ctssSamples: SamplesLikeTable, - +unfilteredCtssSamples: SamplesLikeTable, - +tracedTiming: CallTreeTimingsNonInverted | null, - +displayStackType: boolean, -|}; - -type Props = {| - ...OwnProps, +export type OwnProps = { + readonly thread: Thread; + readonly weightType: WeightType; + readonly innerWindowIDToPageMap: Map | null; + readonly unfilteredThread: Thread; + readonly ctssSampleIndexOffset: number; + readonly maxStackDepthPlusOne: number; + readonly flameGraphTiming: FlameGraphTiming; + readonly callNodeInfo: CallNodeInfo; + readonly callTree: CallTree; + readonly stackFrameHeight: CssPixels; + readonly selectedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly rightClickedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly onSelectionChange: (param: IndexIntoCallNodeTable | null) => void; + readonly onRightClick: (param: IndexIntoCallNodeTable | null) => void; + readonly onDoubleClick: (param: IndexIntoCallNodeTable | null) => void; + readonly shouldDisplayTooltips: () => boolean; + readonly scrollToSelectionGeneration: number; + readonly categories: CategoryList; + readonly interval: Milliseconds; + readonly isInverted: boolean; + readonly callTreeSummaryStrategy: CallTreeSummaryStrategy; + readonly ctssSamples: SamplesLikeTable; + readonly unfilteredCtssSamples: SamplesLikeTable; + readonly tracedTiming: CallTreeTimingsNonInverted | null; + readonly displayStackType: boolean; +}; + +type Props = OwnProps & { // Bring in the viewport props from the higher order Viewport component. - +viewport: Viewport, -|}; + readonly viewport: Viewport; +}; -type HoveredStackTiming = {| - +depth: FlameGraphDepth, - +flameGraphTimingIndex: IndexIntoFlameGraphTiming, -|}; +type HoveredStackTiming = { + readonly depth: FlameGraphDepth; + readonly flameGraphTimingIndex: IndexIntoFlameGraphTiming; +}; import './Canvas.css'; @@ -117,10 +114,10 @@ function snapValueToMultipleOf( } class FlameGraphCanvasImpl extends React.PureComponent { - _textMeasurement: null | TextMeasurement; + _textMeasurement: TextMeasurement | null = null; _textMeasurementCssToDeviceScale: number = 1; - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: Props) { // If the stack depth changes (say, when changing the time range // selection or applying a transform), move the viewport // vertically so that its offset from the base of the flame graph @@ -351,7 +348,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { _getHoveredStackInfo = ({ depth, flameGraphTimingIndex, - }: HoveredStackTiming): React.Node => { + }: HoveredStackTiming): React.ReactNode => { const { thread, unfilteredThread, @@ -499,7 +496,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { return null; }; - render() { + override render() { const { containerWidth, containerHeight, isDragging } = this.props.viewport; return ( diff --git a/src/components/flame-graph/FlameGraph.js b/src/components/flame-graph/FlameGraph.tsx similarity index 87% rename from src/components/flame-graph/FlameGraph.js rename to src/components/flame-graph/FlameGraph.tsx index cadbfa7d99..2fd764de6f 100644 --- a/src/components/flame-graph/FlameGraph.js +++ b/src/components/flame-graph/FlameGraph.tsx @@ -1,11 +1,9 @@ /* 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/. */ - -// @flow import * as React from 'react'; -import explicitConnect from '../../utils/connect'; +import { explicitConnectWithForwardRef } from '../../utils/connect'; import { FlameGraphCanvas } from './Canvas'; import { @@ -68,47 +66,54 @@ const STACK_FRAME_HEIGHT = 16; */ const SELECTABLE_THRESHOLD = 0.001; -type StateProps = {| - +thread: Thread, - +weightType: WeightType, - +innerWindowIDToPageMap: Map | null, - +unfilteredThread: Thread, - +ctssSampleIndexOffset: number, - +maxStackDepthPlusOne: number, - +timeRange: StartEndRange, - +previewSelection: PreviewSelection, - +flameGraphTiming: FlameGraphTiming, - +callTree: CallTree, - +callNodeInfo: CallNodeInfo, - +threadsKey: ThreadsKey, - +selectedCallNodeIndex: IndexIntoCallNodeTable | null, - +rightClickedCallNodeIndex: IndexIntoCallNodeTable | null, - +scrollToSelectionGeneration: number, - +categories: CategoryList, - +interval: Milliseconds, - +isInverted: boolean, - +callTreeSummaryStrategy: CallTreeSummaryStrategy, - +ctssSamples: SamplesLikeTable, - +unfilteredCtssSamples: SamplesLikeTable, - +tracedTiming: CallTreeTimings | null, - +displayStackType: boolean, -|}; -type DispatchProps = {| - +changeSelectedCallNode: typeof changeSelectedCallNode, - +changeRightClickedCallNode: typeof changeRightClickedCallNode, - +handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut, - +updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen, -|}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; - -class FlameGraphImpl extends React.PureComponent { +type StateProps = { + readonly thread: Thread; + readonly weightType: WeightType; + readonly innerWindowIDToPageMap: Map | null; + readonly unfilteredThread: Thread; + readonly ctssSampleIndexOffset: number; + readonly maxStackDepthPlusOne: number; + readonly timeRange: StartEndRange; + readonly previewSelection: PreviewSelection; + readonly flameGraphTiming: FlameGraphTiming; + readonly callTree: CallTree; + readonly callNodeInfo: CallNodeInfo; + readonly threadsKey: ThreadsKey; + readonly selectedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly rightClickedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly scrollToSelectionGeneration: number; + readonly categories: CategoryList; + readonly interval: Milliseconds; + readonly isInverted: boolean; + readonly callTreeSummaryStrategy: CallTreeSummaryStrategy; + readonly ctssSamples: SamplesLikeTable; + readonly unfilteredCtssSamples: SamplesLikeTable; + readonly tracedTiming: CallTreeTimings | null; + readonly displayStackType: boolean; +}; +type DispatchProps = { + readonly changeSelectedCallNode: typeof changeSelectedCallNode; + readonly changeRightClickedCallNode: typeof changeRightClickedCallNode; + readonly handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut; + readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen; +}; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; + +export interface FlameGraphHandle { + focus(): void; +} + +class FlameGraphImpl + extends React.PureComponent + implements FlameGraphHandle +{ _viewport: HTMLDivElement | null = null; - componentDidMount() { + override componentDidMount() { document.addEventListener('copy', this._onCopy, false); } - componentWillUnmount() { + override componentWillUnmount() { document.removeEventListener('copy', this._onCopy, false); } @@ -209,7 +214,7 @@ class FlameGraphImpl extends React.PureComponent { return callNodeIndex; }; - _handleKeyDown = (event: SyntheticKeyboardEvent) => { + _handleKeyDown = (event: React.KeyboardEvent) => { const { threadsKey, callTree, @@ -311,12 +316,12 @@ class FlameGraphImpl extends React.PureComponent { const funcName = thread.stringTable.getString( thread.funcTable.name[funcIndex] ); - event.clipboardData.setData('text/plain', funcName); + event.clipboardData!.setData('text/plain', funcName); } } }; - render() { + override render() { const { thread, unfilteredThread, @@ -424,7 +429,12 @@ function viewportNeedsUpdate() { return false; } -export const FlameGraph = explicitConnect<{||}, StateProps, DispatchProps>({ +export const FlameGraph = explicitConnectWithForwardRef< + {}, + StateProps, + DispatchProps, + FlameGraphHandle +>({ mapStateToProps: (state) => ({ thread: selectedThreadSelectors.getFilteredThread(state), unfilteredThread: selectedThreadSelectors.getThread(state), diff --git a/src/components/flame-graph/FlameGraphEmptyReasons.js b/src/components/flame-graph/FlameGraphEmptyReasons.tsx similarity index 84% rename from src/components/flame-graph/FlameGraphEmptyReasons.js rename to src/components/flame-graph/FlameGraphEmptyReasons.tsx index 11afe7673c..7b61a235fd 100644 --- a/src/components/flame-graph/FlameGraphEmptyReasons.js +++ b/src/components/flame-graph/FlameGraphEmptyReasons.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from '../shared/EmptyReasons'; import { selectedThreadSelectors } from '../../selectors/per-thread'; @@ -14,20 +12,20 @@ import explicitConnect, { import type { Thread, State } from 'firefox-profiler/types'; -type StateProps = {| - threadName: string, - rangeFilteredThread: Thread, - thread: Thread, -|}; +type StateProps = { + threadName: string; + rangeFilteredThread: Thread; + thread: Thread; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; /** * This component attempts to tell why exactly a flame graph is empty with no samples * and display a friendly message to the end user. */ class FlameGraphEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { thread, rangeFilteredThread, threadName } = this.props; let reason; @@ -52,7 +50,7 @@ class FlameGraphEmptyReasonsImpl extends PureComponent { } } -export const FlameGraphEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const FlameGraphEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), thread: selectedThreadSelectors.getThread(state), diff --git a/src/components/flame-graph/MaybeFlameGraph.js b/src/components/flame-graph/MaybeFlameGraph.tsx similarity index 64% rename from src/components/flame-graph/MaybeFlameGraph.js rename to src/components/flame-graph/MaybeFlameGraph.tsx index 189c0652fa..ef281aa4c5 100644 --- a/src/components/flame-graph/MaybeFlameGraph.js +++ b/src/components/flame-graph/MaybeFlameGraph.tsx @@ -1,16 +1,14 @@ /* 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/. */ - -// @flow import * as React from 'react'; -import explicitConnect from '../../utils/connect'; +import { explicitConnectWithForwardRef } from 'firefox-profiler/utils/connect'; import { getInvertCallstack } from '../../selectors/url-state'; import { selectedThreadSelectors } from '../../selectors/per-thread'; import { changeInvertCallstack } from '../../actions/profile-view'; import { FlameGraphEmptyReasons } from './FlameGraphEmptyReasons'; -import { FlameGraph } from './FlameGraph'; +import { FlameGraph, type FlameGraphHandle } from './FlameGraph'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; @@ -20,30 +18,30 @@ import './MaybeFlameGraph.css'; // is "flame-graph", `invertCallstack` will be `false`. is // only used in the "flame-graph" tab. -type StateProps = {| - +isPreviewSelectionEmpty: boolean, - +invertCallstack: boolean, -|}; -type DispatchProps = {| - +changeInvertCallstack: typeof changeInvertCallstack, -|}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type StateProps = { + readonly isPreviewSelectionEmpty: boolean; + readonly invertCallstack: boolean; +}; +type DispatchProps = { + readonly changeInvertCallstack: typeof changeInvertCallstack; +}; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class MaybeFlameGraphImpl extends React.PureComponent { - _flameGraph: {| current: HTMLDivElement | null |} = React.createRef(); + _flameGraph: React.RefObject = React.createRef(); _onSwitchToNormalCallstackClick = () => { this.props.changeInvertCallstack(false); }; - componentDidMount() { + override componentDidMount() { const flameGraph = this._flameGraph.current; if (flameGraph) { flameGraph.focus(); } } - render() { + override render() { const { isPreviewSelectionEmpty, invertCallstack } = this.props; if (isPreviewSelectionEmpty) { @@ -70,18 +68,21 @@ class MaybeFlameGraphImpl extends React.PureComponent { } } -export const MaybeFlameGraph = explicitConnect<{||}, StateProps, DispatchProps>( - { - mapStateToProps: (state) => { - return { - invertCallstack: getInvertCallstack(state), - isPreviewSelectionEmpty: - !selectedThreadSelectors.getHasPreviewFilteredCtssSamples(state), - }; - }, - mapDispatchToProps: { - changeInvertCallstack, - }, - component: MaybeFlameGraphImpl, - } -); +export const MaybeFlameGraph = explicitConnectWithForwardRef< + {}, + StateProps, + DispatchProps, + MaybeFlameGraphImpl +>({ + mapStateToProps: (state) => { + return { + invertCallstack: getInvertCallstack(state), + isPreviewSelectionEmpty: + !selectedThreadSelectors.getHasPreviewFilteredCtssSamples(state), + }; + }, + mapDispatchToProps: { + changeInvertCallstack, + }, + component: MaybeFlameGraphImpl, +}); diff --git a/src/components/flame-graph/index.js b/src/components/flame-graph/index.tsx similarity index 94% rename from src/components/flame-graph/index.js rename to src/components/flame-graph/index.tsx index 235165bf23..141147302c 100644 --- a/src/components/flame-graph/index.js +++ b/src/components/flame-graph/index.tsx @@ -2,9 +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/. */ -// @flow -import * as React from 'react'; - import { StackSettings } from '../shared/StackSettings'; import { TransformNavigator } from '../shared/TransformNavigator'; import { MaybeFlameGraph } from './MaybeFlameGraph'; diff --git a/src/components/js-tracer/Canvas.js b/src/components/js-tracer/Canvas.tsx similarity index 93% rename from src/components/js-tracer/Canvas.js rename to src/components/js-tracer/Canvas.tsx index 1f3c63f8ef..c748609d75 100644 --- a/src/components/js-tracer/Canvas.js +++ b/src/components/js-tracer/Canvas.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { GREY_20 } from 'photon-colors'; import * as React from 'react'; import classNames from 'classnames'; @@ -17,7 +15,7 @@ import { import { ChartCanvas } from 'firefox-profiler/components/shared/chart/Canvas'; import TextMeasurement from 'firefox-profiler/utils/text-measurement'; import { FastFillStyle } from 'firefox-profiler/utils'; -import { updatePreviewSelection } from 'firefox-profiler/actions/profile-view'; +import type { updatePreviewSelection } from 'firefox-profiler/actions/profile-view'; import { BLUE_40 } from 'firefox-profiler/utils/colors'; import type { @@ -38,54 +36,53 @@ import type { import type { WrapFunctionInDispatch } from 'firefox-profiler/utils/connect'; -type OwnProps = {| - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +jsTracerTimingRows: JsTracerTiming[], - +jsTracerTable: JsTracerTable, - +rowHeight: CssPixels, - +threadsKey: ThreadsKey, - +doFadeIn: boolean, - +updatePreviewSelection: WrapFunctionInDispatch< - typeof updatePreviewSelection, - >, -|}; - -type Props = {| - ...OwnProps, +type OwnProps = { + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly jsTracerTimingRows: JsTracerTiming[]; + readonly jsTracerTable: JsTracerTable; + readonly rowHeight: CssPixels; + readonly threadsKey: ThreadsKey; + readonly doFadeIn: boolean; + readonly updatePreviewSelection: WrapFunctionInDispatch< + typeof updatePreviewSelection + >; +}; + +type Props = OwnProps & { // Bring in the viewport props from the higher order Viewport component. - +viewport: Viewport, -|}; + readonly viewport: Viewport; +}; -type State = {| +type State = { // hoveredItem: null | number, - hasFirstDraw: boolean, -|}; + hasFirstDraw: boolean; +}; /** * Collect all of values that are dependent on the current rendering pass. * These values will be reset on every draw call. */ -type RenderPass = {| - +ctx: CanvasRenderingContext2D, - +textMeasurement: TextMeasurement, - +fastFillStyle: FastFillStyle, - +startRow: number, - +endRow: number, - +devicePixels: {| - +rowHeight: DevicePixels, - +containerWidth: DevicePixels, - +innerContainerWidth: DevicePixels, - +containerHeight: DevicePixels, - +viewportTop: DevicePixels, - +textOffsetStart: DevicePixels, - +textOffsetTop: DevicePixels, - +timelineMarginLeft: DevicePixels, - +timelineMarginRight: DevicePixels, - +oneCssPixel: DevicePixels, - +rowLabelOffsetLeft: DevicePixels, - |}, -|}; +type RenderPass = { + readonly ctx: CanvasRenderingContext2D; + readonly textMeasurement: TextMeasurement; + readonly fastFillStyle: FastFillStyle; + readonly startRow: number; + readonly endRow: number; + readonly devicePixels: { + readonly rowHeight: DevicePixels; + readonly containerWidth: DevicePixels; + readonly innerContainerWidth: DevicePixels; + readonly containerHeight: DevicePixels; + readonly viewportTop: DevicePixels; + readonly textOffsetStart: DevicePixels; + readonly textOffsetTop: DevicePixels; + readonly timelineMarginLeft: DevicePixels; + readonly timelineMarginRight: DevicePixels; + readonly oneCssPixel: DevicePixels; + readonly rowLabelOffsetLeft: DevicePixels; + }; +}; const TEXT_OFFSET_TOP: CssPixels = 11; const TEXT_OFFSET_START: CssPixels = 3; @@ -93,10 +90,10 @@ const ROW_LABEL_OFFSET_LEFT: CssPixels = 5; const FONT_SIZE: CssPixels = 10; class JsTracerCanvasImpl extends React.PureComponent { - state = { + override state = { hasFirstDraw: false, }; - _textMeasurement: null | TextMeasurement; + _textMeasurement: TextMeasurement | null = null; _textMeasurementCssToDeviceScale: number = 1; /** @@ -624,7 +621,9 @@ class JsTracerCanvasImpl extends React.PureComponent { * These methods were left, but commented out since the intent is to enable them * as follow-ups. */ - getHoveredItemInfo = (_hoveredItem: IndexIntoJsTracerEvents): React.Node => { + getHoveredItemInfo = ( + _hoveredItem: IndexIntoJsTracerEvents + ): React.ReactNode => { return null; // return ( // { // ); }; - render() { + override render() { const { containerWidth, containerHeight, isDragging } = this.props.viewport; return ( ; @@ -76,7 +74,7 @@ class JsTracerExpensiveChartImpl extends React.PureComponent { return JS_TRACER_MAXIMUM_CHART_ZOOM / (end - start); } - render() { + override render() { const { timeRange, threadsKey, @@ -121,8 +119,8 @@ class JsTracerExpensiveChartImpl extends React.PureComponent { // This function is given the JsTracerCanvas's chartProps. function viewportNeedsUpdate( - prevProps: { +jsTracerTimingRows: JsTracerTiming[] }, - newProps: { +jsTracerTimingRows: JsTracerTiming[] } + prevProps: { readonly jsTracerTimingRows: JsTracerTiming[] }, + newProps: { readonly jsTracerTimingRows: JsTracerTiming[] } ) { return prevProps.jsTracerTimingRows !== newProps.jsTracerTimingRows; } @@ -133,7 +131,7 @@ function viewportNeedsUpdate( const JsTracerExpensiveChart = explicitConnect< OwnProps, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state, ownProps) => ({ timeRange: getCommittedRange(state), @@ -151,16 +149,16 @@ const JsTracerExpensiveChart = explicitConnect< component: JsTracerExpensiveChartImpl, }); -type ChartLoaderProps = {| - +profile: Profile, - +jsTracerTable: JsTracerTable, - +showJsTracerSummary: boolean, - +keyString: string, -|}; +type ChartLoaderProps = { + readonly profile: Profile; + readonly jsTracerTable: JsTracerTable; + readonly showJsTracerSummary: boolean; + readonly keyString: string; +}; -type ChartLoaderState = {| - readyToRenderExpensiveChart: boolean, -|}; +type ChartLoaderState = { + readyToRenderExpensiveChart: boolean; +}; // Keep track of all the React keys seen for a component. If everything is correctly // memoized, then it should only be slow and expensive to compute the timing information @@ -176,9 +174,9 @@ const _seenChartKeysPerProfile: WeakMap> = new WeakMap(); */ class JsTracerChartLoader extends React.PureComponent< ChartLoaderProps, - ChartLoaderState, + ChartLoaderState > { - state = { + override state = { // The loader needs to be mounted before rendering the chart, as it has expensive // selectors. readyToRenderExpensiveChart: false, @@ -192,7 +190,7 @@ class JsTracerChartLoader extends React.PureComponent< // Look up the seenChartKeys per-profile. If not found, create a new Set. let seenChartKeys = _seenChartKeysPerProfile.get(props.profile); if (seenChartKeys === undefined) { - seenChartKeys = new Set(); + seenChartKeys = new Set(); _seenChartKeysPerProfile.set(props.profile, seenChartKeys); } @@ -204,7 +202,7 @@ class JsTracerChartLoader extends React.PureComponent< } } - componentDidMount() { + override componentDidMount() { if (this._doFadeIn) { // Let the screen render at least once, then start computing the expensive chart. requestAnimationFrame(() => { @@ -215,7 +213,7 @@ class JsTracerChartLoader extends React.PureComponent< } } - render() { + override render() { const { jsTracerTable, showJsTracerSummary } = this.props; return this.state.readyToRenderExpensiveChart || !this._doFadeIn ? ( { - render() { + override render() { const { profile, jsTracerTable, showJsTracerSummary, threadsKey } = this.props; const key = `${threadsKey}-${showJsTracerSummary ? 'true' : 'false'}`; diff --git a/src/components/js-tracer/EmptyReasons.js b/src/components/js-tracer/EmptyReasons.tsx similarity index 70% rename from src/components/js-tracer/EmptyReasons.js rename to src/components/js-tracer/EmptyReasons.tsx index 04a0b6af16..60b2e7e332 100644 --- a/src/components/js-tracer/EmptyReasons.js +++ b/src/components/js-tracer/EmptyReasons.tsx @@ -1,26 +1,24 @@ /* 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/. */ -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from 'firefox-profiler/components/shared/EmptyReasons'; import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread'; -import explicitConnect, { - type ConnectedProps, -} from 'firefox-profiler/utils/connect'; +import type { ConnectedProps } from 'firefox-profiler/utils/connect'; +import explicitConnect from 'firefox-profiler/utils/connect'; import type { State } from 'firefox-profiler/types'; -type StateProps = {| - +threadName: string, -|}; +type StateProps = { + readonly threadName: string; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class MarkerChartEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { threadName } = this.props; return ( @@ -33,7 +31,7 @@ class MarkerChartEmptyReasonsImpl extends PureComponent { } } -export const JsTracerEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const JsTracerEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), }), diff --git a/src/components/js-tracer/Settings.js b/src/components/js-tracer/Settings.tsx similarity index 81% rename from src/components/js-tracer/Settings.js rename to src/components/js-tracer/Settings.tsx index b3a06caae6..45c9f32d33 100644 --- a/src/components/js-tracer/Settings.js +++ b/src/components/js-tracer/Settings.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { changeShowJsTracerSummary } from 'firefox-profiler/actions/profile-view'; import { getShowJsTracerSummary } from 'firefox-profiler/selectors/url-state'; import explicitConnect, { @@ -14,22 +12,22 @@ import explicitConnect, { import './Settings.css'; import { Localized } from '@fluent/react'; -type StateProps = {| - +showJsTracerSummary: boolean, -|}; +type StateProps = { + readonly showJsTracerSummary: boolean; +}; -type DispatchProps = {| - +changeShowJsTracerSummary: typeof changeShowJsTracerSummary, -|}; +type DispatchProps = { + readonly changeShowJsTracerSummary: typeof changeShowJsTracerSummary; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class JsTracerSettingsImpl extends PureComponent { _onCheckboxChange = () => { this.props.changeShowJsTracerSummary(!this.props.showJsTracerSummary); }; - render() { + override render() { const { showJsTracerSummary } = this.props; return (
    @@ -56,11 +54,7 @@ class JsTracerSettingsImpl extends PureComponent { } } -export const JsTracerSettings = explicitConnect< - {||}, - StateProps, - DispatchProps, ->({ +export const JsTracerSettings = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ showJsTracerSummary: getShowJsTracerSummary(state), }), diff --git a/src/components/js-tracer/index.js b/src/components/js-tracer/index.tsx similarity index 82% rename from src/components/js-tracer/index.js rename to src/components/js-tracer/index.tsx index cc8049fa4e..c19808f5d4 100644 --- a/src/components/js-tracer/index.js +++ b/src/components/js-tracer/index.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import explicitConnect from 'firefox-profiler/utils/connect'; import { JsTracerChart } from './Chart'; @@ -26,21 +24,21 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './index.css'; -type DispatchProps = {| - +updatePreviewSelection: typeof updatePreviewSelection, -|}; +type DispatchProps = { + readonly updatePreviewSelection: typeof updatePreviewSelection; +}; -type StateProps = {| - +profile: Profile, - +threadsKey: ThreadsKey, - +jsTracerTable: JsTracerTable | null, - +showJsTracerSummary: boolean, -|}; +type StateProps = { + readonly profile: Profile; + readonly threadsKey: ThreadsKey; + readonly jsTracerTable: JsTracerTable | null; + readonly showJsTracerSummary: boolean; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class JsTracerImpl extends React.PureComponent { - render() { + override render() { const { profile, jsTracerTable, showJsTracerSummary, threadsKey } = this.props; return ( @@ -63,7 +61,7 @@ class JsTracerImpl extends React.PureComponent { } } -export const JsTracer = explicitConnect<{||}, StateProps, DispatchProps>({ +export const JsTracer = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => { return { profile: getProfile(state), diff --git a/src/components/marker-chart/Canvas.js b/src/components/marker-chart/Canvas.tsx similarity index 93% rename from src/components/marker-chart/Canvas.js rename to src/components/marker-chart/Canvas.tsx index 2205f6f9b5..b8d72b5349 100644 --- a/src/components/marker-chart/Canvas.js +++ b/src/components/marker-chart/Canvas.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { GREY_20, GREY_30, BLUE_60, BLUE_80 } from 'photon-colors'; import * as React from 'react'; import { @@ -14,12 +12,17 @@ import { TooltipMarker } from 'firefox-profiler/components/tooltip/Marker'; import TextMeasurement from 'firefox-profiler/utils/text-measurement'; import { bisectionRight } from 'firefox-profiler/utils/bisect'; import memoize from 'memoize-immutable'; -import { - typeof updatePreviewSelection as UpdatePreviewSelection, - typeof changeRightClickedMarker as ChangeRightClickedMarker, - typeof changeMouseTimePosition as ChangeMouseTimePosition, - typeof changeSelectedMarker as ChangeSelectedMarker, +import type { + updatePreviewSelection, + changeRightClickedMarker, + changeMouseTimePosition, + changeSelectedMarker, } from 'firefox-profiler/actions/profile-view'; + +type UpdatePreviewSelection = typeof updatePreviewSelection; +type ChangeRightClickedMarker = typeof changeRightClickedMarker; +type ChangeMouseTimePosition = typeof changeMouseTimePosition; +type ChangeSelectedMarker = typeof changeSelectedMarker; import { TIMELINE_MARGIN_LEFT } from 'firefox-profiler/app-logic/constants'; import type { Milliseconds, @@ -40,40 +43,39 @@ import type { import type { WrapFunctionInDispatch } from 'firefox-profiler/utils/connect'; -type MarkerDrawingInformation = {| - +x: CssPixels, - +y: CssPixels, - +w: CssPixels, - +h: CssPixels, - +isInstantMarker: boolean, - +markerIndex: MarkerIndex, -|}; - -type OwnProps = {| - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +markerTimingAndBuckets: MarkerTimingAndBuckets, - +rowHeight: CssPixels, - +getMarker: (MarkerIndex) => Marker, - +getMarkerLabel: (MarkerIndex) => string, - +markerListLength: number, - +threadsKey: ThreadsKey, - +updatePreviewSelection: WrapFunctionInDispatch, - +changeMouseTimePosition: ChangeMouseTimePosition, - +changeSelectedMarker: ChangeSelectedMarker, - +changeRightClickedMarker: ChangeRightClickedMarker, - +marginLeft: CssPixels, - +marginRight: CssPixels, - +selectedMarkerIndex: MarkerIndex | null, - +rightClickedMarkerIndex: MarkerIndex | null, - +shouldDisplayTooltips: () => boolean, -|}; - -type Props = {| - ...OwnProps, +type MarkerDrawingInformation = { + readonly x: CssPixels; + readonly y: CssPixels; + readonly w: CssPixels; + readonly h: CssPixels; + readonly isInstantMarker: boolean; + readonly markerIndex: MarkerIndex; +}; + +type OwnProps = { + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly markerTimingAndBuckets: MarkerTimingAndBuckets; + readonly rowHeight: CssPixels; + readonly getMarker: (param: MarkerIndex) => Marker; + readonly getMarkerLabel: (param: MarkerIndex) => string; + readonly markerListLength: number; + readonly threadsKey: ThreadsKey; + readonly updatePreviewSelection: WrapFunctionInDispatch; + readonly changeMouseTimePosition: ChangeMouseTimePosition; + readonly changeSelectedMarker: ChangeSelectedMarker; + readonly changeRightClickedMarker: ChangeRightClickedMarker; + readonly marginLeft: CssPixels; + readonly marginRight: CssPixels; + readonly selectedMarkerIndex: MarkerIndex | null; + readonly rightClickedMarkerIndex: MarkerIndex | null; + readonly shouldDisplayTooltips: () => boolean; +}; + +type Props = OwnProps & { // Bring in the viewport props from the higher order Viewport component. - +viewport: Viewport, -|}; + readonly viewport: Viewport; +}; const TEXT_OFFSET_TOP = 11; const TEXT_OFFSET_START = 3; @@ -82,7 +84,7 @@ const LABEL_PADDING = 5; const MARKER_BORDER_COLOR = '#2c77d1'; class MarkerChartCanvasImpl extends React.PureComponent { - _textMeasurement: null | TextMeasurement; + _textMeasurement: TextMeasurement | null = null; drawCanvas = ( ctx: CanvasRenderingContext2D, @@ -168,7 +170,7 @@ class MarkerChartCanvasImpl extends React.PureComponent { } }; - highlightRow = (ctx, row) => { + highlightRow = (ctx: CanvasRenderingContext2D, row: number) => { const { rowHeight, viewport: { viewportTop, containerWidth }, @@ -580,7 +582,12 @@ class MarkerChartCanvasImpl extends React.PureComponent { } // Draw the marker name. const { name } = markerTiming; - if (rowIndex > 0 && name === markerTimingAndBuckets[rowIndex - 1].name) { + const prevItem = markerTimingAndBuckets[rowIndex - 1]; + if ( + rowIndex > 0 && + typeof prevItem !== 'string' && + name === prevItem.name + ) { continue; } @@ -673,7 +680,7 @@ class MarkerChartCanvasImpl extends React.PureComponent { // This is a small utility function to define if some marker timing is in // our hit test range. - const isMarkerTimingInDotRadius = (index) => + const isMarkerTimingInDotRadius = (index: number) => markerTiming.start[index] < xInTime + dotRadiusInTime && markerTiming.end[index] > xInTime - dotRadiusInTime; @@ -785,7 +792,7 @@ class MarkerChartCanvasImpl extends React.PureComponent { changeRightClickedMarker(threadsKey, markerIndex); }; - getHoveredMarkerInfo = (markerIndex: MarkerIndex): React.Node => { + getHoveredMarkerInfo = (markerIndex: MarkerIndex): React.ReactNode => { if (!this.props.shouldDisplayTooltips() || markerIndex === null) { return null; } @@ -801,7 +808,7 @@ class MarkerChartCanvasImpl extends React.PureComponent { ); }; - render() { + override render() { const { containerWidth, containerHeight, isDragging } = this.props.viewport; return ( diff --git a/src/components/marker-chart/MarkerChartEmptyReasons.js b/src/components/marker-chart/MarkerChartEmptyReasons.tsx similarity index 73% rename from src/components/marker-chart/MarkerChartEmptyReasons.js rename to src/components/marker-chart/MarkerChartEmptyReasons.tsx index f32a4d6d6e..2ac8841766 100644 --- a/src/components/marker-chart/MarkerChartEmptyReasons.js +++ b/src/components/marker-chart/MarkerChartEmptyReasons.tsx @@ -1,27 +1,25 @@ /* 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/. */ -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from 'firefox-profiler/components/shared/EmptyReasons'; import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread'; -import explicitConnect, { - type ConnectedProps, -} from 'firefox-profiler/utils/connect'; +import type { ConnectedProps } from 'firefox-profiler/utils/connect'; +import explicitConnect from 'firefox-profiler/utils/connect'; import type { State } from 'firefox-profiler/types'; -type StateProps = {| - +threadName: string, - +isMarkerChartEmptyInFullRange: boolean, -|}; +type StateProps = { + readonly threadName: string; + readonly isMarkerChartEmptyInFullRange: boolean; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class MarkerChartEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { isMarkerChartEmptyInFullRange, threadName } = this.props; return ( @@ -38,7 +36,7 @@ class MarkerChartEmptyReasonsImpl extends PureComponent { } } -export const MarkerChartEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const MarkerChartEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), isMarkerChartEmptyInFullRange: diff --git a/src/components/marker-chart/index.js b/src/components/marker-chart/index.tsx similarity index 83% rename from src/components/marker-chart/index.js rename to src/components/marker-chart/index.tsx index 06a7ff52b2..d26b3bcd5d 100644 --- a/src/components/marker-chart/index.js +++ b/src/components/marker-chart/index.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import { TIMELINE_MARGIN_RIGHT, @@ -43,27 +41,27 @@ import './index.css'; const ROW_HEIGHT = 16; -type DispatchProps = {| - +updatePreviewSelection: typeof updatePreviewSelection, - +changeRightClickedMarker: typeof changeRightClickedMarker, - +changeMouseTimePosition: typeof changeMouseTimePosition, - +changeSelectedMarker: typeof changeSelectedMarker, -|}; - -type StateProps = {| - +getMarker: (MarkerIndex) => Marker, - +getMarkerLabel: (MarkerIndex) => string, - +markerTimingAndBuckets: MarkerTimingAndBuckets, - +maxMarkerRows: number, - +markerListLength: number, - +timeRange: StartEndRange, - +threadsKey: ThreadsKey, - +previewSelection: PreviewSelection, - +rightClickedMarkerIndex: MarkerIndex | null, - +selectedMarkerIndex: MarkerIndex | null, -|}; - -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type DispatchProps = { + readonly updatePreviewSelection: typeof updatePreviewSelection; + readonly changeRightClickedMarker: typeof changeRightClickedMarker; + readonly changeMouseTimePosition: typeof changeMouseTimePosition; + readonly changeSelectedMarker: typeof changeSelectedMarker; +}; + +type StateProps = { + readonly getMarker: (param: MarkerIndex) => Marker; + readonly getMarkerLabel: (param: MarkerIndex) => string; + readonly markerTimingAndBuckets: MarkerTimingAndBuckets; + readonly maxMarkerRows: number; + readonly markerListLength: number; + readonly timeRange: StartEndRange; + readonly threadsKey: ThreadsKey; + readonly previewSelection: PreviewSelection; + readonly rightClickedMarkerIndex: MarkerIndex | null; + readonly selectedMarkerIndex: MarkerIndex | null; +}; + +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class MarkerChartImpl extends React.PureComponent { _viewport: HTMLDivElement | null = null; @@ -95,11 +93,11 @@ class MarkerChartImpl extends React.PureComponent { } }; - componentDidMount() { + override componentDidMount() { this._focusViewport(); } - render() { + override render() { const { maxMarkerRows, markerListLength, @@ -180,13 +178,13 @@ class MarkerChartImpl extends React.PureComponent { // This function is given the MarkerChartCanvas's chartProps. function viewportNeedsUpdate( - prevProps: { +markerTimingAndBuckets: MarkerTimingAndBuckets }, - newProps: { +markerTimingAndBuckets: MarkerTimingAndBuckets } + prevProps: { readonly markerTimingAndBuckets: MarkerTimingAndBuckets }, + newProps: { readonly markerTimingAndBuckets: MarkerTimingAndBuckets } ) { return prevProps.markerTimingAndBuckets !== newProps.markerTimingAndBuckets; } -export const MarkerChart = explicitConnect<{||}, StateProps, DispatchProps>({ +export const MarkerChart = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => { const markerTimingAndBuckets = selectedThreadSelectors.getMarkerChartTimingAndBuckets(state); diff --git a/src/components/marker-table/MarkerTableEmptyReasons.js b/src/components/marker-table/MarkerTableEmptyReasons.tsx similarity index 73% rename from src/components/marker-table/MarkerTableEmptyReasons.js rename to src/components/marker-table/MarkerTableEmptyReasons.tsx index 85cb1e1b11..e7763ade4f 100644 --- a/src/components/marker-table/MarkerTableEmptyReasons.js +++ b/src/components/marker-table/MarkerTableEmptyReasons.tsx @@ -1,25 +1,25 @@ /* 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/. */ -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from '../shared/EmptyReasons'; import { selectedThreadSelectors } from '../../selectors/per-thread'; -import explicitConnect, { type ConnectedProps } from '../../utils/connect'; +import type { ConnectedProps } from '../../utils/connect'; +import explicitConnect from '../../utils/connect'; import type { State } from 'firefox-profiler/types'; -type StateProps = {| - +threadName: string, - +isMarkerTableEmptyInFullRange: boolean, -|}; +type StateProps = { + readonly threadName: string; + readonly isMarkerTableEmptyInFullRange: boolean; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class MarkerTableEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { isMarkerTableEmptyInFullRange, threadName } = this.props; return ( @@ -36,7 +36,7 @@ class MarkerTableEmptyReasonsImpl extends PureComponent { } } -export const MarkerTableEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const MarkerTableEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), isMarkerTableEmptyInFullRange: diff --git a/src/components/marker-table/index.js b/src/components/marker-table/index.tsx similarity index 79% rename from src/components/marker-table/index.js rename to src/components/marker-table/index.tsx index 046d4c1917..57728e3b64 100644 --- a/src/components/marker-table/index.js +++ b/src/components/marker-table/index.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from '../../utils/connect'; @@ -43,27 +41,27 @@ import type { ConnectedProps } from '../../utils/connect'; // Limit how many characters in the description get sent to the DOM. const MAX_DESCRIPTION_CHARACTERS = 500; -type MarkerDisplayData = {| - start: string, - duration: string | null, - name: string, - details: string, -|}; +type MarkerDisplayData = { + start: string; + duration: string | null; + name: string; + details: string; +}; class MarkerTree { - _getMarker: (MarkerIndex) => Marker; + _getMarker: (param: MarkerIndex) => Marker; _markerIndexes: MarkerIndex[]; _zeroAt: Milliseconds; _displayDataByIndex: Map; _markerSchemaByName: MarkerSchemaByName; - _getMarkerLabel: (MarkerIndex) => string; + _getMarkerLabel: (param: MarkerIndex) => string; constructor( - getMarker: (MarkerIndex) => Marker, + getMarker: (param: MarkerIndex) => Marker, markerIndexes: MarkerIndex[], zeroAt: Milliseconds, markerSchemaByName: MarkerSchemaByName, - getMarkerLabel: (MarkerIndex) => string + getMarkerLabel: (param: MarkerIndex) => string ) { this._getMarker = getMarker; this._markerIndexes = markerIndexes; @@ -129,30 +127,30 @@ class MarkerTree { } } -function _formatStart(start: number, zeroAt) { +function _formatStart(start: number, zeroAt: number) { return formatSeconds(start - zeroAt); } -type StateProps = {| - +threadsKey: ThreadsKey, - +getMarker: (MarkerIndex) => Marker, - +markerIndexes: MarkerIndex[], - +selectedMarker: MarkerIndex | null, - +rightClickedMarkerIndex: MarkerIndex | null, - +zeroAt: Milliseconds, - +scrollToSelectionGeneration: number, - +markerSchemaByName: MarkerSchemaByName, - +getMarkerLabel: (MarkerIndex) => string, - +tableViewOptions: TableViewOptions, -|}; - -type DispatchProps = {| - +changeSelectedMarker: typeof changeSelectedMarker, - +changeRightClickedMarker: typeof changeRightClickedMarker, - +onTableViewOptionsChange: (TableViewOptions) => any, -|}; - -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type StateProps = { + readonly threadsKey: ThreadsKey; + readonly getMarker: (param: MarkerIndex) => Marker; + readonly markerIndexes: MarkerIndex[]; + readonly selectedMarker: MarkerIndex | null; + readonly rightClickedMarkerIndex: MarkerIndex | null; + readonly zeroAt: Milliseconds; + readonly scrollToSelectionGeneration: number; + readonly markerSchemaByName: MarkerSchemaByName; + readonly getMarkerLabel: (param: MarkerIndex) => string; + readonly tableViewOptions: TableViewOptions; +}; + +type DispatchProps = { + readonly changeSelectedMarker: typeof changeSelectedMarker; + readonly changeRightClickedMarker: typeof changeRightClickedMarker; + readonly onTableViewOptionsChange: (param: TableViewOptions) => any; +}; + +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class MarkerTableImpl extends PureComponent { _fixedColumns = [ @@ -181,19 +179,36 @@ class MarkerTableImpl extends PureComponent { _mainColumn = { propName: 'details', titleL10nId: 'MarkerTable--details' }; _expandedNodeIds: Array = []; _onExpandedNodeIdsChange = () => {}; - _treeView: ?TreeView; - _takeTreeViewRef = (treeView) => (this._treeView = treeView); - - getMarkerTree = memoize((...args) => new MarkerTree(...args), { limit: 1 }); - - componentDidMount() { + _treeView: TreeView | null = null; + _takeTreeViewRef = (treeView: TreeView | null) => + (this._treeView = treeView); + + getMarkerTree = memoize( + ( + getMarker: any, + markerIndexes: any, + zeroAt: any, + markerSchemaByName: any, + getMarkerLabel: any + ) => + new MarkerTree( + getMarker, + markerIndexes, + zeroAt, + markerSchemaByName, + getMarkerLabel + ), + { limit: 1 } + ); + + override componentDidMount() { this.focus(); if (this._treeView) { this._treeView.scrollSelectionIntoView(); } } - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: Props) { if ( this.props.scrollToSelectionGeneration > prevProps.scrollToSelectionGeneration @@ -224,7 +239,7 @@ class MarkerTableImpl extends PureComponent { changeRightClickedMarker(threadsKey, selectedMarker); }; - render() { + override render() { const { getMarker, markerIndexes, @@ -254,7 +269,7 @@ class MarkerTableImpl extends PureComponent { ) : ( { } } -export const MarkerTable = explicitConnect<{||}, StateProps, DispatchProps>({ +export const MarkerTable = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ threadsKey: getSelectedThreadsKey(state), scrollToSelectionGeneration: getScrollToSelectionGeneration(state), diff --git a/src/components/network-chart/NetworkChartEmptyReasons.js b/src/components/network-chart/NetworkChartEmptyReasons.tsx similarity index 66% rename from src/components/network-chart/NetworkChartEmptyReasons.js rename to src/components/network-chart/NetworkChartEmptyReasons.tsx index 6502303d29..0ad5f5bf5e 100644 --- a/src/components/network-chart/NetworkChartEmptyReasons.js +++ b/src/components/network-chart/NetworkChartEmptyReasons.tsx @@ -1,26 +1,26 @@ /* 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/. */ -// @flow -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from '../shared/EmptyReasons'; import { selectedThreadSelectors } from '../../selectors/per-thread'; import { oneLine } from 'common-tags'; -import explicitConnect, { type ConnectedProps } from '../../utils/connect'; +import type { ConnectedProps } from '../../utils/connect'; +import explicitConnect from '../../utils/connect'; import type { State } from 'firefox-profiler/types'; -type StateProps = {| - +threadName: string, -|}; +type StateProps = { + readonly threadName: string; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class NetworkChartEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { threadName } = this.props; return ( @@ -40,11 +40,9 @@ class NetworkChartEmptyReasonsImpl extends PureComponent { } } -export const NetworkChartEmptyReasons = explicitConnect<{||}, StateProps, {||}>( - { - mapStateToProps: (state: State) => ({ - threadName: selectedThreadSelectors.getFriendlyThreadName(state), - }), - component: NetworkChartEmptyReasonsImpl, - } -); +export const NetworkChartEmptyReasons = explicitConnect<{}, StateProps, {}>({ + mapStateToProps: (state: State) => ({ + threadName: selectedThreadSelectors.getFriendlyThreadName(state), + }), + component: NetworkChartEmptyReasonsImpl, +}); diff --git a/src/components/network-chart/NetworkChartRow.js b/src/components/network-chart/NetworkChartRow.tsx similarity index 89% rename from src/components/network-chart/NetworkChartRow.js rename to src/components/network-chart/NetworkChartRow.tsx index a3598def11..92e2618ba9 100644 --- a/src/components/network-chart/NetworkChartRow.js +++ b/src/components/network-chart/NetworkChartRow.tsx @@ -1,9 +1,8 @@ /* 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/. */ -// @flow -import * as React from 'react'; +import React from 'react'; import classNames from 'classnames'; import { TooltipMarker } from '../tooltip/Marker'; @@ -33,7 +32,6 @@ import type { MarkerIndex, NetworkPayload, NetworkPhaseName, - MixedObject, } from 'firefox-profiler/types'; // This regexp is used to split a pathname into a directory path and a filename. @@ -79,19 +77,19 @@ const PHASE_NAMES_IN_ORDER: NetworkPhaseName[] = [ const PHASE_OPACITIES = PHASE_NAMES_IN_ORDER.reduce( (result, property, i, { length }) => { - result[property] = length > 1 ? i / (length - 1) : 0; + (result as any)[property] = length > 1 ? i / (length - 1) : 0; return result; }, - {} + {} as { [key: string]: number } ); -type NetworkPhaseProps = {| - +name: NetworkPhaseName, - +previousName: NetworkPhaseName, - +value: number | string, - +duration: Milliseconds, - +style: MixedObject, -|}; +type NetworkPhaseProps = { + readonly name: NetworkPhaseName; + readonly previousName: NetworkPhaseName; + readonly value: number | string; + readonly duration: Milliseconds; + readonly style: React.CSSProperties; +}; function NetworkPhase({ name, @@ -115,14 +113,14 @@ function NetworkPhase({ ); } -export type NetworkChartRowBarProps = {| - +marker: Marker, - +width: CssPixels, - +timeRange: StartEndRange, +export type NetworkChartRowBarProps = { + readonly marker: Marker; + readonly width: CssPixels; + readonly timeRange: StartEndRange; // Pass the payload in as well, since our types can't express a Marker with // a specific payload. - +networkPayload: NetworkPayload, -|}; + readonly networkPayload: NetworkPayload; +}; // This component splits a network marker duration in different phases, // and renders each phase as a differently colored bar. @@ -149,7 +147,7 @@ class NetworkChartRowBar extends React.PureComponent { * This returns the preconnect component, or null if there's no preconnect * operation for this marker. */ - _preconnectComponent(): React.Node { + _preconnectComponent(): React.ReactNode { const { networkPayload, marker } = this.props; const preconnectStart = networkPayload.domainLookupStart; @@ -187,7 +185,7 @@ class NetworkChartRowBar extends React.PureComponent { const preconnectPhase = { name: latestPreconnectEndProperty.phase, - previousName: 'domainLookupStart', + previousName: 'domainLookupStart' as const, value: preconnectEnd, duration: preconnectDuration, style: { @@ -207,7 +205,7 @@ class NetworkChartRowBar extends React.PureComponent { ); } - render() { + override render() { const { marker, networkPayload } = this.props; const start = marker.start; const end = ensureExists( @@ -235,9 +233,9 @@ class NetworkChartRowBar extends React.PureComponent { preconnectComponent ? PHASE_NAMES_IN_ORDER.slice(1) : PHASE_NAMES_IN_ORDER ); - const mainBarPhases = []; + const mainBarPhases: NetworkPhaseProps[] = []; let previousValue = start; - let previousName = 'startTime'; + let previousName: NetworkPhaseName = 'startTime'; // In this loop we add the various phases to the array. availablePhases.forEach(({ phase, value }, i) => { @@ -287,42 +285,42 @@ class NetworkChartRowBar extends React.PureComponent { } } -type NetworkChartRowProps = {| - +index: number, - +marker: Marker, - +markerIndex: MarkerIndex, +type NetworkChartRowProps = { + readonly index: number; + readonly marker: Marker; + readonly markerIndex: MarkerIndex; // Pass the payload in as well, since our types can't express a Marker with // a specific payload. - +networkPayload: NetworkPayload, - +timeRange: StartEndRange, - +width: CssPixels, - +threadsKey: ThreadsKey, - +isRightClicked: boolean, - +isSelected: boolean, - +isHoveredFromState: boolean, - +onLeftClick?: (MarkerIndex) => mixed, - +onRightClick?: (MarkerIndex) => mixed, - +onHover?: (MarkerIndex | null) => mixed, - +shouldDisplayTooltips: () => boolean, -|}; - -type State = {| - pageX: CssPixels, - pageY: CssPixels, - hovered: ?boolean, -|}; + readonly networkPayload: NetworkPayload; + readonly timeRange: StartEndRange; + readonly width: CssPixels; + readonly threadsKey: ThreadsKey; + readonly isRightClicked: boolean; + readonly isSelected: boolean; + readonly isHoveredFromState: boolean; + readonly onLeftClick?: (param: MarkerIndex) => void; + readonly onRightClick?: (param: MarkerIndex) => void; + readonly onHover?: (param: MarkerIndex | null) => void; + readonly shouldDisplayTooltips: () => boolean; +}; + +type State = { + pageX: CssPixels; + pageY: CssPixels; + hovered: boolean | null; +}; export class NetworkChartRow extends React.PureComponent< NetworkChartRowProps, - State, + State > { - state = { + override state = { pageX: 0, pageY: 0, hovered: false, }; - _hoverIn = (event: SyntheticMouseEvent<>) => { + _hoverIn = (event: React.MouseEvent) => { const pageX = event.pageX; const pageY = event.pageY; @@ -347,7 +345,7 @@ export class NetworkChartRow extends React.PureComponent< } }; - _onMouseDown = (e: SyntheticMouseEvent<>) => { + _onMouseDown = (e: React.MouseEvent) => { const { markerIndex, onLeftClick, onRightClick } = this.props; if (e.button === 0) { if (onLeftClick) { @@ -375,8 +373,7 @@ export class NetworkChartRow extends React.PureComponent< if (colonPos === null) { return ''; } - const url = name.slice(this._findIndexOfLoadid(name) + 2); - return url; + return name.slice(colonPos + 2); } _extractURI(url: string): URL | null { @@ -390,7 +387,7 @@ export class NetworkChartRow extends React.PureComponent< // Split markers.name in loadID and parts of URL to highlight domain // and filename, shorten the rest if needed. - _splitsURI(name: string): React.Node { + _splitsURI(name: string): React.ReactNode { // Extract URI from markers.name const uri = this._extractURI(name); if (uri !== null) { @@ -440,7 +437,7 @@ export class NetworkChartRow extends React.PureComponent< return getColorClassNameForMimeType(mimeType); } - render() { + override render() { const { index, markerIndex, diff --git a/src/components/network-chart/index.js b/src/components/network-chart/index.tsx similarity index 91% rename from src/components/network-chart/index.js rename to src/components/network-chart/index.tsx index 2d0b476cdc..3d61cd247d 100644 --- a/src/components/network-chart/index.js +++ b/src/components/network-chart/index.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { oneLine } from 'common-tags'; import * as React from 'react'; import memoize from 'memoize-immutable'; @@ -48,31 +46,29 @@ import './index.css'; const ROW_HEIGHT = 16; -type OwnProps = {||}; - -type DispatchProps = {| - +changeSelectedNetworkMarker: typeof changeSelectedNetworkMarker, - +changeRightClickedMarker: typeof changeRightClickedMarker, - +changeHoveredMarker: typeof changeHoveredMarker, - +changeMouseTimePosition: typeof changeMouseTimePosition, -|}; - -type StateProps = {| - +markerIndexes: MarkerIndex[], - +getMarker: (MarkerIndex) => Marker, - +selectedNetworkMarkerIndex: MarkerIndex | null, - +rightClickedMarkerIndex: MarkerIndex | null, - +hoveredMarkerIndexFromState: MarkerIndex | null, - +disableOverscan: boolean, - +timeRange: StartEndRange, - +threadsKey: ThreadsKey, - +scrollToSelectionGeneration: number, -|}; - -type Props = {| - ...SizeProps, - ...ConnectedProps, -|}; +type OwnProps = {}; + +// The SizeProps are injected by the WithSize higher order component. +type DispatchProps = { + readonly changeSelectedNetworkMarker: typeof changeSelectedNetworkMarker; + readonly changeRightClickedMarker: typeof changeRightClickedMarker; + readonly changeHoveredMarker: typeof changeHoveredMarker; + readonly changeMouseTimePosition: typeof changeMouseTimePosition; +}; + +type StateProps = { + readonly markerIndexes: MarkerIndex[]; + readonly getMarker: (param: MarkerIndex) => Marker; + readonly selectedNetworkMarkerIndex: MarkerIndex | null; + readonly rightClickedMarkerIndex: MarkerIndex | null; + readonly hoveredMarkerIndexFromState: MarkerIndex | null; + readonly disableOverscan: boolean; + readonly timeRange: StartEndRange; + readonly threadsKey: ThreadsKey; + readonly scrollToSelectionGeneration: number; +}; + +type Props = ConnectedProps & SizeProps; class NetworkChartImpl extends React.PureComponent { _virtualListRef = React.createRef>(); @@ -98,12 +94,12 @@ class NetworkChartImpl extends React.PureComponent { { limit: 1 } ); - componentDidMount() { + override componentDidMount() { this.focus(); this.scrollSelectionIntoView(); } - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: Props) { if ( this.props.scrollToSelectionGeneration > prevProps.scrollToSelectionGeneration @@ -146,7 +142,7 @@ class NetworkChartImpl extends React.PureComponent { // Not implemented. }; - _onKeyDown = (event: SyntheticKeyboardEvent<>) => { + _onKeyDown = (event: React.KeyboardEvent) => { const hasModifier = event.ctrlKey || event.altKey; const isNavigationKey = event.key.startsWith('Arrow') || @@ -265,7 +261,7 @@ class NetworkChartImpl extends React.PureComponent { changeHoveredMarker(threadsKey, null); }; - _onMouseMove = (event: SyntheticMouseEvent) => { + _onMouseMove = (event: React.MouseEvent) => { const { timeRange, width, changeMouseTimePosition } = this.props; // Calculate the mouse position relative to the chart area @@ -298,7 +294,7 @@ class NetworkChartImpl extends React.PureComponent { _shouldDisplayTooltips = () => this.props.rightClickedMarkerIndex === null; - _renderRow = (markerIndex: MarkerIndex, index: number): React.Node => { + _renderRow = (markerIndex: MarkerIndex, index: number): React.ReactNode => { const { threadsKey, getMarker, @@ -342,7 +338,7 @@ class NetworkChartImpl extends React.PureComponent { ); }; - render() { + override render() { const { selectedNetworkMarkerIndex, markerIndexes, @@ -382,7 +378,7 @@ class NetworkChartImpl extends React.PureComponent { ariaActiveDescendant={ selectedNetworkMarkerIndex !== null ? `networkChartRowItem-${selectedNetworkMarkerIndex}` - : null + : undefined } items={markerIndexes} renderItem={this._renderRow} @@ -412,7 +408,7 @@ class NetworkChartImpl extends React.PureComponent { export const NetworkChart = explicitConnect< OwnProps, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ markerIndexes: diff --git a/src/components/sidebar/CallTreeSidebar.js b/src/components/sidebar/CallTreeSidebar.tsx similarity index 89% rename from src/components/sidebar/CallTreeSidebar.js rename to src/components/sidebar/CallTreeSidebar.tsx index 68056db812..7092e761f1 100644 --- a/src/components/sidebar/CallTreeSidebar.js +++ b/src/components/sidebar/CallTreeSidebar.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import memoize from 'memoize-immutable'; import { Localized } from '@fluent/react'; @@ -45,13 +43,13 @@ import { } from 'firefox-profiler/utils/format-numbers'; import classNames from 'classnames'; -type SidebarDetailProps = {| - +label: React.Node, - +color?: string, - +indent?: boolean, - +value: React.Node, - +percentage?: string | number, -|}; +type SidebarDetailProps = { + readonly label: React.ReactNode; + readonly color?: string; + readonly indent?: boolean; + readonly value: React.ReactNode; + readonly percentage?: string | number; +}; function SidebarDetail({ label, @@ -75,36 +73,38 @@ function SidebarDetail({ ); } -type CategoryBreakdownOwnProps = {| +type CategoryBreakdownOwnProps = { /** for total or self breakdown */ - +kind: 'total' | 'self', - +breakdown: BreakdownByCategory, - +categoryList: CategoryList, - +number: (number) => string, -|}; + readonly kind: 'total' | 'self'; + readonly breakdown: BreakdownByCategory; + readonly categoryList: CategoryList; + readonly number: (num: number) => string; +}; -type CategoryBreakdownStateProps = {| - +sidebarOpenCategories: Map>, -|}; +type CategoryBreakdownStateProps = { + readonly sidebarOpenCategories: Map>; +}; -type CategoryBreakdownDispatchProps = {| - +toggleOpenCategoryInSidebar: typeof toggleOpenCategoryInSidebar, -|}; +type CategoryBreakdownDispatchProps = { + readonly toggleOpenCategoryInSidebar: typeof toggleOpenCategoryInSidebar; +}; type CategoryBreakdownAllProps = ConnectedProps< CategoryBreakdownOwnProps, CategoryBreakdownStateProps, - CategoryBreakdownDispatchProps, + CategoryBreakdownDispatchProps >; class CategoryBreakdownImpl extends React.PureComponent { - _toggleCategory = (event: SyntheticInputEvent<>) => { + _toggleCategory = (event: React.MouseEvent) => { const { toggleOpenCategoryInSidebar, kind } = this.props; - const { categoryIndex } = event.target.dataset; - toggleOpenCategoryInSidebar(kind, parseInt(categoryIndex, 10)); + const { categoryIndex } = (event.target as HTMLButtonElement).dataset; + if (categoryIndex) { + toggleOpenCategoryInSidebar(kind, parseInt(categoryIndex, 10)); + } }; - render() { + override render() { const { breakdown, categoryList, number, sidebarOpenCategories, kind } = this.props; @@ -201,7 +201,7 @@ class CategoryBreakdownImpl extends React.PureComponent({ mapStateToProps: (state) => { return { @@ -212,24 +212,24 @@ export const CategoryBreakdown = explicitConnect< component: CategoryBreakdownImpl, }); -type StateProps = {| - +selectedNodeIndex: IndexIntoCallNodeTable | null, - +selectedThreadsKey: ThreadsKey, - +name: string, - +lib: string, - +timings: TimingsForPath, - +categoryList: CategoryList, - +weightType: WeightType, - +selectedNodeTracedSelfAndTotal: SelfAndTotal | null, -|}; +type StateProps = { + readonly selectedNodeIndex: IndexIntoCallNodeTable | null; + readonly selectedThreadsKey: ThreadsKey; + readonly name: string; + readonly lib: string; + readonly timings: TimingsForPath; + readonly categoryList: CategoryList; + readonly weightType: WeightType; + readonly selectedNodeTracedSelfAndTotal: SelfAndTotal | null; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; -type WeightDetails = {| - +runningL10nId: string, - +selfL10nId: string, - +number: (n: number) => string, -|}; +type WeightDetails = { + readonly runningL10nId: string; + readonly selfL10nId: string; + readonly number: (n: number) => string; +}; function getRunningWeightTypeLabelL10nId(weightType: WeightType): string { switch (weightType) { @@ -286,7 +286,7 @@ class CallTreeSidebarImpl extends React.PureComponent { { cache: new Map() } ); - render() { + override render() { const { selectedNodeIndex, name, @@ -435,7 +435,7 @@ class CallTreeSidebarImpl extends React.PureComponent { } } -export const CallTreeSidebar = explicitConnect<{||}, StateProps, {||}>({ +export const CallTreeSidebar = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state) => ({ selectedNodeIndex: selectedThreadSelectors.getSelectedCallNodeIndex(state), selectedThreadsKey: getSelectedThreadsKey(state), diff --git a/src/components/sidebar/CanSelectContent.js b/src/components/sidebar/CanSelectContent.js deleted file mode 100644 index f006a8c43c..0000000000 --- a/src/components/sidebar/CanSelectContent.js +++ /dev/null @@ -1,46 +0,0 @@ -/* 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/. */ - -// @flow - -import * as React from 'react'; -import classNames from 'classnames'; - -type Props = {| - +tagName?: string, - +content: string, - +className?: string, -|}; - -export class CanSelectContent extends React.PureComponent { - _selectContent(e: SyntheticMouseEvent) { - const input = e.currentTarget; - input.focus(); - input.select(); - } - - _unselectContent(e: SyntheticMouseEvent) { - e.currentTarget.setSelectionRange(0, 0); - } - - render() { - const { tagName, content, className } = this.props; - const TagName = tagName || 'div'; - - return ( - - - - ); - } -} diff --git a/src/components/sidebar/CanSelectContent.tsx b/src/components/sidebar/CanSelectContent.tsx new file mode 100644 index 0000000000..c2b14aa970 --- /dev/null +++ b/src/components/sidebar/CanSelectContent.tsx @@ -0,0 +1,43 @@ +/* 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/. */ + +import * as React from 'react'; +import classNames from 'classnames'; + +type Props = { + readonly tagName?: string; + readonly content: string; + readonly className?: string; +}; + +export class CanSelectContent extends React.PureComponent { + _selectContent(e: React.FocusEvent) { + const input = e.currentTarget; + input.focus(); + input.select(); + } + + _unselectContent(e: React.FocusEvent) { + e.currentTarget.setSelectionRange(0, 0); + } + + override render() { + const { tagName, content, className } = this.props; + + return React.createElement( + tagName ?? 'div', + { + className: classNames(className, 'can-select-content'), + title: `${content}\n(click to select)`, + }, + + ); + } +} diff --git a/src/components/sidebar/MarkerSidebar.js b/src/components/sidebar/MarkerSidebar.tsx similarity index 86% rename from src/components/sidebar/MarkerSidebar.js rename to src/components/sidebar/MarkerSidebar.tsx index 3117c24952..ca661dab3c 100644 --- a/src/components/sidebar/MarkerSidebar.js +++ b/src/components/sidebar/MarkerSidebar.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import { Localized } from '@fluent/react'; @@ -15,16 +13,16 @@ import { TooltipMarker } from 'firefox-profiler/components/tooltip/Marker'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import type { ThreadsKey, Marker, MarkerIndex } from 'firefox-profiler/types'; -type StateProps = {| - +selectedThreadsKey: ThreadsKey, - +marker: Marker | null, - +markerIndex: MarkerIndex | null, -|}; +type StateProps = { + readonly selectedThreadsKey: ThreadsKey; + readonly marker: Marker | null; + readonly markerIndex: MarkerIndex | null; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class MarkerSidebarImpl extends React.PureComponent { - render() { + override render() { const { marker, markerIndex, selectedThreadsKey } = this.props; if (marker === null || markerIndex === null) { @@ -54,7 +52,7 @@ class MarkerSidebarImpl extends React.PureComponent { } } -export const MarkerSidebar = explicitConnect<{||}, StateProps, {||}>({ +export const MarkerSidebar = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state) => ({ marker: selectedThreadSelectors.getSelectedMarker(state), markerIndex: selectedThreadSelectors.getSelectedMarkerIndex(state), diff --git a/src/components/sidebar/index.js b/src/components/sidebar/index.tsx similarity index 89% rename from src/components/sidebar/index.js rename to src/components/sidebar/index.tsx index 7309373ce7..a8115372a3 100644 --- a/src/components/sidebar/index.js +++ b/src/components/sidebar/index.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import * as React from 'react'; +import type * as React from 'react'; import { CallTreeSidebar } from './CallTreeSidebar'; import { MarkerSidebar } from './MarkerSidebar'; @@ -14,7 +12,7 @@ import './sidebar.css'; export function selectSidebar( selectedTab: TabSlug -): React.ComponentType<{||}> | null { +): React.ComponentType<{}> | null { return { calltree: CallTreeSidebar, 'flame-graph': CallTreeSidebar, diff --git a/src/components/stack-chart/Canvas.js b/src/components/stack-chart/Canvas.tsx similarity index 90% rename from src/components/stack-chart/Canvas.js rename to src/components/stack-chart/Canvas.tsx index fa41a07260..2cf2433a11 100644 --- a/src/components/stack-chart/Canvas.js +++ b/src/components/stack-chart/Canvas.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import { GREY_30 } from 'photon-colors'; import * as React from 'react'; import { TIMELINE_MARGIN_RIGHT } from '../../app-logic/constants'; @@ -12,10 +10,12 @@ import { FastFillStyle } from '../../utils'; import TextMeasurement from '../../utils/text-measurement'; import { formatMilliseconds } from '../../utils/format-numbers'; import { bisectionLeft, bisectionRight } from '../../utils/bisect'; -import { +import type { updatePreviewSelection, - typeof changeMouseTimePosition as ChangeMouseTimePosition, + changeMouseTimePosition, } from '../../actions/profile-view'; + +type ChangeMouseTimePosition = typeof changeMouseTimePosition; import { mapCategoryColorNameToStackChartStyles } from '../../utils/colors'; import { TooltipCallNode } from '../tooltip/CallNode'; import { TooltipMarker } from '../tooltip/Marker'; @@ -51,43 +51,44 @@ import type { } from '../../profile-logic/stack-timing'; import type { WrapFunctionInDispatch } from '../../utils/connect'; -type OwnProps = {| - +thread: Thread, - +innerWindowIDToPageMap: Map | null, - +threadsKey: ThreadsKey, - +interval: Milliseconds, - +weightType: WeightType, - +rangeStart: Milliseconds, - +rangeEnd: Milliseconds, - +combinedTimingRows: CombinedTimingRows, - +sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap, - +stackFrameHeight: CssPixels, - +updatePreviewSelection: WrapFunctionInDispatch< - typeof updatePreviewSelection, - >, - +changeMouseTimePosition: ChangeMouseTimePosition, - +getMarker: (MarkerIndex) => Marker, - +categories: CategoryList, - +callNodeInfo: CallNodeInfo, - +selectedCallNodeIndex: IndexIntoCallNodeTable | null, - +onSelectionChange: (IndexIntoCallNodeTable | null) => void, - +onRightClick: (IndexIntoCallNodeTable | null) => void, - +shouldDisplayTooltips: () => boolean, - +scrollToSelectionGeneration: number, - +marginLeft: CssPixels, - +displayStackType: boolean, - +useStackChartSameWidths: boolean, -|}; - -type Props = $ReadOnly<{| - ...OwnProps, - +viewport: Viewport, -|}>; - -type HoveredStackTiming = {| - +depth: StackTimingDepth, - +stackTimingIndex: IndexIntoStackTiming, -|}; +type OwnProps = { + readonly thread: Thread; + readonly innerWindowIDToPageMap: Map | null; + readonly threadsKey: ThreadsKey; + readonly interval: Milliseconds; + readonly weightType: WeightType; + readonly rangeStart: Milliseconds; + readonly rangeEnd: Milliseconds; + readonly combinedTimingRows: CombinedTimingRows; + readonly sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap; + readonly stackFrameHeight: CssPixels; + readonly updatePreviewSelection: WrapFunctionInDispatch< + typeof updatePreviewSelection + >; + readonly changeMouseTimePosition: ChangeMouseTimePosition; + readonly getMarker: (param: MarkerIndex) => Marker; + readonly categories: CategoryList; + readonly callNodeInfo: CallNodeInfo; + readonly selectedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly onSelectionChange: (param: IndexIntoCallNodeTable | null) => void; + readonly onRightClick: (param: IndexIntoCallNodeTable | null) => void; + readonly shouldDisplayTooltips: () => boolean; + readonly scrollToSelectionGeneration: number; + readonly marginLeft: CssPixels; + readonly displayStackType: boolean; + readonly useStackChartSameWidths: boolean; +}; + +type Props = Readonly< + OwnProps & { + readonly viewport: Viewport; + } +>; + +type HoveredStackTiming = { + readonly depth: StackTimingDepth; + readonly stackTimingIndex: IndexIntoStackTiming; +}; import './Canvas.css'; @@ -98,7 +99,7 @@ const FONT_SIZE = 10; const BORDER_OPACITY = 0.4; class StackChartCanvasImpl extends React.PureComponent { - _textMeasurement: null | TextMeasurement; + _textMeasurement: TextMeasurement | null = null; _textMeasurementCssToDeviceScale: number = 1; // When the user checks the "use same widths for each stack" checkbox, some @@ -108,12 +109,12 @@ class StackChartCanvasImpl extends React.PureComponent { // The index at viewport start is the index of the first visible block inside // the viewport (the margins excluded). It's used for hit testing as the // start offset. - _sameWidthsIndexAtViewportStart: null | number; + _sameWidthsIndexAtViewportStart: number | null = null; // The range length is how many "blocks" are present in the viewport // (excluding the margins). - _sameWidthsRangeLength: null | number; + _sameWidthsRangeLength: number | null = null; - componentDidUpdate(prevProps) { + override componentDidUpdate(prevProps: Props) { // We want to scroll the selection into view when this component // is mounted, but using componentDidMount won't work here as the // viewport will not have completed setting its size by @@ -369,6 +370,7 @@ class StackChartCanvasImpl extends React.PureComponent { // Only draw boxes that overlap with the canvas. const isTimingBoxBeforeCanvas = useStackChartSameWidths && + 'sameWidthsEnd' in stackTiming && stackTiming.sameWidthsEnd && sameWidthsIndexAtCanvasStart !== null ? stackTiming.sameWidthsEnd[i] < sameWidthsIndexAtCanvasStart @@ -379,6 +381,7 @@ class StackChartCanvasImpl extends React.PureComponent { const isTimingBoxAfterCanvas = useStackChartSameWidths && + 'sameWidthsStart' in stackTiming && stackTiming.sameWidthsStart && sameWidthsIndexAtCanvasEnd !== null ? stackTiming.sameWidthsStart[i] > sameWidthsIndexAtCanvasEnd @@ -407,6 +410,8 @@ class StackChartCanvasImpl extends React.PureComponent { let floatW: DevicePixels; if ( useStackChartSameWidths && + 'sameWidthsStart' in stackTiming && + 'sameWidthsEnd' in stackTiming && stackTiming.sameWidthsStart && stackTiming.sameWidthsEnd && this._sameWidthsRangeLength !== null && @@ -473,7 +478,7 @@ class StackChartCanvasImpl extends React.PureComponent { // Look up information about this stack frame. let text, category, isSelected; - if (stackTiming.callNode) { + if ('callNode' in stackTiming && stackTiming.callNode) { const callNodeIndex = stackTiming.callNode[i]; const funcIndex = callNodeTable.func[callNodeIndex]; const funcNameIndex = thread.funcTable.name[funcIndex]; @@ -481,13 +486,18 @@ class StackChartCanvasImpl extends React.PureComponent { const categoryIndex = callNodeTable.category[callNodeIndex]; category = categories[categoryIndex]; isSelected = selectedCallNodeIndex === callNodeIndex; - } else { + } else if ('index' in stackTiming) { const markerIndex = stackTiming.index[i]; - const markerPayload = ((getMarker(markerIndex) - .data: any): UserTimingMarkerPayload); + const markerPayload = getMarker(markerIndex) + .data as UserTimingMarkerPayload; text = markerPayload.name; category = categories[categoryForUserTiming]; isSelected = selectedCallNodeIndex === markerIndex; + } else { + // Fallback case + text = 'Unknown'; + category = categories[0]; + isSelected = false; } const isHovered = @@ -560,7 +570,7 @@ class StackChartCanvasImpl extends React.PureComponent { _getHoveredStackInfo = ({ depth, stackTimingIndex, - }: HoveredStackTiming): React.Node | null => { + }: HoveredStackTiming): React.ReactNode | null => { const { thread, weightType, @@ -584,7 +594,7 @@ class StackChartCanvasImpl extends React.PureComponent { return null; } - if (timing.index) { + if ('index' in timing && timing.index) { const markerIndex = timing.index[stackTimingIndex]; return ( @@ -597,6 +607,9 @@ class StackChartCanvasImpl extends React.PureComponent { ); } + if (!('callNode' in timing) || !timing.callNode) { + return null; + } const callNodeIndex = timing.callNode[stackTimingIndex]; if (callNodeIndex === undefined) { return null; @@ -637,7 +650,7 @@ class StackChartCanvasImpl extends React.PureComponent { _getCallNodeIndexOrMarkerIndexFromHoveredItem( hoveredItem: HoveredStackTiming | null - ): {| index: number, type: 'marker' | 'call-node' |} | null { + ): { index: number; type: 'marker' | 'call-node' } | null { if (hoveredItem === null) { return null; } @@ -645,14 +658,18 @@ class StackChartCanvasImpl extends React.PureComponent { const { depth, stackTimingIndex } = hoveredItem; const { combinedTimingRows } = this.props; - if (combinedTimingRows[depth].callNode) { - const callNodeIndex = - combinedTimingRows[depth].callNode[stackTimingIndex]; + const timing = combinedTimingRows[depth]; + if ('callNode' in timing && timing.callNode) { + const callNodeIndex = timing.callNode[stackTimingIndex]; return { index: callNodeIndex, type: 'call-node' }; } - const index = combinedTimingRows[depth].index[stackTimingIndex]; - return { index, type: 'marker' }; + if ('index' in timing && timing.index) { + const index = timing.index[stackTimingIndex]; + return { index, type: 'marker' }; + } + + return null; } _onSelectItem = (hoveredItem: HoveredStackTiming | null) => { @@ -733,7 +750,12 @@ class StackChartCanvasImpl extends React.PureComponent { return null; } - if (!stackTiming.sameWidthsStart || !stackTiming.sameWidthsEnd) { + if ( + !('sameWidthsStart' in stackTiming) || + !('sameWidthsEnd' in stackTiming) || + !stackTiming.sameWidthsStart || + !stackTiming.sameWidthsEnd + ) { // Probably a user timing marker return this._hitTest(x, y); } @@ -797,7 +819,7 @@ class StackChartCanvasImpl extends React.PureComponent { this.props.changeMouseTimePosition(null); }; - render() { + override render() { const { containerWidth, containerHeight, isDragging } = this.props.viewport; const { useStackChartSameWidths } = this.props; diff --git a/src/components/stack-chart/StackChartEmptyReasons.js b/src/components/stack-chart/StackChartEmptyReasons.tsx similarity index 84% rename from src/components/stack-chart/StackChartEmptyReasons.js rename to src/components/stack-chart/StackChartEmptyReasons.tsx index 12dda10ca0..d08ff26ec9 100644 --- a/src/components/stack-chart/StackChartEmptyReasons.js +++ b/src/components/stack-chart/StackChartEmptyReasons.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { EmptyReasons } from '../shared/EmptyReasons'; import { selectedThreadSelectors } from '../../selectors/per-thread'; @@ -12,20 +10,20 @@ import explicitConnect, { type ConnectedProps } from '../../utils/connect'; import type { Thread, State } from 'firefox-profiler/types'; -type StateProps = {| - threadName: string, - rangeFilteredThread: Thread, - thread: Thread, -|}; +type StateProps = { + threadName: string; + rangeFilteredThread: Thread; + thread: Thread; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; /** * This component attempts to tell why exactly a stack chart is empty with no samples * and display a friendly message to the end user. */ class StackChartEmptyReasonsImpl extends PureComponent { - render() { + override render() { const { thread, rangeFilteredThread, threadName } = this.props; let reason; @@ -50,7 +48,7 @@ class StackChartEmptyReasonsImpl extends PureComponent { } } -export const StackChartEmptyReasons = explicitConnect<{||}, StateProps, {||}>({ +export const StackChartEmptyReasons = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state: State) => ({ threadName: selectedThreadSelectors.getFriendlyThreadName(state), thread: selectedThreadSelectors.getThread(state), diff --git a/src/components/stack-chart/index.js b/src/components/stack-chart/index.tsx similarity index 83% rename from src/components/stack-chart/index.js rename to src/components/stack-chart/index.tsx index 8bf7851033..5e45e7713b 100644 --- a/src/components/stack-chart/index.js +++ b/src/components/stack-chart/index.tsx @@ -1,8 +1,6 @@ /* 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/. */ - -// @flow import * as React from 'react'; import { TIMELINE_MARGIN_RIGHT, @@ -66,38 +64,38 @@ import './index.css'; const STACK_FRAME_HEIGHT = 16; -type StateProps = {| - +thread: Thread, - +weightType: WeightType, - +innerWindowIDToPageMap: Map | null, - +combinedTimingRows: CombinedTimingRows, - +sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap, - +timeRange: StartEndRange, - +interval: Milliseconds, - +previewSelection: PreviewSelection, - +threadsKey: ThreadsKey, - +callNodeInfo: CallNodeInfo, - +categories: CategoryList, - +selectedCallNodeIndex: IndexIntoCallNodeTable | null, - +rightClickedCallNodeIndex: IndexIntoCallNodeTable | null, - +scrollToSelectionGeneration: number, - +getMarker: (MarkerIndex) => Marker, - +userTimings: MarkerIndex[], - +displayStackType: boolean, - +hasFilteredCtssSamples: boolean, - +useStackChartSameWidths: boolean, -|}; +type StateProps = { + readonly thread: Thread; + readonly weightType: WeightType; + readonly innerWindowIDToPageMap: Map | null; + readonly combinedTimingRows: CombinedTimingRows; + readonly sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap; + readonly timeRange: StartEndRange; + readonly interval: Milliseconds; + readonly previewSelection: PreviewSelection; + readonly threadsKey: ThreadsKey; + readonly callNodeInfo: CallNodeInfo; + readonly categories: CategoryList; + readonly selectedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly rightClickedCallNodeIndex: IndexIntoCallNodeTable | null; + readonly scrollToSelectionGeneration: number; + readonly getMarker: (param: MarkerIndex) => Marker; + readonly userTimings: MarkerIndex[]; + readonly displayStackType: boolean; + readonly hasFilteredCtssSamples: boolean; + readonly useStackChartSameWidths: boolean; +}; -type DispatchProps = {| - +changeSelectedCallNode: typeof changeSelectedCallNode, - +changeRightClickedCallNode: typeof changeRightClickedCallNode, - +updatePreviewSelection: typeof updatePreviewSelection, - +handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut, - +updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen, - +changeMouseTimePosition: typeof changeMouseTimePosition, -|}; +type DispatchProps = { + readonly changeSelectedCallNode: typeof changeSelectedCallNode; + readonly changeRightClickedCallNode: typeof changeRightClickedCallNode; + readonly updatePreviewSelection: typeof updatePreviewSelection; + readonly handleCallNodeTransformShortcut: typeof handleCallNodeTransformShortcut; + readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen; + readonly changeMouseTimePosition: typeof changeMouseTimePosition; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class StackChartImpl extends React.PureComponent { _viewport: HTMLDivElement | null = null; @@ -146,7 +144,7 @@ class StackChartImpl extends React.PureComponent { } }; - _handleKeyDown = (event: SyntheticKeyboardEvent) => { + _handleKeyDown = (event: React.KeyboardEvent) => { const { threadsKey, thread, @@ -187,21 +185,23 @@ class StackChartImpl extends React.PureComponent { const funcName = thread.stringTable.getString( thread.funcTable.name[funcIndex] ); - event.clipboardData.setData('text/plain', funcName); + if (event.clipboardData) { + event.clipboardData.setData('text/plain', funcName); + } } } }; - componentDidMount() { + override componentDidMount() { document.addEventListener('copy', this._onCopy, false); this._focusViewport(); } - componentWillUnmount() { + override componentWillUnmount() { document.removeEventListener('copy', this._onCopy, false); } - render() { + override render() { const { thread, threadsKey, @@ -293,7 +293,7 @@ class StackChartImpl extends React.PureComponent { } } -export const StackChart = explicitConnect<{||}, StateProps, DispatchProps>({ +export const StackChart = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => { const showUserTimings = getShowUserTimings(state); const combinedTimingRows = showUserTimings @@ -340,8 +340,8 @@ export const StackChart = explicitConnect<{||}, StateProps, DispatchProps>({ // This function is given the StackChartCanvas's chartProps. function viewportNeedsUpdate( - prevProps: { +combinedTimingRows: CombinedTimingRows }, - newProps: { +combinedTimingRows: CombinedTimingRows } + prevProps: { readonly combinedTimingRows: CombinedTimingRows }, + newProps: { readonly combinedTimingRows: CombinedTimingRows } ) { return prevProps.combinedTimingRows !== newProps.combinedTimingRows; } From 98652470a0781d43860862ecdabde04d04c2e927 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 22:55:49 -0400 Subject: [PATCH 057/124] Convert hooks, context, and the remaining components. --- .../app/{AppHeader.js => AppHeader.tsx} | 10 +- ...rovider.js => AppLocalizationProvider.tsx} | 74 +++++----- .../{AppViewRouter.js => AppViewRouter.tsx} | 36 +++-- ...CodeFetcher.js => AssemblyCodeFetcher.tsx} | 46 +++---- ...Button.js => AssemblyViewToggleButton.tsx} | 23 ++-- ...loadManager.js => BeforeUnloadManager.tsx} | 20 ++- .../app/{BottomBox.js => BottomBox.tsx} | 41 +++--- ...deErrorOverlay.js => CodeErrorOverlay.tsx} | 11 +- ...adingOverlay.js => CodeLoadingOverlay.tsx} | 13 +- .../app/{CompareHome.js => CompareHome.tsx} | 32 ++--- ...rrentProfileUploadedInformationLoader.tsx} | 26 ++-- .../app/{DebugWarning.js => DebugWarning.tsx} | 16 +-- .../app/{Details.js => Details.tsx} | 30 ++-- ...tailsContainer.js => DetailsContainer.tsx} | 26 ++-- .../app/{DragAndDrop.js => DragAndDrop.tsx} | 82 ++++++----- .../{ErrorBoundary.js => ErrorBoundary.tsx} | 35 +++-- .../app/{FooterLinks.js => FooterLinks.tsx} | 11 +- src/components/app/{Home.js => Home.tsx} | 92 ++++++------- ...yboardShortcut.js => KeyboardShortcut.tsx} | 58 ++++---- ...nguageSwitcher.js => LanguageSwitcher.tsx} | 8 +- ...rofiles.js => ListOfPublishedProfiles.tsx} | 63 ++++----- .../MenuButtons/{MetaInfo.js => MetaInfo.tsx} | 39 +++--- ...atistics.js => MetaOverheadStatistics.tsx} | 22 ++- .../{Permalink.js => Permalink.tsx} | 24 ++-- .../MenuButtons/{Publish.js => Publish.tsx} | 122 ++++++++--------- .../app/MenuButtons/{index.js => index.tsx} | 63 ++++----- ...eleteButton.js => ProfileDeleteButton.tsx} | 54 ++++---- ...avigator.js => ProfileFilterNavigator.tsx} | 32 ++--- .../{ProfileLoader.js => ProfileLoader.tsx} | 36 +++-- ...nimation.js => ProfileLoaderAnimation.tsx} | 30 ++-- .../app/{ProfileName.js => ProfileName.tsx} | 45 +++--- ...eRootMessage.js => ProfileRootMessage.tsx} | 18 ++- .../{ProfileViewer.js => ProfileViewer.tsx} | 42 +++--- src/components/app/{Root.js => Root.tsx} | 7 +- ...kerManager.js => ServiceWorkerManager.tsx} | 49 ++++--- ...ceCodeFetcher.js => SourceCodeFetcher.tsx} | 77 +++++------ ...rlay.js => SymbolicationStatusOverlay.tsx} | 30 ++-- src/components/app/{TabBar.js => TabBar.tsx} | 22 ++- ...ingsHome.js => UploadedRecordingsHome.tsx} | 8 +- .../app/{UrlManager.js => UrlManager.tsx} | 60 ++++---- .../app/{WindowTitle.js => WindowTitle.tsx} | 28 ++-- .../{ZipFileViewer.js => ZipFileViewer.tsx} | 83 ++++++----- ...dIndicator.js => EmptyThreadIndicator.tsx} | 37 +++-- .../{FullTimeline.js => FullTimeline.tsx} | 75 +++++----- .../{GlobalTrack.js => GlobalTrack.tsx} | 88 ++++++------ .../{LocalTrack.js => LocalTrack.tsx} | 55 ++++---- .../timeline/{Markers.js => Markers.tsx} | 129 +++++++++--------- ...Indicator.js => OverflowEdgeIndicator.tsx} | 28 ++-- .../timeline/{Ruler.js => Ruler.tsx} | 34 +++-- .../timeline/{Selection.js => Selection.tsx} | 83 +++++------ .../{TrackBandwidth.js => TrackBandwidth.tsx} | 36 ++--- ...dwidthGraph.js => TrackBandwidthGraph.tsx} | 127 +++++++++-------- ...ackContextMenu.js => TrackContextMenu.tsx} | 124 +++++++++-------- ...kCustomMarker.js => TrackCustomMarker.tsx} | 36 ++--- ...kerGraph.js => TrackCustomMarkerGraph.tsx} | 102 +++++++------- ...TrackEventDelay.js => TrackEventDelay.tsx} | 24 ++-- ...DelayGraph.js => TrackEventDelayGraph.tsx} | 93 ++++++------- .../timeline/{TrackIPC.js => TrackIPC.tsx} | 36 ++--- .../{TrackMemory.js => TrackMemory.tsx} | 40 +++--- ...ackMemoryGraph.js => TrackMemoryGraph.tsx} | 103 +++++++------- .../{TrackNetwork.js => TrackNetwork.tsx} | 112 +++++++-------- .../{TrackPower.js => TrackPower.tsx} | 38 +++--- ...TrackPowerGraph.js => TrackPowerGraph.tsx} | 107 +++++++-------- ...TrackProcessCPU.js => TrackProcessCPU.tsx} | 40 +++--- ...ssCPUGraph.js => TrackProcessCPUGraph.tsx} | 97 +++++++------ ...ackScreenshots.js => TrackScreenshots.tsx} | 129 +++++++++--------- .../{TrackThread.js => TrackThread.tsx} | 97 +++++++------ ...ualProgress.js => TrackVisualProgress.tsx} | 40 +++--- ...sGraph.js => TrackVisualProgressGraph.tsx} | 81 ++++++----- ...alIndicators.js => VerticalIndicators.tsx} | 36 ++--- .../timeline/{index.js => index.tsx} | 16 +-- .../{L10nContext.js => L10nContext.ts} | 7 +- src/hooks/{useL10n.js => useL10n.ts} | 1 - src/test/components/ErrorBoundary.test.js | 2 +- 74 files changed, 1793 insertions(+), 1904 deletions(-) rename src/components/app/{AppHeader.js => AppHeader.tsx} (94%) rename src/components/app/{AppLocalizationProvider.js => AppLocalizationProvider.tsx} (86%) rename src/components/app/{AppViewRouter.js => AppViewRouter.tsx} (87%) rename src/components/app/{AssemblyCodeFetcher.js => AssemblyCodeFetcher.tsx} (79%) rename src/components/app/{AssemblyViewToggleButton.js => AssemblyViewToggleButton.tsx} (87%) rename src/components/app/{BeforeUnloadManager.js => BeforeUnloadManager.tsx} (82%) rename src/components/app/{BottomBox.js => BottomBox.tsx} (91%) rename src/components/app/{CodeErrorOverlay.js => CodeErrorOverlay.tsx} (96%) rename src/components/app/{CodeLoadingOverlay.js => CodeLoadingOverlay.tsx} (82%) rename src/components/app/{CompareHome.js => CompareHome.tsx} (83%) rename src/components/app/{CurrentProfileUploadedInformationLoader.js => CurrentProfileUploadedInformationLoader.tsx} (87%) rename src/components/app/{DebugWarning.js => DebugWarning.tsx} (82%) rename src/components/app/{Details.js => Details.tsx} (90%) rename src/components/app/{DetailsContainer.js => DetailsContainer.tsx} (80%) rename src/components/app/{DragAndDrop.js => DragAndDrop.tsx} (83%) rename src/components/app/{ErrorBoundary.js => ErrorBoundary.tsx} (90%) rename src/components/app/{FooterLinks.js => FooterLinks.tsx} (90%) rename src/components/app/{Home.js => Home.tsx} (92%) rename src/components/app/{KeyboardShortcut.js => KeyboardShortcut.tsx} (88%) rename src/components/app/{LanguageSwitcher.js => LanguageSwitcher.tsx} (87%) rename src/components/app/{ListOfPublishedProfiles.js => ListOfPublishedProfiles.tsx} (88%) rename src/components/app/MenuButtons/{MetaInfo.js => MetaInfo.tsx} (96%) rename src/components/app/MenuButtons/{MetaOverheadStatistics.js => MetaOverheadStatistics.tsx} (94%) rename src/components/app/MenuButtons/{Permalink.js => Permalink.tsx} (92%) rename src/components/app/MenuButtons/{Publish.js => Publish.tsx} (87%) rename src/components/app/MenuButtons/{index.js => index.tsx} (91%) rename src/components/app/{ProfileDeleteButton.js => ProfileDeleteButton.tsx} (88%) rename src/components/app/{ProfileFilterNavigator.js => ProfileFilterNavigator.tsx} (91%) rename src/components/app/{ProfileLoader.js => ProfileLoader.tsx} (80%) rename src/components/app/{ProfileLoaderAnimation.js => ProfileLoaderAnimation.tsx} (86%) rename src/components/app/{ProfileName.js => ProfileName.tsx} (84%) rename src/components/app/{ProfileRootMessage.js => ProfileRootMessage.tsx} (90%) rename src/components/app/{ProfileViewer.js => ProfileViewer.tsx} (88%) rename src/components/app/{Root.js => Root.tsx} (95%) rename src/components/app/{ServiceWorkerManager.js => ServiceWorkerManager.tsx} (95%) rename src/components/app/{SourceCodeFetcher.js => SourceCodeFetcher.tsx} (65%) rename src/components/app/{SymbolicationStatusOverlay.js => SymbolicationStatusOverlay.tsx} (79%) rename src/components/app/{TabBar.js => TabBar.tsx} (84%) rename src/components/app/{UploadedRecordingsHome.js => UploadedRecordingsHome.tsx} (86%) rename src/components/app/{UrlManager.js => UrlManager.tsx} (89%) rename src/components/app/{WindowTitle.js => WindowTitle.tsx} (89%) rename src/components/app/{ZipFileViewer.js => ZipFileViewer.tsx} (85%) rename src/components/timeline/{EmptyThreadIndicator.js => EmptyThreadIndicator.tsx} (87%) rename src/components/timeline/{FullTimeline.js => FullTimeline.tsx} (85%) rename src/components/timeline/{GlobalTrack.js => GlobalTrack.tsx} (85%) rename src/components/timeline/{LocalTrack.js => LocalTrack.tsx} (88%) rename src/components/timeline/{Markers.js => Markers.tsx} (88%) rename src/components/timeline/{OverflowEdgeIndicator.js => OverflowEdgeIndicator.tsx} (89%) rename src/components/timeline/{Ruler.js => Ruler.tsx} (80%) rename src/components/timeline/{Selection.js => Selection.tsx} (90%) rename src/components/timeline/{TrackBandwidth.js => TrackBandwidth.tsx} (79%) rename src/components/timeline/{TrackBandwidthGraph.js => TrackBandwidthGraph.tsx} (90%) rename src/components/timeline/{TrackContextMenu.js => TrackContextMenu.tsx} (93%) rename src/components/timeline/{TrackCustomMarker.js => TrackCustomMarker.tsx} (77%) rename src/components/timeline/{TrackCustomMarkerGraph.js => TrackCustomMarkerGraph.tsx} (91%) rename src/components/timeline/{TrackEventDelay.js => TrackEventDelay.tsx} (77%) rename src/components/timeline/{TrackEventDelayGraph.js => TrackEventDelayGraph.tsx} (89%) rename src/components/timeline/{TrackIPC.js => TrackIPC.tsx} (80%) rename src/components/timeline/{TrackMemory.js => TrackMemory.tsx} (80%) rename src/components/timeline/{TrackMemoryGraph.js => TrackMemoryGraph.tsx} (91%) rename src/components/timeline/{TrackNetwork.js => TrackNetwork.tsx} (87%) rename src/components/timeline/{TrackPower.js => TrackPower.tsx} (78%) rename src/components/timeline/{TrackPowerGraph.js => TrackPowerGraph.tsx} (91%) rename src/components/timeline/{TrackProcessCPU.js => TrackProcessCPU.tsx} (78%) rename src/components/timeline/{TrackProcessCPUGraph.js => TrackProcessCPUGraph.tsx} (90%) rename src/components/timeline/{TrackScreenshots.js => TrackScreenshots.tsx} (80%) rename src/components/timeline/{TrackThread.js => TrackThread.tsx} (86%) rename src/components/timeline/{TrackVisualProgress.js => TrackVisualProgress.tsx} (80%) rename src/components/timeline/{TrackVisualProgressGraph.js => TrackVisualProgressGraph.tsx} (88%) rename src/components/timeline/{VerticalIndicators.js => VerticalIndicators.tsx} (84%) rename src/components/timeline/{index.js => index.tsx} (91%) rename src/contexts/{L10nContext.js => L10nContext.ts} (74%) rename src/hooks/{useL10n.js => useL10n.ts} (98%) diff --git a/src/components/app/AppHeader.js b/src/components/app/AppHeader.tsx similarity index 94% rename from src/components/app/AppHeader.js rename to src/components/app/AppHeader.tsx index 02b4eb5a92..1e39e76b0b 100644 --- a/src/components/app/AppHeader.js +++ b/src/components/app/AppHeader.tsx @@ -1,8 +1,6 @@ /* 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/. */ -// @flow - /* * This file implements a header to be used on top of our content pages. It * renders a title as well as links to our github and our home page. @@ -15,8 +13,8 @@ import { InnerNavigationLink } from 'firefox-profiler/components/shared/InnerNav import './AppHeader.css'; import { Localized } from '@fluent/react'; -export class AppHeader extends React.PureComponent<{||}> { - render() { +export class AppHeader extends React.PureComponent<{}> { + override render() { return (

    @@ -28,7 +26,9 @@ export class AppHeader extends React.PureComponent<{||}> { + > + <> + ), subheader: , }} diff --git a/src/components/app/AppLocalizationProvider.js b/src/components/app/AppLocalizationProvider.tsx similarity index 86% rename from src/components/app/AppLocalizationProvider.js rename to src/components/app/AppLocalizationProvider.tsx index e932544bab..1584be60e4 100644 --- a/src/components/app/AppLocalizationProvider.js +++ b/src/components/app/AppLocalizationProvider.tsx @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import { LocalizationProvider, ReactLocalization } from '@fluent/react'; import { negotiateLanguages } from '@fluent/langneg'; @@ -16,19 +15,19 @@ import { } from 'firefox-profiler/app-logic/l10n'; import { ensureExists } from 'firefox-profiler/utils/flow'; -import type { Localization, PseudoStrategy } from 'firefox-profiler/types'; +import type { PseudoStrategy } from 'firefox-profiler/types'; import { L10nContext } from 'firefox-profiler/contexts/L10nContext'; import type { L10nContextType } from 'firefox-profiler/contexts/L10nContext'; -type FetchProps = {| - +requestedLocales: null | string[], - +pseudoStrategy: PseudoStrategy, - +receiveL10n: ( - localization: Localization, +type FetchProps = { + readonly requestedLocales: null | string[]; + readonly pseudoStrategy: PseudoStrategy; + readonly receiveL10n: ( + localization: ReactLocalization, primaryLocale: string, direction: 'ltr' | 'rtl' - ) => void, -|}; + ) => void; +}; /** * This class is responsible for handling the changes of the requested locales @@ -80,35 +79,35 @@ class AppLocalizationFetcher extends React.PureComponent { receiveL10n(localization, primaryLocale, direction); } - componentDidMount() { + override componentDidMount() { this._setupLocalization(); } - componentDidUpdate() { + override componentDidUpdate() { this._setupLocalization(); } - render() { + override render() { return null; } } -type InitProps = {| - +requestL10n: (locales: string[]) => void, - +requestedLocales: null | string[], -|}; +type InitProps = { + readonly requestL10n: (locales: string[]) => void; + readonly requestedLocales: null | string[]; +}; /** * This component is responsible for initializing the locales as well as * persisting the current locale to localStorage. */ class AppLocalizationInit extends React.PureComponent { - componentDidMount() { + override componentDidMount() { const { requestL10n } = this.props; - requestL10n(this._getPersistedLocale() ?? navigator.languages); + requestL10n(this._getPersistedLocale() ?? Array.from(navigator.languages)); } - componentDidUpdate() { + override componentDidUpdate() { this._persistCurrentLocale(); } @@ -167,22 +166,22 @@ class AppLocalizationInit extends React.PureComponent { } } - render() { + override render() { return null; } } -type L10nState = {| - +requestedLocales: null | string[], - +pseudoStrategy: PseudoStrategy, - +localization: Localization, - +primaryLocale: string | null, - +direction: 'ltr' | 'rtl', -|}; +type L10nState = { + readonly requestedLocales: null | string[]; + readonly pseudoStrategy: PseudoStrategy; + readonly localization: ReactLocalization; + readonly primaryLocale: string | null; + readonly direction: 'ltr' | 'rtl'; +}; -type ProviderProps = {| - children: React.Node, -|}; +type ProviderProps = { + children: React.ReactNode; +}; // Global reference to the AppLocalizationProvider instance for console access let globalL10nProvider: AppLocalizationProvider | null = null; @@ -195,9 +194,9 @@ let globalL10nProvider: AppLocalizationProvider | null = null; */ export class AppLocalizationProvider extends React.PureComponent< ProviderProps, - L10nState, + L10nState > { - state: L10nState = { + override state: L10nState = { requestedLocales: null, pseudoStrategy: null, localization: new ReactLocalization([]), @@ -205,18 +204,19 @@ export class AppLocalizationProvider extends React.PureComponent< direction: 'ltr', }; - componentDidMount() { + override componentDidMount() { this._updateLocalizationDocumentAttribute(); + // eslint-disable-next-line @typescript-eslint/no-this-alias globalL10nProvider = this; } - componentWillUnmount() { + override componentWillUnmount() { if (globalL10nProvider === this) { globalL10nProvider = null; } } - componentDidUpdate() { + override componentDidUpdate() { this._updateLocalizationDocumentAttribute(); } @@ -236,7 +236,7 @@ export class AppLocalizationProvider extends React.PureComponent< }; _receiveL10n = ( - localization: Localization, + localization: ReactLocalization, primaryLocale: string, direction: 'ltr' | 'rtl' ) => { @@ -249,7 +249,7 @@ export class AppLocalizationProvider extends React.PureComponent< this.setState({ pseudoStrategy }); }; - render() { + override render() { const { primaryLocale, localization, requestedLocales, pseudoStrategy } = this.state; const { children } = this.props; diff --git a/src/components/app/AppViewRouter.js b/src/components/app/AppViewRouter.tsx similarity index 87% rename from src/components/app/AppViewRouter.js rename to src/components/app/AppViewRouter.tsx index 0951d64e0b..2e95a8e893 100644 --- a/src/components/app/AppViewRouter.js +++ b/src/components/app/AppViewRouter.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import explicitConnect from 'firefox-profiler/utils/connect'; import { ProfileViewer } from './ProfileViewer'; @@ -27,7 +25,7 @@ import type { AppViewState, State, DataSource } from 'firefox-profiler/types'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import { Localized } from '@fluent/react'; -const ERROR_MESSAGES_L10N_ID: { [string]: string } = Object.freeze({ +const ERROR_MESSAGES_L10N_ID: { [key: string]: string } = Object.freeze({ 'from-browser': 'AppViewRouter--error-unpublished', 'from-post-message': 'AppViewRouter--error-from-post-message', unpublished: 'AppViewRouter--error-unpublished', @@ -38,17 +36,17 @@ const ERROR_MESSAGES_L10N_ID: { [string]: string } = Object.freeze({ compare: 'AppViewRouter--error-compare', }); -type AppViewRouterStateProps = {| - +view: AppViewState, - +dataSource: DataSource, - +profilesToCompare: string[] | null, - +hasZipFile: boolean, -|}; +type AppViewRouterStateProps = { + readonly view: AppViewState; + readonly dataSource: DataSource; + readonly profilesToCompare: string[] | null; + readonly hasZipFile: boolean; +}; -type AppViewRouterProps = ConnectedProps<{||}, AppViewRouterStateProps, {||}>; +type AppViewRouterProps = ConnectedProps<{}, AppViewRouterStateProps, {}>; class AppViewRouterImpl extends PureComponent { - render() { + override render() { const { view, dataSource, profilesToCompare, hasZipFile } = this.props; const phase = view.phase; @@ -132,9 +130,11 @@ class AppViewRouterImpl extends PureComponent { return null; case 'ROUTE_NOT_FOUND': default: - // Assert with Flow that we've handled all the cases, as the only thing left - // should be 'ROUTE_NOT_FOUND' or 'PROFILE_LOADED'. - (phase: 'ROUTE_NOT_FOUND'); + if (phase !== 'ROUTE_NOT_FOUND') { + // Assert with TypeScript that we've handled all the cases, as the only thing left + // should be 'ROUTE_NOT_FOUND'. + throw assertExhaustiveCheck(phase); + } return ( { } } -export const AppViewRouter = explicitConnect< - {||}, - AppViewRouterStateProps, - {||}, ->({ +export const AppViewRouter = explicitConnect<{}, AppViewRouterStateProps, {}>({ mapStateToProps: (state: State) => ({ view: getView(state), dataSource: getDataSource(state), diff --git a/src/components/app/AssemblyCodeFetcher.js b/src/components/app/AssemblyCodeFetcher.tsx similarity index 79% rename from src/components/app/AssemblyCodeFetcher.js rename to src/components/app/AssemblyCodeFetcher.tsx index d38ec7db42..173f108cce 100644 --- a/src/components/app/AssemblyCodeFetcher.js +++ b/src/components/app/AssemblyCodeFetcher.tsx @@ -1,10 +1,8 @@ /* 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/. */ -// @flow import React from 'react'; - import { getProfileOrNull, getAssemblyViewCode, @@ -32,30 +30,30 @@ import type { NativeSymbolInfo, } from 'firefox-profiler/types'; -type StateProps = {| - +assemblyViewNativeSymbol: NativeSymbolInfo | null, - +assemblyViewCode: AssemblyCodeStatus | void, - +assemblyViewIsOpen: boolean, - +symbolServerUrl: string, - +profile: Profile | null, - +browserConnection: BrowserConnection | null, -|}; +type StateProps = { + readonly assemblyViewNativeSymbol: NativeSymbolInfo | null; + readonly assemblyViewCode: AssemblyCodeStatus | void; + readonly assemblyViewIsOpen: boolean; + readonly symbolServerUrl: string; + readonly profile: Profile | null; + readonly browserConnection: BrowserConnection | null; +}; -type DispatchProps = {| - +beginLoadingAssemblyCodeFromUrl: typeof beginLoadingAssemblyCodeFromUrl, - +beginLoadingAssemblyCodeFromBrowserConnection: typeof beginLoadingAssemblyCodeFromBrowserConnection, - +finishLoadingAssemblyCode: typeof finishLoadingAssemblyCode, - +failLoadingAssemblyCode: typeof failLoadingAssemblyCode, -|}; +type DispatchProps = { + readonly beginLoadingAssemblyCodeFromUrl: typeof beginLoadingAssemblyCodeFromUrl; + readonly beginLoadingAssemblyCodeFromBrowserConnection: typeof beginLoadingAssemblyCodeFromBrowserConnection; + readonly finishLoadingAssemblyCode: typeof finishLoadingAssemblyCode; + readonly failLoadingAssemblyCode: typeof failLoadingAssemblyCode; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class AssemblyCodeFetcherImpl extends React.PureComponent { - componentDidMount() { + override componentDidMount() { this._triggerAssemblyLoadingIfNeeded(); } - componentDidUpdate() { + override componentDidUpdate() { this._triggerAssemblyLoadingIfNeeded(); } @@ -90,7 +88,7 @@ class AssemblyCodeFetcherImpl extends React.PureComponent { const delegate = new RegularExternalCommunicationDelegate( browserConnection, { - onBeginUrlRequest: (url) => { + onBeginUrlRequest: (url: string) => { beginLoadingAssemblyCodeFromUrl(nativeSymbolKey, url); }, onBeginBrowserConnectionQuery: () => { @@ -117,19 +115,19 @@ class AssemblyCodeFetcherImpl extends React.PureComponent { failLoadingAssemblyCode(nativeSymbolKey, fetchAssemblyResult.errors); break; default: - throw assertExhaustiveCheck(fetchAssemblyResult.type); + throw assertExhaustiveCheck(fetchAssemblyResult); } } - render() { + override render() { return null; } } export const AssemblyCodeFetcher = explicitConnect< - {||}, + {}, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ assemblyViewNativeSymbol: getAssemblyViewNativeSymbol(state), diff --git a/src/components/app/AssemblyViewToggleButton.js b/src/components/app/AssemblyViewToggleButton.tsx similarity index 87% rename from src/components/app/AssemblyViewToggleButton.js rename to src/components/app/AssemblyViewToggleButton.tsx index 30bca04f7a..d65bb7e5bb 100644 --- a/src/components/app/AssemblyViewToggleButton.js +++ b/src/components/app/AssemblyViewToggleButton.tsx @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import React from 'react'; import classNames from 'classnames'; @@ -17,16 +16,16 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import { Localized } from '@fluent/react'; -type StateProps = {| - +assemblyViewIsOpen: boolean, -|}; +type StateProps = { + readonly assemblyViewIsOpen: boolean; +}; -type DispatchProps = {| - +openAssemblyView: typeof openAssemblyView, - +closeAssemblyView: typeof closeAssemblyView, -|}; +type DispatchProps = { + readonly openAssemblyView: typeof openAssemblyView; + readonly closeAssemblyView: typeof closeAssemblyView; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class AssemblyViewToggleButtonImpl extends React.PureComponent { _onClick = () => { @@ -37,7 +36,7 @@ class AssemblyViewToggleButtonImpl extends React.PureComponent { } }; - render() { + override render() { const { assemblyViewIsOpen } = this.props; return assemblyViewIsOpen ? ( @@ -72,9 +71,9 @@ class AssemblyViewToggleButtonImpl extends React.PureComponent { } export const AssemblyViewToggleButton = explicitConnect< - {||}, + {}, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ assemblyViewIsOpen: getAssemblyViewIsOpen(state), diff --git a/src/components/app/BeforeUnloadManager.js b/src/components/app/BeforeUnloadManager.tsx similarity index 82% rename from src/components/app/BeforeUnloadManager.js rename to src/components/app/BeforeUnloadManager.tsx index ea7b38972b..60ddbdded8 100644 --- a/src/components/app/BeforeUnloadManager.js +++ b/src/components/app/BeforeUnloadManager.tsx @@ -2,19 +2,17 @@ * 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/. */ -// @flow - import * as React from 'react'; import explicitConnect from 'firefox-profiler/utils/connect'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import { getUploadPhase } from 'firefox-profiler/selectors/publish'; -type StateProps = {| - +isUploading: boolean, -|}; +type StateProps = { + readonly isUploading: boolean; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class BeforeUnloadManagerImpl extends React.PureComponent { manageBeforeUnloadListener() { @@ -34,24 +32,24 @@ class BeforeUnloadManagerImpl extends React.PureComponent { event.returnValue = 'Are you sure you want to close while uploading?'; }; - componentDidMount() { + override componentDidMount() { this.manageBeforeUnloadListener(); } - componentDidUpdate() { + override componentDidUpdate() { this.manageBeforeUnloadListener(); } - componentWillUnmount() { + override componentWillUnmount() { window.removeEventListener('beforeunload', this.handleUnload); } - render() { + override render() { return false; } } -export const BeforeUnloadManager = explicitConnect<{||}, StateProps, {||}>({ +export const BeforeUnloadManager = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state) => ({ isUploading: getUploadPhase(state) === 'uploading', }), diff --git a/src/components/app/BottomBox.js b/src/components/app/BottomBox.tsx similarity index 91% rename from src/components/app/BottomBox.js rename to src/components/app/BottomBox.tsx index 348ecd048c..8ab0b1448c 100644 --- a/src/components/app/BottomBox.js +++ b/src/components/app/BottomBox.tsx @@ -1,7 +1,6 @@ /* 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/. */ -// @flow import React from 'react'; import SplitterLayout from 'react-splitter-layout'; @@ -48,26 +47,26 @@ import { Localized } from '@fluent/react'; import './BottomBox.css'; -type StateProps = {| - +sourceViewFile: string | null, - +sourceViewCode: SourceCodeStatus | void, - +sourceViewScrollGeneration: number, - +globalLineTimings: LineTimings, - +selectedCallNodeLineTimings: LineTimings, - +assemblyViewIsOpen: boolean, - +assemblyViewNativeSymbol: NativeSymbolInfo | null, - +assemblyViewCode: AssemblyCodeStatus | void, - +assemblyViewScrollGeneration: number, - +globalAddressTimings: AddressTimings, - +selectedCallNodeAddressTimings: AddressTimings, - +disableOverscan: boolean, -|}; +type StateProps = { + readonly sourceViewFile: string | null; + readonly sourceViewCode: SourceCodeStatus | void; + readonly sourceViewScrollGeneration: number; + readonly globalLineTimings: LineTimings; + readonly selectedCallNodeLineTimings: LineTimings; + readonly assemblyViewIsOpen: boolean; + readonly assemblyViewNativeSymbol: NativeSymbolInfo | null; + readonly assemblyViewCode: AssemblyCodeStatus | void; + readonly assemblyViewScrollGeneration: number; + readonly globalAddressTimings: AddressTimings; + readonly selectedCallNodeAddressTimings: AddressTimings; + readonly disableOverscan: boolean; +}; -type DispatchProps = {| - +closeBottomBox: typeof closeBottomBox, -|}; +type DispatchProps = { + readonly closeBottomBox: typeof closeBottomBox; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; export function SourceCodeErrorOverlay({ errors }: CodeErrorOverlayProps) { return ( @@ -151,7 +150,7 @@ class BottomBoxImpl extends React.PureComponent { this.props.closeBottomBox(); }; - render() { + override render() { const { sourceViewFile, sourceViewCode, @@ -280,7 +279,7 @@ function convertErrors(errors: ApiQueryError[]): SourceCodeLoadingError[] { return errors.map((e) => e); } -export const BottomBox = explicitConnect<{||}, StateProps, DispatchProps>({ +export const BottomBox = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ sourceViewFile: getSourceViewFile(state), sourceViewCode: getSourceViewCode(state), diff --git a/src/components/app/CodeErrorOverlay.js b/src/components/app/CodeErrorOverlay.tsx similarity index 96% rename from src/components/app/CodeErrorOverlay.js rename to src/components/app/CodeErrorOverlay.tsx index f833fd19c6..f0d81e4ccf 100644 --- a/src/components/app/CodeErrorOverlay.js +++ b/src/components/app/CodeErrorOverlay.tsx @@ -1,17 +1,14 @@ /* 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/. */ -// @flow - -import React from 'react'; import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; import type { SourceCodeLoadingError } from 'firefox-profiler/types'; import { Localized } from '@fluent/react'; -export type CodeErrorOverlayProps = {| - errors: SourceCodeLoadingError[], -|}; +export type CodeErrorOverlayProps = { + errors: SourceCodeLoadingError[]; +}; export function CodeErrorOverlay({ errors }: CodeErrorOverlayProps) { return ( @@ -116,7 +113,7 @@ export function CodeErrorOverlay({ errors }: CodeErrorOverlayProps) { ); } default: - throw assertExhaustiveCheck(error.type); + throw assertExhaustiveCheck(error); } })} diff --git a/src/components/app/CodeLoadingOverlay.js b/src/components/app/CodeLoadingOverlay.tsx similarity index 82% rename from src/components/app/CodeLoadingOverlay.js rename to src/components/app/CodeLoadingOverlay.tsx index 8857e7f3cc..d24f75b5a8 100644 --- a/src/components/app/CodeLoadingOverlay.js +++ b/src/components/app/CodeLoadingOverlay.tsx @@ -1,17 +1,14 @@ /* 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/. */ -// @flow - -import React from 'react'; import { assertExhaustiveCheck } from 'firefox-profiler/utils/flow'; import { Localized } from '@fluent/react'; -import type { CodeLoadingSource } from 'firefox-profiler/types'; +import type { CodeLoadingSource } from 'firefox-profiler/types/state'; -type CodeLoadingOverlayProps = {| - source: CodeLoadingSource, -|}; +type CodeLoadingOverlayProps = { + source: CodeLoadingSource; +}; export function CodeLoadingOverlay({ source }: CodeLoadingOverlayProps) { switch (source.type) { @@ -37,7 +34,7 @@ export function CodeLoadingOverlay({ source }: CodeLoadingOverlayProps) { ); } default: - throw assertExhaustiveCheck(source.type); + throw assertExhaustiveCheck(source); } } diff --git a/src/components/app/CompareHome.js b/src/components/app/CompareHome.tsx similarity index 83% rename from src/components/app/CompareHome.js rename to src/components/app/CompareHome.tsx index ea478256e1..71a1923459 100644 --- a/src/components/app/CompareHome.js +++ b/src/components/app/CompareHome.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import { AppHeader } from './AppHeader'; @@ -13,33 +11,33 @@ import explicitConnect from 'firefox-profiler/utils/connect'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './CompareHome.css'; -type DispatchProps = {| - +changeProfilesToCompare: typeof changeProfilesToCompare, -|}; +type DispatchProps = { + readonly changeProfilesToCompare: typeof changeProfilesToCompare; +}; -type Props = ConnectedProps<{||}, {||}, DispatchProps>; +type Props = ConnectedProps<{}, {}, DispatchProps>; -type State = {| - profile1: string, - profile2: string, -|}; +type State = { + profile1: string; + profile2: string; +}; class CompareHomeImpl extends PureComponent { - state = { profile1: '', profile2: '' }; + override state = { profile1: '', profile2: '' }; - handleInputChange = (event: SyntheticInputEvent<>) => { + handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target; - this.setState({ [name]: value }); + this.setState((prevState) => ({ ...prevState, [name]: value })); }; - handleFormSubmit = (e: SyntheticEvent<>) => { + handleFormSubmit = (e: React.FormEvent) => { e.preventDefault(); const { profile1, profile2 } = this.state; const { changeProfilesToCompare } = this.props; changeProfilesToCompare([profile1, profile2]); }; - render() { + override render() { const { profile1, profile2 } = this.state; return ( @@ -101,7 +99,7 @@ class CompareHomeImpl extends PureComponent { } } -export const CompareHome = explicitConnect<{||}, {||}, DispatchProps>({ +export const CompareHome = explicitConnect<{}, {}, DispatchProps>({ mapDispatchToProps: { changeProfilesToCompare }, component: CompareHomeImpl, }); diff --git a/src/components/app/CurrentProfileUploadedInformationLoader.js b/src/components/app/CurrentProfileUploadedInformationLoader.tsx similarity index 87% rename from src/components/app/CurrentProfileUploadedInformationLoader.js rename to src/components/app/CurrentProfileUploadedInformationLoader.tsx index f1acb132db..5726285531 100644 --- a/src/components/app/CurrentProfileUploadedInformationLoader.js +++ b/src/components/app/CurrentProfileUploadedInformationLoader.tsx @@ -2,8 +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/. */ -// @flow - // This component is responsible for caching the stored profile data in the // redux state. This will control whether we can delete this profile. @@ -24,15 +22,15 @@ import explicitConnect from 'firefox-profiler/utils/connect'; import type { ConnectedProps } from 'firefox-profiler/utils/connect'; -type StateProps = {| - +hash: string, -|}; +type StateProps = { + readonly hash: string; +}; -type DispatchProps = {| - +setCurrentProfileUploadedInformation: typeof setCurrentProfileUploadedInformation, -|}; +type DispatchProps = { + readonly setCurrentProfileUploadedInformation: typeof setCurrentProfileUploadedInformation; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; class CurrentProfileUploadedInformationLoaderImpl extends PureComponent { async updateCurrentProfileInformationState() { @@ -53,23 +51,23 @@ class CurrentProfileUploadedInformationLoaderImpl extends PureComponent { } } - componentDidMount() { + override componentDidMount() { this.updateCurrentProfileInformationState(); } - componentDidUpdate() { + override componentDidUpdate() { this.updateCurrentProfileInformationState(); } - render() { + override render() { return null; } } export const CurrentProfileUploadedInformationLoader = explicitConnect< - {||}, + {}, StateProps, - DispatchProps, + DispatchProps >({ mapStateToProps: (state) => ({ hash: getHash(state), diff --git a/src/components/app/DebugWarning.js b/src/components/app/DebugWarning.tsx similarity index 82% rename from src/components/app/DebugWarning.js rename to src/components/app/DebugWarning.tsx index db04795766..74a35a1d82 100644 --- a/src/components/app/DebugWarning.js +++ b/src/components/app/DebugWarning.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { Localized } from '@fluent/react'; import { Warning } from '../shared/Warning'; @@ -14,13 +12,13 @@ import { getMeta } from '../../selectors/profile'; import type { ProfileMeta } from 'firefox-profiler/types'; import type { ConnectedProps } from '../../utils/connect'; -type StateProps = {| - +meta: ProfileMeta, -|}; +type StateProps = { + readonly meta: ProfileMeta; +}; -type Props = ConnectedProps<{||}, StateProps, {||}>; +type Props = ConnectedProps<{}, StateProps, {}>; class DebugWarningImp extends PureComponent { - render() { + override render() { const { meta } = this.props; return ( @@ -38,7 +36,7 @@ class DebugWarningImp extends PureComponent { } } -export const DebugWarning = explicitConnect<{||}, StateProps, {||}>({ +export const DebugWarning = explicitConnect<{}, StateProps, {}>({ mapStateToProps: (state) => ({ meta: getMeta(state), }), diff --git a/src/components/app/Details.js b/src/components/app/Details.tsx similarity index 90% rename from src/components/app/Details.js rename to src/components/app/Details.tsx index 25859bf48b..185884289b 100644 --- a/src/components/app/Details.js +++ b/src/components/app/Details.tsx @@ -2,9 +2,7 @@ * 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/. */ -// @flow - -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import classNames from 'classnames'; import { Localized } from '@fluent/react'; @@ -36,18 +34,18 @@ import type { TabSlug } from 'firefox-profiler/app-logic/tabs-handling'; import './Details.css'; -type StateProps = {| - +visibleTabs: $ReadOnlyArray, - +selectedTab: TabSlug, - +isSidebarOpen: boolean, -|}; +type StateProps = { + readonly visibleTabs: ReadonlyArray; + readonly selectedTab: TabSlug; + readonly isSidebarOpen: boolean; +}; -type DispatchProps = {| - +changeSelectedTab: typeof changeSelectedTab, - +changeSidebarOpenState: typeof changeSidebarOpenState, -|}; +type DispatchProps = { + readonly changeSelectedTab: typeof changeSelectedTab; + readonly changeSidebarOpenState: typeof changeSidebarOpenState; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; const SMALL_SCREEN_WIDTH = 768; @@ -66,7 +64,7 @@ class ProfileViewerImpl extends PureComponent { changeSidebarOpenState(selectedTab, !isSidebarOpen); }; - componentDidMount() { + override componentDidMount() { const width = window.innerWidth; const { selectedTab, isSidebarOpen, changeSidebarOpenState } = this.props; @@ -75,7 +73,7 @@ class ProfileViewerImpl extends PureComponent { } } - render() { + override render() { const { visibleTabs, selectedTab, isSidebarOpen } = this.props; const hasSidebar = selectSidebar(selectedTab) !== null; return ( @@ -141,7 +139,7 @@ class ProfileViewerImpl extends PureComponent { } } -export const Details = explicitConnect<{||}, StateProps, DispatchProps>({ +export const Details = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ visibleTabs: selectedThreadSelectors.getUsefulTabs(state), selectedTab: getSelectedTab(state), diff --git a/src/components/app/DetailsContainer.js b/src/components/app/DetailsContainer.tsx similarity index 80% rename from src/components/app/DetailsContainer.js rename to src/components/app/DetailsContainer.tsx index a9ae356b96..5f3911efbd 100644 --- a/src/components/app/DetailsContainer.js +++ b/src/components/app/DetailsContainer.tsx @@ -1,9 +1,7 @@ /* 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/. */ -// @flow - -import React from 'react'; +// React imported for JSX import SplitterLayout from 'react-splitter-layout'; import { Details } from './Details'; @@ -19,16 +17,16 @@ import type { ConnectedProps } from 'firefox-profiler/utils/connect'; import './DetailsContainer.css'; -type StateProps = {| - +selectedTab: TabSlug, - +isSidebarOpen: boolean, -|}; +type StateProps = { + readonly selectedTab: TabSlug; + readonly isSidebarOpen: boolean; +}; -type DispatchProps = {| - +invalidatePanelLayout: typeof invalidatePanelLayout, -|}; +type DispatchProps = { + readonly invalidatePanelLayout: typeof invalidatePanelLayout; +}; -type Props = ConnectedProps<{||}, StateProps, DispatchProps>; +type Props = ConnectedProps<{}, StateProps, DispatchProps>; function DetailsContainerImpl({ selectedTab, @@ -50,11 +48,7 @@ function DetailsContainerImpl({ ); } -export const DetailsContainer = explicitConnect< - {||}, - StateProps, - DispatchProps, ->({ +export const DetailsContainer = explicitConnect<{}, StateProps, DispatchProps>({ mapStateToProps: (state) => ({ selectedTab: getSelectedTab(state), isSidebarOpen: getIsSidebarOpen(state), diff --git a/src/components/app/DragAndDrop.js b/src/components/app/DragAndDrop.tsx similarity index 83% rename from src/components/app/DragAndDrop.js rename to src/components/app/DragAndDrop.tsx index fa631e546d..4a72f4825a 100644 --- a/src/components/app/DragAndDrop.js +++ b/src/components/app/DragAndDrop.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import classNames from 'classnames'; import { retrieveProfileFromFile } from 'firefox-profiler/actions/receive-profile'; @@ -30,22 +28,22 @@ function _dragPreventDefault(event: DragEvent) { event.preventDefault(); } -type OwnProps = {| - +className?: string, - +children?: React.Node, -|}; +type OwnProps = { + readonly className?: string; + readonly children?: React.ReactNode; +}; -type StateProps = {| - +isNewProfileLoadAllowed: boolean, - +useDefaultOverlay: boolean, - +browserConnection: BrowserConnection | null, -|}; +type StateProps = { + readonly isNewProfileLoadAllowed: boolean; + readonly useDefaultOverlay: boolean; + readonly browserConnection: BrowserConnection | null; +}; -type DispatchProps = {| - +retrieveProfileFromFile: typeof retrieveProfileFromFile, - +startDragging: typeof startDragging, - +stopDragging: typeof stopDragging, -|}; +type DispatchProps = { + readonly retrieveProfileFromFile: typeof retrieveProfileFromFile; + readonly startDragging: typeof startDragging; + readonly stopDragging: typeof stopDragging; +}; type Props = ConnectedProps; @@ -85,10 +83,10 @@ class DragAndDropImpl extends React.PureComponent { // As the mouse moves over various nested elements inside this element, // every time the mouse enters a new element, we first get the dragenter // event for that new element and then a dragleave for the previous element. - _enteredElements: Set = new Set(); + _enteredElements: Set = new Set(); _updateDragLocation( - event: SyntheticDragEvent + event: React.DragEvent ): [DragLocation, DragLocation] { const before = this._enteredElements.size > 0 ? 'INSIDE' : 'OUTSIDE'; @@ -97,7 +95,7 @@ class DragAndDropImpl extends React.PureComponent { // `container` is always our container div; we use currentTarget here so that // we don't have to set up a react ref for the element. const container = event.currentTarget; - this._enteredElements = new Set( + this._enteredElements = new Set( [...this._enteredElements].filter((el) => container.contains(el)) ); @@ -116,23 +114,23 @@ class DragAndDropImpl extends React.PureComponent { } _resetDragLocation() { - this._enteredElements = new Set(); + this._enteredElements = new Set(); } - componentDidMount() { + override componentDidMount() { // Prevent dropping files on the document. document.addEventListener('drag', _dragPreventDefault, false); document.addEventListener('dragover', _dragPreventDefault, false); document.addEventListener('drop', _dragPreventDefault, false); } - componentWillUnmount() { + override componentWillUnmount() { document.removeEventListener('drag', _dragPreventDefault, false); document.removeEventListener('dragover', _dragPreventDefault, false); document.removeEventListener('drop', _dragPreventDefault, false); } - _onDragEnter = (event: SyntheticDragEvent) => { + _onDragEnter = (event: React.DragEvent) => { event.preventDefault(); const [before, after] = this._updateDragLocation(event); @@ -141,7 +139,7 @@ class DragAndDropImpl extends React.PureComponent { } }; - _onDragLeave = (event: SyntheticDragEvent) => { + _onDragLeave = (event: React.DragEvent) => { event.preventDefault(); const [before, after] = this._updateDragLocation(event); @@ -150,7 +148,7 @@ class DragAndDropImpl extends React.PureComponent { } }; - _handleProfileDrop = (event: DragEvent) => { + _handleProfileDrop = (event: React.DragEvent) => { event.preventDefault(); this._resetDragLocation(); @@ -169,7 +167,7 @@ class DragAndDropImpl extends React.PureComponent { } }; - render() { + override render() { const { className, children } = this.props; return ( @@ -212,21 +210,21 @@ export const DragAndDrop = explicitConnect( } ); -type OverlayOwnProps = {| - +isDefault?: boolean, -|}; -type OverlayStateProps = {| - +isDragging: boolean, - +isNewProfileLoadAllowed: boolean, -|}; -type OverlayDispatchProps = {| - +registerDragAndDropOverlay: typeof registerDragAndDropOverlay, - +unregisterDragAndDropOverlay: typeof unregisterDragAndDropOverlay, -|}; +type OverlayOwnProps = { + readonly isDefault?: boolean; +}; +type OverlayStateProps = { + readonly isDragging: boolean; + readonly isNewProfileLoadAllowed: boolean; +}; +type OverlayDispatchProps = { + readonly registerDragAndDropOverlay: typeof registerDragAndDropOverlay; + readonly unregisterDragAndDropOverlay: typeof unregisterDragAndDropOverlay; +}; type OverlayProps = ConnectedProps< OverlayOwnProps, OverlayStateProps, - OverlayDispatchProps, + OverlayDispatchProps >; /** @@ -237,19 +235,19 @@ type OverlayProps = ConnectedProps< * rendered. */ class DragAndDropOverlayImpl extends React.PureComponent { - componentDidMount() { + override componentDidMount() { if (!this.props.isDefault) { this.props.registerDragAndDropOverlay(); } } - componentWillUnmount() { + override componentWillUnmount() { if (!this.props.isDefault) { this.props.unregisterDragAndDropOverlay(); } } - render() { + override render() { return (
    { export const DragAndDropOverlay = explicitConnect< OverlayOwnProps, OverlayStateProps, - OverlayDispatchProps, + OverlayDispatchProps >({ mapStateToProps: (state) => ({ isDragging: getIsDragAndDropDragging(state), diff --git a/src/components/app/ErrorBoundary.js b/src/components/app/ErrorBoundary.tsx similarity index 90% rename from src/components/app/ErrorBoundary.js rename to src/components/app/ErrorBoundary.tsx index 54ce4270f7..6ea45d83d7 100644 --- a/src/components/app/ErrorBoundary.js +++ b/src/components/app/ErrorBoundary.tsx @@ -1,28 +1,25 @@ /* 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/. */ -// @flow - import * as React from 'react'; import { Localized } from '@fluent/react'; import { reportError } from 'firefox-profiler/utils/analytics'; import './ErrorBoundary.css'; -type State = {| - hasError: boolean, - errorString: string | null, -|}; +type State = { + hasError: boolean; + errorString: string | null; +}; -type ExternalProps = {| - +children: React.Node, - +message: string, -|}; +type ExternalProps = { + readonly children: React.ReactNode; + readonly message: string; +}; -type InternalProps = {| - ...ExternalProps, - buttonContent: React.Node, - reportExplanationMessage: React.Node, -|}; +type InternalProps = ExternalProps & { + buttonContent: React.ReactNode; + reportExplanationMessage: React.ReactNode; +}; /** * This component will catch errors in components, and display a more friendly error @@ -31,13 +28,13 @@ type InternalProps = {| * See: https://reactjs.org/docs/error-boundaries.html */ class ErrorBoundaryInternal extends React.Component { - state = { + override state: State = { hasError: false, errorString: null, }; - componentDidCatch( - error: mixed, + override componentDidCatch( + error: unknown, { componentStack }: { componentStack: string } ) { console.error( @@ -68,7 +65,7 @@ class ErrorBoundaryInternal extends React.Component { }); } - render() { + override render() { if (this.state.hasError) { const { errorString } = this.state; return ( diff --git a/src/components/app/FooterLinks.js b/src/components/app/FooterLinks.tsx similarity index 90% rename from src/components/app/FooterLinks.js rename to src/components/app/FooterLinks.tsx index 03b6d72b85..d1fb2745c8 100644 --- a/src/components/app/FooterLinks.js +++ b/src/components/app/FooterLinks.tsx @@ -1,26 +1,25 @@ /* 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/. */ -// @flow import { Localized } from '@fluent/react'; -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; import { LanguageSwitcher } from './LanguageSwitcher'; import './FooterLinks.css'; -type State = {| hide: boolean |}; +type State = { hide: boolean }; -export class FooterLinks extends PureComponent<{||}, State> { +export class FooterLinks extends PureComponent<{}, State> { _onClick = () => { this.setState({ hide: true }); }; - state = { + override state = { hide: false, }; - render() { + override render() { if (this.state.hide) { return null; } diff --git a/src/components/app/Home.js b/src/components/app/Home.tsx similarity index 92% rename from src/components/app/Home.js rename to src/components/app/Home.tsx index c11b8de722..54f53a7a03 100644 --- a/src/components/app/Home.js +++ b/src/components/app/Home.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import { AppHeader } from './AppHeader'; @@ -33,39 +31,39 @@ import './Home.css'; import { DragAndDropOverlay } from './DragAndDrop'; -type ActionButtonsProps = {| - +onLoadProfileFromFileRequested: (file: File) => void, - +onLoadProfileFromUrlRequested: (url: string) => void, -|}; +type ActionButtonsProps = { + readonly onLoadProfileFromFileRequested: (file: File) => void; + readonly onLoadProfileFromUrlRequested: (url: string) => void; +}; type ActionButtonsState = { - isLoadFromUrlPressed: boolean, + isLoadFromUrlPressed: boolean; }; -type LoadFromUrlProps = {| - +onLoadProfileFromUrlRequested: (url: string) => void, -|}; +type LoadFromUrlProps = { + readonly onLoadProfileFromUrlRequested: (url: string) => void; +}; type LoadFromUrlState = { - value: string, + value: string; }; class ActionButtons extends React.PureComponent< ActionButtonsProps, - ActionButtonsState, + ActionButtonsState > { _fileInput: HTMLInputElement | null = null; - state = { + override state = { isLoadFromUrlPressed: false, }; - _takeInputRef = (input) => { + _takeInputRef = (input: HTMLInputElement | null) => { this._fileInput = input; }; _uploadProfileFromFile = async () => { - if (this._fileInput) { + if (this._fileInput && this._fileInput.files?.[0]) { this.props.onLoadProfileFromFileRequested(this._fileInput.files[0]); } }; @@ -77,14 +75,14 @@ class ActionButtons extends React.PureComponent< } }; - _loadFromUrlPressed = (event: SyntheticEvent<>) => { + _loadFromUrlPressed = (event: React.MouseEvent) => { event.preventDefault(); this.setState((prevState) => { return { isLoadFromUrlPressed: !prevState.isLoadFromUrlPressed }; }); }; - render() { + override render() { return (
    @@ -129,27 +127,27 @@ class ActionButtons extends React.PureComponent< class LoadFromUrl extends React.PureComponent< LoadFromUrlProps, - LoadFromUrlState, + LoadFromUrlState > { - state = { + override state = { value: '', }; - handleChange = (event: SyntheticEvent) => { + handleChange = (event: React.ChangeEvent) => { event.preventDefault(); this.setState({ value: event.currentTarget.value, }); }; - _upload = (event: SyntheticEvent<>) => { + _upload = (event: React.FormEvent) => { event.preventDefault(); if (this.state.value) { this.props.onLoadProfileFromUrlRequested(this.state.value); } }; - render() { + override render() { return ( ; type HomeState = { - popupInstallPhase: PopupInstallPhase, + popupInstallPhase: PopupInstallPhase; }; type PopupInstallPhase = @@ -257,7 +255,7 @@ class HomeImpl extends React.PureComponent { } this.state = { - popupInstallPhase, + popupInstallPhase: popupInstallPhase as PopupInstallPhase, }; } @@ -283,7 +281,7 @@ class HomeImpl extends React.PureComponent { } } - _enableMenuButton = (e) => { + _enableMenuButton = (e: React.MouseEvent) => { e.preventDefault(); enableMenuButton().then( () => { @@ -576,7 +574,7 @@ class HomeImpl extends React.PureComponent { this.props.triggerLoadingFromUrl(url); }; - render() { + override render() { const { specialMessage } = this.props; return ( @@ -648,13 +646,13 @@ class HomeImpl extends React.PureComponent { >

    The Firefox Profiler can also import profiles from other - profilers, such as Linux perf, - Android SimplePerf, the Chrome + profilers, such as {'Linux perf'}, + {'Android SimplePerf'}, the Chrome performance panel,{' '} - Android Studio, or any file - using the dhat format or{' '} - Google’s Trace Event Format.{' '} - Learn how to write your own importer. + {'Android Studio'}, or any file + using the {'dhat format'} or{' '} + {"Google's Trace Event Format"}.{' '} + {'Learn how to write your own importer'}.

    @@ -663,7 +661,9 @@ class HomeImpl extends React.PureComponent { elems={{ a: ( // $FlowExpectError Flow doesn't know about this fluent rule for react component. - + + Compare + ), }} > @@ -704,7 +704,7 @@ function _isChromium(): boolean { export const Home = explicitConnect< OwnHomeProps, StateHomeProps, - DispatchHomeProps, + DispatchHomeProps >({ mapStateToProps: (state) => ({ browserConnection: getBrowserConnection(state), diff --git a/src/components/app/KeyboardShortcut.js b/src/components/app/KeyboardShortcut.tsx similarity index 88% rename from src/components/app/KeyboardShortcut.js rename to src/components/app/KeyboardShortcut.tsx index 0b49de2b26..58ee157ca1 100644 --- a/src/components/app/KeyboardShortcut.js +++ b/src/components/app/KeyboardShortcut.tsx @@ -2,40 +2,37 @@ * 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/. */ -// @flow - import * as React from 'react'; -import { coerce } from '../../utils/flow'; import classNames from 'classnames'; import './KeyboardShortcut.css'; -type Props = {| - +wrapperClassName: string, - +children: React.Node, -|}; +type Props = { + readonly wrapperClassName: string; + readonly children: React.ReactNode; +}; -type State = {| - +isOpen: boolean, +type State = { + readonly isOpen: boolean; // The modal steals the focus of the screen. This is the element that was focused // before showing the modal. The focus will be restored once the modal is dismissed. - +focusAfterClosed: HTMLElement | null, -|}; + readonly focusAfterClosed: HTMLElement | null; +}; /** * Display a list of shortcuts that overlays the screen. */ export class KeyboardShortcut extends React.PureComponent { - state = { + override state = { isOpen: false, // The eslint error is a false positive due to how it's used, see the line: // `focusAfterClosed.focus()` - focusAfterClosed: null, // eslint-disable-line react/no-unused-state + focusAfterClosed: null, }; _focusArea = React.createRef(); - componentDidMount() { + override componentDidMount() { window.addEventListener('keydown', this._handleKeyPress); } @@ -59,7 +56,7 @@ export class KeyboardShortcut extends React.PureComponent { // Do nothing. return state; } - const focusAfterClosed = document.activeElement; + const focusAfterClosed = document.activeElement as HTMLElement; this._trapFocus(); this._focus(); return { isOpen: true, focusAfterClosed }; @@ -86,11 +83,20 @@ export class KeyboardShortcut extends React.PureComponent { }; _handleCloseClick = () => { - this.setState(this._close); + if (this.state.isOpen) { + const focusAfterClosed = this.state.focusAfterClosed; + this._untrapFocus(); + this.setState({ isOpen: false, focusAfterClosed: null }); + if (focusAfterClosed && 'focus' in focusAfterClosed) { + requestAnimationFrame(() => { + (focusAfterClosed as HTMLElement).focus(); + }); + } + } }; _handleKeyPress = (event: KeyboardEvent) => { - const target = coerce(event.target); + const target = event.target! as HTMLElement; switch (event.key) { case '?': { if ( @@ -118,7 +124,7 @@ export class KeyboardShortcut extends React.PureComponent { } }; - componentWillUnmount() { + override componentWillUnmount() { window.removeEventListener('keydown', this._handleKeyPress); this._untrapFocus(); } @@ -138,7 +144,7 @@ export class KeyboardShortcut extends React.PureComponent { if (!div) { return; } - if (!div.contains(coerce(event.target))) { + if (!div.contains(event.target! as Node)) { // TODO - This does not handle shift-tabbing going to the last focusable // element in the list. div.focus(); @@ -222,7 +228,7 @@ export class KeyboardShortcut extends React.PureComponent { ); } - render() { + override render() { const { wrapperClassName, children } = this.props; const { isOpen } = this.state; return ( @@ -243,7 +249,7 @@ export class KeyboardShortcut extends React.PureComponent {
    { } } -type ShortcutProps = $ReadOnly<{| - label: string, - shortcut: string, - macShortcut?: string, -|}>; +type ShortcutProps = Readonly<{ + label: string; + shortcut: string; + macShortcut?: string; +}>; function Shortcut(props: ShortcutProps) { let shortcut = props.shortcut; diff --git a/src/components/app/LanguageSwitcher.js b/src/components/app/LanguageSwitcher.tsx similarity index 87% rename from src/components/app/LanguageSwitcher.js rename to src/components/app/LanguageSwitcher.tsx index 2708362bde..b2d0250121 100644 --- a/src/components/app/LanguageSwitcher.js +++ b/src/components/app/LanguageSwitcher.tsx @@ -2,8 +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/. */ -// @flow - import * as React from 'react'; import { Localized } from '@fluent/react'; @@ -14,11 +12,11 @@ import { } from 'firefox-profiler/app-logic/l10n'; import { useL10n } from 'firefox-profiler/hooks/useL10n'; -export function LanguageSwitcher(): React.Node { +export function LanguageSwitcher(): React.ReactNode { const { primaryLocale, requestL10n } = useL10n(); const onLocaleChange = React.useCallback( - (event: SyntheticEvent) => { + (event: React.ChangeEvent) => { requestL10n([event.currentTarget.value]); }, [requestL10n] @@ -43,7 +41,7 @@ export function LanguageSwitcher(): React.Node { > {AVAILABLE_LOCALES.map((locale) => ( ))} diff --git a/src/components/app/ListOfPublishedProfiles.js b/src/components/app/ListOfPublishedProfiles.tsx similarity index 88% rename from src/components/app/ListOfPublishedProfiles.js rename to src/components/app/ListOfPublishedProfiles.tsx index bcb2f74ebb..650c87f432 100644 --- a/src/components/app/ListOfPublishedProfiles.js +++ b/src/components/app/ListOfPublishedProfiles.tsx @@ -2,8 +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/. */ -// @flow - import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { Localized } from '@fluent/react'; @@ -34,24 +32,24 @@ function _formatRange(range: StartEndRange): string { return formatSeconds(range.end - range.start, 3, 1); } -type PublishedProfileProps = {| - +onProfileDelete: () => void, - +uploadedProfileInformation: UploadedProfileInformation, - +withActionButtons: boolean, -|}; +type PublishedProfileProps = { + readonly onProfileDelete: () => void; + readonly uploadedProfileInformation: UploadedProfileInformation; + readonly withActionButtons: boolean; +}; -type PublishedProfileState = {| - +confirmDialogIsOpen: boolean, -|}; +type PublishedProfileState = { + readonly confirmDialogIsOpen: boolean; +}; /** * This implements one line in the list of published profiles. */ class PublishedProfile extends React.PureComponent< PublishedProfileProps, - PublishedProfileState, + PublishedProfileState > { - state = { + override state = { confirmDialogIsOpen: false, }; @@ -67,7 +65,7 @@ class PublishedProfile extends React.PureComponent< this.props.onProfileDelete(); }; - render() { + override render() { const { uploadedProfileInformation, withActionButtons } = this.props; const { confirmDialogIsOpen } = this.state; @@ -152,19 +150,19 @@ class PublishedProfile extends React.PureComponent< } } -type Props = {| - withActionButtons: boolean, - limit?: number, -|}; +type Props = { + withActionButtons: boolean; + limit?: number; +}; -type State = {| - uploadedProfileInformationList: null | UploadedProfileInformation[], -|}; +type State = { + uploadedProfileInformationList: null | UploadedProfileInformation[]; +}; export class ListOfPublishedProfiles extends PureComponent { _isMounted = false; - state = { + override state = { uploadedProfileInformationList: null, }; @@ -181,13 +179,13 @@ export class ListOfPublishedProfiles extends PureComponent { } }; - async componentDidMount() { + override async componentDidMount() { this._isMounted = true; this._refreshList(); window.addEventListener('focus', this._refreshList); } - componentWillUnmount() { + override componentWillUnmount() { this._isMounted = false; window.removeEventListener('focus', this._refreshList); } @@ -196,7 +194,7 @@ export class ListOfPublishedProfiles extends PureComponent { this._refreshList(); }; - render() { + override render() { const { limit, withActionButtons } = this.props; const { uploadedProfileInformationList } = this.state; @@ -204,7 +202,11 @@ export class ListOfPublishedProfiles extends PureComponent { return null; } - if (!uploadedProfileInformationList.length) { + // TypeScript type narrowing help + const profileList: UploadedProfileInformation[] = + uploadedProfileInformationList; + + if (!profileList.length) { return (

    @@ -215,12 +217,11 @@ export class ListOfPublishedProfiles extends PureComponent { } const reducedUploadedProfileInformationList = limit - ? uploadedProfileInformationList.slice(0, limit) - : uploadedProfileInformationList; + ? profileList.slice(0, limit) + : profileList; const profilesRestCount = - uploadedProfileInformationList.length - - reducedUploadedProfileInformationList.length; + profileList.length - reducedUploadedProfileInformationList.length; let profileRestLabel; if (profilesRestCount > 0) { @@ -237,7 +238,7 @@ export class ListOfPublishedProfiles extends PureComponent { <>Manage this recording @@ -249,7 +250,7 @@ export class ListOfPublishedProfiles extends PureComponent { <>