From 7a111d93230d8b14d833e721990890a65eda9933 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 7 Aug 2025 20:29:33 -0400 Subject: [PATCH 01/41] 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 02/41] 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 03/41] 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 04/41] 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 05/41] 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 06/41] 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 07/41] 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 08/41] 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 09/41] 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 10/41] 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 11/41] 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 12/41] 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 13/41] 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 14/41] 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 15/41] 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 16/41] 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 17/41] 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 18/41] 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 19/41] 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 { <>