@samitouri / QOS-React-2 / commits / f4161c3ec7

[DRAFT] Import scheduling profiler into DevTools Profiler (#21897)

Brian Vaughn committed Jul 22, 2021 at 13:58 UTC f4161c3ec7d2ab2993695a458f771bb331e256d5
63 files changed +931 -1128
.circleci/config.yml
-52
@@ -227,48 +227,6 @@ jobs:
227 - store_artifacts:
228 path: ./build/devtools.tgz
229
230 - build_devtools_scheduling_profiler:
231 - docker: *docker
232 - environment: *environment
233 - steps:
234 - - checkout
235 - - attach_workspace: *attach_workspace
236 - - run: yarn workspaces info | head -n -1 > workspace_info.txt
237 - - *restore_yarn_cache
238 - - *restore_node_modules
239 - - run:
240 - name: Install Packages
241 - command: yarn --frozen-lockfile --cache-folder ~/.cache/yarn
242 - - run:
243 - name: Build and archive
244 - command: |
245 - mkdir -p build/devtools
246 - cd packages/react-devtools-scheduling-profiler
247 - yarn build
248 - cd dist
249 - tar -zcvf ../../../build/devtools-scheduling-profiler.tgz .
250 - - store_artifacts:
251 - path: ./build/devtools-scheduling-profiler.tgz
252 - - persist_to_workspace:
253 - root: packages/react-devtools-scheduling-profiler
254 - paths:
255 - - dist
256 -
257 - deploy_devtools_scheduling_profiler:
258 - docker: *docker
259 - environment: *environment
260 - steps:
261 - - checkout
262 - - attach_workspace:
263 - at: packages/react-devtools-scheduling-profiler
264 - - run: yarn workspaces info | head -n -1 > workspace_info.txt
265 - - *restore_node_modules
266 - - run:
267 - name: Deploy
268 - command: |
269 - cd packages/react-devtools-scheduling-profiler
270 - yarn vercel deploy dist --prod --confirm --token $SCHEDULING_PROFILER_DEPLOY_VERCEL_TOKEN
271 -
230 yarn_lint_build:
231 docker: *docker
232 environment: *environment
@@ -408,16 +366,6 @@ workflows:
366 - build_devtools_and_process_artifacts:
367 requires:
368 - yarn_build
411 - - build_devtools_scheduling_profiler:
412 - requires:
413 - - yarn_build
414 - - deploy_devtools_scheduling_profiler:
415 - requires:
416 - - build_devtools_scheduling_profiler
417 - filters:
418 - branches:
419 - only:
420 - - main
369
370 # New workflow that will replace "stable" and "experimental"
371 build_and_test:
packages/react-devtools-core/webpack.standalone.js
+25 -8
@@ -18,6 +18,15 @@ const __DEV__ = NODE_ENV === 'development';
18
19 const DEVTOOLS_VERSION = getVersionString();
20
21 +const babelOptions = {
22 + configFile: resolve(
23 + __dirname,
24 + '..',
25 + 'react-devtools-shared',
26 + 'babel.config.js',
27 + ),
28 +};
29 +
30 module.exports = {
31 mode: __DEV__ ? 'development' : 'production',
32 devtool: __DEV__ ? 'cheap-module-eval-source-map' : 'source-map',
@@ -62,17 +71,25 @@ module.exports = {
71 ],
72 module: {
73 rules: [
74 + {
75 + test: /\.worker\.js$/,
76 + use: [
77 + {
78 + loader: 'workerize-loader',
79 + options: {
80 + inline: true,
81 + },
82 + },
83 + {
84 + loader: 'babel-loader',
85 + options: babelOptions,
86 + },
87 + ],
88 + },
89 {
90 test: /\.js$/,
91 loader: 'babel-loader',
68 - options: {
69 - configFile: resolve(
70 - __dirname,
71 - '..',
72 - 'react-devtools-shared',
73 - 'babel.config.js',
74 - ),
75 - },
92 + options: babelOptions,
93 },
94 {
95 test: /\.css$/,
packages/react-devtools-extensions/package.json
+1 -1
@@ -37,6 +37,7 @@
37 "chrome-launch": "^1.1.4",
38 "crx": "^5.0.0",
39 "css-loader": "^1.0.1",
40 + "file-loader": "^6.1.0",
41 "firefox-profile": "^1.0.2",
42 "fs-extra": "^4.0.2",
43 "jest-fetch-mock": "^3.0.3",
@@ -52,7 +53,6 @@
53 "source-map": "^0.8.0-beta.0",
54 "sourcemap-codec": "^1.4.8",
55 "style-loader": "^0.23.1",
55 - "web-ext": "^3.0.0",
56 "webpack": "^4.43.0",
57 "webpack-cli": "^3.3.11",
58 "webpack-dev-server": "^3.10.3",
packages/react-devtools-extensions/src/main.js
+2
@@ -140,6 +140,8 @@ function createPanelIfReactLoaded() {
140 isProfiling,
141 supportsReloadAndProfile: isChrome,
142 supportsProfiling,
143 + // At this time, the scheduling profiler can only parse Chrome performance profiles.
144 + supportsSchedulingProfiler: isChrome,
145 supportsTraceUpdates: true,
146 });
147 store.profilerStore.profilingData = profilingData;
packages/react-devtools-extensions/src/parseHookNames/index.js
+2 -3
@@ -9,9 +9,8 @@
9 * @flow
10 */
11
12 -// This file uses workerize to load ./parseHookNames.worker as a webworker
13 -// and instanciates it, exposing flow typed functions that can be used
14 -// on other files.
12 +// This file uses workerize to load ./parseHookNames.worker as a webworker and instanciates it,
13 +// exposing flow typed functions that can be used on other files.
14
15 import * as parseHookNamesModule from './parseHookNames';
16 import WorkerizedParseHookNames from './parseHookNames.worker';
packages/react-devtools-extensions/src/parseHookNames/parseHookNames.worker.js
+7
@@ -1,3 +1,10 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 import * as parseHookNamesModule from './parseHookNames';
9
10 export const parseHookNames = parseHookNamesModule.parseHookNames;
packages/react-devtools-extensions/webpack.config.js
+25 -13
@@ -19,6 +19,15 @@ const DEVTOOLS_VERSION = getVersionString();
19
20 const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
21
22 +const babelOptions = {
23 + configFile: resolve(
24 + __dirname,
25 + '..',
26 + 'react-devtools-shared',
27 + 'babel.config.js',
28 + ),
29 +};
30 +
31 module.exports = {
32 mode: __DEV__ ? 'development' : 'production',
33 devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
@@ -81,17 +90,25 @@ module.exports = {
90 ],
91
92 rules: [
93 + {
94 + test: /\.worker\.js$/,
95 + use: [
96 + {
97 + loader: 'workerize-loader',
98 + options: {
99 + inline: true,
100 + },
101 + },
102 + {
103 + loader: 'babel-loader',
104 + options: babelOptions,
105 + },
106 + ],
107 + },
108 {
109 test: /\.js$/,
110 loader: 'babel-loader',
87 - options: {
88 - configFile: resolve(
89 - __dirname,
90 - '..',
91 - 'react-devtools-shared',
92 - 'babel.config.js',
93 - ),
94 - },
111 + options: babelOptions,
112 },
113 {
114 test: /\.css$/,
@@ -109,11 +126,6 @@ module.exports = {
126 },
127 ],
128 },
112 - {
113 - test: /\.worker\.js$/,
114 - // inline: true due to limitations with extensions
115 - use: {loader: 'workerize-loader', options: {inline: true}},
116 - },
129 ],
130 },
131 };
packages/react-devtools-inline/package.json
+3 -1
@@ -34,10 +34,12 @@
34 "babel-loader": "^8.0.4",
35 "cross-env": "^3.1.4",
36 "css-loader": "^1.0.1",
37 + "file-loader": "^6.1.0",
38 "raw-loader": "^3.1.0",
39 "style-loader": "^0.23.1",
40 "webpack": "^4.43.0",
41 "webpack-cli": "^3.3.11",
41 - "webpack-dev-server": "^3.10.3"
42 + "webpack-dev-server": "^3.10.3",
43 + "worker-loader": "^3.0.3"
44 }
45 }
packages/react-devtools-inline/src/frontend.js
+1
@@ -24,6 +24,7 @@ export function createStore(bridge: FrontendBridge): Store {
24 return new Store(bridge, {
25 checkBridgeProtocolCompatibility: true,
26 supportsTraceUpdates: true,
27 + supportsSchedulingProfiler: true,
28 });
29 }
30
packages/react-devtools-inline/webpack.config.js
+25 -8
@@ -16,6 +16,15 @@ const __DEV__ = NODE_ENV === 'development';
16
17 const DEVTOOLS_VERSION = getVersionString();
18
19 +const babelOptions = {
20 + configFile: resolve(
21 + __dirname,
22 + '..',
23 + 'react-devtools-shared',
24 + 'babel.config.js',
25 + ),
26 +};
27 +
28 module.exports = {
29 mode: __DEV__ ? 'development' : 'production',
30 devtool: __DEV__ ? 'eval-cheap-source-map' : 'source-map',
@@ -65,17 +74,25 @@ module.exports = {
74 ],
75 module: {
76 rules: [
77 + {
78 + test: /\.worker\.js$/,
79 + use: [
80 + {
81 + loader: 'workerize-loader',
82 + options: {
83 + inline: true,
84 + },
85 + },
86 + {
87 + loader: 'babel-loader',
88 + options: babelOptions,
89 + },
90 + ],
91 + },
92 {
93 test: /\.js$/,
94 loader: 'babel-loader',
71 - options: {
72 - configFile: resolve(
73 - __dirname,
74 - '..',
75 - 'react-devtools-shared',
76 - 'babel.config.js',
77 - ),
78 - },
95 + options: babelOptions,
96 },
97 {
98 test: /\.css$/,
packages/react-devtools-scheduling-profiler/README.md
+2 -14
@@ -1,15 +1,3 @@
1 -# Experimental React Concurrent Mode Profiler
1 +# React Concurrent Mode Profiler
2
3 -https://react-devtools-scheduling-profiler.vercel.app/
4 -
5 -## Setting up continuous deployment with CircleCI and Vercel
6 -
7 -These instructions are intended for internal use, but may be useful if you are setting up a custom production deployment of the scheduling profiler.
8 -
9 -1. Create a Vercel token at https://vercel.com/account/tokens.
10 -2. Configure CircleCI:
11 - 1. In CircleCI, navigate to the repository's Project Settings.
12 - 2. In the Advanced tab, ensure that "Pass secrets to builds from forked pull requests" is set to false.
13 - 3. In the Environment Variables tab, add the Vercel token as a new `SCHEDULING_PROFILER_DEPLOY_VERCEL_TOKEN` environment variable.
14 -
15 -The Vercel project will be created when the deploy job runs.
3 +This package contains the new/experimental "scheduling profiler" for React 18. This profiler exists as its own project because it was initially deployed as a standalone app. It has since been moved into the DevTools Profiler under the "Scheduling" tab. This package will likely eventually be moved into `react-devtools-shared`.
\ No newline at end of file
packages/react-devtools-scheduling-profiler/buildUtils.js deleted
-36
@@ -1,36 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -const {execSync} = require('child_process');
9 -const {readFileSync} = require('fs');
10 -const {resolve} = require('path');
11 -
12 -function getGitCommit() {
13 - try {
14 - return execSync('git show -s --format=%h')
15 - .toString()
16 - .trim();
17 - } catch (error) {
18 - // Mozilla runs this command from a git archive.
19 - // In that context, there is no Git revision.
20 - return null;
21 - }
22 -}
23 -
24 -function getVersionString() {
25 - const packageVersion = JSON.parse(
26 - readFileSync(resolve(__dirname, './package.json')),
27 - ).version;
28 -
29 - const commit = getGitCommit();
30 -
31 - return `${packageVersion}-${commit}`;
32 -}
33 -
34 -module.exports = {
35 - getVersionString,
36 -};
packages/react-devtools-scheduling-profiler/package.json
+1 -5
@@ -1,12 +1,8 @@
1 {
2 "private": true,
3 "name": "react-devtools-scheduling-profiler",
4 - "version": "0.0.0",
4 + "version": "4.14.0",
5 "license": "MIT",
6 - "scripts": {
7 - "build": "cross-env NODE_ENV=production cross-env TARGET=remote webpack --config webpack.config.js",
8 - "start": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open"
9 - },
6 "dependencies": {
7 "@elg/speedscope": "1.9.0-a6f84db",
8 "clipboard-js": "^0.3.6",
packages/react-devtools-scheduling-profiler/src/App.css deleted
-19
@@ -1,19 +0,0 @@
1 -.DevTools {
2 - width: 100%;
3 - height: 100%;
4 - display: flex;
5 - flex-direction: column;
6 - background-color: var(--color-background);
7 - color: var(--color-text);
8 -}
9 -
10 -.TabContent {
11 - flex: 1 1 100%;
12 - overflow: auto;
13 - -webkit-app-region: no-drag;
14 -}
15 -
16 -.DevTools, .DevTools * {
17 - box-sizing: border-box;
18 - -webkit-font-smoothing: var(--font-smoothing);
19 -}
packages/react-devtools-scheduling-profiler/src/App.js deleted
-34
@@ -1,34 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -// Reach styles need to come before any component styles.
11 -// This makes overriding the styles simpler.
12 -import '@reach/menu-button/styles.css';
13 -import '@reach/tooltip/styles.css';
14 -
15 -import * as React from 'react';
16 -
17 -import {SchedulingProfiler} from './SchedulingProfiler';
18 -import {useBrowserTheme, useDisplayDensity} from './hooks';
19 -
20 -import styles from './App.css';
21 -import 'react-devtools-shared/src/devtools/views/root.css';
22 -
23 -export default function App() {
24 - useBrowserTheme();
25 - useDisplayDensity();
26 -
27 - return (
28 - <div className={styles.DevTools}>
29 - <div className={styles.TabContent}>
30 - <SchedulingProfiler />
31 - </div>
32 - </div>
33 - );
34 -}
packages/react-devtools-scheduling-profiler/src/CanvasPage.css
+4 -4
@@ -1,7 +1,7 @@
1 .CanvasPage {
2 position: absolute;
3 - top: 0.5rem;
4 - bottom: 0.5rem;
5 - left: 0.5rem;
6 - right: 0.5rem;
3 + top: 0;
4 + bottom: 0;
5 + left: 0;
6 + right: 0;
7 }
packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+11 -6
@@ -52,9 +52,9 @@ import {
52 import {COLORS} from './content-views/constants';
53
54 import EventTooltip from './EventTooltip';
55 -import ContextMenu from './context/ContextMenu';
56 -import ContextMenuItem from './context/ContextMenuItem';
57 -import useContextMenu from './context/useContextMenu';
55 +import ContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenu';
56 +import ContextMenuItem from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem';
57 +import useContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/useContextMenu';
58 import {getBatchRange} from './utils/getBatchRange';
59
60 import styles from './CanvasPage.css';
@@ -94,6 +94,7 @@ const copySummary = (data: ReactProfilerData, measure: ReactMeasure) => {
94 );
95 };
96
97 +// TODO (scheduling profiler) Why is the "zoom" feature so much slower than normal rendering?
98 const zoomToBatch = (
99 data: ReactProfilerData,
100 measure: ReactMeasure,
@@ -102,8 +103,7 @@ const zoomToBatch = (
103 const {batchUID} = measure;
104 const [startTime, stopTime] = getBatchRange(batchUID, data);
105 syncedHorizontalPanAndZoomViews.forEach(syncedView =>
105 - // Using time as range works because the views' intrinsic content size is
106 - // based on time.
106 + // Using time as range works because the views' intrinsic content size is based on time.
107 syncedView.zoomToRange(startTime, stopTime),
108 );
109 };
@@ -243,6 +243,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
243 defaultFrame,
244 reactMeasuresHorizontalPanAndZoomView,
245 flamechartHorizontalPanAndZoomView,
246 + canvasRef,
247 );
248
249 const rootView = new View(
@@ -281,13 +282,17 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
282
283 useCanvasInteraction(canvasRef, interactor);
284
285 + const setIsContextMenuShownWrapper = (...args) => {
286 + console.log('setIsContextMenuShown()', ...args);
287 + setIsContextMenuShown(...args);
288 + };
289 useContextMenu({
290 data: {
291 data,
292 hoveredEvent,
293 },
294 id: CONTEXT_MENU_ID,
290 - onChange: setIsContextMenuShown,
295 + onChange: setIsContextMenuShownWrapper,
296 ref: canvasRef,
297 });
298
packages/react-devtools-scheduling-profiler/src/EventTooltip.css
+9 -8
@@ -6,9 +6,10 @@
6 padding: 0.25rem;
7 user-select: none;
8 pointer-events: none;
9 - background-color: #ffffff;
10 - border: 1px solid #ccc;
11 - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
9 + background-color: var(--color-tooltip-background);
10 + border: 1px solid var(border);
11 + box-shadow: 1px 1px 2px var(--color-shadow);
12 + color: var(--color-tooltip-text);
13 font-size: 11px;
14 }
15
@@ -26,7 +27,7 @@
27 }
28
29 .DetailsGridLabel {
29 - color: #666;
30 + color: var(--color-dim);
31 text-align: right;
32 }
33
@@ -56,14 +57,14 @@
57 line-height: 1.5;
58 -webkit-mask-image: linear-gradient(
59 180deg,
59 - #fff,
60 - #fff 5em,
60 + var(--color-tooltip-background),
61 + var(--color-tooltip-background) 5em,
62 transparent
63 );
64 mask-image: linear-gradient(
65 180deg,
65 - #fff,
66 - #fff 5em,
66 + var(--color-tooltip-background),
67 + var(--color-tooltip-background) 5em,
68 transparent
69 );
70 white-space: pre;
packages/react-devtools-scheduling-profiler/src/ImportButton.js
+3 -2
@@ -9,17 +9,17 @@
9
10 import * as React from 'react';
11 import {useCallback, useRef} from 'react';
12 -
12 import Button from 'react-devtools-shared/src/devtools/views/Button';
13 import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
14
15 import styles from './ImportButton.css';
16
17 type Props = {|
18 + children?: mixed,
19 onFileSelect: (file: File) => void,
20 |};
21
22 -export default function ImportButton({onFileSelect}: Props) {
22 +export default function ImportButton({children = null, onFileSelect}: Props) {
23 const inputRef = useRef<HTMLInputElement | null>(null);
24
25 const handleFiles = useCallback(() => {
@@ -51,6 +51,7 @@ export default function ImportButton({onFileSelect}: Props) {
51 />
52 <Button onClick={uploadData} title="Load profile...">
53 <ButtonIcon type="import" />
54 + {children}
55 </Button>
56 </>
57 );
packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.css
+14 -61
@@ -1,21 +1,5 @@
1 -.SchedulingProfiler {
2 - width: 100%;
3 - height: 100%;
4 - position: relative;
5 - display: flex;
6 - flex-direction: column;
7 - font-family: var(--font-family-sans);
8 - font-size: var(--font-size-sans-normal);
9 - background-color: var(--color-background);
10 - color: var(--color-text);
11 -}
12 -
13 -.SchedulingProfiler, .SchedulingProfiler * {
14 - box-sizing: border-box;
15 - -webkit-font-smoothing: var(--font-smoothing);
16 -}
17 -
1 .Content {
2 + width: 100%;
3 position: relative;
4 flex: 1 1 auto;
5 display: flex;
@@ -52,57 +36,26 @@
36 margin-bottom: 0.5rem;
37 }
38
55 -.Toolbar {
56 - height: 2.25rem;
57 - padding: 0 0.25rem;
58 - flex: 0 0 auto;
59 - display: flex;
60 - align-items: center;
61 - border-bottom: 1px solid var(--color-border);
62 -}
63 -
64 -.VRule {
65 - height: 20px;
66 - width: 1px;
67 - border-left: 1px solid var(--color-border);
68 - padding-left: 0.25rem;
69 - margin-left: 0.25rem;
70 -}
71 -
72 -.Spacer {
73 - flex: 1;
74 -}
75 -
76 -.Link {
77 - color: var(--color-button);
78 -}
79 -
80 -.ScreenshotWrapper {
81 - max-width: 30rem;
82 - padding: 0 1rem;
83 - margin-bottom: 2rem;
39 +.WelcomeInstructionsList {
40 }
41
86 -.Screenshot {
87 - width: 100%;
88 - border-radius: 0.4em;
89 - border: 2px solid var(--color-border);
42 +.WelcomeInstructionsListItem {
43 + display: flex;
44 + align-items: center;
45 + line-height: 1.5rem;
46 + counter-increment: li;
47 }
48
92 -.AppName {
93 - font-size: var(--font-size-sans-large);
49 +.WelcomeInstructionsListItem::before {
50 + content: counter(li);
51 margin-right: 0.5rem;
95 - user-select: none;
52 }
53
98 -@media screen and (max-width: 350px) {
99 - .AppName {
100 - display: none;
101 - }
54 +.WelcomeInstructionsListItemLink {
55 + color: var(--color-link);
56 + margin-left: 0.25rem;
57 }
58
104 -@media screen and (max-height: 600px) {
105 - .ScreenshotWrapper {
106 - display: none;
107 - }
59 +.ImportButtonLabel {
60 + margin-left: 0.25rem;
61 }
\ No newline at end of file
packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
+66 -80
@@ -7,102 +7,88 @@
7 * @flow
8 */
9
10 -import type {Resource} from 'react-devtools-shared/src/devtools/cache';
11 -import type {ReactProfilerData} from './types';
12 -import type {ImportWorkerOutputData} from './import-worker/import.worker';
10 +import type {DataResource} from './createDataResourceFromImportedFile';
11
12 import * as React from 'react';
15 -import {Suspense, useCallback, useState} from 'react';
16 -import {createResource} from 'react-devtools-shared/src/devtools/cache';
17 -import ReactLogo from 'react-devtools-shared/src/devtools/views/ReactLogo';
18 -
13 +import {
14 + Suspense,
15 + useContext,
16 + useDeferredValue,
17 + useLayoutEffect,
18 + useState,
19 +} from 'react';
20 +import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
21 +import {updateColorsToMatchTheme} from './content-views/constants';
22 +import {SchedulingProfilerContext} from './SchedulingProfilerContext';
23 import ImportButton from './ImportButton';
24 import CanvasPage from './CanvasPage';
21 -import ImportWorker from './import-worker/import.worker';
25
23 -import profilerBrowser from './assets/profilerBrowser.png';
26 import styles from './SchedulingProfiler.css';
27
26 -type DataResource = Resource<void, File, ReactProfilerData | Error>;
27 -
28 -function createDataResourceFromImportedFile(file: File): DataResource {
29 - return createResource(
30 - () => {
31 - return new Promise<ReactProfilerData | Error>((resolve, reject) => {
32 - const worker: Worker = new (ImportWorker: any)();
33 -
34 - worker.onmessage = function(event) {
35 - const data = ((event.data: any): ImportWorkerOutputData);
36 - switch (data.status) {
37 - case 'SUCCESS':
38 - resolve(data.processedData);
39 - break;
40 - case 'INVALID_PROFILE_ERROR':
41 - resolve(data.error);
42 - break;
43 - case 'UNEXPECTED_ERROR':
44 - reject(data.error);
45 - break;
46 - }
47 - worker.terminate();
48 - };
49 -
50 - worker.postMessage({file});
51 - });
52 - },
53 - () => file,
54 - {useWeakMap: true},
55 - );
56 -}
57 -
28 export function SchedulingProfiler(_: {||}) {
59 - const [dataResource, setDataResource] = useState<DataResource | null>(null);
29 + const {importSchedulingProfilerData, schedulingProfilerData} = useContext(
30 + SchedulingProfilerContext,
31 + );
32
61 - const handleFileSelect = useCallback((file: File) => {
62 - setDataResource(createDataResourceFromImportedFile(file));
63 - }, []);
33 + // HACK: Canvas rendering uses an imperative API,
34 + // but DevTools colors are stored in CSS variables (see root.css and SettingsContext).
35 + // When the theme changes, we need to trigger update the imperative colors and re-draw the Canvas.
36 + const {theme} = useContext(SettingsContext);
37 + // HACK: SettingsContext also uses a useLayoutEffect to update styles;
38 + // make sure the theme context in SettingsContext updates before this code.
39 + const deferredTheme = useDeferredValue(theme);
40 + // HACK: Schedule a re-render of the Canvas once colors have been updated.
41 + // The easiest way to guarangee this happens is to recreate the inner Canvas component.
42 + const [key, setKey] = useState<string>(theme);
43 + useLayoutEffect(() => {
44 + updateColorsToMatchTheme();
45 + setKey(deferredTheme);
46 + }, [deferredTheme]);
47
48 return (
66 - <div className={styles.SchedulingProfiler}>
67 - <div className={styles.Toolbar}>
68 - <ReactLogo />
69 - <span className={styles.AppName}>Concurrent Mode Profiler</span>
70 - <div className={styles.VRule} />
71 - <ImportButton onFileSelect={handleFileSelect} />
72 - <div className={styles.Spacer} />
73 - </div>
74 - <div className={styles.Content}>
75 - {dataResource ? (
76 - <Suspense fallback={<ProcessingData />}>
77 - <DataResourceComponent
78 - dataResource={dataResource}
79 - onFileSelect={handleFileSelect}
80 - />
81 - </Suspense>
82 - ) : (
83 - <Welcome onFileSelect={handleFileSelect} />
84 - )}
85 - </div>
49 + <div className={styles.Content}>
50 + {schedulingProfilerData ? (
51 + <Suspense fallback={<ProcessingData />}>
52 + <DataResourceComponent
53 + dataResource={schedulingProfilerData}
54 + key={key}
55 + onFileSelect={importSchedulingProfilerData}
56 + />
57 + </Suspense>
58 + ) : (
59 + <Welcome onFileSelect={importSchedulingProfilerData} />
60 + )}
61 </div>
62 );
63 }
64
65 const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
91 - <div className={styles.EmptyStateContainer}>
92 - <div className={styles.ScreenshotWrapper}>
93 - <img
94 - src={profilerBrowser}
95 - className={styles.Screenshot}
96 - alt="Profiler screenshot"
97 - />
98 - </div>
99 - <div className={styles.Header}>Welcome!</div>
100 - <div className={styles.Row}>
101 - Click the import button
102 - <ImportButton onFileSelect={onFileSelect} /> to import a Chrome
103 - performance profile.
104 - </div>
105 - </div>
66 + <ol className={styles.WelcomeInstructionsList}>
67 + <li className={styles.WelcomeInstructionsListItem}>
68 + Open a website that's built with the
69 + <a
70 + className={styles.WelcomeInstructionsListItemLink}
71 + href="https://reactjs.org/link/profiling"
72 + rel="noopener noreferrer"
73 + target="_blank">
74 + profiling build of ReactDOM
75 + </a>
76 + .
77 + </li>
78 + <li className={styles.WelcomeInstructionsListItem}>
79 + Open the "Performance" tab in Chrome and record some performance data.
80 + </li>
81 + <li className={styles.WelcomeInstructionsListItem}>
82 + Click the "Save profile..." button in Chrome to export the data.
83 + </li>
84 + <li className={styles.WelcomeInstructionsListItem}>
85 + Import the data into the profiler:
86 + <br />
87 + <ImportButton onFileSelect={onFileSelect}>
88 + <span className={styles.ImportButtonLabel}>Import</span>
89 + </ImportButton>
90 + </li>
91 + </ol>
92 );
93
94 const ProcessingData = () => (
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js new
+62
@@ -0,0 +1,62 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +import {createContext, useCallback, useMemo, useState} from 'react';
12 +import createDataResourceFromImportedFile from './createDataResourceFromImportedFile';
13 +
14 +import type {DataResource} from './createDataResourceFromImportedFile';
15 +
16 +export type Context = {|
17 + importSchedulingProfilerData: (file: File) => void,
18 + schedulingProfilerData: DataResource | null,
19 +|};
20 +
21 +const SchedulingProfilerContext = createContext<Context>(
22 + ((null: any): Context),
23 +);
24 +SchedulingProfilerContext.displayName = 'SchedulingProfilerContext';
25 +
26 +type Props = {|
27 + children: React$Node,
28 +|};
29 +
30 +function SchedulingProfilerContextController({children}: Props) {
31 + const [
32 + schedulingProfilerData,
33 + setSchedulingProfilerData,
34 + ] = useState<DataResource | null>(null);
35 +
36 + const importSchedulingProfilerData = useCallback((file: File) => {
37 + setSchedulingProfilerData(createDataResourceFromImportedFile(file));
38 + }, []);
39 +
40 + // TODO (scheduling profiler) Start/stop time ref here?
41 +
42 + const value = useMemo(
43 + () => ({
44 + importSchedulingProfilerData,
45 + schedulingProfilerData,
46 + // TODO (scheduling profiler)
47 + }),
48 + [
49 + importSchedulingProfilerData,
50 + schedulingProfilerData,
51 + // TODO (scheduling profiler)
52 + ],
53 + );
54 +
55 + return (
56 + <SchedulingProfilerContext.Provider value={value}>
57 + {children}
58 + </SchedulingProfilerContext.Provider>
59 + );
60 +}
61 +
62 +export {SchedulingProfilerContext, SchedulingProfilerContextController};
packages/react-devtools-scheduling-profiler/src/assets/logo.svg deleted
-1
@@ -1 +0,0 @@
1 -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170.96 159.93"><defs><style>.cls-1{fill:#09d3ac;}</style></defs><path class="cls-1" d="M141.35,73.27c0-6.89-8.11-13-20.58-16.73,3-12.67,1.69-22.75-4.28-26.16a9.55,9.55,0,0,0-4.79-1.2c-5.57,0-12.61,3.89-19.72,10.62-7.11-6.68-14.13-10.55-19.69-10.55a9.46,9.46,0,0,0-4.86,1.22c-5.94,3.42-7.17,13.44-4.21,26.05-12.41,3.71-20.48,9.77-20.5,16.63s8.12,13,20.59,16.72c-3,12.68-1.7,22.75,4.28,26.16a9.41,9.41,0,0,0,4.78,1.2c5.58,0,12.62-3.89,19.73-10.62,7.1,6.68,14.12,10.55,19.69,10.55a9.59,9.59,0,0,0,4.86-1.22c5.94-3.42,7.16-13.44,4.21-26C133.27,86.18,141.34,80.12,141.35,73.27ZM96.56,42.06c8.19-7.33,13.31-8.12,15.13-8.12h0a4.71,4.71,0,0,1,2.42.58c2.86,1.63,4,7.38,3.09,15a56.73,56.73,0,0,1-1,5.79,97.7,97.7,0,0,0-12.58-2.07,98.47,98.47,0,0,0-8.24-10.08C95.75,42.79,96.15,42.42,96.56,42.06ZM71.77,78.94c.78,1.5,1.61,3,2.47,4.51S76,86.49,77,88c-2.71-.39-5.34-.88-7.84-1.46C69.86,84,70.75,81.5,71.77,78.94ZM69,59.9c2.53-.59,5.18-1.08,7.93-1.46-1,1.48-1.86,3-2.76,4.59s-1.69,3-2.46,4.52Q70.18,63.65,69,59.9Zm5.21,13.34q1.86-3.93,4.09-7.86c1.5-2.62,3.11-5.17,4.77-7.61,2.91-.22,5.91-.34,9-.33s6,.12,8.89.35c1.66,2.42,3.25,5,4.75,7.55s2.88,5.22,4.12,7.83c-1.23,2.62-2.6,5.25-4.08,7.85s-3.11,5.18-4.77,7.62c-2.91.23-5.91.34-9,.34s-6-.13-8.89-.36c-1.66-2.42-3.26-4.94-4.76-7.55S75.5,75.85,74.25,73.24Zm32.86-14.77c2.72.38,5.35.87,7.84,1.45-.74,2.47-1.62,5-2.64,7.55-.79-1.5-1.61-3-2.48-4.51S108,59.93,107.11,58.47Zm2.76,24.92q1.29-2.27,2.46-4.53c1,2.6,2,5.16,2.7,7.66-2.52.59-5.17,1.07-7.92,1.45Q108.52,85.75,109.87,83.39ZM92,46.56c1.8,1.92,3.57,4,5.3,6.23-1.71-.07-3.46-.12-5.23-.12s-3.58,0-5.33.12C88.45,50.57,90.2,48.48,92,46.56Zm-22.18-12A4.82,4.82,0,0,1,72.29,34a13.11,13.11,0,0,1,5.19,1.31,39.07,39.07,0,0,1,10,6.78l1.17,1.07a98.49,98.49,0,0,0-8.16,10,98.88,98.88,0,0,0-12.65,2.06c-.44-1.94-.8-3.84-1-5.67C65.8,42,67,36.24,69.81,34.6ZM64.53,85.26a58.75,58.75,0,0,1-5.54-2c-7.1-3-11.5-6.85-11.5-10.14S51.91,66,59,63.05a57.63,57.63,0,0,1,5.44-1.94A97.93,97.93,0,0,0,69,73.25,98.72,98.72,0,0,0,64.53,85.26Zm23,19.1c-8.19,7.33-13.31,8.11-15.14,8.11a4.69,4.69,0,0,1-2.42-.58c-2.86-1.63-4-7.38-3.09-15a56.07,56.07,0,0,1,1-5.78,99.51,99.51,0,0,0,12.58,2.06,97.17,97.17,0,0,0,8.24,10.08Zm4.57-4.51c-1.8-1.92-3.57-4-5.31-6.23,1.72.08,3.47.12,5.24.12s3.58,0,5.33-.11C95.63,95.85,93.87,97.93,92.09,99.85Zm22.18,12a4.82,4.82,0,0,1-2.48.59c-1.82,0-7-.8-15.16-8.1l-1.17-1.07a98.44,98.44,0,0,0,8.15-10,97,97,0,0,0,12.66-2.06c.44,1.94.79,3.84,1,5.67C118.27,104.42,117.12,110.18,114.27,111.81Zm10.8-28.44c-1.71.7-3.52,1.35-5.44,1.93a98.54,98.54,0,0,0-4.57-12.14,98.1,98.1,0,0,0,4.49-12,58.75,58.75,0,0,1,5.54,2c7.09,3,11.5,6.85,11.49,10.14S132.17,80.42,125.07,83.37ZM92,82.39a9.18,9.18,0,1,0-9.17-9.19A9.17,9.17,0,0,0,92,82.39ZM31,17.88V128.53H153.07V17.88ZM148.3,123.77H35.78V22.65H148.3Zm-85-33.9c-3,12.68-1.7,22.75,4.28,26.16a9.41,9.41,0,0,0,4.78,1.2c5.58,0,12.62-3.89,19.73-10.62,7.1,6.68,14.12,10.55,19.69,10.55a9.59,9.59,0,0,0,4.86-1.22c5.94-3.42,7.16-13.44,4.21-26,12.41-3.72,20.48-9.78,20.49-16.63s-8.11-13-20.58-16.73c3-12.67,1.69-22.75-4.28-26.16a9.55,9.55,0,0,0-4.79-1.2c-5.57,0-12.61,3.89-19.72,10.62-7.11-6.68-14.13-10.55-19.69-10.55a9.46,9.46,0,0,0-4.86,1.22c-5.94,3.42-7.17,13.44-4.21,26.05-12.41,3.71-20.48,9.77-20.5,16.63S50.84,86.13,63.31,89.87Zm24.21,14.49c-8.19,7.33-13.31,8.11-15.14,8.11a4.69,4.69,0,0,1-2.42-.58c-2.86-1.63-4-7.38-3.09-15a56.07,56.07,0,0,1,1-5.78,99.51,99.51,0,0,0,12.58,2.06,97.17,97.17,0,0,0,8.24,10.08Zm24.79-36.89c-.79-1.5-1.61-3-2.48-4.51s-1.8-3-2.72-4.49c2.72.38,5.35.87,7.84,1.45C114.21,62.39,113.33,64.92,112.31,67.47Zm2.72,19c-2.52.59-5.17,1.07-7.92,1.45q1.41-2.22,2.76-4.58t2.46-4.53C113.37,81.46,114.28,84,115,86.52Zm-5.21-13.35c-1.23,2.62-2.6,5.25-4.08,7.85s-3.11,5.18-4.77,7.62c-2.91.23-5.91.34-9,.34s-6-.13-8.89-.36c-1.66-2.42-3.26-4.94-4.76-7.55s-2.87-5.22-4.12-7.83q1.86-3.93,4.09-7.86c1.5-2.62,3.11-5.17,4.77-7.61,2.91-.22,5.91-.34,9-.33s6,.12,8.89.35c1.66,2.42,3.25,5,4.75,7.55S108.58,70.56,109.82,73.17ZM77,88c-2.71-.39-5.34-.88-7.84-1.46.74-2.46,1.63-5,2.65-7.55.78,1.5,1.61,3,2.47,4.51S76,86.49,77,88ZM74.21,63c-.87,1.5-1.69,3-2.46,4.52Q70.18,63.65,69,59.9c2.53-.59,5.18-1.08,7.93-1.46C76,59.92,75.11,61.45,74.21,63ZM92.09,99.85c-1.8-1.92-3.57-4-5.31-6.23,1.72.08,3.47.12,5.24.12s3.58,0,5.33-.11C95.63,95.85,93.87,97.93,92.09,99.85Zm22.18,12a4.82,4.82,0,0,1-2.48.59c-1.82,0-7-.8-15.16-8.1l-1.17-1.07a98.44,98.44,0,0,0,8.15-10,97,97,0,0,0,12.66-2.06c.44,1.94.79,3.84,1,5.67C118.27,104.42,117.12,110.18,114.27,111.81Zm5.28-50.66a58.75,58.75,0,0,1,5.54,2c7.09,3,11.5,6.85,11.49,10.14s-4.41,7.16-11.51,10.11c-1.71.7-3.52,1.35-5.44,1.93a98.54,98.54,0,0,0-4.57-12.14A98.1,98.1,0,0,0,119.55,61.15Zm-23-19.09c8.19-7.33,13.31-8.12,15.13-8.12h0a4.71,4.71,0,0,1,2.42.58c2.86,1.63,4,7.38,3.09,15a56.73,56.73,0,0,1-1,5.79,97.7,97.7,0,0,0-12.58-2.07,98.47,98.47,0,0,0-8.24-10.08C95.75,42.79,96.15,42.42,96.56,42.06ZM92,46.56c1.8,1.92,3.57,4,5.3,6.23-1.71-.07-3.46-.12-5.23-.12s-3.58,0-5.33.12C88.45,50.57,90.2,48.48,92,46.56Zm-22.18-12A4.82,4.82,0,0,1,72.29,34a13.11,13.11,0,0,1,5.19,1.31,39.07,39.07,0,0,1,10,6.78l1.17,1.07a98.49,98.49,0,0,0-8.16,10,98.88,98.88,0,0,0-12.65,2.06c-.44-1.94-.8-3.84-1-5.67C65.8,42,67,36.24,69.81,34.6ZM59,63.05a57.63,57.63,0,0,1,5.44-1.94A97.93,97.93,0,0,0,69,73.25a98.72,98.72,0,0,0-4.49,12,58.75,58.75,0,0,1-5.54-2c-7.1-3-11.5-6.85-11.5-10.14S51.91,66,59,63.05Zm33,1a9.18,9.18,0,1,0,9.17,9.19A9.17,9.17,0,0,0,92,64Zm0,0a9.18,9.18,0,1,0,9.17,9.19A9.17,9.17,0,0,0,92,64Zm0,0a9.18,9.18,0,1,0,9.17,9.19A9.17,9.17,0,0,0,92,64Zm49.35,9.24c0-6.89-8.11-13-20.58-16.73,3-12.67,1.69-22.75-4.28-26.16a9.55,9.55,0,0,0-4.79-1.2c-5.57,0-12.61,3.89-19.72,10.62-7.11-6.68-14.13-10.55-19.69-10.55a9.46,9.46,0,0,0-4.86,1.22c-5.94,3.42-7.17,13.44-4.21,26.05-12.41,3.71-20.48,9.77-20.5,16.63s8.12,13,20.59,16.72c-3,12.68-1.7,22.75,4.28,26.16a9.41,9.41,0,0,0,4.78,1.2c5.58,0,12.62-3.89,19.73-10.62,7.1,6.68,14.12,10.55,19.69,10.55a9.59,9.59,0,0,0,4.86-1.22c5.94-3.42,7.16-13.44,4.21-26C133.27,86.18,141.34,80.12,141.35,73.27ZM96.56,42.06c8.19-7.33,13.31-8.12,15.13-8.12h0a4.71,4.71,0,0,1,2.42.58c2.86,1.63,4,7.38,3.09,15a56.73,56.73,0,0,1-1,5.79,97.7,97.7,0,0,0-12.58-2.07,98.47,98.47,0,0,0-8.24-10.08C95.75,42.79,96.15,42.42,96.56,42.06ZM71.77,78.94c.78,1.5,1.61,3,2.47,4.51S76,86.49,77,88c-2.71-.39-5.34-.88-7.84-1.46C69.86,84,70.75,81.5,71.77,78.94ZM69,59.9c2.53-.59,5.18-1.08,7.93-1.46-1,1.48-1.86,3-2.76,4.59s-1.69,3-2.46,4.52Q70.18,63.65,69,59.9Zm5.21,13.34q1.86-3.93,4.09-7.86c1.5-2.62,3.11-5.17,4.77-7.61,2.91-.22,5.91-.34,9-.33s6,.12,8.89.35c1.66,2.42,3.25,5,4.75,7.55s2.88,5.22,4.12,7.83c-1.23,2.62-2.6,5.25-4.08,7.85s-3.11,5.18-4.77,7.62c-2.91.23-5.91.34-9,.34s-6-.13-8.89-.36c-1.66-2.42-3.26-4.94-4.76-7.55S75.5,75.85,74.25,73.24Zm32.86-14.77c2.72.38,5.35.87,7.84,1.45-.74,2.47-1.62,5-2.64,7.55-.79-1.5-1.61-3-2.48-4.51S108,59.93,107.11,58.47Zm2.76,24.92q1.29-2.27,2.46-4.53c1,2.6,2,5.16,2.7,7.66-2.52.59-5.17,1.07-7.92,1.45Q108.52,85.75,109.87,83.39ZM92,46.56c1.8,1.92,3.57,4,5.3,6.23-1.71-.07-3.46-.12-5.23-.12s-3.58,0-5.33.12C88.45,50.57,90.2,48.48,92,46.56Zm-22.18-12A4.82,4.82,0,0,1,72.29,34a13.11,13.11,0,0,1,5.19,1.31,39.07,39.07,0,0,1,10,6.78l1.17,1.07a98.49,98.49,0,0,0-8.16,10,98.88,98.88,0,0,0-12.65,2.06c-.44-1.94-.8-3.84-1-5.67C65.8,42,67,36.24,69.81,34.6ZM64.53,85.26a58.75,58.75,0,0,1-5.54-2c-7.1-3-11.5-6.85-11.5-10.14S51.91,66,59,63.05a57.63,57.63,0,0,1,5.44-1.94A97.93,97.93,0,0,0,69,73.25,98.72,98.72,0,0,0,64.53,85.26Zm23,19.1c-8.19,7.33-13.31,8.11-15.14,8.11a4.69,4.69,0,0,1-2.42-.58c-2.86-1.63-4-7.38-3.09-15a56.07,56.07,0,0,1,1-5.78,99.51,99.51,0,0,0,12.58,2.06,97.17,97.17,0,0,0,8.24,10.08Zm4.57-4.51c-1.8-1.92-3.57-4-5.31-6.23,1.72.08,3.47.12,5.24.12s3.58,0,5.33-.11C95.63,95.85,93.87,97.93,92.09,99.85Zm22.18,12a4.82,4.82,0,0,1-2.48.59c-1.82,0-7-.8-15.16-8.1l-1.17-1.07a98.44,98.44,0,0,0,8.15-10,97,97,0,0,0,12.66-2.06c.44,1.94.79,3.84,1,5.67C118.27,104.42,117.12,110.18,114.27,111.81Zm10.8-28.44c-1.71.7-3.52,1.35-5.44,1.93a98.54,98.54,0,0,0-4.57-12.14,98.1,98.1,0,0,0,4.49-12,58.75,58.75,0,0,1,5.54,2c7.09,3,11.5,6.85,11.49,10.14S132.17,80.42,125.07,83.37ZM92,82.39a9.18,9.18,0,1,0-9.17-9.19A9.17,9.17,0,0,0,92,82.39Zm-69.32,54.5V26.2L17.89,31V141.66H139.94l4.78-4.77Z"/></svg>
\ No newline at end of file
packages/react-devtools-scheduling-profiler/src/assets/profilerBrowser.png
Binary files a/packages/react-devtools-scheduling-profiler/src/assets/profilerBrowser.png and /dev/null differ
packages/react-devtools-scheduling-profiler/src/assets/reactlogo.svg deleted
-7
@@ -1,7 +0,0 @@
1 -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
2 - <g fill="#61DAFB">
3 - <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
4 - <circle cx="420.9" cy="296.5" r="45.7"/>
5 - <path d="M520.5 78.1z"/>
6 - </g>
7 -</svg>
packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
+1 -1
@@ -203,7 +203,7 @@ class FlamechartStackLayerView extends View {
203 );
204
205 if (trimmedName !== null) {
206 - context.fillStyle = COLORS.PRIORITY_LABEL;
206 + context.fillStyle = COLORS.FLAME_GRAPH_LABEL;
207
208 // Prevent text from being drawn outside `viewableArea`
209 const textOverflowsViewableArea = !rectEqualToRect(
packages/react-devtools-scheduling-profiler/src/content-views/constants.js
+126 -31
@@ -41,34 +41,129 @@ export const FLAMECHART_FONT_SIZE = 10;
41 export const FLAMECHART_FRAME_HEIGHT = 16;
42 export const FLAMECHART_TEXT_PADDING = 3;
43
44 -export const COLORS = Object.freeze({
45 - BACKGROUND: '#ffffff',
46 - PRIORITY_BACKGROUND: '#ededf0',
47 - PRIORITY_BORDER: '#d7d7db',
48 - PRIORITY_LABEL: '#272727',
49 - USER_TIMING: '#c9cacd',
50 - USER_TIMING_HOVER: '#93959a',
51 - REACT_IDLE: '#edf6ff',
52 - REACT_IDLE_SELECTED: '#EDF6FF',
53 - REACT_IDLE_HOVER: '#EDF6FF',
54 - REACT_RENDER: '#9fc3f3',
55 - REACT_RENDER_SELECTED: '#64A9F5',
56 - REACT_RENDER_HOVER: '#2683E2',
57 - REACT_COMMIT: '#ff718e',
58 - REACT_COMMIT_SELECTED: '#FF5277',
59 - REACT_COMMIT_HOVER: '#ed0030',
60 - REACT_LAYOUT_EFFECTS: '#c88ff0',
61 - REACT_LAYOUT_EFFECTS_SELECTED: '#934FC1',
62 - REACT_LAYOUT_EFFECTS_HOVER: '#601593',
63 - REACT_PASSIVE_EFFECTS: '#c88ff0',
64 - REACT_PASSIVE_EFFECTS_SELECTED: '#934FC1',
65 - REACT_PASSIVE_EFFECTS_HOVER: '#601593',
66 - REACT_SCHEDULE: '#9fc3f3',
67 - REACT_SCHEDULE_HOVER: '#2683E2',
68 - REACT_SCHEDULE_CASCADING: '#ff718e',
69 - REACT_SCHEDULE_CASCADING_HOVER: '#ed0030',
70 - REACT_SUSPEND: '#a6e59f',
71 - REACT_SUSPEND_HOVER: '#13bc00',
72 - REACT_WORK_BORDER: '#ffffff',
73 - TIME_MARKER_LABEL: '#18212b',
74 -});
44 +// TODO Replace this with "export let" vars
45 +export let COLORS = {
46 + BACKGROUND: '',
47 + PRIORITY_BACKGROUND: '',
48 + PRIORITY_BORDER: '',
49 + PRIORITY_LABEL: '',
50 + FLAME_GRAPH_LABEL: '',
51 + USER_TIMING: '',
52 + USER_TIMING_HOVER: '',
53 + REACT_IDLE: '',
54 + REACT_IDLE_SELECTED: '',
55 + REACT_IDLE_HOVER: '',
56 + REACT_RENDER: '',
57 + REACT_RENDER_SELECTED: '',
58 + REACT_RENDER_HOVER: '',
59 + REACT_COMMIT: '',
60 + REACT_COMMIT_SELECTED: '',
61 + REACT_COMMIT_HOVER: '',
62 + REACT_LAYOUT_EFFECTS: '',
63 + REACT_LAYOUT_EFFECTS_SELECTED: '',
64 + REACT_LAYOUT_EFFECTS_HOVER: '',
65 + REACT_PASSIVE_EFFECTS: '',
66 + REACT_PASSIVE_EFFECTS_SELECTED: '',
67 + REACT_PASSIVE_EFFECTS_HOVER: '',
68 + REACT_RESIZE_BAR: '',
69 + REACT_SCHEDULE: '',
70 + REACT_SCHEDULE_HOVER: '',
71 + REACT_SCHEDULE_CASCADING: '',
72 + REACT_SCHEDULE_CASCADING_HOVER: '',
73 + REACT_SUSPEND: '',
74 + REACT_SUSPEND_HOVER: '',
75 + REACT_WORK_BORDER: '',
76 + TIME_MARKER_LABEL: '',
77 +};
78 +
79 +export function updateColorsToMatchTheme(): void {
80 + const computedStyle = getComputedStyle((document.body: any));
81 +
82 + COLORS = {
83 + BACKGROUND: computedStyle.getPropertyValue('--color-background'),
84 + PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
85 + '--color-scheduling-profiler-priority-background',
86 + ),
87 + PRIORITY_BORDER: computedStyle.getPropertyValue(
88 + '--color-scheduling-profiler-priority-border',
89 + ),
90 + PRIORITY_LABEL: computedStyle.getPropertyValue('--color-text'),
91 + FLAME_GRAPH_LABEL: computedStyle.getPropertyValue(
92 + '--color-scheduling-profiler-flame-graph-label',
93 + ),
94 + USER_TIMING: computedStyle.getPropertyValue(
95 + '--color-scheduling-profiler-user-timing',
96 + ),
97 + USER_TIMING_HOVER: computedStyle.getPropertyValue(
98 + '--color-scheduling-profiler-user-timing-hover',
99 + ),
100 + REACT_IDLE: computedStyle.getPropertyValue(
101 + '--color-scheduling-profiler-react-idle',
102 + ),
103 + REACT_IDLE_SELECTED: computedStyle.getPropertyValue(
104 + '--color-scheduling-profiler-react-idle-selected',
105 + ),
106 + REACT_IDLE_HOVER: computedStyle.getPropertyValue(
107 + '--color-scheduling-profiler-react-idle-hover',
108 + ),
109 + REACT_RENDER: computedStyle.getPropertyValue(
110 + '--color-scheduling-profiler-react-render',
111 + ),
112 + REACT_RENDER_SELECTED: computedStyle.getPropertyValue(
113 + '--color-scheduling-profiler-react-render-selected',
114 + ),
115 + REACT_RENDER_HOVER: computedStyle.getPropertyValue(
116 + '--color-scheduling-profiler-react-render-hover',
117 + ),
118 + REACT_COMMIT: computedStyle.getPropertyValue(
119 + '--color-scheduling-profiler-react-commit',
120 + ),
121 + REACT_COMMIT_SELECTED: computedStyle.getPropertyValue(
122 + '--color-scheduling-profiler-react-commit-selected',
123 + ),
124 + REACT_COMMIT_HOVER: computedStyle.getPropertyValue(
125 + '--color-scheduling-profiler-react-commit-hover',
126 + ),
127 + REACT_LAYOUT_EFFECTS: computedStyle.getPropertyValue(
128 + '--color-scheduling-profiler-react-layout-effects',
129 + ),
130 + REACT_LAYOUT_EFFECTS_SELECTED: computedStyle.getPropertyValue(
131 + '--color-scheduling-profiler-react-layout-effects-selected',
132 + ),
133 + REACT_LAYOUT_EFFECTS_HOVER: computedStyle.getPropertyValue(
134 + '--color-scheduling-profiler-react-layout-effects-hover',
135 + ),
136 + REACT_PASSIVE_EFFECTS: computedStyle.getPropertyValue(
137 + '--color-scheduling-profiler-react-passive-effects',
138 + ),
139 + REACT_PASSIVE_EFFECTS_SELECTED: computedStyle.getPropertyValue(
140 + '--color-scheduling-profiler-react-passive-effects-selected',
141 + ),
142 + REACT_PASSIVE_EFFECTS_HOVER: computedStyle.getPropertyValue(
143 + '--color-scheduling-profiler-react-passive-effects-hover',
144 + ),
145 + REACT_RESIZE_BAR: computedStyle.getPropertyValue('--color-resize-bar'),
146 + REACT_SCHEDULE: computedStyle.getPropertyValue(
147 + '--color-scheduling-profiler-react-schedule',
148 + ),
149 + REACT_SCHEDULE_HOVER: computedStyle.getPropertyValue(
150 + '--color-scheduling-profiler-react-schedule-hover',
151 + ),
152 + REACT_SCHEDULE_CASCADING: computedStyle.getPropertyValue(
153 + '--color-scheduling-profiler-react-schedule-cascading',
154 + ),
155 + REACT_SCHEDULE_CASCADING_HOVER: computedStyle.getPropertyValue(
156 + '--color-scheduling-profiler-react-schedule-cascading-hover',
157 + ),
158 + REACT_SUSPEND: computedStyle.getPropertyValue(
159 + '--color-scheduling-profiler-react-suspend',
160 + ),
161 + REACT_SUSPEND_HOVER: computedStyle.getPropertyValue(
162 + '--color-scheduling-profiler-react-suspend-hover',
163 + ),
164 + REACT_WORK_BORDER: computedStyle.getPropertyValue(
165 + '--color-scheduling-profiler-react-work-border',
166 + ),
167 + TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
168 + };
169 +}
packages/react-devtools-scheduling-profiler/src/context/ContextMenu.css deleted
-10
@@ -1,10 +0,0 @@
1 -.ContextMenu {
2 - position: absolute;
3 - border-radius: 0.125rem;
4 - background-color: #ffffff;
5 - border: 1px solid #ccc;
6 - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
7 - font-size: 11px;
8 - overflow: hidden;
9 - z-index: 10000002;
10 -}
packages/react-devtools-scheduling-profiler/src/context/ContextMenu.js deleted
-143
@@ -1,143 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {RegistryContextType} from './Contexts';
11 -
12 -import * as React from 'react';
13 -import {useContext, useEffect, useLayoutEffect, useRef, useState} from 'react';
14 -import {createPortal} from 'react-dom';
15 -import {RegistryContext} from './Contexts';
16 -
17 -import styles from './ContextMenu.css';
18 -
19 -function repositionToFit(element: HTMLElement, pageX: number, pageY: number) {
20 - const ownerWindow = element.ownerDocument.defaultView;
21 - if (element !== null) {
22 - if (pageY + element.offsetHeight >= ownerWindow.innerHeight) {
23 - if (pageY - element.offsetHeight > 0) {
24 - element.style.top = `${pageY - element.offsetHeight}px`;
25 - } else {
26 - element.style.top = '0px';
27 - }
28 - } else {
29 - element.style.top = `${pageY}px`;
30 - }
31 -
32 - if (pageX + element.offsetWidth >= ownerWindow.innerWidth) {
33 - if (pageX - element.offsetWidth > 0) {
34 - element.style.left = `${pageX - element.offsetWidth}px`;
35 - } else {
36 - element.style.left = '0px';
37 - }
38 - } else {
39 - element.style.left = `${pageX}px`;
40 - }
41 - }
42 -}
43 -
44 -const HIDDEN_STATE = {
45 - data: null,
46 - isVisible: false,
47 - pageX: 0,
48 - pageY: 0,
49 -};
50 -
51 -type Props = {|
52 - children: (data: Object) => React$Node,
53 - id: string,
54 -|};
55 -
56 -export default function ContextMenu({children, id}: Props) {
57 - const {hideMenu, registerMenu} = useContext<RegistryContextType>(
58 - RegistryContext,
59 - );
60 -
61 - const [state, setState] = useState(HIDDEN_STATE);
62 -
63 - const bodyAccessorRef = useRef<HTMLDivElement | null>(null);
64 - const containerRef = useRef<HTMLDivElement | null>(null);
65 - const menuRef = useRef<HTMLDivElement | null>(null);
66 -
67 - useEffect(() => {
68 - if (!bodyAccessorRef.current) {
69 - return;
70 - }
71 - const ownerDocument = bodyAccessorRef.current.ownerDocument;
72 - containerRef.current = ownerDocument.createElement('div');
73 - if (ownerDocument.body) {
74 - ownerDocument.body.appendChild(containerRef.current);
75 - }
76 - return () => {
77 - if (ownerDocument.body && containerRef.current) {
78 - ownerDocument.body.removeChild(containerRef.current);
79 - }
80 - };
81 - }, [bodyAccessorRef, containerRef]);
82 -
83 - useEffect(() => {
84 - const showMenuFn = ({data, pageX, pageY}) => {
85 - setState({data, isVisible: true, pageX, pageY});
86 - };
87 - const hideMenuFn = () => setState(HIDDEN_STATE);
88 - return registerMenu(id, showMenuFn, hideMenuFn);
89 - }, [id]);
90 -
91 - useLayoutEffect(() => {
92 - if (!state.isVisible || !containerRef.current) {
93 - return;
94 - }
95 -
96 - const menu = menuRef.current;
97 - if (!menu) {
98 - return;
99 - }
100 -
101 - const hideUnlessContains: MouseEventHandler &
102 - TouchEventHandler &
103 - KeyboardEventHandler = event => {
104 - if (event.target instanceof HTMLElement && !menu.contains(event.target)) {
105 - hideMenu();
106 - }
107 - };
108 -
109 - const ownerDocument = containerRef.current.ownerDocument;
110 - ownerDocument.addEventListener('mousedown', hideUnlessContains);
111 - ownerDocument.addEventListener('touchstart', hideUnlessContains);
112 - ownerDocument.addEventListener('keydown', hideUnlessContains);
113 -
114 - const ownerWindow = ownerDocument.defaultView;
115 - ownerWindow.addEventListener('resize', hideMenu);
116 -
117 - repositionToFit(menu, state.pageX, state.pageY);
118 -
119 - return () => {
120 - ownerDocument.removeEventListener('mousedown', hideUnlessContains);
121 - ownerDocument.removeEventListener('touchstart', hideUnlessContains);
122 - ownerDocument.removeEventListener('keydown', hideUnlessContains);
123 -
124 - ownerWindow.removeEventListener('resize', hideMenu);
125 - };
126 - }, [state]);
127 -
128 - if (!state.isVisible) {
129 - return <div ref={bodyAccessorRef} />;
130 - } else {
131 - const container = containerRef.current;
132 - if (container !== null) {
133 - return createPortal(
134 - <div ref={menuRef} className={styles.ContextMenu}>
135 - {children(state.data)}
136 - </div>,
137 - container,
138 - );
139 - } else {
140 - return null;
141 - }
142 - }
143 -}
packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.css deleted
-20
@@ -1,20 +0,0 @@
1 -.ContextMenuItem {
2 - display: flex;
3 - align-items: center;
4 - color: #333;
5 - padding: 0.5rem 0.75rem;
6 - cursor: default;
7 - border-top: 1px solid #ccc;
8 -}
9 -.ContextMenuItem:first-of-type {
10 - border-top: none;
11 -}
12 -.ContextMenuItem:hover,
13 -.ContextMenuItem:focus {
14 - outline: 0;
15 - background-color: rgba(0, 136, 250, 0.1);
16 -}
17 -.ContextMenuItem:active {
18 - background-color: #0088fa;
19 - color: #fff;
20 -}
packages/react-devtools-scheduling-profiler/src/context/ContextMenuItem.js deleted
-40
@@ -1,40 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {RegistryContextType} from './Contexts';
11 -
12 -import * as React from 'react';
13 -import {useContext} from 'react';
14 -import {RegistryContext} from './Contexts';
15 -
16 -import styles from './ContextMenuItem.css';
17 -
18 -type Props = {|
19 - children: React$Node,
20 - onClick: () => void,
21 - title: string,
22 -|};
23 -
24 -export default function ContextMenuItem({children, onClick, title}: Props) {
25 - const {hideMenu} = useContext<RegistryContextType>(RegistryContext);
26 -
27 - const handleClick: MouseEventHandler = event => {
28 - onClick();
29 - hideMenu();
30 - };
31 -
32 - return (
33 - <div
34 - className={styles.ContextMenuItem}
35 - onClick={handleClick}
36 - onTouchEnd={handleClick}>
37 - {children}
38 - </div>
39 - );
40 -}
packages/react-devtools-scheduling-profiler/src/context/Contexts.js deleted
-87
@@ -1,87 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import {createContext} from 'react';
11 -
12 -export type ShowFn = ({|data: Object, pageX: number, pageY: number|}) => void;
13 -export type HideFn = () => void;
14 -export type OnChangeFn = boolean => void;
15 -
16 -const idToShowFnMap = new Map<string, ShowFn>();
17 -const idToHideFnMap = new Map<string, HideFn>();
18 -
19 -let currentHideFn: ?HideFn = null;
20 -let currentOnChange: ?OnChangeFn = null;
21 -
22 -function hideMenu() {
23 - if (typeof currentHideFn === 'function') {
24 - currentHideFn();
25 -
26 - if (typeof currentOnChange === 'function') {
27 - currentOnChange(false);
28 - }
29 - }
30 -
31 - currentHideFn = null;
32 - currentOnChange = null;
33 -}
34 -
35 -function showMenu({
36 - data,
37 - id,
38 - onChange,
39 - pageX,
40 - pageY,
41 -}: {|
42 - data: Object,
43 - id: string,
44 - onChange?: OnChangeFn,
45 - pageX: number,
46 - pageY: number,
47 -|}) {
48 - const showFn = idToShowFnMap.get(id);
49 - if (typeof showFn === 'function') {
50 - // Prevent open menus from being left hanging.
51 - hideMenu();
52 -
53 - currentHideFn = idToHideFnMap.get(id);
54 - showFn({data, pageX, pageY});
55 -
56 - if (typeof onChange === 'function') {
57 - currentOnChange = onChange;
58 - onChange(true);
59 - }
60 - }
61 -}
62 -
63 -function registerMenu(id: string, showFn: ShowFn, hideFn: HideFn) {
64 - if (idToShowFnMap.has(id)) {
65 - throw Error(`Context menu with id "${id}" already registered.`);
66 - }
67 -
68 - idToShowFnMap.set(id, showFn);
69 - idToHideFnMap.set(id, hideFn);
70 -
71 - return function unregisterMenu() {
72 - idToShowFnMap.delete(id);
73 - idToHideFnMap.delete(id);
74 - };
75 -}
76 -
77 -export type RegistryContextType = {|
78 - hideMenu: typeof hideMenu,
79 - showMenu: typeof showMenu,
80 - registerMenu: typeof registerMenu,
81 -|};
82 -
83 -export const RegistryContext = createContext<RegistryContextType>({
84 - hideMenu,
85 - showMenu,
86 - registerMenu,
87 -});
packages/react-devtools-scheduling-profiler/src/context/index.js deleted
-15
@@ -1,15 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import {RegistryContext} from './Contexts';
11 -import ContextMenu from './ContextMenu';
12 -import ContextMenuItem from './ContextMenuItem';
13 -import useContextMenu from './useContextMenu';
14 -
15 -export {RegistryContext, ContextMenu, ContextMenuItem, useContextMenu};
packages/react-devtools-scheduling-profiler/src/context/useContextMenu.js deleted
-52
@@ -1,52 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {OnChangeFn, RegistryContextType} from './Contexts';
11 -
12 -import {useContext, useEffect} from 'react';
13 -import {RegistryContext} from './Contexts';
14 -
15 -export default function useContextMenu<T>({
16 - data,
17 - id,
18 - onChange,
19 - ref,
20 -}: {|
21 - data: T,
22 - id: string,
23 - onChange: OnChangeFn,
24 - ref: {+current: HTMLElement | null},
25 -|}) {
26 - const {showMenu} = useContext<RegistryContextType>(RegistryContext);
27 -
28 - useEffect(() => {
29 - if (ref.current !== null) {
30 - const handleContextMenu = (event: MouseEvent | TouchEvent) => {
31 - event.preventDefault();
32 - event.stopPropagation();
33 -
34 - const pageX =
35 - (event: any).pageX ||
36 - (event.touches && (event: any).touches[0].pageX);
37 - const pageY =
38 - (event: any).pageY ||
39 - (event.touches && (event: any).touches[0].pageY);
40 -
41 - showMenu({data, id, onChange, pageX, pageY});
42 - };
43 -
44 - const trigger = ref.current;
45 - trigger.addEventListener('contextmenu', handleContextMenu);
46 -
47 - return () => {
48 - trigger.removeEventListener('contextmenu', handleContextMenu);
49 - };
50 - }
51 - }, [data, id, showMenu]);
52 -}
packages/react-devtools-scheduling-profiler/src/createDataResourceFromImportedFile.js new
+46
@@ -0,0 +1,46 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {createResource} from 'react-devtools-shared/src/devtools/cache';
11 +import {importFile} from './import-worker';
12 +
13 +import type {Resource} from 'react-devtools-shared/src/devtools/cache';
14 +import type {ReactProfilerData} from './types';
15 +import type {ImportWorkerOutputData} from './import-worker/index';
16 +
17 +export type DataResource = Resource<void, File, ReactProfilerData | Error>;
18 +
19 +export default function createDataResourceFromImportedFile(
20 + file: File,
21 +): DataResource {
22 + return createResource(
23 + () => {
24 + return new Promise<ReactProfilerData | Error>((resolve, reject) => {
25 + const promise = ((importFile(
26 + file,
27 + ): any): Promise<ImportWorkerOutputData>);
28 + promise.then(data => {
29 + switch (data.status) {
30 + case 'SUCCESS':
31 + resolve(data.processedData);
32 + break;
33 + case 'INVALID_PROFILE_ERROR':
34 + resolve(data.error);
35 + break;
36 + case 'UNEXPECTED_ERROR':
37 + reject(data.error);
38 + break;
39 + }
40 + });
41 + });
42 + },
43 + () => file,
44 + {useWeakMap: true},
45 + );
46 +}
packages/react-devtools-scheduling-profiler/src/hooks.js deleted
-71
@@ -1,71 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import {
11 - // $FlowFixMe
12 - unstable_createMutableSource as createMutableSource,
13 - useLayoutEffect,
14 - // $FlowFixMe
15 - unstable_useMutableSource as useMutableSource,
16 -} from 'react';
17 -
18 -import {
19 - updateDisplayDensity,
20 - updateThemeVariables,
21 -} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
22 -import {enableDarkMode} from './SchedulingProfilerFeatureFlags';
23 -
24 -export type BrowserTheme = 'dark' | 'light';
25 -
26 -const DARK_MODE_QUERY = '(prefers-color-scheme: dark)';
27 -
28 -const getSnapshot = window =>
29 - window.matchMedia(DARK_MODE_QUERY).matches ? 'dark' : 'light';
30 -
31 -const darkModeMutableSource = createMutableSource(
32 - window,
33 - () => window.matchMedia(DARK_MODE_QUERY).matches,
34 -);
35 -
36 -const subscribe = (window, callback) => {
37 - const mediaQueryList = window.matchMedia(DARK_MODE_QUERY);
38 - mediaQueryList.addEventListener('change', callback);
39 - return () => {
40 - mediaQueryList.removeEventListener('change', callback);
41 - };
42 -};
43 -
44 -export function useBrowserTheme(): void {
45 - const theme = useMutableSource(darkModeMutableSource, getSnapshot, subscribe);
46 -
47 - useLayoutEffect(() => {
48 - const documentElements = [((document.documentElement: any): HTMLElement)];
49 - if (enableDarkMode) {
50 - switch (theme) {
51 - case 'light':
52 - updateThemeVariables('light', documentElements);
53 - break;
54 - case 'dark':
55 - updateThemeVariables('dark', documentElements);
56 - break;
57 - default:
58 - throw Error(`Unsupported theme value "${theme}"`);
59 - }
60 - } else {
61 - updateThemeVariables('light', documentElements);
62 - }
63 - }, [theme]);
64 -}
65 -
66 -export function useDisplayDensity(): void {
67 - useLayoutEffect(() => {
68 - const documentElements = [((document.documentElement: any): HTMLElement)];
69 - updateDisplayDensity('comfortable', documentElements);
70 - }, []);
71 -}
packages/react-devtools-scheduling-profiler/src/import-worker/importFile.js renamed
+9 -20
@@ -10,7 +10,7 @@
10 import 'regenerator-runtime/runtime';
11
12 import type {TimelineEvent} from '@elg/speedscope';
13 -import type {ReactProfilerData} from '../types';
13 +import type {ImportWorkerOutputData} from './index';
14
15 import preprocessData from './preprocessData';
16 import {readInputData} from './readInputData';
@@ -18,18 +18,7 @@ import InvalidProfileError from './InvalidProfileError';
18
19 declare var self: DedicatedWorkerGlobalScope;
20
21 -type ImportWorkerInputData = {|
22 - file: File,
23 -|};
24 -
25 -export type ImportWorkerOutputData =
26 - | {|status: 'SUCCESS', processedData: ReactProfilerData|}
27 - | {|status: 'INVALID_PROFILE_ERROR', error: Error|}
28 - | {|status: 'UNEXPECTED_ERROR', error: Error|};
29 -
30 -self.onmessage = async function(event: MessageEvent) {
31 - const {file} = ((event.data: any): ImportWorkerInputData);
32 -
21 +export async function importFile(file: File): Promise<ImportWorkerOutputData> {
22 try {
23 const readFile = await readInputData(file);
24 const events: TimelineEvent[] = JSON.parse(readFile);
@@ -37,21 +26,21 @@ self.onmessage = async function(event: MessageEvent) {
26 throw new InvalidProfileError('No profiling data found in file.');
27 }
28
40 - self.postMessage({
29 + return {
30 status: 'SUCCESS',
31 processedData: preprocessData(events),
43 - });
32 + };
33 } catch (error) {
34 if (error instanceof InvalidProfileError) {
46 - self.postMessage({
35 + return {
36 status: 'INVALID_PROFILE_ERROR',
37 error,
49 - });
38 + };
39 } else {
51 - self.postMessage({
40 + return {
41 status: 'UNEXPECTED_ERROR',
42 error,
54 - });
43 + };
44 }
45 }
57 -};
46 +}
packages/react-devtools-scheduling-profiler/src/import-worker/importFile.worker.js renamed
+3 -3
@@ -3,8 +3,8 @@
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
6 */
7
10 -export const enableDarkMode = false;
8 +import * as importFileModule from './importFile';
9 +
10 +export const importFile = importFileModule.importFile;
packages/react-devtools-scheduling-profiler/src/import-worker/index.js new
+31
@@ -0,0 +1,31 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +// This file uses workerize to load ./importFile.worker as a webworker and instanciates it,
11 +// exposing flow typed functions that can be used on other files.
12 +
13 +import * as importFileModule from './importFile';
14 +import WorkerizedImportFile from './importFile.worker';
15 +
16 +import type {ReactProfilerData} from '../types';
17 +
18 +type ImportFileModule = typeof importFileModule;
19 +
20 +const workerizedImportFile: ImportFileModule = window.Worker
21 + ? WorkerizedImportFile()
22 + : importFileModule;
23 +
24 +export type ImportWorkerOutputData =
25 + | {|status: 'SUCCESS', processedData: ReactProfilerData|}
26 + | {|status: 'INVALID_PROFILE_ERROR', error: Error|}
27 + | {|status: 'UNEXPECTED_ERROR', error: Error|};
28 +
29 +export type importFileFunction = (file: File) => ImportWorkerOutputData;
30 +
31 +export const importFile = (file: File) => workerizedImportFile.importFile(file);
packages/react-devtools-scheduling-profiler/src/index.css deleted
-21
@@ -1,21 +0,0 @@
1 -html {
2 - height: 100%;
3 -}
4 -
5 -body {
6 - height: 100%;
7 - margin: 0;
8 - font-family: var(--font-family-sans);
9 - font-size: var(--font-size-sans-normal);
10 - background-color: var(--color-background);
11 - color: var(--color-text);
12 -}
13 -
14 -code {
15 - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 - monospace;
17 -}
18 -
19 -.Container {
20 - height: 100%;
21 -}
\ No newline at end of file
packages/react-devtools-scheduling-profiler/src/index.js deleted
-31
@@ -1,31 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import 'regenerator-runtime/runtime';
11 -
12 -import * as React from 'react';
13 -// $FlowFixMe Flow does not yet know about createRoot()
14 -import {createRoot} from 'react-dom';
15 -import nullthrows from 'nullthrows';
16 -import App from './App';
17 -
18 -import styles from './index.css';
19 -
20 -const container = document.createElement('div');
21 -container.className = styles.Container;
22 -container.id = 'root';
23 -
24 -const body = nullthrows(document.body, 'Expect document.body to exist');
25 -body.appendChild(container);
26 -
27 -createRoot(container).render(
28 - <React.StrictMode>
29 - <App />
30 - </React.StrictMode>,
31 -);
packages/react-devtools-scheduling-profiler/src/view-base/ResizableSplitView.js
+18 -4
@@ -15,6 +15,7 @@ import type {
15 } from './useCanvasInteraction';
16 import type {Rect, Size} from './geometry';
17
18 +import {COLORS} from '../content-views/constants';
19 import nullthrows from 'nullthrows';
20 import {Surface} from './Surface';
21 import {View} from './View';
@@ -38,14 +39,11 @@ type LayoutState = $ReadOnly<{|
39 |}>;
40
41 function getColorForBarState(state: ResizeBarState): string {
41 - // Colors obtained from Firefox Profiler
42 switch (state) {
43 case 'normal':
44 - return '#ccc';
44 case 'hovered':
46 - return '#bbb';
45 case 'dragging':
48 - return '#aaa';
46 + return COLORS.REACT_RESIZE_BAR;
47 }
48 throw new Error(`Unknown resize bar state ${state}`);
49 }
@@ -131,6 +129,7 @@ class ResizeBar extends View {
129 }
130
131 export class ResizableSplitView extends View {
132 + _canvasRef: {current: HTMLCanvasElement | null};
133 _resizingState: ResizingState | null = null;
134 _layoutState: LayoutState;
135
@@ -139,9 +138,12 @@ export class ResizableSplitView extends View {
138 frame: Rect,
139 topSubview: View,
140 bottomSubview: View,
141 + canvasRef: {current: HTMLCanvasElement | null},
142 ) {
143 super(surface, frame, noopLayout);
144
145 + this._canvasRef = canvasRef;
146 +
147 this.addSubview(topSubview);
148 this.addSubview(new ResizeBar(surface, frame));
149 this.addSubview(bottomSubview);
@@ -279,6 +281,18 @@ export class ResizableSplitView extends View {
281 }
282
283 _handleMouseMove(interaction: MouseMoveInteraction) {
284 + const cursorLocation = interaction.payload.location;
285 + const resizeBarFrame = this._getResizeBar().frame;
286 +
287 + const canvas = this._canvasRef.current;
288 + if (canvas !== null) {
289 + if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
290 + canvas.style.cursor = 'ns-resize';
291 + } else {
292 + canvas.style.cursor = 'default';
293 + }
294 + }
295 +
296 const {_resizingState} = this;
297 if (_resizingState) {
298 this._resizingState = {
packages/react-devtools-scheduling-profiler/src/view-base/useCanvasInteraction.js
+5 -4
@@ -175,15 +175,16 @@ export function useCanvasInteraction(
175 return false;
176 };
177
178 - document.addEventListener('mousemove', onDocumentMouseMove);
179 - document.addEventListener('mouseup', onDocumentMouseUp);
178 + const ownerDocument = canvas.ownerDocument;
179 + ownerDocument.addEventListener('mousemove', onDocumentMouseMove);
180 + ownerDocument.addEventListener('mouseup', onDocumentMouseUp);
181
182 canvas.addEventListener('mousedown', onCanvasMouseDown);
183 canvas.addEventListener('wheel', onCanvasWheel);
184
185 return () => {
185 - document.removeEventListener('mousemove', onDocumentMouseMove);
186 - document.removeEventListener('mouseup', onDocumentMouseUp);
186 + ownerDocument.removeEventListener('mousemove', onDocumentMouseMove);
187 + ownerDocument.removeEventListener('mouseup', onDocumentMouseUp);
188
189 canvas.removeEventListener('mousedown', onCanvasMouseDown);
190 canvas.removeEventListener('wheel', onCanvasWheel);
packages/react-devtools-scheduling-profiler/vercel.json deleted
-3
@@ -1,3 +0,0 @@
1 -{
2 - "name": "react-devtools-scheduling-profiler"
3 -}
packages/react-devtools-scheduling-profiler/webpack.config.js deleted
-128
@@ -1,128 +0,0 @@
1 -'use strict';
2 -
3 -const {resolve} = require('path');
4 -const {DefinePlugin} = require('webpack');
5 -const HtmlWebpackPlugin = require('html-webpack-plugin');
6 -const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
7 -const {getVersionString} = require('./buildUtils');
8 -
9 -const NODE_ENV = process.env.NODE_ENV;
10 -if (!NODE_ENV) {
11 - console.error('NODE_ENV not set');
12 - process.exit(1);
13 -}
14 -const __DEV__ = NODE_ENV === 'development';
15 -
16 -const TARGET = process.env.TARGET;
17 -if (!TARGET) {
18 - console.error('TARGET not set');
19 - process.exit(1);
20 -}
21 -const shouldUseDevServer = TARGET === 'local';
22 -
23 -const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules');
24 -
25 -const DEVTOOLS_VERSION = getVersionString();
26 -
27 -const imageInlineSizeLimit = 10000;
28 -
29 -const babelOptions = {
30 - configFile: resolve(
31 - __dirname,
32 - '..',
33 - 'react-devtools-shared',
34 - 'babel.config.js',
35 - ),
36 - plugins: shouldUseDevServer
37 - ? [resolve(builtModulesDir, 'react-refresh/babel')]
38 - : [],
39 -};
40 -
41 -const config = {
42 - mode: __DEV__ ? 'development' : 'production',
43 - devtool: __DEV__ ? 'cheap-module-eval-source-map' : 'source-map',
44 - entry: {
45 - app: './src/index.js',
46 - },
47 - resolve: {
48 - alias: {
49 - react: resolve(builtModulesDir, 'react'),
50 - 'react-dom': resolve(builtModulesDir, 'react-dom'),
51 - 'react-refresh': resolve(builtModulesDir, 'react-refresh'),
52 - scheduler: resolve(builtModulesDir, 'scheduler'),
53 - },
54 - },
55 - plugins: [
56 - new DefinePlugin({
57 - __DEV__,
58 - __PROFILE__: false,
59 - __EXPERIMENTAL__: true,
60 - __VARIANT__: false,
61 - 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
62 - }),
63 - new HtmlWebpackPlugin({
64 - title: 'React Concurrent Mode Profiler',
65 - }),
66 - shouldUseDevServer && new ReactRefreshWebpackPlugin(),
67 - ].filter(Boolean),
68 - module: {
69 - rules: [
70 - {
71 - test: /\.worker\.js$/,
72 - use: [
73 - 'worker-loader',
74 - {
75 - loader: 'babel-loader',
76 - options: babelOptions,
77 - },
78 - ],
79 - },
80 - {
81 - test: /\.js$/,
82 - loader: 'babel-loader',
83 - options: babelOptions,
84 - },
85 - {
86 - test: /\.css$/,
87 - use: [
88 - {
89 - loader: 'style-loader',
90 - },
91 - {
92 - loader: 'css-loader',
93 - options: {
94 - sourceMap: true,
95 - modules: {
96 - localIdentName: '[local]___[hash:base64:5]',
97 - },
98 - },
99 - },
100 - ],
101 - },
102 - {
103 - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
104 - loader: 'url-loader',
105 - options: {
106 - limit: imageInlineSizeLimit,
107 - name: 'static/media/[name].[hash:8].[ext]',
108 - },
109 - },
110 - ],
111 - },
112 -};
113 -
114 -if (shouldUseDevServer) {
115 - config.devServer = {
116 - hot: true,
117 - port: 8081,
118 - clientLogLevel: 'warning',
119 - stats: 'errors-only',
120 - };
121 -} else {
122 - config.output = {
123 - path: resolve(__dirname, 'dist'),
124 - filename: '[name].js',
125 - };
126 -}
127 -
128 -module.exports = config;
packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.css
+2
@@ -1,7 +1,9 @@
1 .ContextMenu {
2 position: absolute;
3 background-color: var(--color-context-background);
4 + box-shadow: 1px 1px 2px var(--color-shadow);
5 border-radius: 0.25rem;
6 overflow: hidden;
7 z-index: 10000002;
8 + user-select: none;
9 }
\ No newline at end of file
packages/react-devtools-shared/src/devtools/ContextMenu/ContextMenu.js
+9 -11
@@ -54,7 +54,9 @@ type Props = {|
54 |};
55
56 export default function ContextMenu({children, id}: Props) {
57 - const {registerMenu} = useContext<RegistryContextType>(RegistryContext);
57 + const {hideMenu, registerMenu} = useContext<RegistryContextType>(
58 + RegistryContext,
59 + );
60
61 const [state, setState] = useState(HIDDEN_STATE);
62
@@ -75,11 +77,11 @@ export default function ContextMenu({children, id}: Props) {
77 }, []);
78
79 useEffect(() => {
78 - const showMenu = ({data, pageX, pageY}) => {
80 + const showMenuFn = ({data, pageX, pageY}) => {
81 setState({data, isVisible: true, pageX, pageY});
82 };
81 - const hideMenu = () => setState(HIDDEN_STATE);
82 - return registerMenu(id, showMenu, hideMenu);
83 + const hideMenuFn = () => setState(HIDDEN_STATE);
84 + return registerMenu(id, showMenuFn, hideMenuFn);
85 }, [id]);
86
87 useLayoutEffect(() => {
@@ -92,21 +94,17 @@ export default function ContextMenu({children, id}: Props) {
94 if (container !== null) {
95 const hideUnlessContains = event => {
96 if (!menu.contains(event.target)) {
95 - setState(HIDDEN_STATE);
97 + hideMenu();
98 }
99 };
100
99 - const hide = event => {
100 - setState(HIDDEN_STATE);
101 - };
102 -
101 const ownerDocument = container.ownerDocument;
102 ownerDocument.addEventListener('mousedown', hideUnlessContains);
103 ownerDocument.addEventListener('touchstart', hideUnlessContains);
104 ownerDocument.addEventListener('keydown', hideUnlessContains);
105
106 const ownerWindow = ownerDocument.defaultView;
109 - ownerWindow.addEventListener('resize', hide);
107 + ownerWindow.addEventListener('resize', hideMenu);
108
109 repositionToFit(menu, state.pageX, state.pageY);
110
@@ -115,7 +113,7 @@ export default function ContextMenu({children, id}: Props) {
113 ownerDocument.removeEventListener('touchstart', hideUnlessContains);
114 ownerDocument.removeEventListener('keydown', hideUnlessContains);
115
118 - ownerWindow.removeEventListener('resize', hide);
116 + ownerWindow.removeEventListener('resize', hideMenu);
117 };
118 }
119 }, [state]);
packages/react-devtools-shared/src/devtools/ContextMenu/Contexts.js
+27 -12
@@ -11,33 +11,53 @@ import {createContext} from 'react';
11
12 export type ShowFn = ({|data: Object, pageX: number, pageY: number|}) => void;
13 export type HideFn = () => void;
14 +export type OnChangeFn = boolean => void;
15
16 const idToShowFnMap = new Map<string, ShowFn>();
17 const idToHideFnMap = new Map<string, HideFn>();
18
18 -let currentHideFn = null;
19 +let currentHide: ?HideFn = null;
20 +let currentOnChange: ?OnChangeFn = null;
21
22 function hideMenu() {
21 - if (typeof currentHideFn === 'function') {
22 - currentHideFn();
23 + if (typeof currentHide === 'function') {
24 + currentHide();
25 +
26 + if (typeof currentOnChange === 'function') {
27 + currentOnChange(false);
28 + }
29 }
30 +
31 + currentHide = null;
32 + currentOnChange = null;
33 }
34
35 function showMenu({
36 data,
37 id,
38 + onChange,
39 pageX,
40 pageY,
41 }: {|
42 data: Object,
43 id: string,
44 + onChange?: OnChangeFn,
45 pageX: number,
46 pageY: number,
47 |}) {
48 const showFn = idToShowFnMap.get(id);
49 if (typeof showFn === 'function') {
39 - currentHideFn = idToHideFnMap.get(id);
50 + // Prevent open menus from being left hanging.
51 + hideMenu();
52 +
53 + currentHide = idToHideFnMap.get(id);
54 +
55 showFn({data, pageX, pageY});
56 +
57 + if (typeof onChange === 'function') {
58 + currentOnChange = onChange;
59 + onChange(true);
60 + }
61 }
62 }
63
@@ -56,14 +76,9 @@ function registerMenu(id: string, showFn: ShowFn, hideFn: HideFn) {
76 }
77
78 export type RegistryContextType = {|
59 - hideMenu: () => void,
60 - showMenu: ({|
61 - data: Object,
62 - id: string,
63 - pageX: number,
64 - pageY: number,
65 - |}) => void,
66 - registerMenu: (string, ShowFn, HideFn) => Function,
79 + hideMenu: typeof hideMenu,
80 + showMenu: typeof showMenu,
81 + registerMenu: typeof registerMenu,
82 |};
83
84 export const RegistryContext = createContext<RegistryContextType>({
packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js
+5 -3
@@ -10,17 +10,19 @@
10 import {useContext, useEffect} from 'react';
11 import {RegistryContext} from './Contexts';
12
13 -import type {RegistryContextType} from './Contexts';
13 +import type {OnChangeFn, RegistryContextType} from './Contexts';
14 import type {ElementRef} from 'react';
15
16 export default function useContextMenu({
17 data,
18 id,
19 + onChange,
20 ref,
21 }: {|
22 data: Object,
23 id: string,
23 - ref: {current: ElementRef<'div'> | null},
24 + onChange?: OnChangeFn,
25 + ref: {current: ElementRef<*> | null},
26 |}) {
27 const {showMenu} = useContext<RegistryContextType>(RegistryContext);
28
@@ -37,7 +39,7 @@ export default function useContextMenu({
39 (event: any).pageY ||
40 (event.touches && (event: any).touches[0].pageY);
41
40 - showMenu({data, id, pageX, pageY});
42 + showMenu({data, id, onChange, pageX, pageY});
43 };
44
45 const trigger = ref.current;
packages/react-devtools-shared/src/devtools/store.js
+10
@@ -62,6 +62,7 @@ type Config = {|
62 isProfiling?: boolean,
63 supportsNativeInspection?: boolean,
64 supportsReloadAndProfile?: boolean,
65 + supportsSchedulingProfiler?: boolean,
66 supportsProfiling?: boolean,
67 supportsTraceUpdates?: boolean,
68 |};
@@ -159,6 +160,7 @@ export default class Store extends EventEmitter<{|
160 _supportsNativeInspection: boolean = true;
161 _supportsProfiling: boolean = false;
162 _supportsReloadAndProfile: boolean = false;
163 + _supportsSchedulingProfiler: boolean = false;
164 _supportsTraceUpdates: boolean = false;
165
166 _unsupportedBridgeProtocol: BridgeProtocol | null = null;
@@ -193,6 +195,7 @@ export default class Store extends EventEmitter<{|
195 supportsNativeInspection,
196 supportsProfiling,
197 supportsReloadAndProfile,
198 + supportsSchedulingProfiler,
199 supportsTraceUpdates,
200 } = config;
201 this._supportsNativeInspection = supportsNativeInspection !== false;
@@ -202,6 +205,9 @@ export default class Store extends EventEmitter<{|
205 if (supportsReloadAndProfile) {
206 this._supportsReloadAndProfile = true;
207 }
208 + if (supportsSchedulingProfiler) {
209 + this._supportsSchedulingProfiler = true;
210 + }
211 if (supportsTraceUpdates) {
212 this._supportsTraceUpdates = true;
213 }
@@ -414,6 +420,10 @@ export default class Store extends EventEmitter<{|
420 );
421 }
422
423 + get supportsSchedulingProfiler(): boolean {
424 + return this._supportsSchedulingProfiler;
425 + }
426 +
427 get supportsTraceUpdates(): boolean {
428 return this._supportsTraceUpdates;
429 }
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
+14 -10
@@ -23,6 +23,7 @@ import useContextMenu from '../../ContextMenu/useContextMenu';
23 import {meta} from '../../../hydration';
24 import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
25 import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
26 +import HookNamesContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesContext';
27
28 import type {InspectedElement} from './types';
29 import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
@@ -52,6 +53,8 @@ export function InspectedElementHooksTree({
53 }: HooksTreeViewProps) {
54 const {hooks, id} = inspectedElement;
55
56 + const {loadHookNames: loadHookNamesFunction} = useContext(HookNamesContext);
57 +
58 // Changing parseHookNames is done in a transition, because it suspends.
59 // This value is done outside of the transition, so the UI toggle feels responsive.
60 const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
@@ -82,16 +85,17 @@ export function InspectedElementHooksTree({
85 <div className={styles.HooksTreeView}>
86 <div className={styles.HeaderRow}>
87 <div className={styles.Header}>hooks</div>
85 - {(!parseHookNames || hookParsingFailed) && (
86 - <Toggle
87 - className={hookParsingFailed ? styles.ToggleError : null}
88 - isChecked={parseHookNamesOptimistic}
89 - isDisabled={parseHookNamesOptimistic || hookParsingFailed}
90 - onChange={handleChange}
91 - title={toggleTitle}>
92 - <ButtonIcon type="parse-hook-names" />
93 - </Toggle>
94 - )}
88 + {loadHookNamesFunction !== null &&
89 + (!parseHookNames || hookParsingFailed) && (
90 + <Toggle
91 + className={hookParsingFailed ? styles.ToggleError : null}
92 + isChecked={parseHookNamesOptimistic}
93 + isDisabled={parseHookNamesOptimistic || hookParsingFailed}
94 + onChange={handleChange}
95 + title={toggleTitle}>
96 + <ButtonIcon type="parse-hook-names" />
97 + </Toggle>
98 + )}
99 <Button onClick={handleCopy} title="Copy to clipboard">
100 <ButtonIcon type="copy" />
101 </Button>
packages/react-devtools-shared/src/devtools/views/DevTools.js
+32 -27
@@ -24,6 +24,7 @@ import {TreeContextController} from './Components/TreeContext';
24 import ViewElementSourceContext from './Components/ViewElementSourceContext';
25 import HookNamesContext from './Components/HookNamesContext';
26 import {ProfilerContextController} from './Profiler/ProfilerContext';
27 +import {SchedulingProfilerContextController} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
28 import {ModalDialogContextController} from './ModalDialog';
29 import ReactLogo from './ReactLogo';
30 import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog';
@@ -218,36 +219,40 @@ export default function DevTools({
219 <HookNamesContext.Provider value={hookNamesContext}>
220 <TreeContextController>
221 <ProfilerContextController>
221 - <div className={styles.DevTools} ref={devToolsRef}>
222 - {showTabBar && (
223 - <div className={styles.TabBar}>
224 - <ReactLogo />
225 - <span className={styles.DevToolsVersion}>
226 - {process.env.DEVTOOLS_VERSION}
227 - </span>
228 - <div className={styles.Spacer} />
229 - <TabBar
230 - currentTab={tab}
231 - id="DevTools"
232 - selectTab={setTab}
233 - tabs={tabs}
234 - type="navigation"
222 + <SchedulingProfilerContextController>
223 + <div className={styles.DevTools} ref={devToolsRef}>
224 + {showTabBar && (
225 + <div className={styles.TabBar}>
226 + <ReactLogo />
227 + <span className={styles.DevToolsVersion}>
228 + {process.env.DEVTOOLS_VERSION}
229 + </span>
230 + <div className={styles.Spacer} />
231 + <TabBar
232 + currentTab={tab}
233 + id="DevTools"
234 + selectTab={setTab}
235 + tabs={tabs}
236 + type="navigation"
237 + />
238 + </div>
239 + )}
240 + <div
241 + className={styles.TabContent}
242 + hidden={tab !== 'components'}>
243 + <Components
244 + portalContainer={componentsPortalContainer}
245 + />
246 + </div>
247 + <div
248 + className={styles.TabContent}
249 + hidden={tab !== 'profiler'}>
250 + <Profiler
251 + portalContainer={profilerPortalContainer}
252 />
253 </div>
237 - )}
238 - <div
239 - className={styles.TabContent}
240 - hidden={tab !== 'components'}>
241 - <Components
242 - portalContainer={componentsPortalContainer}
243 - />
244 - </div>
245 - <div
246 - className={styles.TabContent}
247 - hidden={tab !== 'profiler'}>
248 - <Profiler portalContainer={profilerPortalContainer} />
254 </div>
250 - </div>
255 + </SchedulingProfilerContextController>
256 </ProfilerContextController>
257 </TreeContextController>
258 </HookNamesContext.Provider>
packages/react-devtools-shared/src/devtools/views/Icon.js
+9
@@ -21,6 +21,7 @@ export type IconType =
21 | 'flame-chart'
22 | 'profiler'
23 | 'ranked-chart'
24 + | 'scheduling-profiler'
25 | 'search'
26 | 'settings'
27 | 'store-as-global-variable'
@@ -64,6 +65,9 @@ export default function Icon({className = '', type}: Props) {
65 case 'ranked-chart':
66 pathData = PATH_RANKED_CHART;
67 break;
68 + case 'scheduling-profiler':
69 + pathData = PATH_SCHEDULING_PROFILER;
70 + break;
71 case 'search':
72 pathData = PATH_SEARCH;
73 break;
@@ -136,6 +140,11 @@ const PATH_FLAME_CHART = `
140
141 const PATH_PROFILER = 'M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z';
142
143 +const PATH_SCHEDULING_PROFILER = `
144 + M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0
145 + 16H5V9h14v10zm0-12H5V5h14v2zM7 11h5v5H7z
146 +`;
147 +
148 const PATH_SEARCH = `
149 M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91
150 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99
packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+26 -3
@@ -16,6 +16,7 @@ import ClearProfilingDataButton from './ClearProfilingDataButton';
16 import CommitFlamegraph from './CommitFlamegraph';
17 import CommitRanked from './CommitRanked';
18 import RootSelector from './RootSelector';
19 +import {SchedulingProfiler} from 'react-devtools-scheduling-profiler/src/SchedulingProfiler';
20 import RecordToggle from './RecordToggle';
21 import ReloadAndProfileButton from './ReloadAndProfileButton';
22 import ProfilingImportExportButtons from './ProfilingImportExportButtons';
@@ -26,6 +27,7 @@ import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/Set
27 import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
28 import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
29 import portaledContent from '../portaledContent';
30 +import {StoreContext} from '../context';
31
32 import styles from './Profiler.css';
33
@@ -41,8 +43,12 @@ function Profiler(_: {||}) {
43 supportsProfiling,
44 } = useContext(ProfilerContext);
45
46 + const {supportsSchedulingProfiler} = useContext(StoreContext);
47 +
48 + let showRightColumn = true;
49 +
50 let view = null;
45 - if (didRecordCommits) {
51 + if (didRecordCommits || selectedTabID === 'scheduling-profiler') {
52 switch (selectedTabID) {
53 case 'flame-chart':
54 view = <CommitFlamegraph />;
@@ -50,6 +56,10 @@ function Profiler(_: {||}) {
56 case 'ranked-chart':
57 view = <CommitRanked />;
58 break;
59 + case 'scheduling-profiler':
60 + view = <SchedulingProfiler />;
61 + showRightColumn = false;
62 + break;
63 default:
64 break;
65 }
@@ -101,7 +111,9 @@ function Profiler(_: {||}) {
111 currentTab={selectedTabID}
112 id="Profiler"
113 selectTab={selectTab}
104 - tabs={tabs}
114 + tabs={
115 + supportsSchedulingProfiler ? tabsWithSchedulingProfiler : tabs
116 + }
117 type="profiler"
118 />
119 <RootSelector />
@@ -119,7 +131,7 @@ function Profiler(_: {||}) {
131 <ModalDialog />
132 </div>
133 </div>
122 - <div className={styles.RightColumn}>{sidebar}</div>
134 + {showRightColumn && <div className={styles.RightColumn}>{sidebar}</div>}
135 <SettingsModal />
136 </div>
137 </SettingsModalContextController>
@@ -141,6 +153,17 @@ const tabs = [
153 },
154 ];
155
156 +const tabsWithSchedulingProfiler = [
157 + ...tabs,
158 + null, // Divider/separator
159 + {
160 + id: 'scheduling-profiler',
161 + icon: 'scheduling-profiler',
162 + label: 'Scheduling',
163 + title: 'Scheduling Profiler',
164 + },
165 +];
166 +
167 const NoProfilingData = () => (
168 <div className={styles.Column}>
169 <div className={styles.Header}>No profiling data has been recorded.</div>
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+2 -1
@@ -19,7 +19,8 @@ import {StoreContext} from '../context';
19
20 import type {ProfilingDataFrontend} from './types';
21
22 -export type TabID = 'flame-chart' | 'ranked-chart';
22 +// TODO (scheduling profiler) Should this be its own context?
23 +export type TabID = 'flame-chart' | 'ranked-chart' | 'scheduling-profiler';
24
25 export type Context = {|
26 // Which tab is selected in the Profiler UI?
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
+25 -6
@@ -19,13 +19,17 @@ import {
19 prepareProfilingDataFrontendFromExport,
20 } from './utils';
21 import {downloadFile} from '../utils';
22 +import {SchedulingProfilerContext} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
23
24 import styles from './ProfilingImportExportButtons.css';
25
26 import type {ProfilingDataExport} from './types';
27
28 export default function ProfilingImportExportButtons() {
28 - const {isProfiling, profilingData, rootID} = useContext(ProfilerContext);
29 + const {isProfiling, profilingData, rootID, selectedTabID} = useContext(
30 + ProfilerContext,
31 + );
32 + const {importSchedulingProfilerData} = useContext(SchedulingProfilerContext);
33 const store = useContext(StoreContext);
34 const {profilerStore} = store;
35
@@ -64,13 +68,13 @@ export default function ProfilingImportExportButtons() {
68 }
69 }, [rootID, profilingData]);
70
67 - const uploadData = useCallback(() => {
71 + const clickInputElement = useCallback(() => {
72 if (inputRef.current !== null) {
73 inputRef.current.click();
74 }
75 }, []);
76
73 - const handleFiles = useCallback(() => {
77 + const importProfilerData = useCallback(() => {
78 const input = inputRef.current;
79 if (input !== null && input.files.length > 0) {
80 const fileReader = new FileReader();
@@ -104,6 +108,13 @@ export default function ProfilingImportExportButtons() {
108 }
109 }, [modalDialogDispatch, profilerStore]);
110
111 + const importSchedulingProfilerDataWrapper = event => {
112 + const input = inputRef.current;
113 + if (input !== null && input.files.length > 0) {
114 + importSchedulingProfilerData(input.files[0]);
115 + }
116 + };
117 +
118 return (
119 <Fragment>
120 <div className={styles.VRule} />
@@ -111,18 +122,26 @@ export default function ProfilingImportExportButtons() {
122 ref={inputRef}
123 className={styles.Input}
124 type="file"
114 - onChange={handleFiles}
125 + onChange={
126 + selectedTabID === 'scheduling-profiler'
127 + ? importSchedulingProfilerDataWrapper
128 + : importProfilerData
129 + }
130 tabIndex={-1}
131 />
132 <a ref={downloadRef} className={styles.Input} />
133 <Button
134 disabled={isProfiling}
120 - onClick={uploadData}
135 + onClick={clickInputElement}
136 title="Load profile...">
137 <ButtonIcon type="import" />
138 </Button>
139 <Button
125 - disabled={isProfiling || !profilerStore.didRecordCommits}
140 + disabled={
141 + isProfiling ||
142 + !profilerStore.didRecordCommits ||
143 + selectedTabID === 'scheduling-profiler'
144 + }
145 onClick={downloadData}
146 title="Save profile...">
147 <ButtonIcon type="export" />
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+137 -1
@@ -411,11 +411,16 @@ export function updateThemeVariables(
411 updateStyleHelper(theme, 'color-record-active', documentElements);
412 updateStyleHelper(theme, 'color-record-hover', documentElements);
413 updateStyleHelper(theme, 'color-record-inactive', documentElements);
414 + updateStyleHelper(theme, 'color-resize-bar', documentElements);
415 updateStyleHelper(theme, 'color-color-scroll-thumb', documentElements);
416 updateStyleHelper(theme, 'color-color-scroll-track', documentElements);
417 updateStyleHelper(theme, 'color-search-match', documentElements);
417 - updateStyleHelper(theme, 'color-shadow', documentElements);
418 updateStyleHelper(theme, 'color-search-match-current', documentElements);
419 + updateStyleHelper(
420 + theme,
421 + 'color-scheduling-profiler-flame-graph-label',
422 + documentElements,
423 + );
424 updateStyleHelper(
425 theme,
426 'color-selected-tree-highlight-active',
@@ -426,6 +431,137 @@ export function updateThemeVariables(
431 'color-selected-tree-highlight-inactive',
432 documentElements,
433 );
434 + updateStyleHelper(
435 + theme,
436 + 'color-scheduling-profiler-priority-background',
437 + documentElements,
438 + );
439 + updateStyleHelper(
440 + theme,
441 + 'color-scheduling-profiler-priority-border',
442 + documentElements,
443 + );
444 + updateStyleHelper(
445 + theme,
446 + 'color-scheduling-profiler-user-timing',
447 + documentElements,
448 + );
449 + updateStyleHelper(
450 + theme,
451 + 'color-scheduling-profiler-user-timing-hover',
452 + documentElements,
453 + );
454 + updateStyleHelper(
455 + theme,
456 + 'color-scheduling-profiler-react-idle',
457 + documentElements,
458 + );
459 + updateStyleHelper(
460 + theme,
461 + 'color-scheduling-profiler-react-idle-selected',
462 + documentElements,
463 + );
464 + updateStyleHelper(
465 + theme,
466 + 'color-scheduling-profiler-react-idle-hover',
467 + documentElements,
468 + );
469 + updateStyleHelper(
470 + theme,
471 + 'color-scheduling-profiler-react-render',
472 + documentElements,
473 + );
474 + updateStyleHelper(
475 + theme,
476 + 'color-scheduling-profiler-react-render-selected',
477 + documentElements,
478 + );
479 + updateStyleHelper(
480 + theme,
481 + 'color-scheduling-profiler-react-render-hover',
482 + documentElements,
483 + );
484 + updateStyleHelper(
485 + theme,
486 + 'color-scheduling-profiler-react-commit',
487 + documentElements,
488 + );
489 + updateStyleHelper(
490 + theme,
491 + 'color-scheduling-profiler-react-commit-selected',
492 + documentElements,
493 + );
494 + updateStyleHelper(
495 + theme,
496 + 'color-scheduling-profiler-react-commit-hover',
497 + documentElements,
498 + );
499 + updateStyleHelper(
500 + theme,
501 + 'color-scheduling-profiler-react-layout-effects',
502 + documentElements,
503 + );
504 + updateStyleHelper(
505 + theme,
506 + 'color-scheduling-profiler-react-layout-effects-selected',
507 + documentElements,
508 + );
509 + updateStyleHelper(
510 + theme,
511 + 'color-scheduling-profiler-react-layout-effects-hover',
512 + documentElements,
513 + );
514 + updateStyleHelper(
515 + theme,
516 + 'color-scheduling-profiler-react-passive-effects',
517 + documentElements,
518 + );
519 + updateStyleHelper(
520 + theme,
521 + 'color-scheduling-profiler-react-passive-effects-selected',
522 + documentElements,
523 + );
524 + updateStyleHelper(
525 + theme,
526 + 'color-scheduling-profiler-react-passive-effects-hover',
527 + documentElements,
528 + );
529 + updateStyleHelper(
530 + theme,
531 + 'color-scheduling-profiler-react-schedule',
532 + documentElements,
533 + );
534 + updateStyleHelper(
535 + theme,
536 + 'color-scheduling-profiler-react-schedule-hover',
537 + documentElements,
538 + );
539 + updateStyleHelper(
540 + theme,
541 + 'color-scheduling-profiler-react-schedule-cascading',
542 + documentElements,
543 + );
544 + updateStyleHelper(
545 + theme,
546 + 'color-scheduling-profiler-react-schedule-cascading-hover',
547 + documentElements,
548 + );
549 + updateStyleHelper(
550 + theme,
551 + 'color-scheduling-profiler-react-suspend',
552 + documentElements,
553 + );
554 + updateStyleHelper(
555 + theme,
556 + 'color-scheduling-profiler-react-suspend-hover',
557 + documentElements,
558 + );
559 + updateStyleHelper(
560 + theme,
561 + 'color-scheduling-profiler-react-work-border',
562 + documentElements,
563 + );
564 + updateStyleHelper(theme, 'color-shadow', documentElements);
565 updateStyleHelper(theme, 'color-tab-selected-border', documentElements);
566 updateStyleHelper(theme, 'color-text', documentElements);
567 updateStyleHelper(theme, 'color-text-invalid', documentElements);
packages/react-devtools-shared/src/devtools/views/TabBar.css
+8
@@ -85,6 +85,14 @@
85 .TabLabelSettings {
86 }
87
88 +.VRule {
89 + height: 20px;
90 + width: 1px;
91 + border-left: 1px solid var(--color-border);
92 + padding-left: 0.25rem;
93 + margin-left: 0.25rem;
94 +}
95 +
96 @media screen and (max-width: 525px) {
97 .IconSizeNavigation {
98 margin-right: 0;
packages/react-devtools-shared/src/devtools/views/TabBar.js
+11 -4
@@ -29,7 +29,7 @@ export type Props = {|
29 disabled?: boolean,
30 id: string,
31 selectTab: (tabID: any) => void,
32 - tabs: Array<TabInfo>,
32 + tabs: Array<TabInfo | null>,
33 type: 'navigation' | 'profiler' | 'settings',
34 |};
35
@@ -41,8 +41,9 @@ export default function TabBar({
41 tabs,
42 type,
43 }: Props) {
44 - if (!tabs.some(tab => tab.id === currentTab)) {
45 - selectTab(tabs[0].id);
44 + if (!tabs.some(tab => tab !== null && tab.id === currentTab)) {
45 + const firstTab = ((tabs.find(tab => tab !== null): any): TabInfo);
46 + selectTab(firstTab.id);
47 }
48
49 const onChange = useCallback(
@@ -88,7 +89,13 @@ export default function TabBar({
89
90 return (
91 <Fragment>
91 - {tabs.map(({icon, id, label, title}) => {
92 + {tabs.map(tab => {
93 + if (tab === null) {
94 + return <div className={styles.VRule} />;
95 + }
96 +
97 + const {icon, id, label, title} = tab;
98 +
99 let button = (
100 <label
101 className={[
packages/react-devtools-shared/src/devtools/views/Tooltip.css
+1
@@ -6,6 +6,7 @@
6 font-size: 12px;
7 background-color: var(--color-tooltip-background);
8 color: var(--color-tooltip-text);
9 + box-shadow: 1px 1px 2px var(--color-shadow);
10
11 /* Make sure this is above the DevTools, which are above the Overlay */
12 z-index: 10000002;
packages/react-devtools-shared/src/devtools/views/root.css
+58 -2
@@ -77,6 +77,34 @@
77 --light-color-record-active: #fc3a4b;
78 --light-color-record-hover: #3578e5;
79 --light-color-record-inactive: #0088fa;
80 + --light-color-resize-bar: #cccccc;
81 + --light-color-scheduling-profiler-flame-graph-label: #000000;
82 + --light-color-scheduling-profiler-priority-background: #ededf0;
83 + --light-color-scheduling-profiler-priority-border: #d7d7db;
84 + --light-color-scheduling-profiler-user-timing: #c9cacd;
85 + --light-color-scheduling-profiler-user-timing-hover:#93959a;
86 + --light-color-scheduling-profiler-react-idle: #edf6ff;
87 + --light-color-scheduling-profiler-react-idle-selected:#EDF6FF;
88 + --light-color-scheduling-profiler-react-idle-hover:#EDF6FF;
89 + --light-color-scheduling-profiler-react-render: #9fc3f3;
90 + --light-color-scheduling-profiler-react-render-selected:#64A9F5;
91 + --light-color-scheduling-profiler-react-render-hover:#2683E2;
92 + --light-color-scheduling-profiler-react-commit: #ff718e;
93 + --light-color-scheduling-profiler-react-commit-selected:#FF5277;
94 + --light-color-scheduling-profiler-react-commit-hover:#ed0030;
95 + --light-color-scheduling-profiler-react-layout-effects:#c88ff0;
96 + --light-color-scheduling-profiler-react-layout-effects-selected:#934FC1;
97 + --light-color-scheduling-profiler-react-layout-effects-hover:#601593;
98 + --light-color-scheduling-profiler-react-passive-effects:#c88ff0;
99 + --light-color-scheduling-profiler-react-passive-effects-selected:#934FC1;
100 + --light-color-scheduling-profiler-react-passive-effects-hover:#601593;
101 + --light-color-scheduling-profiler-react-schedule: #9fc3f3;
102 + --light-color-scheduling-profiler-react-schedule-hover:#2683E2;
103 + --light-color-scheduling-profiler-react-schedule-cascading:#ff718e;
104 + --light-color-scheduling-profiler-react-schedule-cascading-hover:#ed0030;
105 + --light-color-scheduling-profiler-react-suspend: #a6e59f;
106 + --light-color-scheduling-profiler-react-suspend-hover:#13bc00;
107 + --light-color-scheduling-profiler-react-work-border:#ffffff;
108 --light-color-scroll-thumb: #c2c2c2;
109 --light-color-scroll-track: #fafafa;
110 --light-color-search-match: yellow;
@@ -146,7 +174,7 @@
174 --dark-color-console-warning-border: #665500;
175 --dark-color-console-warning-icon: #f4bd00;
176 --dark-color-console-warning-text: #f5f2ed;
149 - --dark-color-context-background: rgba(255,255,255,.9);
177 + --dark-color-context-background: rgba(255,255,255,.95);
178 --dark-color-context-background-hover: rgba(0, 136, 250, 0.1);
179 --dark-color-context-background-selected: #0088fa;
180 --dark-color-context-border: #eeeeee;
@@ -169,6 +197,34 @@
197 --dark-color-record-active: #fc3a4b;
198 --dark-color-record-hover: #a2e9fc;
199 --dark-color-record-inactive: #61dafb;
200 + --dark-color-resize-bar: #3d424a;
201 + --dark-color-scheduling-profiler-flame-graph-label: #000000;
202 + --dark-color-scheduling-profiler-priority-background: #1d2129;
203 + --dark-color-scheduling-profiler-priority-border: #282c34;
204 + --dark-color-scheduling-profiler-user-timing: #c9cacd;
205 + --dark-color-scheduling-profiler-user-timing-hover:#93959a;
206 + --dark-color-scheduling-profiler-react-idle: #3d485b;
207 + --dark-color-scheduling-profiler-react-idle-selected:#465269;
208 + --dark-color-scheduling-profiler-react-idle-hover:#465269;
209 + --dark-color-scheduling-profiler-react-render: #9fc3f3;
210 + --dark-color-scheduling-profiler-react-render-selected:#64A9F5;
211 + --dark-color-scheduling-profiler-react-render-hover:#2683E2;
212 + --dark-color-scheduling-profiler-react-commit: #ff718e;
213 + --dark-color-scheduling-profiler-react-commit-selected:#FF5277;
214 + --dark-color-scheduling-profiler-react-commit-hover:#ed0030;
215 + --dark-color-scheduling-profiler-react-layout-effects:#c88ff0;
216 + --dark-color-scheduling-profiler-react-layout-effects-selected:#934FC1;
217 + --dark-color-scheduling-profiler-react-layout-effects-hover:#601593;
218 + --dark-color-scheduling-profiler-react-passive-effects:#c88ff0;
219 + --dark-color-scheduling-profiler-react-passive-effects-selected:#934FC1;
220 + --dark-color-scheduling-profiler-react-passive-effects-hover:#601593;
221 + --dark-color-scheduling-profiler-react-schedule: #9fc3f3;
222 + --dark-color-scheduling-profiler-react-schedule-hover:#2683E2;
223 + --dark-color-scheduling-profiler-react-schedule-cascading:#ff718e;
224 + --dark-color-scheduling-profiler-react-schedule-cascading-hover:#ed0030;
225 + --dark-color-scheduling-profiler-react-suspend: #a6e59f;
226 + --dark-color-scheduling-profiler-react-suspend-hover:#13bc00;
227 + --dark-color-scheduling-profiler-react-work-border:#ffffff;
228 --dark-color-scroll-thumb: #afb3b9;
229 --dark-color-scroll-track: #313640;
230 --dark-color-search-match: yellow;
@@ -184,7 +240,7 @@
240 --dark-color-toggle-background-on: #178fb9;
241 --dark-color-toggle-background-off: #777d88;
242 --dark-color-toggle-text: #ffffff;
187 - --dark-color-tooltip-background: rgba(255, 255, 255, 0.9);
243 + --dark-color-tooltip-background: rgba(255, 255, 255, 0.95);
244 --dark-color-tooltip-text: #000000;
245
246 /* Font smoothing */
packages/react-devtools-shared/src/hookNamesCache.js
+8 -1
@@ -103,7 +103,14 @@ export function loadHookNames(
103
104 let didTimeout = false;
105
106 - loadHookNamesFunction(hooksTree).then(
106 + const response = loadHookNamesFunction(hooksTree);
107 + console.log(
108 + 'loadHookNamesFunction:',
109 + loadHookNamesFunction,
110 + '->',
111 + response,
112 + );
113 + response.then(
114 function onSuccess(hookNames) {
115 if (didTimeout) {
116 return;
yarn.lock
+45
@@ -2236,6 +2236,11 @@
2236 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
2237 integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
2238
2239 +"@types/json-schema@^7.0.8":
2240 + version "7.0.8"
2241 + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
2242 + integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==
2243 +
2244 "@types/minimatch@*", "@types/minimatch@^3.0.3":
2245 version "3.0.3"
2246 resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -2841,6 +2846,11 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
2846 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
2847 integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
2848
2849 +ajv-keywords@^3.5.2:
2850 + version "3.5.2"
2851 + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
2852 + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
2853 +
2854 ajv-merge-patch@4.1.0:
2855 version "4.1.0"
2856 resolved "https://registry.yarnpkg.com/ajv-merge-patch/-/ajv-merge-patch-4.1.0.tgz#cd580e5860ac53431d6aa901fa3d5e2eb2b74a6c"
@@ -2869,6 +2879,16 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2:
2879 json-schema-traverse "^0.4.1"
2880 uri-js "^4.2.2"
2881
2882 +ajv@^6.12.5:
2883 + version "6.12.6"
2884 + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
2885 + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
2886 + dependencies:
2887 + fast-deep-equal "^3.1.1"
2888 + fast-json-stable-stringify "^2.0.0"
2889 + json-schema-traverse "^0.4.1"
2890 + uri-js "^4.2.2"
2891 +
2892 ajv@^6.5.5, ajv@^6.9.1:
2893 version "6.10.2"
2894 resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
@@ -6768,6 +6788,14 @@ file-loader@^6.0.0:
6788 loader-utils "^2.0.0"
6789 schema-utils "^2.6.5"
6790
6791 +file-loader@^6.1.0:
6792 + version "6.2.0"
6793 + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
6794 + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
6795 + dependencies:
6796 + loader-utils "^2.0.0"
6797 + schema-utils "^3.0.0"
6798 +
6799 file-uri-to-path@1.0.0:
6800 version "1.0.0"
6801 resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -12631,6 +12659,15 @@ schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
12659 ajv "^6.12.2"
12660 ajv-keywords "^3.4.1"
12661
12662 +schema-utils@^3.0.0:
12663 + version "3.1.1"
12664 + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
12665 + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==
12666 + dependencies:
12667 + "@types/json-schema" "^7.0.8"
12668 + ajv "^6.12.5"
12669 + ajv-keywords "^3.5.2"
12670 +
12671 select-hose@^2.0.0:
12672 version "2.0.0"
12673 resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -14877,6 +14914,14 @@ worker-loader@^3.0.2:
14914 loader-utils "^2.0.0"
14915 schema-utils "^2.7.0"
14916
14917 +worker-loader@^3.0.3:
14918 + version "3.0.8"
14919 + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37"
14920 + integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==
14921 + dependencies:
14922 + loader-utils "^2.0.0"
14923 + schema-utils "^3.0.0"
14924 +
14925 workerize-loader@^1.3.0:
14926 version "1.3.0"
14927 resolved "https://registry.yarnpkg.com/workerize-loader/-/workerize-loader-1.3.0.tgz#4995cf2ff2b45dd6dc60e4411e63f5ae2c704d36"