@samitouri / QOS-React-1 / commits / 7dfc7ccd12

[DevTools] Remove the dead Timeline profiler code (#37187)

## Summary With the tab gone, everything that fed it is unreachable. This deletes `packages/react-devtools-timeline` (74 files) and the backend that produced its data, `backend/profilingHooks.js`, along with `SidebarEventInfo`, the two timeline test suites, the `timelineData` snapshot serializer, and the scheduling-profiler fixture. It also unwires the plumbing that only existed to carry timeline data: `recordTimeline` across the reload-and-profile path (hook → sessionStorage → agent → renderer), `timelineData` on `ProfilingDataBackend` and the profile export, the `supportsTimeline` Store config, the `rootSupportsTimelineProfiling` capability, the `DevToolsProfilingHooks` type and the `ReactRenderer` members DevTools used to inject it, the 40 `--color-timeline-*` theme variables in both themes plus the orphaned `--color-scroll-caret`, and `hook.js`'s internal-module-range tracking with its `react-devtools-facade` stubs. `yarn.lock` is regenerated: 52 distinct package-versions and 68 requirement specs drop out, with no additions and no version changes to anything that remains. ## Deliberate non-changes - **`PROFILER_EXPORT_VERSION` stays at 5.** `prepareProfilingDataFrontendFromExport` compares versions with `!==`, so a bump would reject every profile anyone has already saved. `timelineData` was an optional key, so dropping it is invisible in both directions. - **Profiling flag bit `0b010` is retired, not reused**, and the constant is replaced by a comment saying so. Shipped backends keep setting it, so renumbering `PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT` into that slot would make a new frontend misread older backends as tracks-capable. - **The `displayName` properties on DevTools' cache thenables are kept.** They look timeline-only, but `ReactFiberThenable` reads `thenable.displayName` to name I/O in async debug info, which feeds the Performance tracks. Only their stale comments are corrected. - **`react-reconciler`, `shared/ReactFeatureFlags.js` and `scripts/rollup` are untouched**; `enableSchedulingProfiler` is still live for www and native-fb. ## Follow-ups (not in this stack) Three stale comments still name the removed package: `scripts/rollup/wrappers.js:532` and `ReactFiberLane.js:38,125`. Left alone to keep this stack purely DevTools-side. ## Test plan `yarn linc`, `yarn flow dom-node`, and the DevTools suite all pass on this commit in isolation (40/40 suites, 582 tests).

Ruslan Lesiutin committed Aug 3, 2026 at 16:32 UTC 7dfc7ccd12d0294debc69b9b9b4e9dd1fd42e08a
115 files changed +108 -19239
.eslintignore
-2
@@ -26,8 +26,6 @@ packages/react-devtools-inline/dist
26 packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
27 packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
28 packages/react-devtools-shell/dist
29 -packages/react-devtools-timeline/dist
30 -packages/react-devtools-timeline/static
29 packages/react-devtools-cdt-mcp/dist
30 packages/react-devtools-cdt-mcp/fixtures
31
.eslintrc.js
+1 -2
@@ -337,8 +337,7 @@ module.exports = {
337 'packages/react-debug-tools/**/*.js',
338 'packages/react-devtools-extensions/**/*.js',
339 'packages/react-devtools-facade/**/*.js',
340 - 'packages/react-devtools-timeline/**/*.js',
341 - 'packages/react-native-renderer/**/*.js',
340 + 'packages/react-native-renderer/**/*.js',
341 'packages/eslint-plugin-react-hooks/**/*.js',
342 'packages/jest-react/**/*.js',
343 'packages/internal-test-utils/**/*.js',
.gitignore
-1
@@ -41,5 +41,4 @@ packages/react-devtools-extensions/.tempUserDataDir
41 packages/react-devtools-fusebox/dist
42 packages/react-devtools-inline/dist
43 packages/react-devtools-shell/dist
44 -packages/react-devtools-timeline/dist
44 packages/react-devtools-cdt-mcp/dist
.prettierignore
-2
@@ -12,8 +12,6 @@ packages/react-devtools-inline/dist
12 packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
13 packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
14 packages/react-devtools-shell/dist
15 -packages/react-devtools-timeline/dist
16 -packages/react-devtools-timeline/static
15
16 # react compiler
17 compiler/**/dist
fixtures/devtools/scheduling-profiler/.gitignore deleted
-1
@@ -1 +0,0 @@
1 -dependencies
fixtures/devtools/scheduling-profiler/README.md deleted
-15
@@ -1,15 +0,0 @@
1 -# Test fixture for `packages/react-devtools-scheduling-profiler`
2 -
3 -1. First, run the fixture:
4 -```sh
5 -# In the root directory
6 -# Download the latest *experimental* React build
7 -scripts/release/download-experimental-build.js
8 -
9 -# Run this fixtures
10 -fixtures/devtools/scheduling-profiler/run.js
11 -```
12 -
13 -2. Then open [localhost:8000/](http://localhost:8000/) and use the Performance tab in Chrome to reload-and-profile.
14 -3. Now stop profiling and export JSON.
15 -4. Lastly, open [react-scheduling-profiler.vercel.app](https://react-scheduling-profiler.vercel.app/) and upload the performance JSON data you just recorded.
\ No newline at end of file
fixtures/devtools/scheduling-profiler/app.js deleted
-14
@@ -1,14 +0,0 @@
1 -const {createElement, useLayoutEffect, useState} = React;
2 -const {createRoot} = ReactDOM;
3 -
4 -function App() {
5 - const [isMounted, setIsMounted] = useState(false);
6 - useLayoutEffect(() => {
7 - setIsMounted(true);
8 - }, []);
9 - return createElement('div', null, `isMounted? ${isMounted}`);
10 -}
11 -
12 -const container = document.getElementById('container');
13 -const root = createRoot(container);
14 -root.render(createElement(App));
fixtures/devtools/scheduling-profiler/index.html deleted
-14
@@ -1,14 +0,0 @@
1 -<!DOCTYPE html>
2 -<html>
3 - <head>
4 - <title>Scheduling Profiler Fixture</title>
5 -
6 - <script src="./scheduler.js"></script>
7 - <script src="./react.js"></script>
8 - <script src="./react-dom.js"></script>
9 - </head>
10 - <body>
11 - <div id="container"></div>
12 - <script src="./app.js"></script>
13 - </body>
14 -</html>
\ No newline at end of file
fixtures/devtools/scheduling-profiler/run.js deleted
-78
@@ -1,78 +0,0 @@
1 -#!/usr/bin/env node
2 -
3 -'use strict';
4 -
5 -const {
6 - copyFileSync,
7 - existsSync,
8 - mkdirSync,
9 - readFileSync,
10 - rmdirSync,
11 -} = require('fs');
12 -const {join} = require('path');
13 -const http = require('http');
14 -
15 -const DEPENDENCIES = [
16 - ['scheduler/umd/scheduler.development.js', 'scheduler.js'],
17 - ['react/umd/react.development.js', 'react.js'],
18 - ['react-dom/umd/react-dom.development.js', 'react-dom.js'],
19 -];
20 -
21 -const BUILD_DIRECTORY = '../../../build/oss-experimental/';
22 -const DEPENDENCIES_DIRECTORY = 'dependencies';
23 -
24 -function initDependencies() {
25 - if (existsSync(DEPENDENCIES_DIRECTORY)) {
26 - rmdirSync(DEPENDENCIES_DIRECTORY, {recursive: true});
27 - }
28 - mkdirSync(DEPENDENCIES_DIRECTORY);
29 -
30 - DEPENDENCIES.forEach(([from, to]) => {
31 - const fromPath = join(__dirname, BUILD_DIRECTORY, from);
32 - const toPath = join(__dirname, DEPENDENCIES_DIRECTORY, to);
33 - console.log(`Copying ${fromPath} => ${toPath}`);
34 - copyFileSync(fromPath, toPath);
35 - });
36 -}
37 -
38 -function initServer() {
39 - const host = 'localhost';
40 - const port = 8000;
41 -
42 - const requestListener = function (request, response) {
43 - let contents;
44 - switch (request.url) {
45 - case '/react.js':
46 - case '/react-dom.js':
47 - case '/scheduler.js':
48 - response.setHeader('Content-Type', 'text/javascript');
49 - response.writeHead(200);
50 - contents = readFileSync(
51 - join(__dirname, DEPENDENCIES_DIRECTORY, request.url)
52 - );
53 - response.end(contents);
54 - break;
55 - case '/app.js':
56 - response.setHeader('Content-Type', 'text/javascript');
57 - response.writeHead(200);
58 - contents = readFileSync(join(__dirname, 'app.js'));
59 - response.end(contents);
60 - break;
61 - case '/index.html':
62 - default:
63 - response.setHeader('Content-Type', 'text/html');
64 - response.writeHead(200);
65 - contents = readFileSync(join(__dirname, 'index.html'));
66 - response.end(contents);
67 - break;
68 - }
69 - };
70 -
71 - const server = http.createServer(requestListener);
72 - server.listen(port, host, () => {
73 - console.log(`Server is running on http://${host}:${port}`);
74 - });
75 -}
76 -
77 -initDependencies();
78 -initServer();
packages/react-devtools-core/README.md
+1 -1
@@ -29,7 +29,7 @@ if (process.env.NODE_ENV !== 'production') {
29 |---------------------------|-------------|
30 | `settings` | Optional. If not specified, or received as null, then default settings are used. Can be plain object or a Promise that resolves with the [plain settings object](#Settings). If Promise rejects, the console will not be patched and some console features from React DevTools will not work. |
31 | `shouldStartProfilingNow` | Optional. Whether to start profiling immediately after installing the hook. Defaults to `false`. |
32 -| `profilingSettings` | Optional. Profiling settings used when `shouldStartProfilingNow` is `true`. Defaults to `{ recordChangeDescriptions: false, recordTimeline: false }`. |
32 +| `profilingSettings` | Optional. Profiling settings used when `shouldStartProfilingNow` is `true`. Defaults to `{ recordChangeDescriptions: false }`. |
33 | `componentFilters` | Optional. Array or Promise that resolves to an array of component filters to apply before DevTools connects. Defaults to the built-in host component filter. See [Component filters](#component-filters) for the full spec. |
34
35 #### `Settings`
packages/react-devtools-extensions/src/main/index.js
-2
@@ -255,8 +255,6 @@ function createDevToolsInstance(): DevToolsInstance {
255 const store = new Store(bridge, {
256 isProfiling,
257 supportsReloadAndProfile: __IS_CHROME__ || __IS_EDGE__,
258 - // At this time, the timeline can only parse Chrome performance profiles.
259 - supportsTimeline: __IS_CHROME__,
258 supportsTraceUpdates: true,
259 supportsInspectMatchingDOMElement: true,
260 supportsClickToInspect: true,
packages/react-devtools-facade/src/DevToolsFacade.js
-5
@@ -288,11 +288,6 @@ export function installFacade(target?: any = globalThis): Facade {
288 profilingState.onPostCommit(root);
289 }
290 },
291 - getInternalModuleRanges(): Array<[string, string]> {
292 - return [];
293 - },
294 - registerInternalModuleStart() {},
295 - registerInternalModuleStop() {},
291 };
292
293 Object.defineProperty(target, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {
packages/react-devtools-inline/src/frontend.js
-1
@@ -17,7 +17,6 @@ export function createStore(bridge: FrontendBridge, config?: Config): Store {
17 const store = new Store(bridge, {
18 checkBridgeProtocolCompatibility: true,
19 supportsTraceUpdates: true,
20 - supportsTimeline: true,
20 ...config,
21 });
22 subscribeToStoreErrors(store, bridge);
packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js deleted
-2643
@@ -1,2643 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -'use strict';
11 -
12 -import {
13 - getLegacyRenderImplementation,
14 - getModernRenderImplementation,
15 - normalizeCodeLocInfo,
16 -} from './utils';
17 -
18 -let React = require('react');
19 -let Scheduler;
20 -let store;
21 -let utils;
22 -
23 -// This flag is on experimental which disables timeline profiler.
24 -const enableComponentPerformanceTrack =
25 - React.version.startsWith('19') && React.version.includes('experimental');
26 -
27 -describe('Timeline profiler', () => {
28 - if (enableComponentPerformanceTrack) {
29 - test('no tests', () => {});
30 - // Ignore all tests.
31 - return;
32 - }
33 -
34 - beforeEach(() => {
35 - utils = require('./utils');
36 - utils.beforeEachProfiling();
37 -
38 - React = require('react');
39 - Scheduler = require('scheduler');
40 -
41 - store = global.store;
42 - });
43 -
44 - afterEach(() => {
45 - jest.restoreAllMocks();
46 - });
47 -
48 - describe('User Timing API', () => {
49 - let currentlyNotClearedMarks;
50 - let registeredMarks;
51 - let featureDetectionMarkName = null;
52 - let setPerformanceMock;
53 -
54 - function createUserTimingPolyfill() {
55 - featureDetectionMarkName = null;
56 -
57 - currentlyNotClearedMarks = [];
58 - registeredMarks = [];
59 -
60 - // Remove file-system specific bits or version-specific bits of information from the module range marks.
61 - function filterMarkData(markName) {
62 - if (markName.startsWith('--react-internal-module-start')) {
63 - return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
64 - } else if (markName.startsWith('--react-internal-module-stop')) {
65 - return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
66 - } else if (markName.startsWith('--react-version')) {
67 - return '--react-version-<filtered-version>';
68 - } else {
69 - return markName;
70 - }
71 - }
72 -
73 - // This is not a true polyfill, but it gives us enough to capture marks.
74 - // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
75 - return {
76 - clearMarks(markName) {
77 - markName = filterMarkData(markName);
78 -
79 - currentlyNotClearedMarks = currentlyNotClearedMarks.filter(
80 - mark => mark !== markName,
81 - );
82 - },
83 - mark(markName, markOptions) {
84 - markName = filterMarkData(markName);
85 -
86 - if (featureDetectionMarkName === null) {
87 - featureDetectionMarkName = markName;
88 - }
89 -
90 - registeredMarks.push(markName);
91 - currentlyNotClearedMarks.push(markName);
92 -
93 - if (markOptions != null) {
94 - // This is triggers the feature detection.
95 - markOptions.startTime++;
96 - }
97 - },
98 - };
99 - }
100 -
101 - function eraseRegisteredMarks() {
102 - registeredMarks.splice(0);
103 - }
104 -
105 - function dispatchAndSetCurrentEvent(element, event) {
106 - try {
107 - window.event = event;
108 - element.dispatchEvent(event);
109 - } finally {
110 - window.event = undefined;
111 - }
112 - }
113 -
114 - beforeEach(() => {
115 - setPerformanceMock =
116 - require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
117 - setPerformanceMock(createUserTimingPolyfill());
118 - });
119 -
120 - afterEach(() => {
121 - // Verify all logged marks also get cleared.
122 - expect(currentlyNotClearedMarks).toHaveLength(0);
123 -
124 - setPerformanceMock(null);
125 - });
126 -
127 - describe('with legacy render', () => {
128 - const {render: legacyRender} = getLegacyRenderImplementation();
129 -
130 - // @reactVersion <= 18.2
131 - // @reactVersion >= 18.0
132 - it('should mark sync render without suspends or state updates', () => {
133 - utils.act(() => store.profilerStore.startProfiling());
134 - legacyRender(<div />);
135 - utils.act(() => store.profilerStore.stopProfiling());
136 -
137 - expect(registeredMarks).toMatchInlineSnapshot(`
138 - [
139 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
140 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
141 - "--schedule-render-1",
142 - "--render-start-1",
143 - "--render-stop",
144 - "--commit-start-1",
145 - "--react-version-<filtered-version>",
146 - "--profiler-version-1",
147 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
148 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
149 - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
150 - "--layout-effects-start-1",
151 - "--layout-effects-stop",
152 - "--commit-stop",
153 - ]
154 - `);
155 - });
156 -
157 - // TODO(hoxyq): investigate why running this test with React 18 fails
158 - // @reactVersion <= 18.2
159 - // @reactVersion >= 18.0
160 - // eslint-disable-next-line jest/no-disabled-tests
161 - it.skip('should mark sync render with suspense that resolves', async () => {
162 - const fakeSuspensePromise = Promise.resolve(true);
163 - function Example() {
164 - throw fakeSuspensePromise;
165 - }
166 -
167 - legacyRender(
168 - <React.Suspense fallback={null}>
169 - <Example />
170 - </React.Suspense>,
171 - );
172 -
173 - expect(registeredMarks).toMatchInlineSnapshot(`
174 - [
175 - "--schedule-render-2",
176 - "--render-start-2",
177 - "--component-render-start-Example",
178 - "--component-render-stop",
179 - "--suspense-suspend-0-Example-mount-2-",
180 - "--render-stop",
181 - "--commit-start-2",
182 - "--react-version-<filtered-version>",
183 - "--profiler-version-1",
184 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
185 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
186 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
187 - "--layout-effects-start-2",
188 - "--layout-effects-stop",
189 - "--commit-stop",
190 - ]
191 - `);
192 -
193 - eraseRegisteredMarks();
194 -
195 - await fakeSuspensePromise;
196 - expect(registeredMarks).toMatchInlineSnapshot(`
197 - [
198 - "--suspense-resolved-0-Example",
199 - ]
200 - `);
201 - });
202 -
203 - // TODO(hoxyq): investigate why running this test with React 18 fails
204 - // @reactVersion <= 18.2
205 - // @reactVersion >= 18.0
206 - // eslint-disable-next-line jest/no-disabled-tests
207 - it.skip('should mark sync render with suspense that rejects', async () => {
208 - const fakeSuspensePromise = Promise.reject(new Error('error'));
209 - function Example() {
210 - throw fakeSuspensePromise;
211 - }
212 -
213 - legacyRender(
214 - <React.Suspense fallback={null}>
215 - <Example />
216 - </React.Suspense>,
217 - );
218 -
219 - expect(registeredMarks).toMatchInlineSnapshot(`
220 - [
221 - "--schedule-render-2",
222 - "--render-start-2",
223 - "--component-render-start-Example",
224 - "--component-render-stop",
225 - "--suspense-suspend-0-Example-mount-2-",
226 - "--render-stop",
227 - "--commit-start-2",
228 - "--react-version-<filtered-version>",
229 - "--profiler-version-1",
230 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
231 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
232 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
233 - "--layout-effects-start-2",
234 - "--layout-effects-stop",
235 - "--commit-stop",
236 - ]
237 - `);
238 -
239 - eraseRegisteredMarks();
240 -
241 - await expect(fakeSuspensePromise).rejects.toThrow();
242 - expect(registeredMarks).toContain(`--suspense-rejected-0-Example`);
243 - });
244 -
245 - // @reactVersion <= 18.2
246 - // @reactVersion >= 18.0
247 - it('should mark sync render that throws', async () => {
248 - jest.spyOn(console, 'error').mockImplementation(() => {});
249 -
250 - class ErrorBoundary extends React.Component {
251 - state = {error: null};
252 - componentDidCatch(error) {
253 - this.setState({error});
254 - }
255 - render() {
256 - if (this.state.error) {
257 - return null;
258 - }
259 - return this.props.children;
260 - }
261 - }
262 -
263 - function ExampleThatThrows() {
264 - throw Error('Expected error');
265 - }
266 -
267 - utils.act(() => store.profilerStore.startProfiling());
268 - legacyRender(
269 - <ErrorBoundary>
270 - <ExampleThatThrows />
271 - </ErrorBoundary>,
272 - );
273 - utils.act(() => store.profilerStore.stopProfiling());
274 -
275 - expect(registeredMarks).toMatchInlineSnapshot(`
276 - [
277 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
278 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
279 - "--schedule-render-1",
280 - "--render-start-1",
281 - "--component-render-start-ErrorBoundary",
282 - "--component-render-stop",
283 - "--component-render-start-ExampleThatThrows",
284 - "--component-render-start-ExampleThatThrows",
285 - "--component-render-stop",
286 - "--error-ExampleThatThrows-mount-Expected error",
287 - "--render-stop",
288 - "--commit-start-1",
289 - "--react-version-<filtered-version>",
290 - "--profiler-version-1",
291 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
292 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
293 - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
294 - "--layout-effects-start-1",
295 - "--schedule-state-update-1-ErrorBoundary",
296 - "--layout-effects-stop",
297 - "--commit-stop",
298 - "--render-start-1",
299 - "--component-render-start-ErrorBoundary",
300 - "--component-render-stop",
301 - "--render-stop",
302 - "--commit-start-1",
303 - "--react-version-<filtered-version>",
304 - "--profiler-version-1",
305 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
306 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
307 - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
308 - "--commit-stop",
309 - ]
310 - `);
311 - });
312 - });
313 -
314 - describe('with createRoot', () => {
315 - let waitFor;
316 - let waitForAll;
317 - let waitForPaint;
318 - let assertLog;
319 -
320 - beforeEach(() => {
321 - const InternalTestUtils = require('internal-test-utils');
322 - waitFor = InternalTestUtils.waitFor;
323 - waitForAll = InternalTestUtils.waitForAll;
324 - waitForPaint = InternalTestUtils.waitForPaint;
325 - assertLog = InternalTestUtils.assertLog;
326 - });
327 -
328 - const {render: modernRender} = getModernRenderImplementation();
329 -
330 - it('should mark concurrent render without suspends or state updates', async () => {
331 - modernRender(<div />);
332 -
333 - expect(registeredMarks).toMatchInlineSnapshot(`
334 - [
335 - "--schedule-render-32",
336 - ]
337 - `);
338 -
339 - eraseRegisteredMarks();
340 -
341 - await waitForPaint([]);
342 -
343 - expect(registeredMarks).toMatchInlineSnapshot(`
344 - [
345 - "--render-start-32",
346 - "--render-stop",
347 - "--commit-start-32",
348 - "--react-version-<filtered-version>",
349 - "--profiler-version-1",
350 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
351 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
352 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
353 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
354 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
355 - "--layout-effects-start-32",
356 - "--layout-effects-stop",
357 - "--commit-stop",
358 - ]
359 - `);
360 - });
361 -
362 - it('should mark render yields', async () => {
363 - function Bar() {
364 - Scheduler.log('Bar');
365 - return null;
366 - }
367 -
368 - function Foo() {
369 - Scheduler.log('Foo');
370 - return <Bar />;
371 - }
372 -
373 - React.startTransition(() => {
374 - modernRender(<Foo />);
375 - });
376 -
377 - await waitFor(['Foo']);
378 -
379 - expect(registeredMarks).toMatchInlineSnapshot(`
380 - [
381 - "--schedule-render-128",
382 - "--render-start-128",
383 - "--component-render-start-Foo",
384 - "--component-render-stop",
385 - "--render-yield",
386 - ]
387 - `);
388 - });
389 -
390 - it('should mark concurrent render with suspense that resolves', async () => {
391 - let resolveFakePromise;
392 - const fakeSuspensePromise = new Promise(
393 - resolve => (resolveFakePromise = resolve),
394 - );
395 -
396 - function Example() {
397 - throw fakeSuspensePromise;
398 - }
399 -
400 - modernRender(
401 - <React.Suspense fallback={null}>
402 - <Example />
403 - </React.Suspense>,
404 - );
405 -
406 - expect(registeredMarks).toMatchInlineSnapshot(`
407 - [
408 - "--schedule-render-32",
409 - ]
410 - `);
411 -
412 - eraseRegisteredMarks();
413 -
414 - await waitForPaint([]);
415 -
416 - expect(registeredMarks).toMatchInlineSnapshot(`
417 - [
418 - "--render-start-32",
419 - "--component-render-start-Example",
420 - "--component-render-stop",
421 - "--suspense-suspend-0-Example-mount-32-",
422 - "--render-stop",
423 - "--commit-start-32",
424 - "--react-version-<filtered-version>",
425 - "--profiler-version-1",
426 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
427 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
428 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
429 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
430 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
431 - "--layout-effects-start-32",
432 - "--layout-effects-stop",
433 - "--commit-stop",
434 - ]
435 - `);
436 -
437 - eraseRegisteredMarks();
438 -
439 - await resolveFakePromise();
440 - expect(registeredMarks).toMatchInlineSnapshot(`
441 - [
442 - "--suspense-resolved-0-Example",
443 - ]
444 - `);
445 - });
446 -
447 - it('should mark concurrent render with suspense that rejects', async () => {
448 - let rejectFakePromise;
449 - const fakeSuspensePromise = new Promise(
450 - (_, reject) => (rejectFakePromise = reject),
451 - );
452 -
453 - function Example() {
454 - throw fakeSuspensePromise;
455 - }
456 -
457 - modernRender(
458 - <React.Suspense fallback={null}>
459 - <Example />
460 - </React.Suspense>,
461 - );
462 -
463 - expect(registeredMarks).toMatchInlineSnapshot(`
464 - [
465 - "--schedule-render-32",
466 - ]
467 - `);
468 -
469 - eraseRegisteredMarks();
470 -
471 - await waitForPaint([]);
472 -
473 - expect(registeredMarks).toMatchInlineSnapshot(`
474 - [
475 - "--render-start-32",
476 - "--component-render-start-Example",
477 - "--component-render-stop",
478 - "--suspense-suspend-0-Example-mount-32-",
479 - "--render-stop",
480 - "--commit-start-32",
481 - "--react-version-<filtered-version>",
482 - "--profiler-version-1",
483 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
484 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
485 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
486 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
487 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
488 - "--layout-effects-start-32",
489 - "--layout-effects-stop",
490 - "--commit-stop",
491 - ]
492 - `);
493 -
494 - eraseRegisteredMarks();
495 -
496 - await expect(() => {
497 - rejectFakePromise(new Error('error'));
498 - return fakeSuspensePromise;
499 - }).rejects.toThrow();
500 - expect(registeredMarks).toMatchInlineSnapshot(`
501 - [
502 - "--suspense-rejected-0-Example",
503 - ]
504 - `);
505 - });
506 -
507 - it('should mark cascading class component state updates', async () => {
508 - class Example extends React.Component {
509 - state = {didMount: false};
510 - componentDidMount() {
511 - this.setState({didMount: true});
512 - }
513 - render() {
514 - return null;
515 - }
516 - }
517 -
518 - modernRender(<Example />);
519 -
520 - expect(registeredMarks).toMatchInlineSnapshot(`
521 - [
522 - "--schedule-render-32",
523 - ]
524 - `);
525 -
526 - eraseRegisteredMarks();
527 -
528 - await waitForPaint([]);
529 -
530 - expect(registeredMarks).toMatchInlineSnapshot(`
531 - [
532 - "--render-start-32",
533 - "--component-render-start-Example",
534 - "--component-render-stop",
535 - "--render-stop",
536 - "--commit-start-32",
537 - "--react-version-<filtered-version>",
538 - "--profiler-version-1",
539 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
540 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
541 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
542 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
543 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
544 - "--layout-effects-start-32",
545 - "--schedule-state-update-2-Example",
546 - "--layout-effects-stop",
547 - "--render-start-2",
548 - "--component-render-start-Example",
549 - "--component-render-stop",
550 - "--render-stop",
551 - "--commit-start-2",
552 - "--react-version-<filtered-version>",
553 - "--profiler-version-1",
554 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
555 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
556 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
557 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
558 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
559 - "--commit-stop",
560 - "--commit-stop",
561 - ]
562 - `);
563 - });
564 -
565 - it('should mark cascading class component force updates', async () => {
566 - class Example extends React.Component {
567 - componentDidMount() {
568 - this.forceUpdate();
569 - }
570 - render() {
571 - return null;
572 - }
573 - }
574 -
575 - modernRender(<Example />);
576 -
577 - expect(registeredMarks).toMatchInlineSnapshot(`
578 - [
579 - "--schedule-render-32",
580 - ]
581 - `);
582 -
583 - eraseRegisteredMarks();
584 -
585 - await waitForPaint([]);
586 -
587 - expect(registeredMarks).toMatchInlineSnapshot(`
588 - [
589 - "--render-start-32",
590 - "--component-render-start-Example",
591 - "--component-render-stop",
592 - "--render-stop",
593 - "--commit-start-32",
594 - "--react-version-<filtered-version>",
595 - "--profiler-version-1",
596 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
597 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
598 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
599 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
600 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
601 - "--layout-effects-start-32",
602 - "--schedule-forced-update-2-Example",
603 - "--layout-effects-stop",
604 - "--render-start-2",
605 - "--component-render-start-Example",
606 - "--component-render-stop",
607 - "--render-stop",
608 - "--commit-start-2",
609 - "--react-version-<filtered-version>",
610 - "--profiler-version-1",
611 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
612 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
613 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
614 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
615 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
616 - "--commit-stop",
617 - "--commit-stop",
618 - ]
619 - `);
620 - });
621 -
622 - it('should mark render phase state updates for class component', async () => {
623 - class Example extends React.Component {
624 - state = {didRender: false};
625 - render() {
626 - if (this.state.didRender === false) {
627 - this.setState({didRender: true});
628 - }
629 - return null;
630 - }
631 - }
632 -
633 - modernRender(<Example />);
634 -
635 - expect(registeredMarks).toMatchInlineSnapshot(`
636 - [
637 - "--schedule-render-32",
638 - ]
639 - `);
640 -
641 - eraseRegisteredMarks();
642 -
643 - let errorMessage;
644 - jest.spyOn(console, 'error').mockImplementation(message => {
645 - errorMessage = message;
646 - });
647 -
648 - await waitForPaint([]);
649 -
650 - expect(console.error).toHaveBeenCalledTimes(1);
651 - expect(errorMessage).toContain(
652 - 'Cannot update during an existing state transition',
653 - );
654 -
655 - expect(registeredMarks).toMatchInlineSnapshot(`
656 - [
657 - "--render-start-32",
658 - "--component-render-start-Example",
659 - "--schedule-state-update-32-Example",
660 - "--component-render-stop",
661 - "--render-stop",
662 - "--commit-start-32",
663 - "--react-version-<filtered-version>",
664 - "--profiler-version-1",
665 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
666 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
667 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
668 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
669 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
670 - "--layout-effects-start-32",
671 - "--layout-effects-stop",
672 - "--commit-stop",
673 - ]
674 - `);
675 - });
676 -
677 - it('should mark render phase force updates for class component', async () => {
678 - let forced = false;
679 - class Example extends React.Component {
680 - render() {
681 - if (!forced) {
682 - forced = true;
683 - this.forceUpdate();
684 - }
685 - return null;
686 - }
687 - }
688 -
689 - modernRender(<Example />);
690 -
691 - expect(registeredMarks).toMatchInlineSnapshot(`
692 - [
693 - "--schedule-render-32",
694 - ]
695 - `);
696 -
697 - eraseRegisteredMarks();
698 -
699 - let errorMessage;
700 - jest.spyOn(console, 'error').mockImplementation(message => {
701 - errorMessage = message;
702 - });
703 -
704 - await waitForPaint([]);
705 -
706 - expect(console.error).toHaveBeenCalledTimes(1);
707 - expect(errorMessage).toContain(
708 - 'Cannot update during an existing state transition',
709 - );
710 -
711 - expect(registeredMarks).toMatchInlineSnapshot(`
712 - [
713 - "--render-start-32",
714 - "--component-render-start-Example",
715 - "--schedule-forced-update-32-Example",
716 - "--component-render-stop",
717 - "--render-stop",
718 - "--commit-start-32",
719 - "--react-version-<filtered-version>",
720 - "--profiler-version-1",
721 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
722 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
723 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
724 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
725 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
726 - "--layout-effects-start-32",
727 - "--layout-effects-stop",
728 - "--commit-stop",
729 - ]
730 - `);
731 - });
732 -
733 - it('should mark cascading layout updates', async () => {
734 - function Example() {
735 - const [didMount, setDidMount] = React.useState(false);
736 - React.useLayoutEffect(() => {
737 - setDidMount(true);
738 - }, []);
739 - return didMount;
740 - }
741 -
742 - modernRender(<Example />);
743 -
744 - expect(registeredMarks).toMatchInlineSnapshot(`
745 - [
746 - "--schedule-render-32",
747 - ]
748 - `);
749 -
750 - eraseRegisteredMarks();
751 -
752 - await waitForPaint([]);
753 -
754 - expect(registeredMarks).toMatchInlineSnapshot(`
755 - [
756 - "--render-start-32",
757 - "--component-render-start-Example",
758 - "--component-render-stop",
759 - "--render-stop",
760 - "--commit-start-32",
761 - "--react-version-<filtered-version>",
762 - "--profiler-version-1",
763 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
764 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
765 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
766 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
767 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
768 - "--layout-effects-start-32",
769 - "--component-layout-effect-mount-start-Example",
770 - "--schedule-state-update-2-Example",
771 - "--component-layout-effect-mount-stop",
772 - "--layout-effects-stop",
773 - "--render-start-2",
774 - "--component-render-start-Example",
775 - "--component-render-stop",
776 - "--render-stop",
777 - "--commit-start-2",
778 - "--react-version-<filtered-version>",
779 - "--profiler-version-1",
780 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
781 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
782 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
783 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
784 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
785 - "--commit-stop",
786 - "--commit-stop",
787 - ]
788 - `);
789 - });
790 -
791 - it('should mark cascading passive updates', async () => {
792 - function Example() {
793 - const [didMount, setDidMount] = React.useState(false);
794 - React.useEffect(() => {
795 - setDidMount(true);
796 - }, []);
797 - return didMount;
798 - }
799 -
800 - modernRender(<Example />);
801 -
802 - await waitForAll([]);
803 -
804 - expect(registeredMarks).toMatchInlineSnapshot(`
805 - [
806 - "--schedule-render-32",
807 - "--render-start-32",
808 - "--component-render-start-Example",
809 - "--component-render-stop",
810 - "--render-stop",
811 - "--commit-start-32",
812 - "--react-version-<filtered-version>",
813 - "--profiler-version-1",
814 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
815 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
816 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
817 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
818 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
819 - "--layout-effects-start-32",
820 - "--layout-effects-stop",
821 - "--commit-stop",
822 - "--passive-effects-start-32",
823 - "--component-passive-effect-mount-start-Example",
824 - "--schedule-state-update-32-Example",
825 - "--component-passive-effect-mount-stop",
826 - "--passive-effects-stop",
827 - "--render-start-32",
828 - "--component-render-start-Example",
829 - "--component-render-stop",
830 - "--render-stop",
831 - "--commit-start-32",
832 - "--react-version-<filtered-version>",
833 - "--profiler-version-1",
834 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
835 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
836 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
837 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
838 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
839 - "--commit-stop",
840 - ]
841 - `);
842 - });
843 -
844 - it('should mark render phase updates', async () => {
845 - function Example() {
846 - const [didRender, setDidRender] = React.useState(false);
847 - if (!didRender) {
848 - setDidRender(true);
849 - }
850 - return didRender;
851 - }
852 -
853 - modernRender(<Example />);
854 -
855 - await waitForAll([]);
856 -
857 - expect(registeredMarks).toMatchInlineSnapshot(`
858 - [
859 - "--schedule-render-32",
860 - "--render-start-32",
861 - "--component-render-start-Example",
862 - "--schedule-state-update-32-Example",
863 - "--component-render-stop",
864 - "--render-stop",
865 - "--commit-start-32",
866 - "--react-version-<filtered-version>",
867 - "--profiler-version-1",
868 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
869 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
870 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
871 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
872 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
873 - "--layout-effects-start-32",
874 - "--layout-effects-stop",
875 - "--commit-stop",
876 - ]
877 - `);
878 - });
879 -
880 - it('should mark concurrent render that throws', async () => {
881 - jest.spyOn(console, 'error').mockImplementation(() => {});
882 -
883 - class ErrorBoundary extends React.Component {
884 - state = {error: null};
885 - componentDidCatch(error) {
886 - this.setState({error});
887 - }
888 - render() {
889 - if (this.state.error) {
890 - return null;
891 - }
892 - return this.props.children;
893 - }
894 - }
895 -
896 - function ExampleThatThrows() {
897 - // eslint-disable-next-line no-throw-literal
898 - throw 'Expected error';
899 - }
900 -
901 - modernRender(
902 - <ErrorBoundary>
903 - <ExampleThatThrows />
904 - </ErrorBoundary>,
905 - );
906 -
907 - expect(registeredMarks).toMatchInlineSnapshot(`
908 - [
909 - "--schedule-render-32",
910 - ]
911 - `);
912 -
913 - eraseRegisteredMarks();
914 -
915 - await waitForPaint([]);
916 -
917 - expect(registeredMarks).toMatchInlineSnapshot(`
918 - [
919 - "--render-start-32",
920 - "--component-render-start-ErrorBoundary",
921 - "--component-render-stop",
922 - "--component-render-start-ExampleThatThrows",
923 - "--component-render-stop",
924 - "--error-ExampleThatThrows-mount-Expected error",
925 - "--render-stop",
926 - "--render-start-32",
927 - "--component-render-start-ErrorBoundary",
928 - "--component-render-stop",
929 - "--component-render-start-ExampleThatThrows",
930 - "--component-render-stop",
931 - "--error-ExampleThatThrows-mount-Expected error",
932 - "--render-stop",
933 - "--commit-start-32",
934 - "--react-version-<filtered-version>",
935 - "--profiler-version-1",
936 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
937 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
938 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
939 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
940 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
941 - "--layout-effects-start-32",
942 - "--schedule-state-update-2-ErrorBoundary",
943 - "--layout-effects-stop",
944 - "--render-start-2",
945 - "--component-render-start-ErrorBoundary",
946 - "--component-render-stop",
947 - "--render-stop",
948 - "--commit-start-2",
949 - "--react-version-<filtered-version>",
950 - "--profiler-version-1",
951 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
952 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
953 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
954 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
955 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
956 - "--commit-stop",
957 - "--commit-stop",
958 - ]
959 - `);
960 - });
961 -
962 - it('should mark passive and layout effects', async () => {
963 - function ComponentWithEffects() {
964 - React.useLayoutEffect(() => {
965 - Scheduler.log('layout 1 mount');
966 - return () => {
967 - Scheduler.log('layout 1 unmount');
968 - };
969 - }, []);
970 -
971 - React.useEffect(() => {
972 - Scheduler.log('passive 1 mount');
973 - return () => {
974 - Scheduler.log('passive 1 unmount');
975 - };
976 - }, []);
977 -
978 - React.useLayoutEffect(() => {
979 - Scheduler.log('layout 2 mount');
980 - return () => {
981 - Scheduler.log('layout 2 unmount');
982 - };
983 - }, []);
984 -
985 - React.useEffect(() => {
986 - Scheduler.log('passive 2 mount');
987 - return () => {
988 - Scheduler.log('passive 2 unmount');
989 - };
990 - }, []);
991 -
992 - React.useEffect(() => {
993 - Scheduler.log('passive 3 mount');
994 - return () => {
995 - Scheduler.log('passive 3 unmount');
996 - };
997 - }, []);
998 -
999 - return null;
1000 - }
1001 -
1002 - const unmount = modernRender(<ComponentWithEffects />);
1003 -
1004 - await waitForPaint(['layout 1 mount', 'layout 2 mount']);
1005 -
1006 - expect(registeredMarks).toMatchInlineSnapshot(`
1007 - [
1008 - "--schedule-render-32",
1009 - "--render-start-32",
1010 - "--component-render-start-ComponentWithEffects",
1011 - "--component-render-stop",
1012 - "--render-stop",
1013 - "--commit-start-32",
1014 - "--react-version-<filtered-version>",
1015 - "--profiler-version-1",
1016 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1017 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1018 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1019 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1020 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
1021 - "--layout-effects-start-32",
1022 - "--component-layout-effect-mount-start-ComponentWithEffects",
1023 - "--component-layout-effect-mount-stop",
1024 - "--component-layout-effect-mount-start-ComponentWithEffects",
1025 - "--component-layout-effect-mount-stop",
1026 - "--layout-effects-stop",
1027 - "--commit-stop",
1028 - ]
1029 - `);
1030 -
1031 - eraseRegisteredMarks();
1032 -
1033 - await waitForAll([
1034 - 'passive 1 mount',
1035 - 'passive 2 mount',
1036 - 'passive 3 mount',
1037 - ]);
1038 -
1039 - expect(registeredMarks).toMatchInlineSnapshot(`
1040 - [
1041 - "--passive-effects-start-32",
1042 - "--component-passive-effect-mount-start-ComponentWithEffects",
1043 - "--component-passive-effect-mount-stop",
1044 - "--component-passive-effect-mount-start-ComponentWithEffects",
1045 - "--component-passive-effect-mount-stop",
1046 - "--component-passive-effect-mount-start-ComponentWithEffects",
1047 - "--component-passive-effect-mount-stop",
1048 - "--passive-effects-stop",
1049 - ]
1050 - `);
1051 -
1052 - eraseRegisteredMarks();
1053 -
1054 - await waitForAll([]);
1055 -
1056 - unmount();
1057 -
1058 - assertLog([
1059 - 'layout 1 unmount',
1060 - 'layout 2 unmount',
1061 - 'passive 1 unmount',
1062 - 'passive 2 unmount',
1063 - 'passive 3 unmount',
1064 - ]);
1065 -
1066 - expect(registeredMarks).toMatchInlineSnapshot(`
1067 - [
1068 - "--schedule-render-2",
1069 - "--render-start-2",
1070 - "--render-stop",
1071 - "--commit-start-2",
1072 - "--react-version-<filtered-version>",
1073 - "--profiler-version-1",
1074 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1075 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1076 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1077 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1078 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
1079 - "--component-layout-effect-unmount-start-ComponentWithEffects",
1080 - "--component-layout-effect-unmount-stop",
1081 - "--component-layout-effect-unmount-start-ComponentWithEffects",
1082 - "--component-layout-effect-unmount-stop",
1083 - "--layout-effects-start-2",
1084 - "--layout-effects-stop",
1085 - "--passive-effects-start-2",
1086 - "--component-passive-effect-unmount-start-ComponentWithEffects",
1087 - "--component-passive-effect-unmount-stop",
1088 - "--component-passive-effect-unmount-start-ComponentWithEffects",
1089 - "--component-passive-effect-unmount-stop",
1090 - "--component-passive-effect-unmount-start-ComponentWithEffects",
1091 - "--component-passive-effect-unmount-stop",
1092 - "--passive-effects-stop",
1093 - "--commit-stop",
1094 - ]
1095 - `);
1096 - });
1097 - });
1098 -
1099 - describe('lane labels', () => {
1100 - describe('with legacy render', () => {
1101 - const {render: legacyRender} = getLegacyRenderImplementation();
1102 -
1103 - // @reactVersion <= 18.2
1104 - // @reactVersion >= 18.0
1105 - it('regression test SyncLane', () => {
1106 - utils.act(() => store.profilerStore.startProfiling());
1107 - legacyRender(<div />);
1108 - utils.act(() => store.profilerStore.stopProfiling());
1109 -
1110 - expect(registeredMarks).toMatchInlineSnapshot(`
1111 - [
1112 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1113 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1114 - "--schedule-render-1",
1115 - "--render-start-1",
1116 - "--render-stop",
1117 - "--commit-start-1",
1118 - "--react-version-<filtered-version>",
1119 - "--profiler-version-1",
1120 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1121 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1122 - "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
1123 - "--layout-effects-start-1",
1124 - "--layout-effects-stop",
1125 - "--commit-stop",
1126 - ]
1127 - `);
1128 - });
1129 - });
1130 -
1131 - describe('with createRoot()', () => {
1132 - let waitForAll;
1133 -
1134 - beforeEach(() => {
1135 - const InternalTestUtils = require('internal-test-utils');
1136 - waitForAll = InternalTestUtils.waitForAll;
1137 - });
1138 -
1139 - const {render: modernRender} = getModernRenderImplementation();
1140 -
1141 - it('regression test DefaultLane', () => {
1142 - modernRender(<div />);
1143 - expect(registeredMarks).toMatchInlineSnapshot(`
1144 - [
1145 - "--schedule-render-32",
1146 - ]
1147 - `);
1148 - });
1149 -
1150 - it('regression test InputDiscreteLane', async () => {
1151 - const targetRef = React.createRef(null);
1152 -
1153 - function App() {
1154 - const [count, setCount] = React.useState(0);
1155 - const handleClick = () => {
1156 - setCount(count + 1);
1157 - };
1158 - return <button ref={targetRef} onClick={handleClick} />;
1159 - }
1160 -
1161 - modernRender(<App />);
1162 - await waitForAll([]);
1163 -
1164 - eraseRegisteredMarks();
1165 -
1166 - targetRef.current.click();
1167 -
1168 - // Wait a frame, for React to process the "click" update.
1169 - await Promise.resolve();
1170 -
1171 - expect(registeredMarks).toMatchInlineSnapshot(`
1172 - [
1173 - "--schedule-state-update-2-App",
1174 - "--render-start-2",
1175 - "--component-render-start-App",
1176 - "--component-render-stop",
1177 - "--render-stop",
1178 - "--commit-start-2",
1179 - "--react-version-<filtered-version>",
1180 - "--profiler-version-1",
1181 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1182 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1183 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1184 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1185 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
1186 - "--layout-effects-start-2",
1187 - "--layout-effects-stop",
1188 - "--commit-stop",
1189 - ]
1190 - `);
1191 - });
1192 -
1193 - it('regression test InputContinuousLane', async () => {
1194 - const targetRef = React.createRef(null);
1195 -
1196 - function App() {
1197 - const [count, setCount] = React.useState(0);
1198 - const handleMouseOver = () => setCount(count + 1);
1199 - return <div ref={targetRef} onMouseOver={handleMouseOver} />;
1200 - }
1201 -
1202 - modernRender(<App />);
1203 - await waitForAll([]);
1204 -
1205 - eraseRegisteredMarks();
1206 -
1207 - const event = document.createEvent('MouseEvents');
1208 - event.initEvent('mouseover', true, true);
1209 - dispatchAndSetCurrentEvent(targetRef.current, event);
1210 -
1211 - await waitForAll([]);
1212 -
1213 - expect(registeredMarks).toMatchInlineSnapshot(`
1214 - [
1215 - "--schedule-state-update-8-App",
1216 - "--render-start-8",
1217 - "--component-render-start-App",
1218 - "--component-render-stop",
1219 - "--render-stop",
1220 - "--commit-start-8",
1221 - "--react-version-<filtered-version>",
1222 - "--profiler-version-1",
1223 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1224 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1225 - "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1226 - "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1227 - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred",
1228 - "--layout-effects-start-8",
1229 - "--layout-effects-stop",
1230 - "--commit-stop",
1231 - ]
1232 - `);
1233 - });
1234 - });
1235 - });
1236 - });
1237 -
1238 - describe('DevTools hook (in memory)', () => {
1239 - let getBatchOfWork;
1240 - let stopProfilingAndGetTimelineData;
1241 -
1242 - beforeEach(() => {
1243 - getBatchOfWork = index => {
1244 - const timelineData = stopProfilingAndGetTimelineData();
1245 - if (timelineData) {
1246 - if (timelineData.batchUIDToMeasuresMap.size > index) {
1247 - return Array.from(timelineData.batchUIDToMeasuresMap.values())[
1248 - index
1249 - ];
1250 - }
1251 - }
1252 -
1253 - return null;
1254 - };
1255 -
1256 - stopProfilingAndGetTimelineData = () => {
1257 - utils.act(() => store.profilerStore.stopProfiling());
1258 -
1259 - const timelineData = store.profilerStore.profilingData?.timelineData;
1260 -
1261 - if (timelineData) {
1262 - expect(timelineData).toHaveLength(1);
1263 -
1264 - // normalize the location for component stack source
1265 - // for snapshot testing
1266 - timelineData.forEach(data => {
1267 - data.schedulingEvents.forEach(event => {
1268 - if (event.componentStack) {
1269 - event.componentStack = normalizeCodeLocInfo(
1270 - event.componentStack,
1271 - );
1272 - }
1273 - });
1274 - });
1275 -
1276 - return timelineData[0];
1277 - } else {
1278 - return null;
1279 - }
1280 - };
1281 - });
1282 -
1283 - describe('when profiling', () => {
1284 - describe('with legacy render', () => {
1285 - const {render: legacyRender} = getLegacyRenderImplementation();
1286 -
1287 - beforeEach(() => {
1288 - utils.act(() => store.profilerStore.startProfiling());
1289 - });
1290 -
1291 - // @reactVersion <= 18.2
1292 - // @reactVersion >= 18.0
1293 - it('should mark sync render without suspends or state updates', () => {
1294 - legacyRender(<div />);
1295 -
1296 - const timelineData = stopProfilingAndGetTimelineData();
1297 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1298 - [
1299 - {
1300 - "lanes": "0b0000000000000000000000000000001",
1301 - "timestamp": 10,
1302 - "type": "schedule-render",
1303 - "warning": null,
1304 - },
1305 - ]
1306 - `);
1307 - });
1308 -
1309 - // @reactVersion <= 18.2
1310 - // @reactVersion >= 18.0
1311 - it('should mark sync render that throws', async () => {
1312 - jest.spyOn(console, 'error').mockImplementation(() => {});
1313 -
1314 - class ErrorBoundary extends React.Component {
1315 - state = {error: null};
1316 - componentDidCatch(error) {
1317 - this.setState({error});
1318 - }
1319 - render() {
1320 - Scheduler.unstable_advanceTime(10);
1321 - if (this.state.error) {
1322 - Scheduler.unstable_yieldValue('ErrorBoundary fallback');
1323 - return null;
1324 - }
1325 - Scheduler.unstable_yieldValue('ErrorBoundary render');
1326 - return this.props.children;
1327 - }
1328 - }
1329 -
1330 - function ExampleThatThrows() {
1331 - Scheduler.unstable_yieldValue('ExampleThatThrows');
1332 - throw Error('Expected error');
1333 - }
1334 -
1335 - legacyRender(
1336 - <ErrorBoundary>
1337 - <ExampleThatThrows />
1338 - </ErrorBoundary>,
1339 - );
1340 -
1341 - expect(Scheduler.unstable_clearYields()).toEqual([
1342 - 'ErrorBoundary render',
1343 - 'ExampleThatThrows',
1344 - 'ExampleThatThrows',
1345 - 'ErrorBoundary fallback',
1346 - ]);
1347 -
1348 - const timelineData = stopProfilingAndGetTimelineData();
1349 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1350 - [
1351 - {
1352 - "componentName": "ErrorBoundary",
1353 - "duration": 10,
1354 - "timestamp": 10,
1355 - "type": "render",
1356 - "warning": null,
1357 - },
1358 - {
1359 - "componentName": "ExampleThatThrows",
1360 - "duration": 0,
1361 - "timestamp": 20,
1362 - "type": "render",
1363 - "warning": null,
1364 - },
1365 - {
1366 - "componentName": "ErrorBoundary",
1367 - "duration": 10,
1368 - "timestamp": 20,
1369 - "type": "render",
1370 - "warning": null,
1371 - },
1372 - ]
1373 - `);
1374 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1375 - [
1376 - {
1377 - "lanes": "0b0000000000000000000000000000001",
1378 - "timestamp": 10,
1379 - "type": "schedule-render",
1380 - "warning": null,
1381 - },
1382 - {
1383 - "componentName": "ErrorBoundary",
1384 - "componentStack": "
1385 - in ErrorBoundary (at **)",
1386 - "lanes": "0b0000000000000000000000000000001",
1387 - "timestamp": 20,
1388 - "type": "schedule-state-update",
1389 - "warning": null,
1390 - },
1391 - ]
1392 - `);
1393 - expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
1394 - [
1395 - {
1396 - "componentName": "ExampleThatThrows",
1397 - "message": "Expected error",
1398 - "phase": "mount",
1399 - "timestamp": 20,
1400 - "type": "thrown-error",
1401 - },
1402 - ]
1403 - `);
1404 - });
1405 -
1406 - // @reactVersion <= 18.2
1407 - // @reactVersion >= 18.0
1408 - it('should mark sync render with suspense that resolves', async () => {
1409 - let resolveFn;
1410 - let resolved = false;
1411 - const suspensePromise = new Promise(resolve => {
1412 - resolveFn = () => {
1413 - resolved = true;
1414 - resolve();
1415 - };
1416 - });
1417 -
1418 - function Example() {
1419 - Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
1420 - if (!resolved) {
1421 - throw suspensePromise;
1422 - }
1423 - return null;
1424 - }
1425 -
1426 - legacyRender(
1427 - <React.Suspense fallback={null}>
1428 - <Example />
1429 - </React.Suspense>,
1430 - );
1431 -
1432 - expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
1433 -
1434 - Scheduler.unstable_advanceTime(10);
1435 - resolveFn();
1436 - await suspensePromise;
1437 -
1438 - await Scheduler.unstable_flushAllWithoutAsserting();
1439 - expect(Scheduler.unstable_clearYields()).toEqual(['resolved']);
1440 -
1441 - const timelineData = stopProfilingAndGetTimelineData();
1442 -
1443 - // Verify the Suspense event and duration was recorded.
1444 - expect(timelineData.suspenseEvents).toHaveLength(1);
1445 - const suspenseEvent = timelineData.suspenseEvents[0];
1446 - expect(suspenseEvent).toMatchInlineSnapshot(`
1447 - {
1448 - "componentName": "Example",
1449 - "depth": 0,
1450 - "duration": 0,
1451 - "id": "0",
1452 - "phase": "mount",
1453 - "promiseName": "",
1454 - "resolution": "unresolved",
1455 - "timestamp": 10,
1456 - "type": "suspense",
1457 - "warning": null,
1458 - }
1459 - `);
1460 -
1461 - // There should be two batches of renders: Suspeneded and resolved.
1462 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1463 - expect(timelineData.componentMeasures).toHaveLength(2);
1464 - });
1465 -
1466 - // @reactVersion = 18.2
1467 - it('should mark sync render with suspense that rejects', async () => {
1468 - let rejectFn;
1469 - let rejected = false;
1470 - const suspensePromise = new Promise((resolve, reject) => {
1471 - rejectFn = () => {
1472 - rejected = true;
1473 - reject(new Error('error'));
1474 - };
1475 - });
1476 -
1477 - function Example() {
1478 - Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
1479 - if (!rejected) {
1480 - throw suspensePromise;
1481 - }
1482 - return null;
1483 - }
1484 -
1485 - legacyRender(
1486 - <React.Suspense fallback={null}>
1487 - <Example />
1488 - </React.Suspense>,
1489 - );
1490 -
1491 - expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
1492 -
1493 - Scheduler.unstable_advanceTime(10);
1494 - rejectFn();
1495 - await expect(suspensePromise).rejects.toThrow();
1496 -
1497 - expect(Scheduler.unstable_clearYields()).toEqual(['rejected']);
1498 -
1499 - const timelineData = stopProfilingAndGetTimelineData();
1500 -
1501 - // Verify the Suspense event and duration was recorded.
1502 - expect(timelineData.suspenseEvents).toHaveLength(1);
1503 - const suspenseEvent = timelineData.suspenseEvents[0];
1504 - expect(suspenseEvent).toMatchInlineSnapshot(`
1505 - {
1506 - "componentName": "Example",
1507 - "depth": 0,
1508 - "duration": 0,
1509 - "id": "0",
1510 - "phase": "mount",
1511 - "promiseName": "",
1512 - "resolution": "unresolved",
1513 - "timestamp": 10,
1514 - "type": "suspense",
1515 - "warning": null,
1516 - }
1517 - `);
1518 -
1519 - // There should be two batches of renders: Suspeneded and resolved.
1520 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1521 - expect(timelineData.componentMeasures).toHaveLength(2);
1522 - });
1523 - });
1524 -
1525 - describe('with createRoot()', () => {
1526 - let waitFor;
1527 - let waitForAll;
1528 - let waitForPaint;
1529 - let assertLog;
1530 -
1531 - beforeEach(() => {
1532 - const InternalTestUtils = require('internal-test-utils');
1533 - waitFor = InternalTestUtils.waitFor;
1534 - waitForAll = InternalTestUtils.waitForAll;
1535 - waitForPaint = InternalTestUtils.waitForPaint;
1536 - assertLog = InternalTestUtils.assertLog;
1537 - });
1538 -
1539 - const {render: modernRender} = getModernRenderImplementation();
1540 -
1541 - beforeEach(() => {
1542 - utils.act(() => store.profilerStore.startProfiling());
1543 - });
1544 -
1545 - it('should mark concurrent render without suspends or state updates', () => {
1546 - utils.act(() => modernRender(<div />));
1547 -
1548 - const timelineData = stopProfilingAndGetTimelineData();
1549 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1550 - [
1551 - {
1552 - "lanes": "0b0000000000000000000000000100000",
1553 - "timestamp": 10,
1554 - "type": "schedule-render",
1555 - "warning": null,
1556 - },
1557 - ]
1558 - `);
1559 - });
1560 -
1561 - it('should mark concurrent render without suspends with state updates', () => {
1562 - let updaterFn;
1563 -
1564 - function Example() {
1565 - const setHigh = React.useState(0)[1];
1566 - const setLow = React.useState(0)[1];
1567 -
1568 - updaterFn = () => {
1569 - React.startTransition(() => {
1570 - setLow(prevLow => prevLow + 1);
1571 - });
1572 - setHigh(prevHigh => prevHigh + 1);
1573 - };
1574 -
1575 - Scheduler.unstable_advanceTime(10);
1576 -
1577 - return null;
1578 - }
1579 -
1580 - utils.act(() => modernRender(<Example />));
1581 - utils.act(() => store.profilerStore.stopProfiling());
1582 - utils.act(() => store.profilerStore.startProfiling());
1583 - utils.act(updaterFn);
1584 -
1585 - const timelineData = stopProfilingAndGetTimelineData();
1586 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1587 - [
1588 - {
1589 - "componentName": "Example",
1590 - "componentStack": "
1591 - in Example (at **)",
1592 - "lanes": "0b0000000000000000000000010000000",
1593 - "timestamp": 10,
1594 - "type": "schedule-state-update",
1595 - "warning": null,
1596 - },
1597 - {
1598 - "componentName": "Example",
1599 - "componentStack": "
1600 - in Example (at **)",
1601 - "lanes": "0b0000000000000000000000000100000",
1602 - "timestamp": 10,
1603 - "type": "schedule-state-update",
1604 - "warning": null,
1605 - },
1606 - ]
1607 - `);
1608 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1609 - [
1610 - {
1611 - "componentName": "Example",
1612 - "duration": 0,
1613 - "timestamp": 10,
1614 - "type": "render",
1615 - "warning": null,
1616 - },
1617 - {
1618 - "componentName": "Example",
1619 - "duration": 10,
1620 - "timestamp": 10,
1621 - "type": "render",
1622 - "warning": null,
1623 - },
1624 - ]
1625 - `);
1626 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1627 - });
1628 -
1629 - it('should mark render yields', async () => {
1630 - function Bar() {
1631 - Scheduler.log('Bar');
1632 - return null;
1633 - }
1634 -
1635 - function Foo() {
1636 - Scheduler.log('Foo');
1637 - return <Bar />;
1638 - }
1639 -
1640 - React.startTransition(() => {
1641 - modernRender(<Foo />);
1642 - });
1643 -
1644 - // Do one step of work.
1645 - await waitFor(['Foo']);
1646 -
1647 - // Finish flushing so React commits;
1648 - // Unless we do this, the ProfilerStore won't collect Profiling data.
1649 - await waitForAll(['Bar']);
1650 -
1651 - // Since we yielded, the batch should report two separate "render" chunks.
1652 - const batch = getBatchOfWork(0);
1653 - expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
1654 - });
1655 -
1656 - it('should mark concurrent render with suspense that resolves', async () => {
1657 - let resolveFn;
1658 - let resolved = false;
1659 - const suspensePromise = new Promise(resolve => {
1660 - resolveFn = () => {
1661 - resolved = true;
1662 - resolve();
1663 - };
1664 - });
1665 -
1666 - function Example() {
1667 - Scheduler.log(resolved ? 'resolved' : 'suspended');
1668 - if (!resolved) {
1669 - throw suspensePromise;
1670 - }
1671 - return null;
1672 - }
1673 -
1674 - modernRender(
1675 - <React.Suspense fallback={null}>
1676 - <Example />
1677 - </React.Suspense>,
1678 - );
1679 -
1680 - await waitForAll([
1681 - 'suspended',
1682 - // pre-warming
1683 - 'suspended',
1684 - ]);
1685 -
1686 - Scheduler.unstable_advanceTime(10);
1687 - resolveFn();
1688 - await suspensePromise;
1689 -
1690 - await waitForAll(['resolved']);
1691 -
1692 - const timelineData = stopProfilingAndGetTimelineData();
1693 -
1694 - // Verify the Suspense event and duration was recorded.
1695 - expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
1696 - [
1697 - {
1698 - "componentName": "Example",
1699 - "depth": 0,
1700 - "duration": 10,
1701 - "id": "0",
1702 - "phase": "mount",
1703 - "promiseName": "",
1704 - "resolution": "resolved",
1705 - "timestamp": 10,
1706 - "type": "suspense",
1707 - "warning": null,
1708 - },
1709 - {
1710 - "componentName": "Example",
1711 - "depth": 0,
1712 - "duration": 10,
1713 - "id": "0",
1714 - "phase": "mount",
1715 - "promiseName": "",
1716 - "resolution": "resolved",
1717 - "timestamp": 10,
1718 - "type": "suspense",
1719 - "warning": null,
1720 - },
1721 - ]
1722 - `);
1723 -
1724 - // There should be two batches of renders: Suspeneded and resolved.
1725 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1726 - // An additional measure with pre-warming
1727 - expect(timelineData.componentMeasures).toHaveLength(3);
1728 - });
1729 -
1730 - it('should mark concurrent render with suspense that rejects', async () => {
1731 - let rejectFn;
1732 - let rejected = false;
1733 - const suspensePromise = new Promise((resolve, reject) => {
1734 - rejectFn = () => {
1735 - rejected = true;
1736 - reject(new Error('error'));
1737 - };
1738 - });
1739 -
1740 - function Example() {
1741 - Scheduler.log(rejected ? 'rejected' : 'suspended');
1742 - if (!rejected) {
1743 - throw suspensePromise;
1744 - }
1745 - return null;
1746 - }
1747 -
1748 - modernRender(
1749 - <React.Suspense fallback={null}>
1750 - <Example />
1751 - </React.Suspense>,
1752 - );
1753 -
1754 - await waitForAll(['suspended', 'suspended']);
1755 -
1756 - Scheduler.unstable_advanceTime(10);
1757 - rejectFn();
1758 - await expect(suspensePromise).rejects.toThrow();
1759 -
1760 - await waitForAll(['rejected']);
1761 -
1762 - const timelineData = stopProfilingAndGetTimelineData();
1763 -
1764 - // Verify the Suspense event and duration was recorded.
1765 - expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
1766 - [
1767 - {
1768 - "componentName": "Example",
1769 - "depth": 0,
1770 - "duration": 10,
1771 - "id": "0",
1772 - "phase": "mount",
1773 - "promiseName": "",
1774 - "resolution": "rejected",
1775 - "timestamp": 10,
1776 - "type": "suspense",
1777 - "warning": null,
1778 - },
1779 - {
1780 - "componentName": "Example",
1781 - "depth": 0,
1782 - "duration": 10,
1783 - "id": "0",
1784 - "phase": "mount",
1785 - "promiseName": "",
1786 - "resolution": "rejected",
1787 - "timestamp": 10,
1788 - "type": "suspense",
1789 - "warning": null,
1790 - },
1791 - ]
1792 - `);
1793 -
1794 - // There should be two batches of renders: Suspeneded and resolved.
1795 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1796 - // An additional measure with pre-warming
1797 - expect(timelineData.componentMeasures).toHaveLength(3);
1798 - });
1799 -
1800 - it('should mark cascading class component state updates', async () => {
1801 - class Example extends React.Component {
1802 - state = {didMount: false};
1803 - componentDidMount() {
1804 - this.setState({didMount: true});
1805 - }
1806 - render() {
1807 - Scheduler.unstable_advanceTime(10);
1808 - Scheduler.log(this.state.didMount ? 'update' : 'mount');
1809 - return null;
1810 - }
1811 - }
1812 -
1813 - modernRender(<Example />);
1814 -
1815 - await waitForPaint(['mount', 'update']);
1816 -
1817 - const timelineData = stopProfilingAndGetTimelineData();
1818 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1819 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1820 - [
1821 - {
1822 - "componentName": "Example",
1823 - "duration": 10,
1824 - "timestamp": 10,
1825 - "type": "render",
1826 - "warning": null,
1827 - },
1828 - {
1829 - "componentName": "Example",
1830 - "duration": 10,
1831 - "timestamp": 20,
1832 - "type": "render",
1833 - "warning": null,
1834 - },
1835 - ]
1836 - `);
1837 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1838 - [
1839 - {
1840 - "lanes": "0b0000000000000000000000000100000",
1841 - "timestamp": 10,
1842 - "type": "schedule-render",
1843 - "warning": null,
1844 - },
1845 - {
1846 - "componentName": "Example",
1847 - "componentStack": "
1848 - in Example (at **)",
1849 - "lanes": "0b0000000000000000000000000000010",
1850 - "timestamp": 20,
1851 - "type": "schedule-state-update",
1852 - "warning": null,
1853 - },
1854 - ]
1855 - `);
1856 - });
1857 -
1858 - it('should mark cascading class component force updates', async () => {
1859 - let forced = false;
1860 - class Example extends React.Component {
1861 - componentDidMount() {
1862 - forced = true;
1863 - this.forceUpdate();
1864 - }
1865 - render() {
1866 - Scheduler.unstable_advanceTime(10);
1867 - Scheduler.log(forced ? 'force update' : 'mount');
1868 - return null;
1869 - }
1870 - }
1871 -
1872 - modernRender(<Example />);
1873 -
1874 - await waitForPaint(['mount', 'force update']);
1875 -
1876 - const timelineData = stopProfilingAndGetTimelineData();
1877 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1878 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1879 - [
1880 - {
1881 - "componentName": "Example",
1882 - "duration": 10,
1883 - "timestamp": 10,
1884 - "type": "render",
1885 - "warning": null,
1886 - },
1887 - {
1888 - "componentName": "Example",
1889 - "duration": 10,
1890 - "timestamp": 20,
1891 - "type": "render",
1892 - "warning": null,
1893 - },
1894 - ]
1895 - `);
1896 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1897 - [
1898 - {
1899 - "lanes": "0b0000000000000000000000000100000",
1900 - "timestamp": 10,
1901 - "type": "schedule-render",
1902 - "warning": null,
1903 - },
1904 - {
1905 - "componentName": "Example",
1906 - "lanes": "0b0000000000000000000000000000010",
1907 - "timestamp": 20,
1908 - "type": "schedule-force-update",
1909 - "warning": null,
1910 - },
1911 - ]
1912 - `);
1913 - });
1914 -
1915 - it('should mark render phase state updates for class component', async () => {
1916 - class Example extends React.Component {
1917 - state = {didRender: false};
1918 - render() {
1919 - if (this.state.didRender === false) {
1920 - this.setState({didRender: true});
1921 - }
1922 - Scheduler.unstable_advanceTime(10);
1923 - Scheduler.log(
1924 - this.state.didRender ? 'second render' : 'first render',
1925 - );
1926 - return null;
1927 - }
1928 - }
1929 -
1930 - modernRender(<Example />);
1931 -
1932 - let errorMessage;
1933 - jest.spyOn(console, 'error').mockImplementation(message => {
1934 - errorMessage = message;
1935 - });
1936 -
1937 - await waitForAll(['first render', 'second render']);
1938 -
1939 - expect(console.error).toHaveBeenCalledTimes(1);
1940 - expect(errorMessage).toContain(
1941 - 'Cannot update during an existing state transition',
1942 - );
1943 -
1944 - const timelineData = stopProfilingAndGetTimelineData();
1945 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1946 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1947 - [
1948 - {
1949 - "componentName": "Example",
1950 - "duration": 10,
1951 - "timestamp": 10,
1952 - "type": "render",
1953 - "warning": null,
1954 - },
1955 - {
1956 - "componentName": "Example",
1957 - "duration": 10,
1958 - "timestamp": 20,
1959 - "type": "render",
1960 - "warning": null,
1961 - },
1962 - ]
1963 - `);
1964 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1965 - [
1966 - {
1967 - "lanes": "0b0000000000000000000000000100000",
1968 - "timestamp": 10,
1969 - "type": "schedule-render",
1970 - "warning": null,
1971 - },
1972 - {
1973 - "componentName": "Example",
1974 - "componentStack": "
1975 - in Example (at **)",
1976 - "lanes": "0b0000000000000000000000000100000",
1977 - "timestamp": 10,
1978 - "type": "schedule-state-update",
1979 - "warning": null,
1980 - },
1981 - ]
1982 - `);
1983 - });
1984 -
1985 - it('should mark render phase force updates for class component', async () => {
1986 - let forced = false;
1987 - class Example extends React.Component {
1988 - render() {
1989 - Scheduler.unstable_advanceTime(10);
1990 - Scheduler.log(forced ? 'force update' : 'render');
1991 - if (!forced) {
1992 - forced = true;
1993 - this.forceUpdate();
1994 - }
1995 - return null;
1996 - }
1997 - }
1998 -
1999 - modernRender(<Example />);
2000 -
2001 - let errorMessage;
2002 - jest.spyOn(console, 'error').mockImplementation(message => {
2003 - errorMessage = message;
2004 - });
2005 -
2006 - await waitForAll(['render', 'force update']);
2007 -
2008 - expect(console.error).toHaveBeenCalledTimes(1);
2009 - expect(errorMessage).toContain(
2010 - 'Cannot update during an existing state transition',
2011 - );
2012 -
2013 - const timelineData = stopProfilingAndGetTimelineData();
2014 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2015 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2016 - [
2017 - {
2018 - "componentName": "Example",
2019 - "duration": 10,
2020 - "timestamp": 10,
2021 - "type": "render",
2022 - "warning": null,
2023 - },
2024 - {
2025 - "componentName": "Example",
2026 - "duration": 10,
2027 - "timestamp": 20,
2028 - "type": "render",
2029 - "warning": null,
2030 - },
2031 - ]
2032 - `);
2033 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2034 - [
2035 - {
2036 - "lanes": "0b0000000000000000000000000100000",
2037 - "timestamp": 10,
2038 - "type": "schedule-render",
2039 - "warning": null,
2040 - },
2041 - {
2042 - "componentName": "Example",
2043 - "lanes": "0b0000000000000000000000000100000",
2044 - "timestamp": 20,
2045 - "type": "schedule-force-update",
2046 - "warning": null,
2047 - },
2048 - ]
2049 - `);
2050 - });
2051 -
2052 - it('should mark cascading layout updates', async () => {
2053 - function Example() {
2054 - const [didMount, setDidMount] = React.useState(false);
2055 - React.useLayoutEffect(() => {
2056 - Scheduler.unstable_advanceTime(1);
2057 - setDidMount(true);
2058 - }, []);
2059 - Scheduler.unstable_advanceTime(10);
2060 - Scheduler.log(didMount ? 'update' : 'mount');
2061 - return didMount;
2062 - }
2063 -
2064 - modernRender(<Example />);
2065 -
2066 - await waitForAll(['mount', 'update']);
2067 -
2068 - const timelineData = stopProfilingAndGetTimelineData();
2069 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2070 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2071 - [
2072 - {
2073 - "componentName": "Example",
2074 - "duration": 10,
2075 - "timestamp": 10,
2076 - "type": "render",
2077 - "warning": null,
2078 - },
2079 - {
2080 - "componentName": "Example",
2081 - "duration": 1,
2082 - "timestamp": 20,
2083 - "type": "layout-effect-mount",
2084 - "warning": null,
2085 - },
2086 - {
2087 - "componentName": "Example",
2088 - "duration": 10,
2089 - "timestamp": 21,
2090 - "type": "render",
2091 - "warning": null,
2092 - },
2093 - ]
2094 - `);
2095 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2096 - [
2097 - {
2098 - "lanes": "0b0000000000000000000000000100000",
2099 - "timestamp": 10,
2100 - "type": "schedule-render",
2101 - "warning": null,
2102 - },
2103 - {
2104 - "componentName": "Example",
2105 - "componentStack": "
2106 - in Example (at **)",
2107 - "lanes": "0b0000000000000000000000000000010",
2108 - "timestamp": 21,
2109 - "type": "schedule-state-update",
2110 - "warning": null,
2111 - },
2112 - ]
2113 - `);
2114 - });
2115 -
2116 - it('should mark cascading passive updates', async () => {
2117 - function Example() {
2118 - const [didMount, setDidMount] = React.useState(false);
2119 - React.useEffect(() => {
2120 - Scheduler.unstable_advanceTime(1);
2121 - setDidMount(true);
2122 - }, []);
2123 - Scheduler.unstable_advanceTime(10);
2124 - Scheduler.log(didMount ? 'update' : 'mount');
2125 - return didMount;
2126 - }
2127 -
2128 - modernRender(<Example />);
2129 - await waitForAll(['mount', 'update']);
2130 -
2131 - const timelineData = stopProfilingAndGetTimelineData();
2132 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2133 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2134 - [
2135 - {
2136 - "componentName": "Example",
2137 - "duration": 10,
2138 - "timestamp": 10,
2139 - "type": "render",
2140 - "warning": null,
2141 - },
2142 - {
2143 - "componentName": "Example",
2144 - "duration": 1,
2145 - "timestamp": 20,
2146 - "type": "passive-effect-mount",
2147 - "warning": null,
2148 - },
2149 - {
2150 - "componentName": "Example",
2151 - "duration": 10,
2152 - "timestamp": 21,
2153 - "type": "render",
2154 - "warning": null,
2155 - },
2156 - ]
2157 - `);
2158 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2159 - [
2160 - {
2161 - "lanes": "0b0000000000000000000000000100000",
2162 - "timestamp": 10,
2163 - "type": "schedule-render",
2164 - "warning": null,
2165 - },
2166 - {
2167 - "componentName": "Example",
2168 - "componentStack": "
2169 - in Example (at **)",
2170 - "lanes": "0b0000000000000000000000000100000",
2171 - "timestamp": 21,
2172 - "type": "schedule-state-update",
2173 - "warning": null,
2174 - },
2175 - ]
2176 - `);
2177 - });
2178 -
2179 - it('should mark render phase updates', async () => {
2180 - function Example() {
2181 - const [didRender, setDidRender] = React.useState(false);
2182 - Scheduler.unstable_advanceTime(10);
2183 - if (!didRender) {
2184 - setDidRender(true);
2185 - }
2186 - Scheduler.log(didRender ? 'update' : 'mount');
2187 - return didRender;
2188 - }
2189 -
2190 - modernRender(<Example />);
2191 - await waitForAll(['mount', 'update']);
2192 -
2193 - const timelineData = stopProfilingAndGetTimelineData();
2194 - // Render phase updates should be retried as part of the same batch.
2195 - expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
2196 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2197 - [
2198 - {
2199 - "componentName": "Example",
2200 - "duration": 20,
2201 - "timestamp": 10,
2202 - "type": "render",
2203 - "warning": null,
2204 - },
2205 - ]
2206 - `);
2207 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2208 - [
2209 - {
2210 - "lanes": "0b0000000000000000000000000100000",
2211 - "timestamp": 10,
2212 - "type": "schedule-render",
2213 - "warning": null,
2214 - },
2215 - {
2216 - "componentName": "Example",
2217 - "componentStack": "
2218 - in Example (at **)",
2219 - "lanes": "0b0000000000000000000000000100000",
2220 - "timestamp": 20,
2221 - "type": "schedule-state-update",
2222 - "warning": null,
2223 - },
2224 - ]
2225 - `);
2226 - });
2227 -
2228 - it('should mark concurrent render that throws', async () => {
2229 - jest.spyOn(console, 'error').mockImplementation(() => {});
2230 -
2231 - class ErrorBoundary extends React.Component {
2232 - state = {error: null};
2233 - componentDidCatch(error) {
2234 - this.setState({error});
2235 - }
2236 - render() {
2237 - Scheduler.unstable_advanceTime(10);
2238 - if (this.state.error) {
2239 - Scheduler.log('ErrorBoundary fallback');
2240 - return null;
2241 - }
2242 - Scheduler.log('ErrorBoundary render');
2243 - return this.props.children;
2244 - }
2245 - }
2246 -
2247 - function ExampleThatThrows() {
2248 - Scheduler.log('ExampleThatThrows');
2249 - // eslint-disable-next-line no-throw-literal
2250 - throw 'Expected error';
2251 - }
2252 -
2253 - modernRender(
2254 - <ErrorBoundary>
2255 - <ExampleThatThrows />
2256 - </ErrorBoundary>,
2257 - );
2258 -
2259 - await waitForAll([
2260 - 'ErrorBoundary render',
2261 - 'ExampleThatThrows',
2262 - 'ErrorBoundary render',
2263 - 'ExampleThatThrows',
2264 - 'ErrorBoundary fallback',
2265 - ]);
2266 -
2267 - const timelineData = stopProfilingAndGetTimelineData();
2268 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2269 - [
2270 - {
2271 - "componentName": "ErrorBoundary",
2272 - "duration": 10,
2273 - "timestamp": 10,
2274 - "type": "render",
2275 - "warning": null,
2276 - },
2277 - {
2278 - "componentName": "ExampleThatThrows",
2279 - "duration": 0,
2280 - "timestamp": 20,
2281 - "type": "render",
2282 - "warning": null,
2283 - },
2284 - {
2285 - "componentName": "ErrorBoundary",
2286 - "duration": 10,
2287 - "timestamp": 20,
2288 - "type": "render",
2289 - "warning": null,
2290 - },
2291 - {
2292 - "componentName": "ExampleThatThrows",
2293 - "duration": 0,
2294 - "timestamp": 30,
2295 - "type": "render",
2296 - "warning": null,
2297 - },
2298 - {
2299 - "componentName": "ErrorBoundary",
2300 - "duration": 10,
2301 - "timestamp": 30,
2302 - "type": "render",
2303 - "warning": null,
2304 - },
2305 - ]
2306 - `);
2307 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2308 - [
2309 - {
2310 - "lanes": "0b0000000000000000000000000100000",
2311 - "timestamp": 10,
2312 - "type": "schedule-render",
2313 - "warning": null,
2314 - },
2315 - {
2316 - "componentName": "ErrorBoundary",
2317 - "componentStack": "
2318 - in ErrorBoundary (at **)",
2319 - "lanes": "0b0000000000000000000000000000010",
2320 - "timestamp": 30,
2321 - "type": "schedule-state-update",
2322 - "warning": null,
2323 - },
2324 - ]
2325 - `);
2326 - expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
2327 - [
2328 - {
2329 - "componentName": "ExampleThatThrows",
2330 - "message": "Expected error",
2331 - "phase": "mount",
2332 - "timestamp": 20,
2333 - "type": "thrown-error",
2334 - },
2335 - {
2336 - "componentName": "ExampleThatThrows",
2337 - "message": "Expected error",
2338 - "phase": "mount",
2339 - "timestamp": 30,
2340 - "type": "thrown-error",
2341 - },
2342 - ]
2343 - `);
2344 - });
2345 -
2346 - it('should mark passive and layout effects', async () => {
2347 - function ComponentWithEffects() {
2348 - React.useLayoutEffect(() => {
2349 - Scheduler.log('layout 1 mount');
2350 - return () => {
2351 - Scheduler.log('layout 1 unmount');
2352 - };
2353 - }, []);
2354 -
2355 - React.useEffect(() => {
2356 - Scheduler.log('passive 1 mount');
2357 - return () => {
2358 - Scheduler.log('passive 1 unmount');
2359 - };
2360 - }, []);
2361 -
2362 - React.useLayoutEffect(() => {
2363 - Scheduler.log('layout 2 mount');
2364 - return () => {
2365 - Scheduler.log('layout 2 unmount');
2366 - };
2367 - }, []);
2368 -
2369 - React.useEffect(() => {
2370 - Scheduler.log('passive 2 mount');
2371 - return () => {
2372 - Scheduler.log('passive 2 unmount');
2373 - };
2374 - }, []);
2375 -
2376 - React.useEffect(() => {
2377 - Scheduler.log('passive 3 mount');
2378 - return () => {
2379 - Scheduler.log('passive 3 unmount');
2380 - };
2381 - }, []);
2382 -
2383 - return null;
2384 - }
2385 -
2386 - const unmount = modernRender(<ComponentWithEffects />);
2387 -
2388 - await waitForPaint(['layout 1 mount', 'layout 2 mount']);
2389 -
2390 - await waitForAll([
2391 - 'passive 1 mount',
2392 - 'passive 2 mount',
2393 - 'passive 3 mount',
2394 - ]);
2395 -
2396 - await waitForAll([]);
2397 -
2398 - unmount();
2399 -
2400 - assertLog([
2401 - 'layout 1 unmount',
2402 - 'layout 2 unmount',
2403 - 'passive 1 unmount',
2404 - 'passive 2 unmount',
2405 - 'passive 3 unmount',
2406 - ]);
2407 -
2408 - const timelineData = stopProfilingAndGetTimelineData();
2409 - expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2410 - [
2411 - {
2412 - "componentName": "ComponentWithEffects",
2413 - "duration": 0,
2414 - "timestamp": 10,
2415 - "type": "render",
2416 - "warning": null,
2417 - },
2418 - {
2419 - "componentName": "ComponentWithEffects",
2420 - "duration": 0,
2421 - "timestamp": 10,
2422 - "type": "layout-effect-mount",
2423 - "warning": null,
2424 - },
2425 - {
2426 - "componentName": "ComponentWithEffects",
2427 - "duration": 0,
2428 - "timestamp": 10,
2429 - "type": "layout-effect-mount",
2430 - "warning": null,
2431 - },
2432 - {
2433 - "componentName": "ComponentWithEffects",
2434 - "duration": 0,
2435 - "timestamp": 10,
2436 - "type": "passive-effect-mount",
2437 - "warning": null,
2438 - },
2439 - {
2440 - "componentName": "ComponentWithEffects",
2441 - "duration": 0,
2442 - "timestamp": 10,
2443 - "type": "passive-effect-mount",
2444 - "warning": null,
2445 - },
2446 - {
2447 - "componentName": "ComponentWithEffects",
2448 - "duration": 0,
2449 - "timestamp": 10,
2450 - "type": "passive-effect-mount",
2451 - "warning": null,
2452 - },
2453 - {
2454 - "componentName": "ComponentWithEffects",
2455 - "duration": 0,
2456 - "timestamp": 10,
2457 - "type": "layout-effect-unmount",
2458 - "warning": null,
2459 - },
2460 - {
2461 - "componentName": "ComponentWithEffects",
2462 - "duration": 0,
2463 - "timestamp": 10,
2464 - "type": "layout-effect-unmount",
2465 - "warning": null,
2466 - },
2467 - {
2468 - "componentName": "ComponentWithEffects",
2469 - "duration": 0,
2470 - "timestamp": 10,
2471 - "type": "passive-effect-unmount",
2472 - "warning": null,
2473 - },
2474 - {
2475 - "componentName": "ComponentWithEffects",
2476 - "duration": 0,
2477 - "timestamp": 10,
2478 - "type": "passive-effect-unmount",
2479 - "warning": null,
2480 - },
2481 - {
2482 - "componentName": "ComponentWithEffects",
2483 - "duration": 0,
2484 - "timestamp": 10,
2485 - "type": "passive-effect-unmount",
2486 - "warning": null,
2487 - },
2488 - ]
2489 - `);
2490 - expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
2491 - Map {
2492 - 1 => [
2493 - {
2494 - "batchUID": 1,
2495 - "depth": 0,
2496 - "duration": 0,
2497 - "lanes": "0b0000000000000000000000000100000",
2498 - "timestamp": 10,
2499 - "type": "render-idle",
2500 - },
2501 - {
2502 - "batchUID": 1,
2503 - "depth": 0,
2504 - "duration": 0,
2505 - "lanes": "0b0000000000000000000000000100000",
2506 - "timestamp": 10,
2507 - "type": "render",
2508 - },
2509 - {
2510 - "batchUID": 1,
2511 - "depth": 0,
2512 - "duration": 0,
2513 - "lanes": "0b0000000000000000000000000100000",
2514 - "timestamp": 10,
2515 - "type": "commit",
2516 - },
2517 - {
2518 - "batchUID": 1,
2519 - "depth": 1,
2520 - "duration": 0,
2521 - "lanes": "0b0000000000000000000000000100000",
2522 - "timestamp": 10,
2523 - "type": "layout-effects",
2524 - },
2525 - {
2526 - "batchUID": 1,
2527 - "depth": 0,
2528 - "duration": 0,
2529 - "lanes": "0b0000000000000000000000000100000",
2530 - "timestamp": 10,
2531 - "type": "passive-effects",
2532 - },
2533 - ],
2534 - 2 => [
2535 - {
2536 - "batchUID": 2,
2537 - "depth": 0,
2538 - "duration": 0,
2539 - "lanes": "0b0000000000000000000000000000010",
2540 - "timestamp": 10,
2541 - "type": "render-idle",
2542 - },
2543 - {
2544 - "batchUID": 2,
2545 - "depth": 0,
2546 - "duration": 0,
2547 - "lanes": "0b0000000000000000000000000000010",
2548 - "timestamp": 10,
2549 - "type": "render",
2550 - },
2551 - {
2552 - "batchUID": 2,
2553 - "depth": 0,
2554 - "duration": 0,
2555 - "lanes": "0b0000000000000000000000000000010",
2556 - "timestamp": 10,
2557 - "type": "commit",
2558 - },
2559 - {
2560 - "batchUID": 2,
2561 - "depth": 1,
2562 - "duration": 0,
2563 - "lanes": "0b0000000000000000000000000000010",
2564 - "timestamp": 10,
2565 - "type": "layout-effects",
2566 - },
2567 - {
2568 - "batchUID": 2,
2569 - "depth": 1,
2570 - "duration": 0,
2571 - "lanes": "0b0000000000000000000000000000010",
2572 - "timestamp": 10,
2573 - "type": "passive-effects",
2574 - },
2575 - ],
2576 - }
2577 - `);
2578 - });
2579 -
2580 - it('should generate component stacks for state update', async () => {
2581 - function CommponentWithChildren({initialRender}) {
2582 - Scheduler.log('Render ComponentWithChildren');
2583 - return <Child initialRender={initialRender} />;
2584 - }
2585 -
2586 - function Child({initialRender}) {
2587 - const [didRender, setDidRender] = React.useState(initialRender);
2588 - if (!didRender) {
2589 - setDidRender(true);
2590 - }
2591 - Scheduler.log('Render Child');
2592 - return null;
2593 - }
2594 -
2595 - modernRender(<CommponentWithChildren initialRender={false} />);
2596 -
2597 - await waitForAll([
2598 - 'Render ComponentWithChildren',
2599 - 'Render Child',
2600 - 'Render Child',
2601 - ]);
2602 -
2603 - const timelineData = stopProfilingAndGetTimelineData();
2604 - expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2605 - [
2606 - {
2607 - "lanes": "0b0000000000000000000000000100000",
2608 - "timestamp": 10,
2609 - "type": "schedule-render",
2610 - "warning": null,
2611 - },
2612 - {
2613 - "componentName": "Child",
2614 - "componentStack": "
2615 - in Child (at **)
2616 - in CommponentWithChildren (at **)",
2617 - "lanes": "0b0000000000000000000000000100000",
2618 - "timestamp": 10,
2619 - "type": "schedule-state-update",
2620 - "warning": null,
2621 - },
2622 - ]
2623 - `);
2624 - });
2625 - });
2626 - });
2627 -
2628 - describe('when not profiling', () => {
2629 - describe('with legacy render', () => {
2630 - const {render: legacyRender} = getLegacyRenderImplementation();
2631 -
2632 - // @reactVersion <= 18.2
2633 - // @reactVersion >= 18.0
2634 - it('should not log any marks', () => {
2635 - legacyRender(<div />);
2636 -
2637 - const timelineData = stopProfilingAndGetTimelineData();
2638 - expect(timelineData).toBeNull();
2639 - });
2640 - });
2641 - });
2642 - });
2643 -});
packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js deleted
-29
@@ -1,29 +0,0 @@
1 -import hasOwnProperty from 'shared/hasOwnProperty';
2 -import isArray from 'shared/isArray';
3 -
4 -function formatLanes(laneArray) {
5 - const lanes = laneArray.reduce((current, reduced) => current + reduced, 0);
6 - return '0b' + lanes.toString(2).padStart(31, '0');
7 -}
8 -
9 -// `test` is part of Jest's serializer API
10 -export function test(maybeTimelineData) {
11 - if (
12 - maybeTimelineData != null &&
13 - typeof maybeTimelineData === 'object' &&
14 - hasOwnProperty.call(maybeTimelineData, 'lanes') &&
15 - isArray(maybeTimelineData.lanes)
16 - ) {
17 - return true;
18 - }
19 -
20 - return false;
21 -}
22 -
23 -// print() is part of Jest's serializer API
24 -export function print(timelineData, serialize, indent) {
25 - return serialize({
26 - ...timelineData,
27 - lanes: formatLanes(timelineData.lanes),
28 - });
29 -}
packages/react-devtools-shared/src/__tests__/preprocessData-test.js deleted
-2457
@@ -1,2457 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -'use strict';
11 -
12 -import semver from 'semver';
13 -
14 -import {getLegacyRenderImplementation, normalizeCodeLocInfo} from './utils';
15 -import {ReactVersion} from '../../../../ReactVersions';
16 -
17 -const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion;
18 -
19 -let React = require('react');
20 -let ReactDOM;
21 -let ReactDOMClient;
22 -let Scheduler;
23 -let utils;
24 -let assertLog;
25 -let waitFor;
26 -
27 -describe('Timeline profiler', () => {
28 - describe('User Timing API', () => {
29 - let currentlyNotClearedMarks;
30 - let registeredMarks;
31 - let featureDetectionMarkName = null;
32 - let setPerformanceMock;
33 -
34 - function createUserTimingPolyfill() {
35 - featureDetectionMarkName = null;
36 -
37 - currentlyNotClearedMarks = [];
38 - registeredMarks = [];
39 -
40 - // Remove file-system specific bits or version-specific bits of information from the module range marks.
41 - function filterMarkData(markName) {
42 - if (markName.startsWith('--react-internal-module-start')) {
43 - return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
44 - } else if (markName.startsWith('--react-internal-module-stop')) {
45 - return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
46 - } else if (markName.startsWith('--react-version')) {
47 - return '--react-version-<filtered-version>';
48 - } else {
49 - return markName;
50 - }
51 - }
52 -
53 - // This is not a true polyfill, but it gives us enough to capture marks.
54 - // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
55 - return {
56 - clearMarks(markName) {
57 - markName = filterMarkData(markName);
58 -
59 - currentlyNotClearedMarks = currentlyNotClearedMarks.filter(
60 - mark => mark !== markName,
61 - );
62 - },
63 - mark(markName, markOptions) {
64 - markName = filterMarkData(markName);
65 -
66 - if (featureDetectionMarkName === null) {
67 - featureDetectionMarkName = markName;
68 - }
69 -
70 - registeredMarks.push(markName);
71 - currentlyNotClearedMarks.push(markName);
72 -
73 - if (markOptions != null) {
74 - // This is triggers the feature detection.
75 - markOptions.startTime++;
76 - }
77 - },
78 - };
79 - }
80 -
81 - function eraseRegisteredMarks() {
82 - registeredMarks.splice(0);
83 - }
84 -
85 - beforeEach(() => {
86 - // Mock react/jsx-dev-runtime for React 16.x
87 - // Although there are no tests in this suite which will run for React 16,
88 - // Jest will report an error trying to resolve this dependency
89 - if (semver.lt(ReactVersionTestingAgainst, '17.0.0')) {
90 - jest.mock('react/jsx-dev-runtime', () => {});
91 - }
92 -
93 - utils = require('./utils');
94 - utils.beforeEachProfiling();
95 -
96 - React = require('react');
97 - ReactDOM = require('react-dom');
98 - ReactDOMClient = require('react-dom/client');
99 - Scheduler = require('scheduler');
100 -
101 - if (typeof Scheduler.log !== 'function') {
102 - // backwards compat for older scheduler versions
103 - Scheduler.log = Scheduler.unstable_yieldValue;
104 - Scheduler.unstable_clearLog = Scheduler.unstable_clearYields;
105 - const InternalTestUtils = require('internal-test-utils');
106 - assertLog = InternalTestUtils.assertLog;
107 -
108 - // polyfill waitFor as Scheduler.toFlushAndYieldThrough
109 - waitFor = expectedYields => {
110 - let actualYields = Scheduler.unstable_clearYields();
111 - if (actualYields.length !== 0) {
112 - throw new Error(
113 - 'Log of yielded values is not empty. ' +
114 - 'Call expect(Scheduler).toHaveYielded(...) first.',
115 - );
116 - }
117 - Scheduler.unstable_flushNumberOfYields(expectedYields.length);
118 - actualYields = Scheduler.unstable_clearYields();
119 - expect(actualYields).toEqual(expectedYields);
120 - };
121 - } else {
122 - const InternalTestUtils = require('internal-test-utils');
123 - assertLog = InternalTestUtils.assertLog;
124 - waitFor = InternalTestUtils.waitFor;
125 - }
126 -
127 - setPerformanceMock =
128 - require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
129 - setPerformanceMock(createUserTimingPolyfill());
130 -
131 - const store = global.store;
132 -
133 - // Start profiling so that data will actually be recorded.
134 - utils.act(() => store.profilerStore.startProfiling());
135 -
136 - global.IS_REACT_ACT_ENVIRONMENT = true;
137 - });
138 -
139 - afterEach(() => {
140 - // Verify all logged marks also get cleared.
141 - expect(currentlyNotClearedMarks).toHaveLength(0);
142 -
143 - eraseRegisteredMarks();
144 - setPerformanceMock(null);
145 - });
146 -
147 - const {render: legacyRender} = getLegacyRenderImplementation();
148 -
149 - describe('getLanesFromTransportDecimalBitmask', () => {
150 - let getLanesFromTransportDecimalBitmask;
151 -
152 - beforeEach(() => {
153 - getLanesFromTransportDecimalBitmask =
154 - require('react-devtools-timeline/src/import-worker/preprocessData').getLanesFromTransportDecimalBitmask;
155 - });
156 -
157 - // @reactVersion >= 18.0
158 - // @reactVersion < 19.2
159 - it('should return array of lane numbers from bitmask string', () => {
160 - expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
161 - expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
162 - expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
163 - expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
164 - 1, 4, 6, 7, 10,
165 - ]); // 2 + 16 + 64 + 128 + 1024
166 - expect(
167 - getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
168 - ).toEqual([30]);
169 - expect(
170 - getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
171 - ).toEqual(Array.from(Array(31).keys()));
172 - });
173 -
174 - // @reactVersion >= 18.0
175 - // @reactVersion < 19.2
176 - it('should return empty array if laneBitmaskString is not a bitmask', () => {
177 - expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
178 - expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
179 - expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
180 - expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
181 - });
182 -
183 - // @reactVersion >= 18.0
184 - // @reactVersion < 19.2
185 - it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
186 - const REACT_TOTAL_NUM_LANES =
187 - require('react-devtools-timeline/src/constants').REACT_TOTAL_NUM_LANES;
188 -
189 - // Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
190 - expect(REACT_TOTAL_NUM_LANES).toBe(31);
191 -
192 - expect(
193 - getLanesFromTransportDecimalBitmask(
194 - '4294967297', // 2^32 + 1
195 - ),
196 - ).toEqual([0]);
197 - });
198 - });
199 -
200 - describe('preprocessData', () => {
201 - let preprocessData;
202 -
203 - beforeEach(() => {
204 - preprocessData =
205 - require('react-devtools-timeline/src/import-worker/preprocessData').default;
206 - });
207 -
208 - // These should be dynamic to mimic a real profile,
209 - // but reprooducible between test runs.
210 - let pid = 0;
211 - let tid = 0;
212 - let startTime = 0;
213 -
214 - function createUserTimingEntry(data) {
215 - return {
216 - pid: ++pid,
217 - tid: ++tid,
218 - ts: ++startTime,
219 - ...data,
220 - };
221 - }
222 -
223 - function createProfilerVersionEntry() {
224 - const SCHEDULING_PROFILER_VERSION =
225 - require('react-devtools-timeline/src/constants').SCHEDULING_PROFILER_VERSION;
226 - return createUserTimingEntry({
227 - cat: 'blink.user_timing',
228 - name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
229 - });
230 - }
231 -
232 - function createReactVersionEntry() {
233 - return createUserTimingEntry({
234 - cat: 'blink.user_timing',
235 - name: '--react-version-<filtered-version>',
236 - });
237 - }
238 -
239 - function createLaneLabelsEntry() {
240 - return createUserTimingEntry({
241 - cat: 'blink.user_timing',
242 - name: '--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen',
243 - });
244 - }
245 -
246 - function createNativeEventEntry(type, duration) {
247 - return createUserTimingEntry({
248 - cat: 'devtools.timeline',
249 - name: 'EventDispatch',
250 - args: {data: {type}},
251 - dur: duration,
252 - tdur: duration,
253 - });
254 - }
255 -
256 - function creactCpuProfilerSample() {
257 - return createUserTimingEntry({
258 - args: {data: {startTime: ++startTime}},
259 - cat: 'disabled-by-default-v8.cpu_profiler',
260 - id: '0x1',
261 - name: 'Profile',
262 - ph: 'P',
263 - });
264 - }
265 -
266 - function createBoilerplateEntries() {
267 - return [
268 - createProfilerVersionEntry(),
269 - createReactVersionEntry(),
270 - createLaneLabelsEntry(),
271 - ];
272 - }
273 -
274 - function createUserTimingData(sampleMarks) {
275 - const cpuProfilerSample = creactCpuProfilerSample();
276 -
277 - const randomSample = createUserTimingEntry({
278 - dur: 100,
279 - tdur: 200,
280 - ph: 'X',
281 - cat: 'disabled-by-default-devtools.timeline',
282 - name: 'RunTask',
283 - args: {},
284 - });
285 -
286 - const userTimingData = [cpuProfilerSample, randomSample];
287 -
288 - sampleMarks.forEach(markName => {
289 - userTimingData.push({
290 - pid: ++pid,
291 - tid: ++tid,
292 - ts: ++startTime,
293 - args: {data: {}},
294 - cat: 'blink.user_timing',
295 - name: markName,
296 - ph: 'R',
297 - });
298 - });
299 -
300 - return userTimingData;
301 - }
302 -
303 - beforeEach(() => {
304 - tid = 0;
305 - pid = 0;
306 - startTime = 0;
307 - });
308 -
309 - // @reactVersion >= 18.0
310 - // @reactVersion < 19.2
311 - it('should throw given an empty timeline', async () => {
312 - await expect(async () => preprocessData([])).rejects.toThrow();
313 - });
314 -
315 - // @reactVersion >= 18.0
316 - // @reactVersion < 19.2
317 - it('should throw given a timeline with no Profile event', async () => {
318 - const randomSample = createUserTimingEntry({
319 - dur: 100,
320 - tdur: 200,
321 - ph: 'X',
322 - cat: 'disabled-by-default-devtools.timeline',
323 - name: 'RunTask',
324 - args: {},
325 - });
326 -
327 - await expect(async () =>
328 - preprocessData([randomSample]),
329 - ).rejects.toThrow();
330 - });
331 -
332 - // @reactVersion >= 18.0
333 - // @reactVersion < 19.2
334 - it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
335 - const cpuProfilerSample = creactCpuProfilerSample();
336 -
337 - await expect(
338 - async () => await preprocessData([cpuProfilerSample]),
339 - ).rejects.toThrow(
340 - 'Please provide profiling data from an React application',
341 - );
342 - });
343 -
344 - // @reactVersion >= 18.0
345 - // @reactVersion < 19.2
346 - it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
347 - const cpuProfilerSample = creactCpuProfilerSample();
348 - const scheduleRenderSample = createUserTimingEntry({
349 - cat: 'blink.user_timing',
350 - name: '--schedule-render-512-',
351 - });
352 - const samples = [cpuProfilerSample, scheduleRenderSample];
353 -
354 - await expect(async () => await preprocessData(samples)).rejects.toThrow(
355 - 'This version of profiling data is not supported',
356 - );
357 - });
358 -
359 - // @reactVersion >= 18.0
360 - // @reactVersion < 19.2
361 - it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
362 - const cpuProfilerSample = creactCpuProfilerSample();
363 - const randomSample = createUserTimingEntry({
364 - dur: 100,
365 - tdur: 200,
366 - ph: 'X',
367 - cat: 'disabled-by-default-devtools.timeline',
368 - name: 'RunTask',
369 - args: {},
370 - });
371 -
372 - const data = await preprocessData([
373 - ...createBoilerplateEntries(),
374 - cpuProfilerSample,
375 - randomSample,
376 - ]);
377 - expect(data).toMatchInlineSnapshot(`
378 - {
379 - "batchUIDToMeasuresMap": Map {},
380 - "componentMeasures": [],
381 - "duration": 0.005,
382 - "flamechart": [],
383 - "internalModuleSourceToRanges": Map {},
384 - "laneToLabelMap": Map {
385 - 0 => "Sync",
386 - 1 => "InputContinuousHydration",
387 - 2 => "InputContinuous",
388 - 3 => "DefaultHydration",
389 - 4 => "Default",
390 - 5 => "TransitionHydration",
391 - 6 => "Transition",
392 - 7 => "Transition",
393 - 8 => "Transition",
394 - 9 => "Transition",
395 - 10 => "Transition",
396 - 11 => "Transition",
397 - 12 => "Transition",
398 - 13 => "Transition",
399 - 14 => "Transition",
400 - 15 => "Transition",
401 - 16 => "Transition",
402 - 17 => "Transition",
403 - 18 => "Transition",
404 - 19 => "Transition",
405 - 20 => "Transition",
406 - 21 => "Transition",
407 - 22 => "Retry",
408 - 23 => "Retry",
409 - 24 => "Retry",
410 - 25 => "Retry",
411 - 26 => "Retry",
412 - 27 => "SelectiveHydration",
413 - 28 => "IdleHydration",
414 - 29 => "Idle",
415 - 30 => "Offscreen",
416 - },
417 - "laneToReactMeasureMap": Map {
418 - 0 => [],
419 - 1 => [],
420 - 2 => [],
421 - 3 => [],
422 - 4 => [],
423 - 5 => [],
424 - 6 => [],
425 - 7 => [],
426 - 8 => [],
427 - 9 => [],
428 - 10 => [],
429 - 11 => [],
430 - 12 => [],
431 - 13 => [],
432 - 14 => [],
433 - 15 => [],
434 - 16 => [],
435 - 17 => [],
436 - 18 => [],
437 - 19 => [],
438 - 20 => [],
439 - 21 => [],
440 - 22 => [],
441 - 23 => [],
442 - 24 => [],
443 - 25 => [],
444 - 26 => [],
445 - 27 => [],
446 - 28 => [],
447 - 29 => [],
448 - 30 => [],
449 - },
450 - "nativeEvents": [],
451 - "networkMeasures": [],
452 - "otherUserTimingMarks": [],
453 - "reactVersion": "<filtered-version>",
454 - "schedulingEvents": [],
455 - "snapshotHeight": 0,
456 - "snapshots": [],
457 - "startTime": 1,
458 - "suspenseEvents": [],
459 - "thrownErrors": [],
460 - }
461 - `);
462 - });
463 -
464 - // @reactVersion >= 18.0
465 - // @reactVersion < 19.2
466 - it('should process legacy data format (before lane labels were added)', async () => {
467 - const cpuProfilerSample = creactCpuProfilerSample();
468 -
469 - // Data below is hard-coded based on an older profile sample.
470 - // Should be fine since this is explicitly a legacy-format test.
471 - const data = await preprocessData([
472 - ...createBoilerplateEntries(),
473 - cpuProfilerSample,
474 - createUserTimingEntry({
475 - cat: 'blink.user_timing',
476 - name: '--schedule-render-512-',
477 - }),
478 - createUserTimingEntry({
479 - cat: 'blink.user_timing',
480 - name: '--render-start-512',
481 - }),
482 - createUserTimingEntry({
483 - cat: 'blink.user_timing',
484 - name: '--render-stop',
485 - }),
486 - createUserTimingEntry({
487 - cat: 'blink.user_timing',
488 - name: '--commit-start-512',
489 - }),
490 - createUserTimingEntry({
491 - cat: 'blink.user_timing',
492 - name: '--layout-effects-start-512',
493 - }),
494 - createUserTimingEntry({
495 - cat: 'blink.user_timing',
496 - name: '--layout-effects-stop',
497 - }),
498 - createUserTimingEntry({
499 - cat: 'blink.user_timing',
500 - name: '--commit-stop',
501 - }),
502 - ]);
503 - expect(data).toMatchInlineSnapshot(`
504 - {
505 - "batchUIDToMeasuresMap": Map {
506 - 0 => [
507 - {
508 - "batchUID": 0,
509 - "depth": 0,
510 - "duration": 0.005,
511 - "lanes": "0b0000000000000000000000000001001",
512 - "timestamp": 0.006,
513 - "type": "render-idle",
514 - },
515 - {
516 - "batchUID": 0,
517 - "depth": 0,
518 - "duration": 0.001,
519 - "lanes": "0b0000000000000000000000000001001",
520 - "timestamp": 0.006,
521 - "type": "render",
522 - },
523 - {
524 - "batchUID": 0,
525 - "depth": 0,
526 - "duration": 0.003,
527 - "lanes": "0b0000000000000000000000000001001",
528 - "timestamp": 0.008,
529 - "type": "commit",
530 - },
531 - {
532 - "batchUID": 0,
533 - "depth": 1,
534 - "duration": 0.001,
535 - "lanes": "0b0000000000000000000000000001001",
536 - "timestamp": 0.009,
537 - "type": "layout-effects",
538 - },
539 - ],
540 - },
541 - "componentMeasures": [],
542 - "duration": 0.011,
543 - "flamechart": [],
544 - "internalModuleSourceToRanges": Map {},
545 - "laneToLabelMap": Map {
546 - 0 => "Sync",
547 - 1 => "InputContinuousHydration",
548 - 2 => "InputContinuous",
549 - 3 => "DefaultHydration",
550 - 4 => "Default",
551 - 5 => "TransitionHydration",
552 - 6 => "Transition",
553 - 7 => "Transition",
554 - 8 => "Transition",
555 - 9 => "Transition",
556 - 10 => "Transition",
557 - 11 => "Transition",
558 - 12 => "Transition",
559 - 13 => "Transition",
560 - 14 => "Transition",
561 - 15 => "Transition",
562 - 16 => "Transition",
563 - 17 => "Transition",
564 - 18 => "Transition",
565 - 19 => "Transition",
566 - 20 => "Transition",
567 - 21 => "Transition",
568 - 22 => "Retry",
569 - 23 => "Retry",
570 - 24 => "Retry",
571 - 25 => "Retry",
572 - 26 => "Retry",
573 - 27 => "SelectiveHydration",
574 - 28 => "IdleHydration",
575 - 29 => "Idle",
576 - 30 => "Offscreen",
577 - },
578 - "laneToReactMeasureMap": Map {
579 - 0 => [],
580 - 1 => [],
581 - 2 => [],
582 - 3 => [],
583 - 4 => [],
584 - 5 => [],
585 - 6 => [],
586 - 7 => [],
587 - 8 => [],
588 - 9 => [
589 - {
590 - "batchUID": 0,
591 - "depth": 0,
592 - "duration": 0.005,
593 - "lanes": "0b0000000000000000000000000001001",
594 - "timestamp": 0.006,
595 - "type": "render-idle",
596 - },
597 - {
598 - "batchUID": 0,
599 - "depth": 0,
600 - "duration": 0.001,
601 - "lanes": "0b0000000000000000000000000001001",
602 - "timestamp": 0.006,
603 - "type": "render",
604 - },
605 - {
606 - "batchUID": 0,
607 - "depth": 0,
608 - "duration": 0.003,
609 - "lanes": "0b0000000000000000000000000001001",
610 - "timestamp": 0.008,
611 - "type": "commit",
612 - },
613 - {
614 - "batchUID": 0,
615 - "depth": 1,
616 - "duration": 0.001,
617 - "lanes": "0b0000000000000000000000000001001",
618 - "timestamp": 0.009,
619 - "type": "layout-effects",
620 - },
621 - ],
622 - 10 => [],
623 - 11 => [],
624 - 12 => [],
625 - 13 => [],
626 - 14 => [],
627 - 15 => [],
628 - 16 => [],
629 - 17 => [],
630 - 18 => [],
631 - 19 => [],
632 - 20 => [],
633 - 21 => [],
634 - 22 => [],
635 - 23 => [],
636 - 24 => [],
637 - 25 => [],
638 - 26 => [],
639 - 27 => [],
640 - 28 => [],
641 - 29 => [],
642 - 30 => [],
643 - },
644 - "nativeEvents": [],
645 - "networkMeasures": [],
646 - "otherUserTimingMarks": [],
647 - "reactVersion": "<filtered-version>",
648 - "schedulingEvents": [
649 - {
650 - "lanes": "0b0000000000000000000000000001001",
651 - "timestamp": 0.005,
652 - "type": "schedule-render",
653 - "warning": null,
654 - },
655 - ],
656 - "snapshotHeight": 0,
657 - "snapshots": [],
658 - "startTime": 1,
659 - "suspenseEvents": [],
660 - "thrownErrors": [],
661 - }
662 - `);
663 - });
664 -
665 - // @reactVersion <= 18.2
666 - // @reactVersion >= 18.0
667 - it('should process a sample legacy render sequence', async () => {
668 - legacyRender(<div />);
669 -
670 - const data = await preprocessData([
671 - ...createBoilerplateEntries(),
672 - ...createUserTimingData(registeredMarks),
673 - ]);
674 - expect(data).toMatchInlineSnapshot(`
675 - {
676 - "batchUIDToMeasuresMap": Map {
677 - 0 => [
678 - {
679 - "batchUID": 0,
680 - "depth": 0,
681 - "duration": 0.01,
682 - "lanes": "0b0000000000000000000000000000000",
683 - "timestamp": 0.006,
684 - "type": "render-idle",
685 - },
686 - {
687 - "batchUID": 0,
688 - "depth": 0,
689 - "duration": 0.001,
690 - "lanes": "0b0000000000000000000000000000000",
691 - "timestamp": 0.006,
692 - "type": "render",
693 - },
694 - {
695 - "batchUID": 0,
696 - "depth": 0,
697 - "duration": 0.008,
698 - "lanes": "0b0000000000000000000000000000000",
699 - "timestamp": 0.008,
700 - "type": "commit",
701 - },
702 - {
703 - "batchUID": 0,
704 - "depth": 1,
705 - "duration": 0.001,
706 - "lanes": "0b0000000000000000000000000000000",
707 - "timestamp": 0.014,
708 - "type": "layout-effects",
709 - },
710 - ],
711 - },
712 - "componentMeasures": [],
713 - "duration": 0.016,
714 - "flamechart": [],
715 - "internalModuleSourceToRanges": Map {
716 - undefined => [
717 - [
718 - {
719 - "columnNumber": 0,
720 - "functionName": "filtered",
721 - "lineNumber": 0,
722 - "source": " at filtered (<anonymous>:0:0)",
723 - },
724 - {
725 - "columnNumber": 1,
726 - "functionName": "filtered",
727 - "lineNumber": 1,
728 - "source": " at filtered (<anonymous>:1:1)",
729 - },
730 - ],
731 - ],
732 - },
733 - "laneToLabelMap": Map {
734 - 0 => "Sync",
735 - 1 => "InputContinuousHydration",
736 - 2 => "InputContinuous",
737 - 3 => "DefaultHydration",
738 - 4 => "Default",
739 - 5 => "TransitionHydration",
740 - 6 => "Transition",
741 - 7 => "Transition",
742 - 8 => "Transition",
743 - 9 => "Transition",
744 - 10 => "Transition",
745 - 11 => "Transition",
746 - 12 => "Transition",
747 - 13 => "Transition",
748 - 14 => "Transition",
749 - 15 => "Transition",
750 - 16 => "Transition",
751 - 17 => "Transition",
752 - 18 => "Transition",
753 - 19 => "Transition",
754 - 20 => "Transition",
755 - 21 => "Transition",
756 - 22 => "Retry",
757 - 23 => "Retry",
758 - 24 => "Retry",
759 - 25 => "Retry",
760 - 26 => "Retry",
761 - 27 => "SelectiveHydration",
762 - 28 => "IdleHydration",
763 - 29 => "Idle",
764 - 30 => "Offscreen",
765 - },
766 - "laneToReactMeasureMap": Map {
767 - 0 => [
768 - {
769 - "batchUID": 0,
770 - "depth": 0,
771 - "duration": 0.01,
772 - "lanes": "0b0000000000000000000000000000000",
773 - "timestamp": 0.006,
774 - "type": "render-idle",
775 - },
776 - {
777 - "batchUID": 0,
778 - "depth": 0,
779 - "duration": 0.001,
780 - "lanes": "0b0000000000000000000000000000000",
781 - "timestamp": 0.006,
782 - "type": "render",
783 - },
784 - {
785 - "batchUID": 0,
786 - "depth": 0,
787 - "duration": 0.008,
788 - "lanes": "0b0000000000000000000000000000000",
789 - "timestamp": 0.008,
790 - "type": "commit",
791 - },
792 - {
793 - "batchUID": 0,
794 - "depth": 1,
795 - "duration": 0.001,
796 - "lanes": "0b0000000000000000000000000000000",
797 - "timestamp": 0.014,
798 - "type": "layout-effects",
799 - },
800 - ],
801 - 1 => [],
802 - 2 => [],
803 - 3 => [],
804 - 4 => [],
805 - 5 => [],
806 - 6 => [],
807 - 7 => [],
808 - 8 => [],
809 - 9 => [],
810 - 10 => [],
811 - 11 => [],
812 - 12 => [],
813 - 13 => [],
814 - 14 => [],
815 - 15 => [],
816 - 16 => [],
817 - 17 => [],
818 - 18 => [],
819 - 19 => [],
820 - 20 => [],
821 - 21 => [],
822 - 22 => [],
823 - 23 => [],
824 - 24 => [],
825 - 25 => [],
826 - 26 => [],
827 - 27 => [],
828 - 28 => [],
829 - 29 => [],
830 - 30 => [],
831 - },
832 - "nativeEvents": [],
833 - "networkMeasures": [],
834 - "otherUserTimingMarks": [],
835 - "reactVersion": "<filtered-version>",
836 - "schedulingEvents": [
837 - {
838 - "lanes": "0b0000000000000000000000000000000",
839 - "timestamp": 0.005,
840 - "type": "schedule-render",
841 - "warning": null,
842 - },
843 - ],
844 - "snapshotHeight": 0,
845 - "snapshots": [],
846 - "startTime": 4,
847 - "suspenseEvents": [],
848 - "thrownErrors": [],
849 - }
850 - `);
851 - });
852 -
853 - // @reactVersion >= 19.1
854 - // @reactVersion < 19.2
855 - it('should process a sample createRoot render sequence', async () => {
856 - function App() {
857 - const [didMount, setDidMount] = React.useState(false);
858 - React.useEffect(() => {
859 - if (!didMount) {
860 - setDidMount(true);
861 - }
862 - });
863 - return true;
864 - }
865 -
866 - const root = ReactDOMClient.createRoot(document.createElement('div'));
867 - utils.act(() => root.render(<App />));
868 -
869 - const data = await preprocessData([
870 - ...createBoilerplateEntries(),
871 - ...createUserTimingData(registeredMarks),
872 - ]);
873 - expect(data).toMatchInlineSnapshot(`
874 - {
875 - "batchUIDToMeasuresMap": Map {
876 - 0 => [
877 - {
878 - "batchUID": 0,
879 - "depth": 0,
880 - "duration": 0.012,
881 - "lanes": "0b0000000000000000000000000000101",
882 - "timestamp": 0.008,
883 - "type": "render-idle",
884 - },
885 - {
886 - "batchUID": 0,
887 - "depth": 0,
888 - "duration": 0.003,
889 - "lanes": "0b0000000000000000000000000000101",
890 - "timestamp": 0.008,
891 - "type": "render",
892 - },
893 - {
894 - "batchUID": 0,
895 - "depth": 0,
896 - "duration": 0.008,
897 - "lanes": "0b0000000000000000000000000000101",
898 - "timestamp": 0.012,
899 - "type": "commit",
900 - },
901 - {
902 - "batchUID": 0,
903 - "depth": 0,
904 - "duration": 0.004,
905 - "lanes": "0b0000000000000000000000000000101",
906 - "timestamp": 0.021,
907 - "type": "passive-effects",
908 - },
909 - ],
910 - 1 => [
911 - {
912 - "batchUID": 1,
913 - "depth": 0,
914 - "duration": 0.012,
915 - "lanes": "0b0000000000000000000000000000101",
916 - "timestamp": 0.026,
917 - "type": "render-idle",
918 - },
919 - {
920 - "batchUID": 1,
921 - "depth": 0,
922 - "duration": 0.003,
923 - "lanes": "0b0000000000000000000000000000101",
924 - "timestamp": 0.026,
925 - "type": "render",
926 - },
927 - {
928 - "batchUID": 1,
929 - "depth": 0,
930 - "duration": 0.008,
931 - "lanes": "0b0000000000000000000000000000101",
932 - "timestamp": 0.03,
933 - "type": "commit",
934 - },
935 - {
936 - "batchUID": 1,
937 - "depth": 0,
938 - "duration": 0.003,
939 - "lanes": "0b0000000000000000000000000000101",
940 - "timestamp": 0.039,
941 - "type": "passive-effects",
942 - },
943 - ],
944 - },
945 - "componentMeasures": [
946 - {
947 - "componentName": "App",
948 - "duration": 0.001,
949 - "timestamp": 0.009,
950 - "type": "render",
951 - "warning": null,
952 - },
953 - {
954 - "componentName": "App",
955 - "duration": 0.002,
956 - "timestamp": 0.022,
957 - "type": "passive-effect-mount",
958 - "warning": null,
959 - },
960 - {
961 - "componentName": "App",
962 - "duration": 0.001,
963 - "timestamp": 0.027,
964 - "type": "render",
965 - "warning": null,
966 - },
967 - {
968 - "componentName": "App",
969 - "duration": 0.001,
970 - "timestamp": 0.04,
971 - "type": "passive-effect-mount",
972 - "warning": null,
973 - },
974 - ],
975 - "duration": 0.042,
976 - "flamechart": [],
977 - "internalModuleSourceToRanges": Map {
978 - undefined => [
979 - [
980 - {
981 - "columnNumber": 0,
982 - "functionName": "filtered",
983 - "lineNumber": 0,
984 - "source": " at filtered (<anonymous>:0:0)",
985 - },
986 - {
987 - "columnNumber": 1,
988 - "functionName": "filtered",
989 - "lineNumber": 1,
990 - "source": " at filtered (<anonymous>:1:1)",
991 - },
992 - ],
993 - ],
994 - },
995 - "laneToLabelMap": Map {
996 - 0 => "Sync",
997 - 1 => "InputContinuousHydration",
998 - 2 => "InputContinuous",
999 - 3 => "DefaultHydration",
1000 - 4 => "Default",
1001 - 5 => "TransitionHydration",
1002 - 6 => "Transition",
1003 - 7 => "Transition",
1004 - 8 => "Transition",
1005 - 9 => "Transition",
1006 - 10 => "Transition",
1007 - 11 => "Transition",
1008 - 12 => "Transition",
1009 - 13 => "Transition",
1010 - 14 => "Transition",
1011 - 15 => "Transition",
1012 - 16 => "Transition",
1013 - 17 => "Transition",
1014 - 18 => "Transition",
1015 - 19 => "Transition",
1016 - 20 => "Transition",
1017 - 21 => "Transition",
1018 - 22 => "Retry",
1019 - 23 => "Retry",
1020 - 24 => "Retry",
1021 - 25 => "Retry",
1022 - 26 => "Retry",
1023 - 27 => "SelectiveHydration",
1024 - 28 => "IdleHydration",
1025 - 29 => "Idle",
1026 - 30 => "Offscreen",
1027 - },
1028 - "laneToReactMeasureMap": Map {
1029 - 0 => [],
1030 - 1 => [],
1031 - 2 => [],
1032 - 3 => [],
1033 - 4 => [],
1034 - 5 => [
1035 - {
1036 - "batchUID": 0,
1037 - "depth": 0,
1038 - "duration": 0.012,
1039 - "lanes": "0b0000000000000000000000000000101",
1040 - "timestamp": 0.008,
1041 - "type": "render-idle",
1042 - },
1043 - {
1044 - "batchUID": 0,
1045 - "depth": 0,
1046 - "duration": 0.003,
1047 - "lanes": "0b0000000000000000000000000000101",
1048 - "timestamp": 0.008,
1049 - "type": "render",
1050 - },
1051 - {
1052 - "batchUID": 0,
1053 - "depth": 0,
1054 - "duration": 0.008,
1055 - "lanes": "0b0000000000000000000000000000101",
1056 - "timestamp": 0.012,
1057 - "type": "commit",
1058 - },
1059 - {
1060 - "batchUID": 0,
1061 - "depth": 0,
1062 - "duration": 0.004,
1063 - "lanes": "0b0000000000000000000000000000101",
1064 - "timestamp": 0.021,
1065 - "type": "passive-effects",
1066 - },
1067 - {
1068 - "batchUID": 1,
1069 - "depth": 0,
1070 - "duration": 0.012,
1071 - "lanes": "0b0000000000000000000000000000101",
1072 - "timestamp": 0.026,
1073 - "type": "render-idle",
1074 - },
1075 - {
1076 - "batchUID": 1,
1077 - "depth": 0,
1078 - "duration": 0.003,
1079 - "lanes": "0b0000000000000000000000000000101",
1080 - "timestamp": 0.026,
1081 - "type": "render",
1082 - },
1083 - {
1084 - "batchUID": 1,
1085 - "depth": 0,
1086 - "duration": 0.008,
1087 - "lanes": "0b0000000000000000000000000000101",
1088 - "timestamp": 0.03,
1089 - "type": "commit",
1090 - },
1091 - {
1092 - "batchUID": 1,
1093 - "depth": 0,
1094 - "duration": 0.003,
1095 - "lanes": "0b0000000000000000000000000000101",
1096 - "timestamp": 0.039,
1097 - "type": "passive-effects",
1098 - },
1099 - ],
1100 - 6 => [],
1101 - 7 => [],
1102 - 8 => [],
1103 - 9 => [],
1104 - 10 => [],
1105 - 11 => [],
1106 - 12 => [],
1107 - 13 => [],
1108 - 14 => [],
1109 - 15 => [],
1110 - 16 => [],
1111 - 17 => [],
1112 - 18 => [],
1113 - 19 => [],
1114 - 20 => [],
1115 - 21 => [],
1116 - 22 => [],
1117 - 23 => [],
1118 - 24 => [],
1119 - 25 => [],
1120 - 26 => [],
1121 - 27 => [],
1122 - 28 => [],
1123 - 29 => [],
1124 - 30 => [],
1125 - },
1126 - "nativeEvents": [],
1127 - "networkMeasures": [],
1128 - "otherUserTimingMarks": [],
1129 - "reactVersion": "<filtered-version>",
1130 - "schedulingEvents": [
1131 - {
1132 - "lanes": "0b0000000000000000000000000000101",
1133 - "timestamp": 0.007,
1134 - "type": "schedule-render",
1135 - "warning": null,
1136 - },
1137 - {
1138 - "componentName": "App",
1139 - "lanes": "0b0000000000000000000000000000101",
1140 - "timestamp": 0.023,
1141 - "type": "schedule-state-update",
1142 - "warning": null,
1143 - },
1144 - ],
1145 - "snapshotHeight": 0,
1146 - "snapshots": [],
1147 - "startTime": 4,
1148 - "suspenseEvents": [],
1149 - "thrownErrors": [],
1150 - }
1151 - `);
1152 - });
1153 -
1154 - // @reactVersion >= 18.0
1155 - // @reactVersion <= 18.2
1156 - it('should error if events and measures are incomplete', async () => {
1157 - legacyRender(<div />);
1158 -
1159 - const invalidMarks = registeredMarks.filter(
1160 - mark => !mark.includes('render-stop'),
1161 - );
1162 - const invalidUserTimingData = createUserTimingData(invalidMarks);
1163 -
1164 - const error = jest.spyOn(console, 'error').mockImplementation(() => {});
1165 - preprocessData([
1166 - ...createBoilerplateEntries(),
1167 - ...invalidUserTimingData,
1168 - ]);
1169 - expect(error).toHaveBeenCalled();
1170 - });
1171 -
1172 - // @reactVersion >= 18.0
1173 - // @reactVersion <= 18.2
1174 - it('should error if work is completed without being started', async () => {
1175 - legacyRender(<div />);
1176 -
1177 - const invalidMarks = registeredMarks.filter(
1178 - mark => !mark.includes('render-start'),
1179 - );
1180 - const invalidUserTimingData = createUserTimingData(invalidMarks);
1181 -
1182 - const error = jest.spyOn(console, 'error').mockImplementation(() => {});
1183 - preprocessData([
1184 - ...createBoilerplateEntries(),
1185 - ...invalidUserTimingData,
1186 - ]);
1187 - expect(error).toHaveBeenCalled();
1188 - });
1189 -
1190 - // @reactVersion >= 18.0
1191 - // @reactVersion < 19.2
1192 - it('should populate other user timing marks', async () => {
1193 - const userTimingData = createUserTimingData([]);
1194 - userTimingData.push(
1195 - createUserTimingEntry({
1196 - args: {},
1197 - cat: 'blink.user_timing',
1198 - id: '0xcdf75f7c',
1199 - name: 'VCWithoutImage: root',
1200 - ph: 'n',
1201 - scope: 'blink.user_timing',
1202 - }),
1203 - );
1204 - userTimingData.push(
1205 - createUserTimingEntry({
1206 - cat: 'blink.user_timing',
1207 - name: '--a-mark-that-looks-like-one-of-ours',
1208 - ph: 'R',
1209 - }),
1210 - );
1211 - userTimingData.push(
1212 - createUserTimingEntry({
1213 - cat: 'blink.user_timing',
1214 - name: 'Some other mark',
1215 - ph: 'R',
1216 - }),
1217 - );
1218 -
1219 - const data = await preprocessData([
1220 - ...createBoilerplateEntries(),
1221 - ...userTimingData,
1222 - ]);
1223 - expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
1224 - [
1225 - {
1226 - "name": "VCWithoutImage: root",
1227 - "timestamp": 0.003,
1228 - },
1229 - {
1230 - "name": "--a-mark-that-looks-like-one-of-ours",
1231 - "timestamp": 0.004,
1232 - },
1233 - {
1234 - "name": "Some other mark",
1235 - "timestamp": 0.005,
1236 - },
1237 - ]
1238 - `);
1239 - });
1240 -
1241 - // @reactVersion >= 18.0
1242 - // @reactVersion < 19.2
1243 - it('should include a suspended resource "displayName" if one is set', async () => {
1244 - let promise = null;
1245 - let resolvedValue = null;
1246 - function readValue(value) {
1247 - if (React.use) {
1248 - if (promise === null) {
1249 - promise = Promise.resolve(true).then(() => {
1250 - return value;
1251 - });
1252 - promise.displayName = 'Testing displayName';
1253 - }
1254 - return React.use(promise);
1255 - }
1256 - if (resolvedValue !== null) {
1257 - return resolvedValue;
1258 - } else if (promise === null) {
1259 - promise = Promise.resolve(true).then(() => {
1260 - resolvedValue = value;
1261 - });
1262 - promise.displayName = 'Testing displayName';
1263 - }
1264 - throw promise;
1265 - }
1266 -
1267 - function Component() {
1268 - const value = readValue(123);
1269 - return value;
1270 - }
1271 -
1272 - const testMarks = [creactCpuProfilerSample()];
1273 -
1274 - const root = ReactDOMClient.createRoot(document.createElement('div'));
1275 - await utils.actAsync(() =>
1276 - root.render(
1277 - <React.Suspense fallback="Loading...">
1278 - <Component />
1279 - </React.Suspense>,
1280 - ),
1281 - );
1282 -
1283 - testMarks.push(...createUserTimingData(registeredMarks));
1284 -
1285 - let data;
1286 - await utils.actAsync(async () => {
1287 - data = await preprocessData(testMarks);
1288 - });
1289 - expect(data.suspenseEvents).toHaveLength(1);
1290 - expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1291 - });
1292 -
1293 - describe('warnings', () => {
1294 - describe('long event handlers', () => {
1295 - // @reactVersion >= 18.0
1296 - // @reactVersion <= 18.2
1297 - it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
1298 - function App() {
1299 - return null;
1300 - }
1301 -
1302 - const testMarks = [
1303 - creactCpuProfilerSample(),
1304 - ...createBoilerplateEntries(),
1305 - createNativeEventEntry('click', 5),
1306 - ];
1307 -
1308 - eraseRegisteredMarks();
1309 - legacyRender(<App />);
1310 -
1311 - testMarks.push(...createUserTimingData(registeredMarks));
1312 -
1313 - const data = await preprocessData(testMarks);
1314 - const event = data.nativeEvents.find(({type}) => type === 'click');
1315 - expect(event.warning).toBe(null);
1316 - });
1317 -
1318 - // @reactVersion >= 18.0
1319 - // @reactVersion <= 18.2
1320 - it('should not warn about long events if the cause was non-React JavaScript', async () => {
1321 - function App() {
1322 - return null;
1323 - }
1324 -
1325 - const testMarks = [
1326 - creactCpuProfilerSample(),
1327 - ...createBoilerplateEntries(),
1328 - createNativeEventEntry('click', 25000),
1329 - ];
1330 -
1331 - startTime += 2000;
1332 -
1333 - eraseRegisteredMarks();
1334 - legacyRender(<App />);
1335 -
1336 - testMarks.push(...createUserTimingData(registeredMarks));
1337 -
1338 - const data = await preprocessData(testMarks);
1339 - const event = data.nativeEvents.find(({type}) => type === 'click');
1340 - expect(event.warning).toBe(null);
1341 - });
1342 -
1343 - // @reactVersion >= 18.0
1344 - // @reactVersion <= 18.2
1345 - it('should warn when React scedules a long (sync) update inside of an event', async () => {
1346 - function App() {
1347 - return null;
1348 - }
1349 -
1350 - const testMarks = [
1351 - creactCpuProfilerSample(),
1352 - ...createBoilerplateEntries(),
1353 - createNativeEventEntry('click', 25000),
1354 - ];
1355 -
1356 - eraseRegisteredMarks();
1357 - legacyRender(<App />);
1358 -
1359 - registeredMarks.forEach(markName => {
1360 - if (markName === '--render-stop') {
1361 - // Fake a long running render
1362 - startTime += 20000;
1363 - }
1364 -
1365 - testMarks.push({
1366 - pid: ++pid,
1367 - tid: ++tid,
1368 - ts: ++startTime,
1369 - args: {data: {}},
1370 - cat: 'blink.user_timing',
1371 - name: markName,
1372 - ph: 'R',
1373 - });
1374 - });
1375 -
1376 - const data = await preprocessData(testMarks);
1377 - const event = data.nativeEvents.find(({type}) => type === 'click');
1378 - expect(event.warning).toMatchInlineSnapshot(
1379 - `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
1380 - );
1381 - });
1382 -
1383 - // @reactVersion >= 18.2
1384 - // @reactVersion < 19.2
1385 - it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
1386 - function Yield({id, value}) {
1387 - Scheduler.log(`${id}:${value}`);
1388 - return null;
1389 - }
1390 -
1391 - const testMarks = [
1392 - creactCpuProfilerSample(),
1393 - ...createBoilerplateEntries(),
1394 - ];
1395 -
1396 - // Advance the clock by some arbitrary amount.
1397 - startTime += 50000;
1398 -
1399 - const root = ReactDOMClient.createRoot(
1400 - document.createElement('div'),
1401 - );
1402 -
1403 - // Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
1404 - global.IS_REACT_ACT_ENVIRONMENT = false;
1405 - React.startTransition(() => {
1406 - // Start rendering an async update (but don't finish).
1407 - root.render(
1408 - <>
1409 - <Yield id="A" value={1} />
1410 - <Yield id="B" value={1} />
1411 - </>,
1412 - );
1413 - });
1414 -
1415 - await waitFor(['A:1']);
1416 -
1417 - testMarks.push(...createUserTimingData(registeredMarks));
1418 - eraseRegisteredMarks();
1419 -
1420 - // Advance the clock some more to make the pending React update seem long.
1421 - startTime += 20000;
1422 -
1423 - // Fake a long "click" event in the middle
1424 - // and schedule a sync update that will also flush the previous work.
1425 - testMarks.push(createNativeEventEntry('click', 25000));
1426 - ReactDOM.flushSync(() => {
1427 - root.render(
1428 - <>
1429 - <Yield id="A" value={2} />
1430 - <Yield id="B" value={2} />
1431 - </>,
1432 - );
1433 - });
1434 -
1435 - assertLog(['A:2', 'B:2']);
1436 -
1437 - testMarks.push(...createUserTimingData(registeredMarks));
1438 -
1439 - const data = await preprocessData(testMarks);
1440 - const event = data.nativeEvents.find(({type}) => type === 'click');
1441 - expect(event.warning).toBe(null);
1442 - });
1443 - });
1444 -
1445 - describe('nested updates', () => {
1446 - // @reactVersion >= 18.2
1447 - // @reactVersion < 19.2
1448 - it('should not warn about short nested (state) updates during layout effects', async () => {
1449 - function Component() {
1450 - const [didMount, setDidMount] = React.useState(false);
1451 - Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
1452 - React.useLayoutEffect(() => {
1453 - setDidMount(true);
1454 - }, []);
1455 - return didMount;
1456 - }
1457 -
1458 - const root = ReactDOMClient.createRoot(
1459 - document.createElement('div'),
1460 - );
1461 - utils.act(() => {
1462 - root.render(<Component />);
1463 - });
1464 -
1465 - assertLog(['Component mount', 'Component update']);
1466 -
1467 - const data = await preprocessData([
1468 - ...createBoilerplateEntries(),
1469 - ...createUserTimingData(registeredMarks),
1470 - ]);
1471 -
1472 - const event = data.schedulingEvents.find(
1473 - ({type}) => type === 'schedule-state-update',
1474 - );
1475 - expect(event.warning).toBe(null);
1476 - });
1477 -
1478 - // @reactVersion >= 18.2
1479 - // @reactVersion < 19.2
1480 - it('should not warn about short (forced) updates during layout effects', async () => {
1481 - class Component extends React.Component {
1482 - _didMount: boolean = false;
1483 - componentDidMount() {
1484 - this._didMount = true;
1485 - this.forceUpdate();
1486 - }
1487 - render() {
1488 - Scheduler.log(
1489 - `Component ${this._didMount ? 'update' : 'mount'}`,
1490 - );
1491 - return null;
1492 - }
1493 - }
1494 -
1495 - const root = ReactDOMClient.createRoot(
1496 - document.createElement('div'),
1497 - );
1498 - utils.act(() => {
1499 - root.render(<Component />);
1500 - });
1501 -
1502 - assertLog(['Component mount', 'Component update']);
1503 -
1504 - const data = await preprocessData([
1505 - ...createBoilerplateEntries(),
1506 - ...createUserTimingData(registeredMarks),
1507 - ]);
1508 -
1509 - const event = data.schedulingEvents.find(
1510 - ({type}) => type === 'schedule-force-update',
1511 - );
1512 - expect(event.warning).toBe(null);
1513 - });
1514 -
1515 - // This is temporarily disabled because the warning doesn't work
1516 - // with useDeferredValue
1517 - // eslint-disable-next-line jest/no-disabled-tests
1518 - it.skip('should warn about long nested (state) updates during layout effects', async () => {
1519 - function Component() {
1520 - const [didMount, setDidMount] = React.useState(false);
1521 - Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
1522 - // Fake a long render
1523 - startTime += 20000;
1524 - React.useLayoutEffect(() => {
1525 - setDidMount(true);
1526 - }, []);
1527 - return didMount;
1528 - }
1529 -
1530 - const cpuProfilerSample = creactCpuProfilerSample();
1531 -
1532 - const root = ReactDOMClient.createRoot(
1533 - document.createElement('div'),
1534 - );
1535 - utils.act(() => {
1536 - root.render(<Component />);
1537 - });
1538 -
1539 - assertLog(['Component mount', 'Component update']);
1540 -
1541 - const testMarks = [];
1542 - registeredMarks.forEach(markName => {
1543 - if (markName === '--component-render-start-Component') {
1544 - // Fake a long running render
1545 - startTime += 20000;
1546 - }
1547 -
1548 - testMarks.push({
1549 - pid: ++pid,
1550 - tid: ++tid,
1551 - ts: ++startTime,
1552 - args: {data: {}},
1553 - cat: 'blink.user_timing',
1554 - name: markName,
1555 - ph: 'R',
1556 - });
1557 - });
1558 -
1559 - const data = await preprocessData([
1560 - cpuProfilerSample,
1561 - ...createBoilerplateEntries(),
1562 - ...testMarks,
1563 - ]);
1564 -
1565 - const event = data.schedulingEvents.find(
1566 - ({type}) => type === 'schedule-state-update',
1567 - );
1568 - expect(event.warning).toMatchInlineSnapshot(
1569 - `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
1570 - );
1571 - });
1572 -
1573 - // This is temporarily disabled because the warning doesn't work
1574 - // with useDeferredValue
1575 - // eslint-disable-next-line jest/no-disabled-tests
1576 - it.skip('should warn about long nested (forced) updates during layout effects', async () => {
1577 - class Component extends React.Component {
1578 - _didMount: boolean = false;
1579 - componentDidMount() {
1580 - this._didMount = true;
1581 - this.forceUpdate();
1582 - }
1583 - render() {
1584 - Scheduler.log(
1585 - `Component ${this._didMount ? 'update' : 'mount'}`,
1586 - );
1587 - return null;
1588 - }
1589 - }
1590 -
1591 - const cpuProfilerSample = creactCpuProfilerSample();
1592 -
1593 - const root = ReactDOMClient.createRoot(
1594 - document.createElement('div'),
1595 - );
1596 - utils.act(() => {
1597 - root.render(<Component />);
1598 - });
1599 -
1600 - assertLog(['Component mount', 'Component update']);
1601 -
1602 - const testMarks = [];
1603 - registeredMarks.forEach(markName => {
1604 - if (markName === '--component-render-start-Component') {
1605 - // Fake a long running render
1606 - startTime += 20000;
1607 - }
1608 -
1609 - testMarks.push({
1610 - pid: ++pid,
1611 - tid: ++tid,
1612 - ts: ++startTime,
1613 - args: {data: {}},
1614 - cat: 'blink.user_timing',
1615 - name: markName,
1616 - ph: 'R',
1617 - });
1618 - });
1619 -
1620 - const data = await preprocessData([
1621 - cpuProfilerSample,
1622 - ...createBoilerplateEntries(),
1623 - ...testMarks,
1624 - ]);
1625 -
1626 - const event = data.schedulingEvents.find(
1627 - ({type}) => type === 'schedule-force-update',
1628 - );
1629 - expect(event.warning).toMatchInlineSnapshot(
1630 - `"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
1631 - );
1632 - });
1633 -
1634 - // @reactVersion >= 18.2
1635 - // @reactVersion < 19.2
1636 - it('should not warn about transition updates scheduled during commit phase', async () => {
1637 - function Component() {
1638 - const [value, setValue] = React.useState(0);
1639 - // eslint-disable-next-line no-unused-vars
1640 - const [isPending, startTransition] = React.useTransition();
1641 -
1642 - Scheduler.log(`Component rendered with value ${value}`);
1643 -
1644 - // Fake a long render
1645 - if (value !== 0) {
1646 - Scheduler.log('Long render');
1647 - startTime += 20000;
1648 - }
1649 -
1650 - React.useLayoutEffect(() => {
1651 - startTransition(() => {
1652 - setValue(1);
1653 - });
1654 - }, []);
1655 -
1656 - return value;
1657 - }
1658 -
1659 - const cpuProfilerSample = creactCpuProfilerSample();
1660 -
1661 - const root = ReactDOMClient.createRoot(
1662 - document.createElement('div'),
1663 - );
1664 - utils.act(() => {
1665 - root.render(<Component />);
1666 - });
1667 -
1668 - assertLog([
1669 - 'Component rendered with value 0',
1670 - 'Component rendered with value 0',
1671 - 'Component rendered with value 1',
1672 - 'Long render',
1673 - ]);
1674 -
1675 - const testMarks = [];
1676 - registeredMarks.forEach(markName => {
1677 - if (markName === '--component-render-start-Component') {
1678 - // Fake a long running render
1679 - startTime += 20000;
1680 - }
1681 -
1682 - testMarks.push({
1683 - pid: ++pid,
1684 - tid: ++tid,
1685 - ts: ++startTime,
1686 - args: {data: {}},
1687 - cat: 'blink.user_timing',
1688 - name: markName,
1689 - ph: 'R',
1690 - });
1691 - });
1692 -
1693 - const data = await preprocessData([
1694 - cpuProfilerSample,
1695 - ...createBoilerplateEntries(),
1696 - ...testMarks,
1697 - ]);
1698 -
1699 - data.schedulingEvents.forEach(event => {
1700 - expect(event.warning).toBeNull();
1701 - });
1702 - });
1703 -
1704 - // This is temporarily disabled because the warning doesn't work
1705 - // with useDeferredValue
1706 - // eslint-disable-next-line jest/no-disabled-tests
1707 - it.skip('should not warn about deferred value updates scheduled during commit phase', async () => {
1708 - function Component() {
1709 - const [value, setValue] = React.useState(0);
1710 - const deferredValue = React.useDeferredValue(value);
1711 -
1712 - Scheduler.log(
1713 - `Component rendered with value ${value} and deferredValue ${deferredValue}`,
1714 - );
1715 -
1716 - // Fake a long render
1717 - if (deferredValue !== 0) {
1718 - Scheduler.log('Long render');
1719 - startTime += 20000;
1720 - }
1721 -
1722 - React.useLayoutEffect(() => {
1723 - setValue(1);
1724 - }, []);
1725 -
1726 - return value + deferredValue;
1727 - }
1728 -
1729 - const cpuProfilerSample = creactCpuProfilerSample();
1730 -
1731 - const root = ReactDOMClient.createRoot(
1732 - document.createElement('div'),
1733 - );
1734 - utils.act(() => {
1735 - root.render(<Component />);
1736 - });
1737 -
1738 - assertLog([
1739 - 'Component rendered with value 0 and deferredValue 0',
1740 - 'Component rendered with value 1 and deferredValue 0',
1741 - 'Component rendered with value 1 and deferredValue 1',
1742 - 'Long render',
1743 - ]);
1744 -
1745 - const testMarks = [];
1746 - registeredMarks.forEach(markName => {
1747 - if (markName === '--component-render-start-Component') {
1748 - // Fake a long running render
1749 - startTime += 20000;
1750 - }
1751 -
1752 - testMarks.push({
1753 - pid: ++pid,
1754 - tid: ++tid,
1755 - ts: ++startTime,
1756 - args: {data: {}},
1757 - cat: 'blink.user_timing',
1758 - name: markName,
1759 - ph: 'R',
1760 - });
1761 - });
1762 -
1763 - const data = await preprocessData([
1764 - cpuProfilerSample,
1765 - ...createBoilerplateEntries(),
1766 - ...testMarks,
1767 - ]);
1768 -
1769 - data.schedulingEvents.forEach(event => {
1770 - expect(event.warning).toBeNull();
1771 - });
1772 - });
1773 - });
1774 -
1775 - describe('errors thrown while rendering', () => {
1776 - // @reactVersion >= 18.0
1777 - // @reactVersion < 19.2
1778 - it('shoult parse Errors thrown during render', async () => {
1779 - jest.spyOn(console, 'error');
1780 -
1781 - class ErrorBoundary extends React.Component {
1782 - state = {error: null};
1783 - componentDidCatch(error) {
1784 - this.setState({error});
1785 - }
1786 - render() {
1787 - if (this.state.error) {
1788 - return null;
1789 - }
1790 - return this.props.children;
1791 - }
1792 - }
1793 -
1794 - function ExampleThatThrows() {
1795 - throw Error('Expected error');
1796 - }
1797 -
1798 - const testMarks = [creactCpuProfilerSample()];
1799 -
1800 - // Mount and commit the app
1801 - const root = ReactDOMClient.createRoot(
1802 - document.createElement('div'),
1803 - );
1804 - utils.act(() =>
1805 - root.render(
1806 - <ErrorBoundary>
1807 - <ExampleThatThrows />
1808 - </ErrorBoundary>,
1809 - ),
1810 - );
1811 -
1812 - testMarks.push(...createUserTimingData(registeredMarks));
1813 -
1814 - const data = await preprocessData(testMarks);
1815 - expect(data.thrownErrors).toHaveLength(2);
1816 - expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
1817 - '"Expected error"',
1818 - );
1819 - });
1820 - });
1821 -
1822 - describe('suspend during an update', () => {
1823 - // This also tests an edge case where a component suspends while profiling
1824 - // before the first commit is logged (so the lane-to-labels map will not yet exist).
1825 - // @reactVersion >= 18.2
1826 - // @reactVersion < 19.2
1827 - it('should warn about suspending during an update', async () => {
1828 - let promise = null;
1829 - let resolvedValue = null;
1830 - function readValue(value) {
1831 - if (React.use) {
1832 - if (promise === null) {
1833 - promise = Promise.resolve(true).then(() => {
1834 - return value;
1835 - });
1836 - }
1837 - return React.use(promise);
1838 - }
1839 - if (resolvedValue !== null) {
1840 - return resolvedValue;
1841 - } else if (promise === null) {
1842 - promise = Promise.resolve(true).then(() => {
1843 - resolvedValue = value;
1844 - });
1845 - }
1846 - throw promise;
1847 - }
1848 -
1849 - function Component({shouldSuspend}) {
1850 - Scheduler.log(`Component ${shouldSuspend}`);
1851 - if (shouldSuspend) {
1852 - readValue(123);
1853 - }
1854 - return null;
1855 - }
1856 -
1857 - // Mount and commit the app
1858 - const root = ReactDOMClient.createRoot(
1859 - document.createElement('div'),
1860 - );
1861 - utils.act(() =>
1862 - root.render(
1863 - <React.Suspense fallback="Loading...">
1864 - <Component shouldSuspend={false} />
1865 - </React.Suspense>,
1866 - ),
1867 - );
1868 -
1869 - const testMarks = [creactCpuProfilerSample()];
1870 -
1871 - // Start profiling and suspend during a render.
1872 - utils.act(() =>
1873 - root.render(
1874 - <React.Suspense fallback="Loading...">
1875 - <Component shouldSuspend={true} />
1876 - </React.Suspense>,
1877 - ),
1878 - );
1879 -
1880 - testMarks.push(...createUserTimingData(registeredMarks));
1881 -
1882 - let data;
1883 - await utils.actAsync(async () => {
1884 - data = await preprocessData(testMarks);
1885 - });
1886 - expect(data.suspenseEvents).toHaveLength(1);
1887 - expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
1888 - `"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
1889 - );
1890 - });
1891 -
1892 - // @reactVersion >= 18.2
1893 - // @reactVersion < 19.2
1894 - it('should not warn about suspending during an transition', async () => {
1895 - let promise = null;
1896 - let resolvedValue = null;
1897 - function readValue(value) {
1898 - if (React.use) {
1899 - if (promise === null) {
1900 - promise = Promise.resolve(true).then(() => {
1901 - return value;
1902 - });
1903 - }
1904 - return React.use(promise);
1905 - }
1906 - if (resolvedValue !== null) {
1907 - return resolvedValue;
1908 - } else if (promise === null) {
1909 - promise = Promise.resolve(true).then(() => {
1910 - resolvedValue = value;
1911 - });
1912 - }
1913 - throw promise;
1914 - }
1915 -
1916 - function Component({shouldSuspend}) {
1917 - Scheduler.log(`Component ${shouldSuspend}`);
1918 - if (shouldSuspend) {
1919 - readValue(123);
1920 - }
1921 - return null;
1922 - }
1923 -
1924 - // Mount and commit the app
1925 - const root = ReactDOMClient.createRoot(
1926 - document.createElement('div'),
1927 - );
1928 - utils.act(() =>
1929 - root.render(
1930 - <React.Suspense fallback="Loading...">
1931 - <Component shouldSuspend={false} />
1932 - </React.Suspense>,
1933 - ),
1934 - );
1935 -
1936 - const testMarks = [creactCpuProfilerSample()];
1937 -
1938 - // Start profiling and suspend during a render.
1939 - await utils.actAsync(async () =>
1940 - React.startTransition(() =>
1941 - root.render(
1942 - <React.Suspense fallback="Loading...">
1943 - <Component shouldSuspend={true} />
1944 - </React.Suspense>,
1945 - ),
1946 - ),
1947 - );
1948 -
1949 - testMarks.push(...createUserTimingData(registeredMarks));
1950 -
1951 - let data;
1952 - await utils.actAsync(async () => {
1953 - data = await preprocessData(testMarks);
1954 - });
1955 - expect(data.suspenseEvents).toHaveLength(1);
1956 - expect(data.suspenseEvents[0].warning).toBe(null);
1957 - });
1958 - });
1959 - });
1960 -
1961 - // TODO: Add test for snapshot base64 parsing
1962 -
1963 - // TODO: Add test for flamechart parsing
1964 - });
1965 - });
1966 -
1967 - // Note the in-memory tests vary slightly (e.g. timestamp values, lane numbers) from the above tests.
1968 - // That's okay; the important thing is the lane-to-label matches the subsequent events/measures.
1969 - describe('DevTools hook (in memory)', () => {
1970 - let store;
1971 -
1972 - beforeEach(() => {
1973 - utils = require('./utils');
1974 - utils.beforeEachProfiling();
1975 -
1976 - React = require('react');
1977 - ReactDOM = require('react-dom');
1978 - ReactDOMClient = require('react-dom/client');
1979 - Scheduler = require('scheduler');
1980 -
1981 - store = global.store;
1982 -
1983 - // Start profiling so that data will actually be recorded.
1984 - utils.act(() => store.profilerStore.startProfiling());
1985 -
1986 - global.IS_REACT_ACT_ENVIRONMENT = true;
1987 - });
1988 -
1989 - const {render: legacyRender} = getLegacyRenderImplementation();
1990 -
1991 - // @reactVersion <= 18.2
1992 - // @reactVersion >= 18.0
1993 - it('should process a sample legacy render sequence', async () => {
1994 - legacyRender(<div />);
1995 - utils.act(() => store.profilerStore.stopProfiling());
1996 -
1997 - const data = store.profilerStore.profilingData?.timelineData;
1998 - expect(data).toHaveLength(1);
1999 - const timelineData = data[0];
2000 - expect(timelineData).toMatchInlineSnapshot(`
2001 - {
2002 - "batchUIDToMeasuresMap": Map {
2003 - 1 => [
2004 - {
2005 - "batchUID": 1,
2006 - "depth": 0,
2007 - "duration": 0,
2008 - "lanes": "0b0000000000000000000000000000001",
2009 - "timestamp": 10,
2010 - "type": "render-idle",
2011 - },
2012 - {
2013 - "batchUID": 1,
2014 - "depth": 0,
2015 - "duration": 0,
2016 - "lanes": "0b0000000000000000000000000000001",
2017 - "timestamp": 10,
2018 - "type": "render",
2019 - },
2020 - {
2021 - "batchUID": 1,
2022 - "depth": 0,
2023 - "duration": 0,
2024 - "lanes": "0b0000000000000000000000000000001",
2025 - "timestamp": 10,
2026 - "type": "commit",
2027 - },
2028 - {
2029 - "batchUID": 1,
2030 - "depth": 1,
2031 - "duration": 0,
2032 - "lanes": "0b0000000000000000000000000000001",
2033 - "timestamp": 10,
2034 - "type": "layout-effects",
2035 - },
2036 - ],
2037 - },
2038 - "componentMeasures": [],
2039 - "duration": 20,
2040 - "flamechart": [],
2041 - "internalModuleSourceToRanges": Map {},
2042 - "laneToLabelMap": Map {
2043 - 1 => "Sync",
2044 - 2 => "InputContinuousHydration",
2045 - 4 => "InputContinuous",
2046 - 8 => "DefaultHydration",
2047 - 16 => "Default",
2048 - 32 => "TransitionHydration",
2049 - 64 => "Transition",
2050 - 128 => "Transition",
2051 - 256 => "Transition",
2052 - 512 => "Transition",
2053 - 1024 => "Transition",
2054 - 2048 => "Transition",
2055 - 4096 => "Transition",
2056 - 8192 => "Transition",
2057 - 16384 => "Transition",
2058 - 32768 => "Transition",
2059 - 65536 => "Transition",
2060 - 131072 => "Transition",
2061 - 262144 => "Transition",
2062 - 524288 => "Transition",
2063 - 1048576 => "Transition",
2064 - 2097152 => "Transition",
2065 - 4194304 => "Retry",
2066 - 8388608 => "Retry",
2067 - 16777216 => "Retry",
2068 - 33554432 => "Retry",
2069 - 67108864 => "Retry",
2070 - 134217728 => "SelectiveHydration",
2071 - 268435456 => "IdleHydration",
2072 - 536870912 => "Idle",
2073 - 1073741824 => "Offscreen",
2074 - },
2075 - "laneToReactMeasureMap": Map {
2076 - 1 => [
2077 - {
2078 - "batchUID": 1,
2079 - "depth": 0,
2080 - "duration": 0,
2081 - "lanes": "0b0000000000000000000000000000001",
2082 - "timestamp": 10,
2083 - "type": "render-idle",
2084 - },
2085 - {
2086 - "batchUID": 1,
2087 - "depth": 0,
2088 - "duration": 0,
2089 - "lanes": "0b0000000000000000000000000000001",
2090 - "timestamp": 10,
2091 - "type": "render",
2092 - },
2093 - {
2094 - "batchUID": 1,
2095 - "depth": 0,
2096 - "duration": 0,
2097 - "lanes": "0b0000000000000000000000000000001",
2098 - "timestamp": 10,
2099 - "type": "commit",
2100 - },
2101 - {
2102 - "batchUID": 1,
2103 - "depth": 1,
2104 - "duration": 0,
2105 - "lanes": "0b0000000000000000000000000000001",
2106 - "timestamp": 10,
2107 - "type": "layout-effects",
2108 - },
2109 - ],
2110 - 2 => [],
2111 - 4 => [],
2112 - 8 => [],
2113 - 16 => [],
2114 - 32 => [],
2115 - 64 => [],
2116 - 128 => [],
2117 - 256 => [],
2118 - 512 => [],
2119 - 1024 => [],
2120 - 2048 => [],
2121 - 4096 => [],
2122 - 8192 => [],
2123 - 16384 => [],
2124 - 32768 => [],
2125 - 65536 => [],
2126 - 131072 => [],
2127 - 262144 => [],
2128 - 524288 => [],
2129 - 1048576 => [],
2130 - 2097152 => [],
2131 - 4194304 => [],
2132 - 8388608 => [],
2133 - 16777216 => [],
2134 - 33554432 => [],
2135 - 67108864 => [],
2136 - 134217728 => [],
2137 - 268435456 => [],
2138 - 536870912 => [],
2139 - 1073741824 => [],
2140 - },
2141 - "nativeEvents": [],
2142 - "networkMeasures": [],
2143 - "otherUserTimingMarks": [],
2144 - "reactVersion": "<filtered-version>",
2145 - "schedulingEvents": [
2146 - {
2147 - "lanes": "0b0000000000000000000000000000001",
2148 - "timestamp": 10,
2149 - "type": "schedule-render",
2150 - "warning": null,
2151 - },
2152 - ],
2153 - "snapshotHeight": 0,
2154 - "snapshots": [],
2155 - "startTime": -10,
2156 - "suspenseEvents": [],
2157 - "thrownErrors": [],
2158 - }
2159 - `);
2160 - });
2161 -
2162 - // @reactVersion >= 19.1
2163 - // @reactVersion < 19.2
2164 - it('should process a sample createRoot render sequence', async () => {
2165 - function App() {
2166 - const [didMount, setDidMount] = React.useState(false);
2167 - React.useEffect(() => {
2168 - if (!didMount) {
2169 - setDidMount(true);
2170 - }
2171 - });
2172 - return true;
2173 - }
2174 -
2175 - const root = ReactDOMClient.createRoot(document.createElement('div'));
2176 - utils.act(() => root.render(<App />));
2177 - utils.act(() => store.profilerStore.stopProfiling());
2178 -
2179 - const data = store.profilerStore.profilingData?.timelineData;
2180 - expect(data).toHaveLength(1);
2181 - const timelineData = data[0];
2182 -
2183 - // normalize the location for component stack source
2184 - // for snapshot testing
2185 - timelineData.schedulingEvents.forEach(event => {
2186 - if (event.componentStack) {
2187 - event.componentStack = normalizeCodeLocInfo(event.componentStack);
2188 - }
2189 - });
2190 -
2191 - expect(timelineData).toMatchInlineSnapshot(`
2192 - {
2193 - "batchUIDToMeasuresMap": Map {
2194 - 1 => [
2195 - {
2196 - "batchUID": 1,
2197 - "depth": 0,
2198 - "duration": 0,
2199 - "lanes": "0b0000000000000000000000000100000",
2200 - "timestamp": 10,
2201 - "type": "render-idle",
2202 - },
2203 - {
2204 - "batchUID": 1,
2205 - "depth": 0,
2206 - "duration": 0,
2207 - "lanes": "0b0000000000000000000000000100000",
2208 - "timestamp": 10,
2209 - "type": "render",
2210 - },
2211 - {
2212 - "batchUID": 1,
2213 - "depth": 0,
2214 - "duration": 0,
2215 - "lanes": "0b0000000000000000000000000100000",
2216 - "timestamp": 10,
2217 - "type": "commit",
2218 - },
2219 - {
2220 - "batchUID": 1,
2221 - "depth": 0,
2222 - "duration": 0,
2223 - "lanes": "0b0000000000000000000000000100000",
2224 - "timestamp": 10,
2225 - "type": "passive-effects",
2226 - },
2227 - ],
2228 - 2 => [
2229 - {
2230 - "batchUID": 2,
2231 - "depth": 0,
2232 - "duration": 0,
2233 - "lanes": "0b0000000000000000000000000100000",
2234 - "timestamp": 10,
2235 - "type": "render-idle",
2236 - },
2237 - {
2238 - "batchUID": 2,
2239 - "depth": 0,
2240 - "duration": 0,
2241 - "lanes": "0b0000000000000000000000000100000",
2242 - "timestamp": 10,
2243 - "type": "render",
2244 - },
2245 - {
2246 - "batchUID": 2,
2247 - "depth": 0,
2248 - "duration": 0,
2249 - "lanes": "0b0000000000000000000000000100000",
2250 - "timestamp": 10,
2251 - "type": "commit",
2252 - },
2253 - {
2254 - "batchUID": 2,
2255 - "depth": 0,
2256 - "duration": 0,
2257 - "lanes": "0b0000000000000000000000000100000",
2258 - "timestamp": 10,
2259 - "type": "passive-effects",
2260 - },
2261 - ],
2262 - },
2263 - "componentMeasures": [
2264 - {
2265 - "componentName": "App",
2266 - "duration": 0,
2267 - "timestamp": 10,
2268 - "type": "render",
2269 - "warning": null,
2270 - },
2271 - {
2272 - "componentName": "App",
2273 - "duration": 0,
2274 - "timestamp": 10,
2275 - "type": "passive-effect-mount",
2276 - "warning": null,
2277 - },
2278 - {
2279 - "componentName": "App",
2280 - "duration": 0,
2281 - "timestamp": 10,
2282 - "type": "render",
2283 - "warning": null,
2284 - },
2285 - {
2286 - "componentName": "App",
2287 - "duration": 0,
2288 - "timestamp": 10,
2289 - "type": "passive-effect-mount",
2290 - "warning": null,
2291 - },
2292 - ],
2293 - "duration": 20,
2294 - "flamechart": [],
2295 - "internalModuleSourceToRanges": Map {},
2296 - "laneToLabelMap": Map {
2297 - 1 => "SyncHydrationLane",
2298 - 2 => "Sync",
2299 - 4 => "InputContinuousHydration",
2300 - 8 => "InputContinuous",
2301 - 16 => "DefaultHydration",
2302 - 32 => "Default",
2303 - 64 => undefined,
2304 - 128 => "TransitionHydration",
2305 - 256 => "Transition",
2306 - 512 => "Transition",
2307 - 1024 => "Transition",
2308 - 2048 => "Transition",
2309 - 4096 => "Transition",
2310 - 8192 => "Transition",
2311 - 16384 => "Transition",
2312 - 32768 => "Transition",
2313 - 65536 => "Transition",
2314 - 131072 => "Transition",
2315 - 262144 => "Transition",
2316 - 524288 => "Transition",
2317 - 1048576 => "Transition",
2318 - 2097152 => "Transition",
2319 - 4194304 => "Retry",
2320 - 8388608 => "Retry",
2321 - 16777216 => "Retry",
2322 - 33554432 => "Retry",
2323 - 67108864 => "SelectiveHydration",
2324 - 134217728 => "IdleHydration",
2325 - 268435456 => "Idle",
2326 - 536870912 => "Offscreen",
2327 - 1073741824 => "Deferred",
2328 - },
2329 - "laneToReactMeasureMap": Map {
2330 - 1 => [],
2331 - 2 => [],
2332 - 4 => [],
2333 - 8 => [],
2334 - 16 => [],
2335 - 32 => [
2336 - {
2337 - "batchUID": 1,
2338 - "depth": 0,
2339 - "duration": 0,
2340 - "lanes": "0b0000000000000000000000000100000",
2341 - "timestamp": 10,
2342 - "type": "render-idle",
2343 - },
2344 - {
2345 - "batchUID": 1,
2346 - "depth": 0,
2347 - "duration": 0,
2348 - "lanes": "0b0000000000000000000000000100000",
2349 - "timestamp": 10,
2350 - "type": "render",
2351 - },
2352 - {
2353 - "batchUID": 1,
2354 - "depth": 0,
2355 - "duration": 0,
2356 - "lanes": "0b0000000000000000000000000100000",
2357 - "timestamp": 10,
2358 - "type": "commit",
2359 - },
2360 - {
2361 - "batchUID": 1,
2362 - "depth": 0,
2363 - "duration": 0,
2364 - "lanes": "0b0000000000000000000000000100000",
2365 - "timestamp": 10,
2366 - "type": "passive-effects",
2367 - },
2368 - {
2369 - "batchUID": 2,
2370 - "depth": 0,
2371 - "duration": 0,
2372 - "lanes": "0b0000000000000000000000000100000",
2373 - "timestamp": 10,
2374 - "type": "render-idle",
2375 - },
2376 - {
2377 - "batchUID": 2,
2378 - "depth": 0,
2379 - "duration": 0,
2380 - "lanes": "0b0000000000000000000000000100000",
2381 - "timestamp": 10,
2382 - "type": "render",
2383 - },
2384 - {
2385 - "batchUID": 2,
2386 - "depth": 0,
2387 - "duration": 0,
2388 - "lanes": "0b0000000000000000000000000100000",
2389 - "timestamp": 10,
2390 - "type": "commit",
2391 - },
2392 - {
2393 - "batchUID": 2,
2394 - "depth": 0,
2395 - "duration": 0,
2396 - "lanes": "0b0000000000000000000000000100000",
2397 - "timestamp": 10,
2398 - "type": "passive-effects",
2399 - },
2400 - ],
2401 - 64 => [],
2402 - 128 => [],
2403 - 256 => [],
2404 - 512 => [],
2405 - 1024 => [],
2406 - 2048 => [],
2407 - 4096 => [],
2408 - 8192 => [],
2409 - 16384 => [],
2410 - 32768 => [],
2411 - 65536 => [],
2412 - 131072 => [],
2413 - 262144 => [],
2414 - 524288 => [],
2415 - 1048576 => [],
2416 - 2097152 => [],
2417 - 4194304 => [],
2418 - 8388608 => [],
2419 - 16777216 => [],
2420 - 33554432 => [],
2421 - 67108864 => [],
2422 - 134217728 => [],
2423 - 268435456 => [],
2424 - 536870912 => [],
2425 - 1073741824 => [],
2426 - },
2427 - "nativeEvents": [],
2428 - "networkMeasures": [],
2429 - "otherUserTimingMarks": [],
2430 - "reactVersion": "<filtered-version>",
2431 - "schedulingEvents": [
2432 - {
2433 - "lanes": "0b0000000000000000000000000100000",
2434 - "timestamp": 10,
2435 - "type": "schedule-render",
2436 - "warning": null,
2437 - },
2438 - {
2439 - "componentName": "App",
2440 - "componentStack": "
2441 - in App (at **)",
2442 - "lanes": "0b0000000000000000000000000100000",
2443 - "timestamp": 10,
2444 - "type": "schedule-state-update",
2445 - "warning": null,
2446 - },
2447 - ],
2448 - "snapshotHeight": 0,
2449 - "snapshots": [],
2450 - "startTime": -10,
2451 - "suspenseEvents": [],
2452 - "thrownErrors": [],
2453 - }
2454 - `);
2455 - });
2456 - });
2457 -});
packages/react-devtools-shared/src/__tests__/profilerContext-test.js
+5 -20
@@ -589,9 +589,6 @@ describe('ProfilerContext', () => {
589 // Context providers
590 const Profiler =
591 require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
592 - const {
593 - TimelineContextController,
594 - } = require('react-devtools-timeline/src/TimelineContext');
592 const {
593 SettingsContextController,
594 } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -615,9 +612,7 @@ describe('ProfilerContext', () => {
612 <Contexts>
613 <SettingsContextController browserTheme="light">
614 <ModalDialogContextController>
618 - <TimelineContextController>
619 - <Profiler />
620 - </TimelineContextController>
615 + <Profiler />
616 </ModalDialogContextController>
617 </SettingsContextController>
618 </Contexts>,
@@ -673,9 +668,6 @@ describe('ProfilerContext', () => {
668
669 const Profiler =
670 require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
676 - const {
677 - TimelineContextController,
678 - } = require('react-devtools-timeline/src/TimelineContext');
671 const {
672 SettingsContextController,
673 } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -699,10 +691,8 @@ describe('ProfilerContext', () => {
691 <Contexts>
692 <SettingsContextController browserTheme="light">
693 <ModalDialogContextController>
702 - <TimelineContextController>
703 - <Profiler />
704 - <ContextReader />
705 - </TimelineContextController>
694 + <Profiler />
695 + <ContextReader />
696 </ModalDialogContextController>
697 </SettingsContextController>
698 </Contexts>,
@@ -886,9 +876,6 @@ describe('ProfilerContext', () => {
876 // Context providers
877 const Profiler =
878 require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default;
889 - const {
890 - TimelineContextController,
891 - } = require('react-devtools-timeline/src/TimelineContext');
879 const {
880 SettingsContextController,
881 } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext');
@@ -912,10 +899,8 @@ describe('ProfilerContext', () => {
899 <Contexts>
900 <SettingsContextController browserTheme="light">
901 <ModalDialogContextController>
915 - <TimelineContextController>
916 - <Profiler />
917 - <ContextReader />
918 - </TimelineContextController>
902 + <Profiler />
903 + <ContextReader />
904 </ModalDialogContextController>
905 </SettingsContextController>
906 </Contexts>,
packages/react-devtools-shared/src/__tests__/setupTests.js
+1 -3
@@ -312,9 +312,7 @@ beforeEach(() => {
312 };
313 const bridge = new Bridge(bridgeWall);
314
315 - const store = new Store(((bridge: any): FrontendBridge), {
316 - supportsTimeline: true,
317 - });
315 + const store = new Store(((bridge: any): FrontendBridge));
316
317 const agent = new Agent(((bridge: any): BackendBridge));
318 const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
packages/react-devtools-shared/src/__tests__/utils.js
-3
@@ -425,9 +425,6 @@ export function exportImportHelper(bridge: FrontendBridge, store: Store): void {
425 expect(profilingDataFrontendInitial.dataForRoots).toEqual(
426 profilingDataFrontend.dataForRoots,
427 );
428 - expect(profilingDataFrontendInitial.timelineData).toEqual(
429 - profilingDataFrontend.timelineData,
430 - );
428
429 // Snapshot the JSON-parsed object, rather than the raw string, because Jest formats the diff nicer.
430 // expect(parsedProfilingDataExport).toMatchSnapshot('imported data');
packages/react-devtools-shared/src/backend/agent.js
+10 -17
@@ -282,17 +282,12 @@ export default class Agent extends EventEmitter<{
282 _persistedSelection: PersistedSelection | null = null;
283 _persistedSelectionMatch: PathMatch | null = null;
284 _traceUpdatesEnabled: boolean = false;
285 - _onReloadAndProfile:
286 - | ((recordChangeDescriptions: boolean, recordTimeline: boolean) => void)
287 - | void;
285 + _onReloadAndProfile: ((recordChangeDescriptions: boolean) => void) | void;
286
287 constructor(
288 bridge: BackendBridge,
289 isProfiling: boolean = false,
292 - onReloadAndProfile?: (
293 - recordChangeDescriptions: boolean,
294 - recordTimeline: boolean,
295 - ) => void,
290 + onReloadAndProfile?: (recordChangeDescriptions: boolean) => void,
291 ) {
292 super();
293
@@ -909,12 +904,11 @@ export default class Agent extends EventEmitter<{
904 this._bridge.send('isReloadAndProfileSupportedByBackend', true);
905 };
906
912 - reloadAndProfile: ({
913 - recordChangeDescriptions: boolean,
914 - recordTimeline: boolean,
915 - }) => void = ({recordChangeDescriptions, recordTimeline}) => {
907 + reloadAndProfile: ({recordChangeDescriptions: boolean}) => void = ({
908 + recordChangeDescriptions,
909 + }) => {
910 if (typeof this._onReloadAndProfile === 'function') {
917 - this._onReloadAndProfile(recordChangeDescriptions, recordTimeline);
911 + this._onReloadAndProfile(recordChangeDescriptions);
912 }
913
914 // This code path should only be hit if the shell has explicitly told the Store that it supports profiling.
@@ -998,16 +992,15 @@ export default class Agent extends EventEmitter<{
992 this.removeAllListeners();
993 };
994
1001 - startProfiling: ({
1002 - recordChangeDescriptions: boolean,
1003 - recordTimeline: boolean,
1004 - }) => void = ({recordChangeDescriptions, recordTimeline}) => {
995 + startProfiling: ({recordChangeDescriptions: boolean}) => void = ({
996 + recordChangeDescriptions,
997 + }) => {
998 this._isProfiling = true;
999 for (const rendererID in this._rendererInterfaces) {
1000 const renderer = this._rendererInterfaces[
1001 rendererID as any
1002 ] as any as RendererInterface;
1010 - renderer.startProfiling(recordChangeDescriptions, recordTimeline);
1003 + renderer.startProfiling(recordChangeDescriptions);
1004 }
1005 this._bridge.send('profilingStatus', this._isProfiling);
1006 };
packages/react-devtools-shared/src/backend/fiber/renderer.js
+2 -82
@@ -75,7 +75,6 @@ import {
75 import {
76 __DEBUG__,
77 PROFILING_FLAG_BASIC_SUPPORT,
78 - PROFILING_FLAG_TIMELINE_SUPPORT,
78 PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT,
79 TREE_OPERATION_ADD,
80 TREE_OPERATION_REMOVE,
@@ -150,9 +149,7 @@ import {
149 } from './DevToolsFiberComponentStack';
150
151 import {getStyleXData} from '../StyleX/utils';
153 -import {createProfilingHooks} from '../profilingHooks';
152
155 -import type {GetTimelineData, ToggleProfilingStatus} from '../profilingHooks';
153 import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
154 import type {
155 ChangeDescription,
@@ -436,8 +433,6 @@ export function attach(
433 } = ReactPriorityLevels;
434
435 const {
439 - getLaneLabelMap,
440 - injectProfilingHooks,
436 overrideHookState,
437 overrideHookStateDeletePath,
438 overrideHookStateRenamePath,
@@ -475,26 +470,6 @@ export function attach(
470 };
471 }
472
478 - let getTimelineData: null | GetTimelineData = null;
479 - let toggleProfilingStatus: null | ToggleProfilingStatus = null;
480 - if (typeof injectProfilingHooks === 'function') {
481 - const response = createProfilingHooks({
482 - getDisplayNameForFiber,
483 - getIsProfiling: () => isProfiling,
484 - getLaneLabelMap,
485 - currentDispatcherRef: getDispatcherRef(renderer),
486 - workTagMap: ReactTypeOfWork,
487 - reactVersion: version,
488 - });
489 -
490 - // Pass the Profiling hooks to the reconciler for it to call during render.
491 - injectProfilingHooks(response.profilingHooks);
492 -
493 - // Hang onto this toggle so we can notify the external methods of profiling status changes.
494 - getTimelineData = response.getTimelineData;
495 - toggleProfilingStatus = response.toggleProfilingStatus;
496 - }
497 -
473 type ComponentLogs = {
474 errors: Map<string, number>,
475 errorsCount: number,
@@ -1776,9 +1751,6 @@ export function attach(
1751 let profilingFlags = 0;
1752 if (isProfilingSupported) {
1753 profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;
1779 - if (typeof injectProfilingHooks === 'function') {
1780 - profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT;
1781 - }
1754 if (supportsPerformanceTracks) {
1755 profilingFlags |= PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT;
1756 }
@@ -7251,7 +7223,6 @@ export function attach(
7223 let isProfiling: boolean = false;
7224 let profilingStartTime: number = 0;
7225 let recordChangeDescriptions: boolean = false;
7254 - let recordTimeline: boolean = false;
7226 let rootToCommitProfilingMetadataMap: CommitProfilingMetadataMap | null =
7227 null;
7228
@@ -7335,43 +7306,9 @@ export function attach(
7306 },
7307 );
7308
7338 - let timelineData = null;
7339 - if (typeof getTimelineData === 'function') {
7340 - const currentTimelineData = getTimelineData();
7341 - if (currentTimelineData) {
7342 - const {
7343 - batchUIDToMeasuresMap,
7344 - internalModuleSourceToRanges,
7345 - laneToLabelMap,
7346 - laneToReactMeasureMap,
7347 - ...rest
7348 - } = currentTimelineData;
7349 -
7350 - timelineData = {
7351 - ...rest,
7352 -
7353 - // Most of the data is safe to parse as-is,
7354 - // but we need to convert the nested Arrays back to Maps.
7355 - // Most of the data is safe to serialize as-is,
7356 - // but we need to convert the Maps to nested Arrays.
7357 - batchUIDToMeasuresKeyValueArray: Array.from(
7358 - batchUIDToMeasuresMap.entries(),
7359 - ),
7360 - internalModuleSourceToRanges: Array.from(
7361 - internalModuleSourceToRanges.entries(),
7362 - ),
7363 - laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
7364 - laneToReactMeasureKeyValueArray: Array.from(
7365 - laneToReactMeasureMap.entries(),
7366 - ),
7367 - };
7368 - }
7369 - }
7370 -
7309 return {
7310 dataForRoots,
7311 rendererID,
7374 - timelineData,
7312 };
7313 }
7314
@@ -7393,16 +7330,12 @@ export function attach(
7330 }
7331 }
7332
7396 - function startProfiling(
7397 - shouldRecordChangeDescriptions: boolean,
7398 - shouldRecordTimeline: boolean,
7399 - ) {
7333 + function startProfiling(shouldRecordChangeDescriptions: boolean) {
7334 if (isProfiling) {
7335 return;
7336 }
7337
7338 recordChangeDescriptions = shouldRecordChangeDescriptions;
7405 - recordTimeline = shouldRecordTimeline;
7339
7340 // Capture initial values as of the time profiling starts.
7341 // It's important we snapshot both the durations and the id-to-root map,
@@ -7434,29 +7367,16 @@ export function attach(
7367 isProfiling = true;
7368 profilingStartTime = getCurrentTime();
7369 rootToCommitProfilingMetadataMap = new Map();
7437 -
7438 - if (toggleProfilingStatus !== null) {
7439 - toggleProfilingStatus(true, recordTimeline);
7440 - }
7370 }
7371
7372 function stopProfiling() {
7373 isProfiling = false;
7374 recordChangeDescriptions = false;
7446 -
7447 - if (toggleProfilingStatus !== null) {
7448 - toggleProfilingStatus(false, recordTimeline);
7449 - }
7450 -
7451 - recordTimeline = false;
7375 }
7376
7377 // Automatically start profiling so that we don't miss timing info from initial "mount".
7378 if (shouldStartProfilingNow) {
7456 - startProfiling(
7457 - profilingSettings.recordChangeDescriptions,
7458 - profilingSettings.recordTimeline,
7459 - );
7379 + startProfiling(profilingSettings.recordChangeDescriptions);
7380 }
7381
7382 function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber {
packages/react-devtools-shared/src/backend/profilingHooks.js deleted
-989
@@ -1,989 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - Lane,
12 - Lanes,
13 - DevToolsProfilingHooks,
14 - WorkTagMap,
15 - CurrentDispatcherRef,
16 -} from 'react-devtools-shared/src/backend/types';
17 -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
18 -import type {Wakeable} from 'shared/ReactTypes';
19 -import type {
20 - BatchUID,
21 - InternalModuleSourceToRanges,
22 - LaneToLabelMap,
23 - ReactComponentMeasure,
24 - ReactLane,
25 - ReactMeasure,
26 - ReactMeasureType,
27 - ReactScheduleStateUpdateEvent,
28 - SchedulingEvent,
29 - SuspenseEvent,
30 - TimelineData,
31 -} from 'react-devtools-timeline/src/types';
32 -
33 -import isArray from 'shared/isArray';
34 -import {
35 - REACT_TOTAL_NUM_LANES,
36 - SCHEDULING_PROFILER_VERSION,
37 -} from 'react-devtools-timeline/src/constants';
38 -import {describeFiber} from './fiber/DevToolsFiberComponentStack';
39 -
40 -// Add padding to the start/stop time of the profile.
41 -// This makes the UI nicer to use.
42 -const TIME_OFFSET = 10;
43 -
44 -let performanceTarget: Performance | null = null;
45 -
46 -// If performance exists and supports the subset of the User Timing API that we require.
47 -let supportsUserTiming =
48 - typeof performance !== 'undefined' &&
49 - // $FlowFixMe[method-unbinding]
50 - typeof performance.mark === 'function' &&
51 - // $FlowFixMe[method-unbinding]
52 - typeof performance.clearMarks === 'function';
53 -
54 -let supportsUserTimingV3 = false;
55 -if (supportsUserTiming) {
56 - const CHECK_V3_MARK = '__v3';
57 - const markOptions: {
58 - detail?: mixed,
59 - startTime?: number,
60 - } = {};
61 - Object.defineProperty(markOptions, 'startTime', {
62 - get: function () {
63 - supportsUserTimingV3 = true;
64 - return 0;
65 - },
66 - set: function () {},
67 - });
68 -
69 - try {
70 - performance.mark(CHECK_V3_MARK, markOptions);
71 - } catch (error) {
72 - // Ignore
73 - } finally {
74 - performance.clearMarks(CHECK_V3_MARK);
75 - }
76 -}
77 -
78 -if (supportsUserTimingV3) {
79 - performanceTarget = performance;
80 -}
81 -
82 -// Some environments (e.g. React Native / Hermes) don't support the performance API yet.
83 -const getCurrentTime =
84 - // $FlowFixMe[method-unbinding]
85 - typeof performance === 'object' && typeof performance.now === 'function'
86 - ? () => performance.now()
87 - : () => Date.now();
88 -
89 -// Mocking the Performance Object (and User Timing APIs) for testing is fragile.
90 -// This API allows tests to directly override the User Timing APIs.
91 -export function setPerformanceMock_ONLY_FOR_TESTING(
92 - performanceMock: Performance | null,
93 -) {
94 - performanceTarget = performanceMock;
95 - supportsUserTiming = performanceMock !== null;
96 - supportsUserTimingV3 = performanceMock !== null;
97 -}
98 -
99 -export type GetTimelineData = () => TimelineData | null;
100 -export type ToggleProfilingStatus = (
101 - value: boolean,
102 - recordTimeline?: boolean,
103 -) => void;
104 -
105 -type Response = {
106 - getTimelineData: GetTimelineData,
107 - profilingHooks: DevToolsProfilingHooks,
108 - toggleProfilingStatus: ToggleProfilingStatus,
109 -};
110 -
111 -export function createProfilingHooks({
112 - getDisplayNameForFiber,
113 - getIsProfiling,
114 - getLaneLabelMap,
115 - workTagMap,
116 - currentDispatcherRef,
117 - reactVersion,
118 -}: {
119 - getDisplayNameForFiber: (fiber: Fiber) => string | null,
120 - getIsProfiling: () => boolean,
121 - getLaneLabelMap?: () => Map<Lane, string> | null,
122 - currentDispatcherRef?: CurrentDispatcherRef,
123 - workTagMap: WorkTagMap,
124 - reactVersion: string,
125 -}): Response {
126 - let currentBatchUID: BatchUID = 0;
127 - let currentReactComponentMeasure: ReactComponentMeasure | null = null;
128 - let currentReactMeasuresStack: Array<ReactMeasure> = [];
129 - let currentTimelineData: TimelineData | null = null;
130 - let currentFiberStacks: Map<SchedulingEvent, Array<Fiber>> = new Map();
131 - let isProfiling: boolean = false;
132 - let nextRenderShouldStartNewBatch: boolean = false;
133 -
134 - function getRelativeTime() {
135 - const currentTime = getCurrentTime();
136 -
137 - if (currentTimelineData) {
138 - if (currentTimelineData.startTime === 0) {
139 - currentTimelineData.startTime = currentTime - TIME_OFFSET;
140 - }
141 -
142 - return currentTime - currentTimelineData.startTime;
143 - }
144 -
145 - return 0;
146 - }
147 -
148 - function getInternalModuleRanges() {
149 - /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
150 - if (
151 - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
152 - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
153 - 'function'
154 - ) {
155 - // Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
156 - // Don't do this eagerly like the laneToLabelMap,
157 - // because some modules might not yet have registered their boundaries when the renderer is injected.
158 - const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
159 -
160 - // This check would not be required,
161 - // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
162 - if (isArray(ranges)) {
163 - return ranges;
164 - }
165 - }
166 -
167 - return null;
168 - }
169 -
170 - function getTimelineData(): TimelineData | null {
171 - return currentTimelineData;
172 - }
173 -
174 - function laneToLanesArray(lanes: Lane) {
175 - const lanesArray = [];
176 -
177 - let lane = 1;
178 - for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
179 - if (lane & lanes) {
180 - lanesArray.push(lane);
181 - }
182 - lane *= 2;
183 - }
184 -
185 - return lanesArray;
186 - }
187 -
188 - const laneToLabelMap: LaneToLabelMap | null =
189 - typeof getLaneLabelMap === 'function' ? getLaneLabelMap() : null;
190 -
191 - function markMetadata() {
192 - markAndClear(`--react-version-${reactVersion}`);
193 - markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
194 -
195 - const ranges = getInternalModuleRanges();
196 - if (ranges) {
197 - for (let i = 0; i < ranges.length; i++) {
198 - const range = ranges[i];
199 - if (isArray(range) && range.length === 2) {
200 - const [startStackFrame, stopStackFrame] = ranges[i];
201 -
202 - markAndClear(`--react-internal-module-start-${startStackFrame}`);
203 - markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
204 - }
205 - }
206 - }
207 -
208 - if (laneToLabelMap != null) {
209 - const labels = Array.from(laneToLabelMap.values()).join(',');
210 - markAndClear(`--react-lane-labels-${labels}`);
211 - }
212 - }
213 -
214 - function markAndClear(markName: string) {
215 - // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
216 - (performanceTarget as any as Performance).mark(markName);
217 - (performanceTarget as any as Performance).clearMarks(markName);
218 - }
219 -
220 - function recordReactMeasureStarted(
221 - type: ReactMeasureType,
222 - lanes: Lanes,
223 - ): void {
224 - // Decide what depth thi work should be rendered at, based on what's on the top of the stack.
225 - // It's okay to render over top of "idle" work but everything else should be on its own row.
226 - let depth = 0;
227 - if (currentReactMeasuresStack.length > 0) {
228 - const top =
229 - currentReactMeasuresStack[currentReactMeasuresStack.length - 1];
230 - depth = top.type === 'render-idle' ? top.depth : top.depth + 1;
231 - }
232 -
233 - const lanesArray = laneToLanesArray(lanes);
234 -
235 - const reactMeasure: ReactMeasure = {
236 - type,
237 - batchUID: currentBatchUID,
238 - depth,
239 - lanes: lanesArray,
240 - timestamp: getRelativeTime(),
241 - duration: 0,
242 - };
243 -
244 - currentReactMeasuresStack.push(reactMeasure);
245 -
246 - if (currentTimelineData) {
247 - const {batchUIDToMeasuresMap, laneToReactMeasureMap} =
248 - currentTimelineData;
249 -
250 - let reactMeasures = batchUIDToMeasuresMap.get(currentBatchUID);
251 - if (reactMeasures != null) {
252 - reactMeasures.push(reactMeasure);
253 - } else {
254 - batchUIDToMeasuresMap.set(currentBatchUID, [reactMeasure]);
255 - }
256 -
257 - lanesArray.forEach(lane => {
258 - reactMeasures = laneToReactMeasureMap.get(lane);
259 - if (reactMeasures) {
260 - reactMeasures.push(reactMeasure);
261 - }
262 - });
263 - }
264 - }
265 -
266 - function recordReactMeasureCompleted(type: ReactMeasureType): void {
267 - const currentTime = getRelativeTime();
268 -
269 - if (currentReactMeasuresStack.length === 0) {
270 - console.error(
271 - 'Unexpected type "%s" completed at %sms while currentReactMeasuresStack is empty.',
272 - type,
273 - currentTime,
274 - );
275 - // Ignore work "completion" user timing mark that doesn't complete anything
276 - return;
277 - }
278 -
279 - const top = currentReactMeasuresStack.pop();
280 - // $FlowFixMe[incompatible-type]
281 - // $FlowFixMe[incompatible-use]
282 - if (top.type !== type) {
283 - console.error(
284 - 'Unexpected type "%s" completed at %sms before "%s" completed.',
285 - type,
286 - currentTime,
287 - // $FlowFixMe[incompatible-use]
288 - top.type,
289 - );
290 - }
291 -
292 - // $FlowFixMe[cannot-write] This property should not be writable outside of this function.
293 - // $FlowFixMe[incompatible-use]
294 - top.duration = currentTime - top.timestamp;
295 -
296 - if (currentTimelineData) {
297 - currentTimelineData.duration = getRelativeTime() + TIME_OFFSET;
298 - }
299 - }
300 -
301 - function markCommitStarted(lanes: Lanes): void {
302 - if (!isProfiling) {
303 - return;
304 - }
305 -
306 - recordReactMeasureStarted('commit', lanes);
307 -
308 - // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering.
309 - // This issue applies to the User Timing data also.
310 - nextRenderShouldStartNewBatch = true;
311 -
312 - if (supportsUserTimingV3) {
313 - markAndClear(`--commit-start-${lanes}`);
314 -
315 - // Some metadata only needs to be logged once per session,
316 - // but if profiling information is being recorded via the Performance tab,
317 - // DevTools has no way of knowing when the recording starts.
318 - // Because of that, we log thie type of data periodically (once per commit).
319 - markMetadata();
320 - }
321 - }
322 -
323 - function markCommitStopped(): void {
324 - if (!isProfiling) {
325 - return;
326 - }
327 -
328 - recordReactMeasureCompleted('commit');
329 - recordReactMeasureCompleted('render-idle');
330 - if (supportsUserTimingV3) {
331 - markAndClear('--commit-stop');
332 - }
333 - }
334 -
335 - function markComponentRenderStarted(fiber: Fiber): void {
336 - if (!isProfiling) {
337 - return;
338 - }
339 -
340 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
341 -
342 - // TODO (timeline) Record and cache component stack
343 - currentReactComponentMeasure = {
344 - componentName,
345 - duration: 0,
346 - timestamp: getRelativeTime(),
347 - type: 'render',
348 - warning: null,
349 - };
350 -
351 - if (supportsUserTimingV3) {
352 - markAndClear(`--component-render-start-${componentName}`);
353 - }
354 - }
355 -
356 - function markComponentRenderStopped(): void {
357 - if (!isProfiling) {
358 - return;
359 - }
360 -
361 - if (currentReactComponentMeasure) {
362 - if (currentTimelineData) {
363 - currentTimelineData.componentMeasures.push(
364 - currentReactComponentMeasure,
365 - );
366 - }
367 -
368 - // $FlowFixMe[incompatible-use] found when upgrading Flow
369 - currentReactComponentMeasure.duration =
370 - // $FlowFixMe[incompatible-use] found when upgrading Flow
371 - getRelativeTime() - currentReactComponentMeasure.timestamp;
372 - currentReactComponentMeasure = null;
373 - }
374 -
375 - if (supportsUserTimingV3) {
376 - markAndClear('--component-render-stop');
377 - }
378 - }
379 -
380 - function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
381 - if (!isProfiling) {
382 - return;
383 - }
384 -
385 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
386 -
387 - // TODO (timeline) Record and cache component stack
388 - currentReactComponentMeasure = {
389 - componentName,
390 - duration: 0,
391 - timestamp: getRelativeTime(),
392 - type: 'layout-effect-mount',
393 - warning: null,
394 - };
395 -
396 - if (supportsUserTimingV3) {
397 - markAndClear(`--component-layout-effect-mount-start-${componentName}`);
398 - }
399 - }
400 -
401 - function markComponentLayoutEffectMountStopped(): void {
402 - if (!isProfiling) {
403 - return;
404 - }
405 -
406 - if (currentReactComponentMeasure) {
407 - if (currentTimelineData) {
408 - currentTimelineData.componentMeasures.push(
409 - currentReactComponentMeasure,
410 - );
411 - }
412 -
413 - // $FlowFixMe[incompatible-use] found when upgrading Flow
414 - currentReactComponentMeasure.duration =
415 - // $FlowFixMe[incompatible-use] found when upgrading Flow
416 - getRelativeTime() - currentReactComponentMeasure.timestamp;
417 - currentReactComponentMeasure = null;
418 - }
419 -
420 - if (supportsUserTimingV3) {
421 - markAndClear('--component-layout-effect-mount-stop');
422 - }
423 - }
424 -
425 - function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
426 - if (!isProfiling) {
427 - return;
428 - }
429 -
430 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
431 -
432 - // TODO (timeline) Record and cache component stack
433 - currentReactComponentMeasure = {
434 - componentName,
435 - duration: 0,
436 - timestamp: getRelativeTime(),
437 - type: 'layout-effect-unmount',
438 - warning: null,
439 - };
440 -
441 - if (supportsUserTimingV3) {
442 - markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
443 - }
444 - }
445 -
446 - function markComponentLayoutEffectUnmountStopped(): void {
447 - if (!isProfiling) {
448 - return;
449 - }
450 -
451 - if (currentReactComponentMeasure) {
452 - if (currentTimelineData) {
453 - currentTimelineData.componentMeasures.push(
454 - currentReactComponentMeasure,
455 - );
456 - }
457 -
458 - // $FlowFixMe[incompatible-use] found when upgrading Flow
459 - currentReactComponentMeasure.duration =
460 - // $FlowFixMe[incompatible-use] found when upgrading Flow
461 - getRelativeTime() - currentReactComponentMeasure.timestamp;
462 - currentReactComponentMeasure = null;
463 - }
464 -
465 - if (supportsUserTimingV3) {
466 - markAndClear('--component-layout-effect-unmount-stop');
467 - }
468 - }
469 -
470 - function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
471 - if (!isProfiling) {
472 - return;
473 - }
474 -
475 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
476 -
477 - // TODO (timeline) Record and cache component stack
478 - currentReactComponentMeasure = {
479 - componentName,
480 - duration: 0,
481 - timestamp: getRelativeTime(),
482 - type: 'passive-effect-mount',
483 - warning: null,
484 - };
485 -
486 - if (supportsUserTimingV3) {
487 - markAndClear(`--component-passive-effect-mount-start-${componentName}`);
488 - }
489 - }
490 -
491 - function markComponentPassiveEffectMountStopped(): void {
492 - if (!isProfiling) {
493 - return;
494 - }
495 -
496 - if (currentReactComponentMeasure) {
497 - if (currentTimelineData) {
498 - currentTimelineData.componentMeasures.push(
499 - currentReactComponentMeasure,
500 - );
501 - }
502 -
503 - // $FlowFixMe[incompatible-use] found when upgrading Flow
504 - currentReactComponentMeasure.duration =
505 - // $FlowFixMe[incompatible-use] found when upgrading Flow
506 - getRelativeTime() - currentReactComponentMeasure.timestamp;
507 - currentReactComponentMeasure = null;
508 - }
509 -
510 - if (supportsUserTimingV3) {
511 - markAndClear('--component-passive-effect-mount-stop');
512 - }
513 - }
514 -
515 - function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
516 - if (!isProfiling) {
517 - return;
518 - }
519 -
520 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
521 -
522 - // TODO (timeline) Record and cache component stack
523 - currentReactComponentMeasure = {
524 - componentName,
525 - duration: 0,
526 - timestamp: getRelativeTime(),
527 - type: 'passive-effect-unmount',
528 - warning: null,
529 - };
530 -
531 - if (supportsUserTimingV3) {
532 - markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
533 - }
534 - }
535 -
536 - function markComponentPassiveEffectUnmountStopped(): void {
537 - if (!isProfiling) {
538 - return;
539 - }
540 -
541 - if (currentReactComponentMeasure) {
542 - if (currentTimelineData) {
543 - currentTimelineData.componentMeasures.push(
544 - currentReactComponentMeasure,
545 - );
546 - }
547 -
548 - // $FlowFixMe[incompatible-use] found when upgrading Flow
549 - currentReactComponentMeasure.duration =
550 - // $FlowFixMe[incompatible-use] found when upgrading Flow
551 - getRelativeTime() - currentReactComponentMeasure.timestamp;
552 - currentReactComponentMeasure = null;
553 - }
554 -
555 - if (supportsUserTimingV3) {
556 - markAndClear('--component-passive-effect-unmount-stop');
557 - }
558 - }
559 -
560 - function markComponentErrored(
561 - fiber: Fiber,
562 - thrownValue: mixed,
563 - lanes: Lanes,
564 - ): void {
565 - if (!isProfiling) {
566 - return;
567 - }
568 -
569 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
570 - const phase = fiber.alternate === null ? 'mount' : 'update';
571 -
572 - let message = '';
573 - if (
574 - thrownValue !== null &&
575 - typeof thrownValue === 'object' &&
576 - typeof thrownValue.message === 'string'
577 - ) {
578 - message = thrownValue.message;
579 - } else if (typeof thrownValue === 'string') {
580 - message = thrownValue;
581 - }
582 -
583 - // TODO (timeline) Record and cache component stack
584 - if (currentTimelineData) {
585 - currentTimelineData.thrownErrors.push({
586 - componentName,
587 - message,
588 - phase,
589 - timestamp: getRelativeTime(),
590 - type: 'thrown-error',
591 - });
592 - }
593 -
594 - if (supportsUserTimingV3) {
595 - markAndClear(`--error-${componentName}-${phase}-${message}`);
596 - }
597 - }
598 -
599 - const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
600 -
601 - // $FlowFixMe[incompatible-type]: Flow cannot handle polymorphic WeakMaps
602 - const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
603 - let wakeableID: number = 0;
604 - function getWakeableID(wakeable: Wakeable): number {
605 - if (!wakeableIDs.has(wakeable)) {
606 - wakeableIDs.set(wakeable, wakeableID++);
607 - }
608 - return wakeableIDs.get(wakeable) as any as number;
609 - }
610 -
611 - function markComponentSuspended(
612 - fiber: Fiber,
613 - wakeable: Wakeable,
614 - lanes: Lanes,
615 - ): void {
616 - if (!isProfiling) {
617 - return;
618 - }
619 -
620 - const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
621 - const id = getWakeableID(wakeable);
622 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
623 - const phase = fiber.alternate === null ? 'mount' : 'update';
624 -
625 - // Following the non-standard fn.displayName convention,
626 - // frameworks like Relay may also annotate Promises with a displayName,
627 - // describing what operation/data the thrown Promise is related to.
628 - // When this is available we should pass it along to the Timeline.
629 - const displayName = (wakeable as any).displayName || '';
630 -
631 - let suspenseEvent: SuspenseEvent | null = null;
632 - // TODO (timeline) Record and cache component stack
633 - suspenseEvent = {
634 - componentName,
635 - depth: 0,
636 - duration: 0,
637 - id: `${id}`,
638 - phase,
639 - promiseName: displayName,
640 - resolution: 'unresolved',
641 - timestamp: getRelativeTime(),
642 - type: 'suspense',
643 - warning: null,
644 - };
645 -
646 - if (currentTimelineData) {
647 - currentTimelineData.suspenseEvents.push(suspenseEvent);
648 - }
649 -
650 - if (supportsUserTimingV3) {
651 - markAndClear(
652 - `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
653 - );
654 -
655 - wakeable.then(
656 - () => {
657 - if (suspenseEvent) {
658 - suspenseEvent.duration =
659 - getRelativeTime() - suspenseEvent.timestamp;
660 - suspenseEvent.resolution = 'resolved';
661 - }
662 -
663 - if (supportsUserTimingV3) {
664 - markAndClear(`--suspense-resolved-${id}-${componentName}`);
665 - }
666 - },
667 - () => {
668 - if (suspenseEvent) {
669 - suspenseEvent.duration =
670 - getRelativeTime() - suspenseEvent.timestamp;
671 - suspenseEvent.resolution = 'rejected';
672 - }
673 -
674 - if (supportsUserTimingV3) {
675 - markAndClear(`--suspense-rejected-${id}-${componentName}`);
676 - }
677 - },
678 - );
679 - }
680 - }
681 -
682 - function markLayoutEffectsStarted(lanes: Lanes): void {
683 - if (!isProfiling) {
684 - return;
685 - }
686 -
687 - recordReactMeasureStarted('layout-effects', lanes);
688 - if (supportsUserTimingV3) {
689 - markAndClear(`--layout-effects-start-${lanes}`);
690 - }
691 - }
692 -
693 - function markLayoutEffectsStopped(): void {
694 - if (!isProfiling) {
695 - return;
696 - }
697 -
698 - recordReactMeasureCompleted('layout-effects');
699 - if (supportsUserTimingV3) {
700 - markAndClear('--layout-effects-stop');
701 - }
702 - }
703 -
704 - function markPassiveEffectsStarted(lanes: Lanes): void {
705 - if (!isProfiling) {
706 - return;
707 - }
708 -
709 - recordReactMeasureStarted('passive-effects', lanes);
710 - if (supportsUserTimingV3) {
711 - markAndClear(`--passive-effects-start-${lanes}`);
712 - }
713 - }
714 -
715 - function markPassiveEffectsStopped(): void {
716 - if (!isProfiling) {
717 - return;
718 - }
719 -
720 - recordReactMeasureCompleted('passive-effects');
721 - if (supportsUserTimingV3) {
722 - markAndClear('--passive-effects-stop');
723 - }
724 - }
725 -
726 - function markRenderStarted(lanes: Lanes): void {
727 - if (!isProfiling) {
728 - return;
729 - }
730 -
731 - if (nextRenderShouldStartNewBatch) {
732 - nextRenderShouldStartNewBatch = false;
733 - currentBatchUID++;
734 - }
735 -
736 - // If this is a new batch of work, wrap an "idle" measure around it.
737 - // Log it before the "render" measure to preserve the stack ordering.
738 - if (
739 - currentReactMeasuresStack.length === 0 ||
740 - currentReactMeasuresStack[currentReactMeasuresStack.length - 1].type !==
741 - 'render-idle'
742 - ) {
743 - recordReactMeasureStarted('render-idle', lanes);
744 - }
745 -
746 - recordReactMeasureStarted('render', lanes);
747 - if (supportsUserTimingV3) {
748 - markAndClear(`--render-start-${lanes}`);
749 - }
750 - }
751 -
752 - function markRenderYielded(): void {
753 - if (!isProfiling) {
754 - return;
755 - }
756 -
757 - recordReactMeasureCompleted('render');
758 - if (supportsUserTimingV3) {
759 - markAndClear('--render-yield');
760 - }
761 - }
762 -
763 - function markRenderStopped(): void {
764 - if (!isProfiling) {
765 - return;
766 - }
767 -
768 - recordReactMeasureCompleted('render');
769 - if (supportsUserTimingV3) {
770 - markAndClear('--render-stop');
771 - }
772 - }
773 -
774 - function markRenderScheduled(lane: Lane): void {
775 - if (!isProfiling) {
776 - return;
777 - }
778 -
779 - if (currentTimelineData) {
780 - currentTimelineData.schedulingEvents.push({
781 - lanes: laneToLanesArray(lane),
782 - timestamp: getRelativeTime(),
783 - type: 'schedule-render',
784 - warning: null,
785 - });
786 - }
787 -
788 - if (supportsUserTimingV3) {
789 - markAndClear(`--schedule-render-${lane}`);
790 - }
791 - }
792 -
793 - function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
794 - if (!isProfiling) {
795 - return;
796 - }
797 -
798 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
799 -
800 - // TODO (timeline) Record and cache component stack
801 - if (currentTimelineData) {
802 - currentTimelineData.schedulingEvents.push({
803 - componentName,
804 - lanes: laneToLanesArray(lane),
805 - timestamp: getRelativeTime(),
806 - type: 'schedule-force-update',
807 - warning: null,
808 - });
809 - }
810 -
811 - if (supportsUserTimingV3) {
812 - markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
813 - }
814 - }
815 -
816 - function getParentFibers(fiber: Fiber): Array<Fiber> {
817 - const parents = [];
818 - let parent: null | Fiber = fiber;
819 - while (parent !== null) {
820 - parents.push(parent);
821 - parent = parent.return;
822 - }
823 - return parents;
824 - }
825 -
826 - function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
827 - if (!isProfiling) {
828 - return;
829 - }
830 -
831 - const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
832 -
833 - // TODO (timeline) Record and cache component stack
834 - if (currentTimelineData) {
835 - const event: ReactScheduleStateUpdateEvent = {
836 - componentName,
837 - // Store the parent fibers so we can post process
838 - // them after we finish profiling
839 - lanes: laneToLanesArray(lane),
840 - timestamp: getRelativeTime(),
841 - type: 'schedule-state-update',
842 - warning: null,
843 - };
844 - currentFiberStacks.set(event, getParentFibers(fiber));
845 - // $FlowFixMe[incompatible-use] found when upgrading Flow
846 - currentTimelineData.schedulingEvents.push(event);
847 - }
848 -
849 - if (supportsUserTimingV3) {
850 - markAndClear(`--schedule-state-update-${lane}-${componentName}`);
851 - }
852 - }
853 -
854 - function toggleProfilingStatus(
855 - value: boolean,
856 - recordTimeline: boolean = false,
857 - ) {
858 - if (isProfiling !== value) {
859 - isProfiling = value;
860 -
861 - if (isProfiling) {
862 - const internalModuleSourceToRanges: InternalModuleSourceToRanges =
863 - new Map();
864 -
865 - if (supportsUserTimingV3) {
866 - const ranges = getInternalModuleRanges();
867 - if (ranges) {
868 - for (let i = 0; i < ranges.length; i++) {
869 - const range = ranges[i];
870 - if (isArray(range) && range.length === 2) {
871 - const [startStackFrame, stopStackFrame] = ranges[i];
872 -
873 - markAndClear(
874 - `--react-internal-module-start-${startStackFrame}`,
875 - );
876 - markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
877 - }
878 - }
879 - }
880 - }
881 -
882 - const laneToReactMeasureMap = new Map<ReactLane, ReactMeasure[]>();
883 - let lane = 1;
884 - for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
885 - laneToReactMeasureMap.set(lane, []);
886 - lane *= 2;
887 - }
888 -
889 - currentBatchUID = 0;
890 - currentReactComponentMeasure = null;
891 - currentReactMeasuresStack = [];
892 - currentFiberStacks = new Map();
893 - if (recordTimeline) {
894 - currentTimelineData = {
895 - // Session wide metadata; only collected once.
896 - internalModuleSourceToRanges,
897 - laneToLabelMap: laneToLabelMap || new Map(),
898 - reactVersion,
899 -
900 - // Data logged by React during profiling session.
901 - componentMeasures: [],
902 - schedulingEvents: [],
903 - suspenseEvents: [],
904 - thrownErrors: [],
905 -
906 - // Data inferred based on what React logs.
907 - batchUIDToMeasuresMap: new Map(),
908 - duration: 0,
909 - laneToReactMeasureMap,
910 - startTime: 0,
911 -
912 - // Data only available in Chrome profiles.
913 - flamechart: [],
914 - nativeEvents: [],
915 - networkMeasures: [],
916 - otherUserTimingMarks: [],
917 - snapshots: [],
918 - snapshotHeight: 0,
919 - };
920 - }
921 - nextRenderShouldStartNewBatch = true;
922 - } else {
923 - // This is __EXPENSIVE__.
924 - // We could end up with hundreds of state updated, and for each one of them
925 - // would try to create a component stack with possibly hundreds of Fibers.
926 - // Creating a cache of component stacks won't help, generating a single stack is already expensive enough.
927 - // We should find a way to lazily generate component stacks on demand, when user inspects a specific event.
928 - // If we succeed with moving React DevTools Timeline Profiler to Performance panel, then Timeline Profiler would probably be removed.
929 - // Now that owner stacks are adopted, revisit this again and cache component stacks per Fiber,
930 - // but only return them when needed, sending hundreds of component stacks is beyond the Bridge's bandwidth.
931 -
932 - // Postprocess Profile data
933 - if (currentTimelineData !== null) {
934 - currentTimelineData.schedulingEvents.forEach(event => {
935 - if (event.type === 'schedule-state-update') {
936 - // TODO(luna): We can optimize this by creating a map of
937 - // fiber to component stack instead of generating the stack
938 - // for every fiber every time
939 - const fiberStack = currentFiberStacks.get(event);
940 - if (fiberStack && currentDispatcherRef != null) {
941 - event.componentStack = fiberStack.reduce((trace, fiber) => {
942 - return (
943 - trace +
944 - describeFiber(workTagMap, fiber, currentDispatcherRef)
945 - );
946 - }, '');
947 - }
948 - }
949 - });
950 - }
951 -
952 - // Clear the current fiber stacks so we don't hold onto the fibers
953 - // in memory after profiling finishes
954 - currentFiberStacks.clear();
955 - }
956 - }
957 - }
958 -
959 - return {
960 - getTimelineData,
961 - profilingHooks: {
962 - markCommitStarted,
963 - markCommitStopped,
964 - markComponentRenderStarted,
965 - markComponentRenderStopped,
966 - markComponentPassiveEffectMountStarted,
967 - markComponentPassiveEffectMountStopped,
968 - markComponentPassiveEffectUnmountStarted,
969 - markComponentPassiveEffectUnmountStopped,
970 - markComponentLayoutEffectMountStarted,
971 - markComponentLayoutEffectMountStopped,
972 - markComponentLayoutEffectUnmountStarted,
973 - markComponentLayoutEffectUnmountStopped,
974 - markComponentErrored,
975 - markComponentSuspended,
976 - markLayoutEffectsStarted,
977 - markLayoutEffectsStopped,
978 - markPassiveEffectsStarted,
979 - markPassiveEffectsStopped,
980 - markRenderStarted,
981 - markRenderYielded,
982 - markRenderStopped,
983 - markRenderScheduled,
984 - markForceUpdateScheduled,
985 - markStateUpdateScheduled,
986 - },
987 - toggleProfilingStatus,
988 - };
989 -}
packages/react-devtools-shared/src/backend/types.js
+2 -64
@@ -14,11 +14,7 @@
14 * Be mindful of backwards compatibility when making changes.
15 */
16
17 -import type {
18 - ReactContext,
19 - Wakeable,
20 - ReactComponentInfo,
21 -} from 'shared/ReactTypes';
17 +import type {ReactContext, ReactComponentInfo} from 'shared/ReactTypes';
18 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
19 import type {
20 ComponentFilter,
@@ -30,7 +26,6 @@ import type {
26 SetupNativeStyleEditor,
27 } from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
28 import type {InitBackend} from 'react-devtools-shared/src/backend';
33 -import type {TimelineDataExport} from 'react-devtools-timeline/src/types';
29 import type {BackendBridge} from 'react-devtools-shared/src/bridge';
30 import type {ReactFunctionLocation, ReactStackTrace} from 'shared/ReactTypes';
31 import type Agent from './agent';
@@ -186,9 +181,6 @@ export type ReactRenderer = {
181 setErrorHandler?: ?(shouldError: (fiber: Object) => ?boolean) => void,
182 // Intentionally opaque type to avoid coupling DevTools to different Fast Refresh versions.
183 scheduleRefresh?: Function,
189 - // 18.0+
190 - injectProfilingHooks?: (profilingHooks: DevToolsProfilingHooks) => void,
191 - getLaneLabelMap?: () => Map<Lane, string> | null,
184 ...
185 };
186
@@ -231,7 +223,6 @@ export type ProfilingDataForRootBackend = {
223 export type ProfilingDataBackend = {
224 dataForRoots: Array<ProfilingDataForRootBackend>,
225 rendererID: number,
234 - timelineData: TimelineDataExport | null,
226 };
227
228 export type PathFrame = {
@@ -474,10 +465,7 @@ export type RendererInterface = {
465 renderer: ReactRenderer | null,
466 setTraceUpdatesEnabled: (enabled: boolean) => void,
467 setTrackedPath: (path: Array<PathFrame> | null) => void,
477 - startProfiling: (
478 - recordChangeDescriptions: boolean,
479 - recordTimeline: boolean,
480 - ) => void,
468 + startProfiling: (recordChangeDescriptions: boolean) => void,
469 stopProfiling: () => void,
470 storeAsGlobal: (
471 id: number,
@@ -488,55 +476,11 @@ export type RendererInterface = {
476 updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
477 getEnvironmentNames: () => Array<string>,
478
491 - // Timeline profiler interface
492 -
479 ...
480 };
481
482 export type Handler = (data: any) => void;
483
498 -// Renderers use these APIs to report profiling data to DevTools at runtime.
499 -// They get passed from the DevTools backend to the reconciler during injection.
500 -export type DevToolsProfilingHooks = {
501 - // Scheduling methods:
502 - markRenderScheduled: (lane: Lane) => void,
503 - markStateUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
504 - markForceUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
505 -
506 - // Work loop level methods:
507 - markRenderStarted: (lanes: Lanes) => void,
508 - markRenderYielded: () => void,
509 - markRenderStopped: () => void,
510 - markCommitStarted: (lanes: Lanes) => void,
511 - markCommitStopped: () => void,
512 - markLayoutEffectsStarted: (lanes: Lanes) => void,
513 - markLayoutEffectsStopped: () => void,
514 - markPassiveEffectsStarted: (lanes: Lanes) => void,
515 - markPassiveEffectsStopped: () => void,
516 -
517 - // Fiber level methods:
518 - markComponentRenderStarted: (fiber: Fiber) => void,
519 - markComponentRenderStopped: () => void,
520 - markComponentErrored: (
521 - fiber: Fiber,
522 - thrownValue: mixed,
523 - lanes: Lanes,
524 - ) => void,
525 - markComponentSuspended: (
526 - fiber: Fiber,
527 - wakeable: Wakeable,
528 - lanes: Lanes,
529 - ) => void,
530 - markComponentLayoutEffectMountStarted: (fiber: Fiber) => void,
531 - markComponentLayoutEffectMountStopped: () => void,
532 - markComponentLayoutEffectUnmountStarted: (fiber: Fiber) => void,
533 - markComponentLayoutEffectUnmountStopped: () => void,
534 - markComponentPassiveEffectMountStarted: (fiber: Fiber) => void,
535 - markComponentPassiveEffectMountStopped: () => void,
536 - markComponentPassiveEffectUnmountStarted: (fiber: Fiber) => void,
537 - markComponentPassiveEffectUnmountStopped: () => void,
538 -};
539 -
484 export type DevToolsBackend = {
485 Agent: Class<Agent>,
486 Bridge: Class<BackendBridge>,
@@ -546,7 +490,6 @@ export type DevToolsBackend = {
490
491 export type ProfilingSettings = {
492 recordChangeDescriptions: boolean,
549 - recordTimeline: boolean,
493 };
494
495 export type DevToolsHook = {
@@ -580,11 +523,6 @@ export type DevToolsHook = {
523 didError?: boolean,
524 ) => void,
525
583 - // Timeline internal module filtering
584 - getInternalModuleRanges: () => Array<[string, string]>,
585 - registerInternalModuleStart: (moduleStartError: Error) => void,
586 - registerInternalModuleStop: (moduleStopError: Error) => void,
587 -
526 // Testing
527 dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
528
packages/react-devtools-shared/src/constants.js
+2 -3
@@ -32,7 +32,8 @@ export const SUSPENSE_TREE_OPERATION_SUSPENDERS = 12;
32 export const TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE = 13;
33
34 export const PROFILING_FLAG_BASIC_SUPPORT /*. */ = 0b001;
35 -export const PROFILING_FLAG_TIMELINE_SUPPORT /* */ = 0b010;
35 +// Bit 0b010 is retired: it flagged support for the removed Timeline profiler.
36 +// Older backends still set it, so it must never be reused for a new flag.
37 export const PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT /* */ = 0b100;
38
39 export const UNKNOWN_SUSPENDERS_NONE: UnknownSuspendersReason = 0; // If we had at least one debugInfo, then that might have been the reason.
@@ -57,8 +58,6 @@ export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
58 'React::DevTools::parseHookNames';
59 export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
60 'React::DevTools::recordChangeDescriptions';
60 -export const SESSION_STORAGE_RECORD_TIMELINE_KEY =
61 - 'React::DevTools::recordTimeline';
61 export const SESSION_STORAGE_RELOAD_AND_PROFILE_KEY =
62 'React::DevTools::reloadAndProfile';
63 export const LOCAL_STORAGE_BROWSER_THEME = 'React::DevTools::theme';
packages/react-devtools-shared/src/devtools/ProfilerStore.js
-1
@@ -195,7 +195,6 @@ export default class ProfilerStore extends EventEmitter<{
195
196 this._bridge.send('startProfiling', {
197 recordChangeDescriptions: this._store.recordChangeDescriptions,
198 - recordTimeline: this._store.supportsTimeline,
198 });
199
200 this._isProfilingBasedOnUserInput = true;
packages/react-devtools-shared/src/devtools/constants.js
-82
@@ -95,52 +95,11 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
95 '--color-resize-bar-active': '#dcdcdc',
96 '--color-resize-bar-border': '#d1d1d1',
97 '--color-resize-bar-dot': '#333333',
98 - '--color-timeline-internal-module': '#d1d1d1',
99 - '--color-timeline-internal-module-hover': '#c9c9c9',
100 - '--color-timeline-internal-module-text': '#444',
101 - '--color-timeline-native-event': '#ccc',
102 - '--color-timeline-native-event-hover': '#aaa',
103 - '--color-timeline-network-primary': '#fcf3dc',
104 - '--color-timeline-network-primary-hover': '#f0e7d1',
105 - '--color-timeline-network-secondary': '#efc457',
106 - '--color-timeline-network-secondary-hover': '#e3ba52',
107 - '--color-timeline-priority-background': '#f6f6f6',
108 - '--color-timeline-priority-border': '#eeeeee',
109 - '--color-timeline-user-timing': '#c9cacd',
110 - '--color-timeline-user-timing-hover': '#93959a',
111 - '--color-timeline-react-idle': '#d3e5f6',
112 - '--color-timeline-react-idle-hover': '#c3d9ef',
113 - '--color-timeline-react-render': '#9fc3f3',
114 - '--color-timeline-react-render-hover': '#83afe9',
115 - '--color-timeline-react-render-text': '#11365e',
116 - '--color-timeline-react-commit': '#c88ff0',
117 - '--color-timeline-react-commit-hover': '#b281d6',
118 - '--color-timeline-react-commit-text': '#3e2c4a',
119 - '--color-timeline-react-layout-effects': '#b281d6',
120 - '--color-timeline-react-layout-effects-hover': '#9d71bd',
121 - '--color-timeline-react-layout-effects-text': '#3e2c4a',
122 - '--color-timeline-react-passive-effects': '#b281d6',
123 - '--color-timeline-react-passive-effects-hover': '#9d71bd',
124 - '--color-timeline-react-passive-effects-text': '#3e2c4a',
125 - '--color-timeline-react-schedule': '#9fc3f3',
126 - '--color-timeline-react-schedule-hover': '#2683E2',
127 - '--color-timeline-react-suspense-rejected': '#f1cc14',
128 - '--color-timeline-react-suspense-rejected-hover': '#ffdf37',
129 - '--color-timeline-react-suspense-resolved': '#a6e59f',
130 - '--color-timeline-react-suspense-resolved-hover': '#89d281',
131 - '--color-timeline-react-suspense-unresolved': '#c9cacd',
132 - '--color-timeline-react-suspense-unresolved-hover': '#93959a',
133 - '--color-timeline-thrown-error': '#ee1638',
134 - '--color-timeline-thrown-error-hover': '#da1030',
135 - '--color-timeline-text-color': '#000000',
136 - '--color-timeline-text-dim-color': '#ccc',
137 - '--color-timeline-react-work-border': '#eeeeee',
98 '--color-timebar-background': '#f6f6f6',
99 '--color-search-match': 'yellow',
100 '--color-search-match-current': '#f7923b',
101 '--color-selected-tree-highlight-active': 'rgba(0, 136, 250, 0.1)',
102 '--color-selected-tree-highlight-inactive': 'rgba(0, 0, 0, 0.05)',
143 - '--color-scroll-caret': 'rgba(150, 150, 150, 0.5)',
103 '--color-tab-selected-border': '#0088fa',
104 '--color-text': '#000000',
105 '--color-text-invalid': '#ff0000',
@@ -255,52 +214,11 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
214 '--color-resize-bar-active': '#31363f',
215 '--color-resize-bar-border': '#3d424a',
216 '--color-resize-bar-dot': '#cfd1d5',
258 - '--color-timeline-internal-module': '#303542',
259 - '--color-timeline-internal-module-hover': '#363b4a',
260 - '--color-timeline-internal-module-text': '#7f8899',
261 - '--color-timeline-native-event': '#b2b2b2',
262 - '--color-timeline-native-event-hover': '#949494',
263 - '--color-timeline-network-primary': '#fcf3dc',
264 - '--color-timeline-network-primary-hover': '#e3dbc5',
265 - '--color-timeline-network-secondary': '#efc457',
266 - '--color-timeline-network-secondary-hover': '#d6af4d',
267 - '--color-timeline-priority-background': '#1d2129',
268 - '--color-timeline-priority-border': '#282c34',
269 - '--color-timeline-user-timing': '#c9cacd',
270 - '--color-timeline-user-timing-hover': '#93959a',
271 - '--color-timeline-react-idle': '#3d485b',
272 - '--color-timeline-react-idle-hover': '#465269',
273 - '--color-timeline-react-render': '#2683E2',
274 - '--color-timeline-react-render-hover': '#1a76d4',
275 - '--color-timeline-react-render-text': '#11365e',
276 - '--color-timeline-react-commit': '#731fad',
277 - '--color-timeline-react-commit-hover': '#611b94',
278 - '--color-timeline-react-commit-text': '#e5c1ff',
279 - '--color-timeline-react-layout-effects': '#611b94',
280 - '--color-timeline-react-layout-effects-hover': '#51167a',
281 - '--color-timeline-react-layout-effects-text': '#e5c1ff',
282 - '--color-timeline-react-passive-effects': '#611b94',
283 - '--color-timeline-react-passive-effects-hover': '#51167a',
284 - '--color-timeline-react-passive-effects-text': '#e5c1ff',
285 - '--color-timeline-react-schedule': '#2683E2',
286 - '--color-timeline-react-schedule-hover': '#1a76d4',
287 - '--color-timeline-react-suspense-rejected': '#f1cc14',
288 - '--color-timeline-react-suspense-rejected-hover': '#e4c00f',
289 - '--color-timeline-react-suspense-resolved': '#a6e59f',
290 - '--color-timeline-react-suspense-resolved-hover': '#89d281',
291 - '--color-timeline-react-suspense-unresolved': '#c9cacd',
292 - '--color-timeline-react-suspense-unresolved-hover': '#93959a',
293 - '--color-timeline-thrown-error': '#fb3655',
294 - '--color-timeline-thrown-error-hover': '#f82042',
295 - '--color-timeline-text-color': '#282c34',
296 - '--color-timeline-text-dim-color': '#555b66',
297 - '--color-timeline-react-work-border': '#3d424a',
217 '--color-timebar-background': '#1d2129',
218 '--color-search-match': 'yellow',
219 '--color-search-match-current': '#f7923b',
220 '--color-selected-tree-highlight-active': 'rgba(23, 143, 185, 0.15)',
221 '--color-selected-tree-highlight-inactive': 'rgba(255, 255, 255, 0.05)',
303 - '--color-scroll-caret': '#4f5766',
222 '--color-shadow': 'rgba(0, 0, 0, 0.5)',
223 '--color-tab-selected-border': '#178fb9',
224 '--color-text': '#ffffff',
packages/react-devtools-shared/src/devtools/store.js
+1 -38
@@ -12,7 +12,6 @@ import EventEmitter from '../events';
12 import {inspect} from 'util';
13 import {
14 PROFILING_FLAG_BASIC_SUPPORT,
15 - PROFILING_FLAG_TIMELINE_SUPPORT,
15 PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT,
16 TREE_OPERATION_ADD,
17 TREE_OPERATION_REMOVE,
@@ -133,14 +132,12 @@ export type Config = {
132 supportsInspectMatchingDOMElement?: boolean,
133 supportsClickToInspect?: boolean,
134 supportsReloadAndProfile?: boolean,
136 - supportsTimeline?: boolean,
135 supportsTraceUpdates?: boolean,
136 };
137
138 const ADVANCED_PROFILING_NONE = 0;
141 -const ADVANCED_PROFILING_TIMELINE = 1;
139 const ADVANCED_PROFILING_PERFORMANCE_TRACKS = 2;
143 -type AdvancedProfiling = 0 | 1 | 2;
140 +type AdvancedProfiling = 0 | 2;
141
142 export type Capabilities = {
143 supportsBasicProfiling: boolean,
@@ -201,7 +198,6 @@ export default class Store extends EventEmitter<{
198 recordChangeDescriptions: [],
199 roots: [],
200 rootSupportsBasicProfiling: [],
204 - rootSupportsTimelineProfiling: [],
201 rootSupportsPerformanceTracks: [],
202 suspenseTreeMutated: [[Map<SuspenseNode['id'], SuspenseNode['id']>]],
203 supportsNativeStyleEditor: [],
@@ -280,7 +276,6 @@ export default class Store extends EventEmitter<{
276 // These options may be initially set by a configuration option when constructing the Store.
277 _supportsInspectMatchingDOMElement: boolean = false;
278 _supportsClickToInspect: boolean = false;
283 - _supportsTimeline: boolean = false;
279 _supportsTraceUpdates: boolean = false;
280
281 _isReloadAndProfileFrontendSupported: boolean = false;
@@ -288,7 +283,6 @@ export default class Store extends EventEmitter<{
283
284 // These options default to false but may be updated as roots are added and removed.
285 _rootSupportsBasicProfiling: boolean = false;
291 - _rootSupportsTimelineProfiling: boolean = false;
286 _rootSupportsPerformanceTracks: boolean = false;
287
288 _bridgeProtocol: BridgeProtocol | null = null;
@@ -336,7 +330,6 @@ export default class Store extends EventEmitter<{
330 supportsInspectMatchingDOMElement,
331 supportsClickToInspect,
332 supportsReloadAndProfile,
339 - supportsTimeline,
333 supportsTraceUpdates,
334 checkBridgeProtocolCompatibility,
335 } = config;
@@ -349,9 +342,6 @@ export default class Store extends EventEmitter<{
342 if (supportsReloadAndProfile) {
343 this._isReloadAndProfileFrontendSupported = true;
344 }
352 - if (supportsTimeline) {
353 - this._supportsTimeline = true;
354 - }
345 if (supportsTraceUpdates) {
346 this._supportsTraceUpdates = true;
347 }
@@ -575,11 +565,6 @@ export default class Store extends EventEmitter<{
565 return this._rootSupportsBasicProfiling;
566 }
567
578 - // At least one of the currently mounted roots support the Timeline profiler.
579 - get rootSupportsTimelineProfiling(): boolean {
580 - return this._rootSupportsTimelineProfiling;
581 - }
582 -
568 // At least one of the currently mounted roots support performance tracks.
569 get rootSupportsPerformanceTracks(): boolean {
570 return this._rootSupportsPerformanceTracks;
@@ -604,12 +589,6 @@ export default class Store extends EventEmitter<{
589 );
590 }
591
607 - // This build of DevTools supports the Timeline profiler.
608 - // This is a static flag, controlled by the Store config.
609 - get supportsTimeline(): boolean {
610 - return this._supportsTimeline;
611 - }
612 -
592 get supportsTraceUpdates(): boolean {
593 return this._supportsTraceUpdates;
594 }
@@ -1537,16 +1516,12 @@ export default class Store extends EventEmitter<{
1516 const profilerFlags = operations[i++];
1517 const supportsBasicProfiling =
1518 (profilerFlags & PROFILING_FLAG_BASIC_SUPPORT) !== 0;
1540 - const supportsTimeline =
1541 - (profilerFlags & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0;
1519 const supportsPerformanceTracks =
1520 (profilerFlags & PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT) !== 0;
1521 let supportsAdvancedProfiling: AdvancedProfiling =
1522 ADVANCED_PROFILING_NONE;
1523 if (supportsPerformanceTracks) {
1524 supportsAdvancedProfiling = ADVANCED_PROFILING_PERFORMANCE_TRACKS;
1548 - } else if (supportsTimeline) {
1549 - supportsAdvancedProfiling = ADVANCED_PROFILING_TIMELINE;
1525 }
1526
1527 let supportsStrictMode = false;
@@ -2301,14 +2276,11 @@ export default class Store extends EventEmitter<{
2276
2277 if (haveRootsChanged) {
2278 const prevRootSupportsProfiling = this._rootSupportsBasicProfiling;
2304 - const prevRootSupportsTimelineProfiling =
2305 - this._rootSupportsTimelineProfiling;
2279 const prevRootSupportsPerformanceTracks =
2280 this._rootSupportsPerformanceTracks;
2281
2282 this._hasOwnerMetadata = false;
2283 this._rootSupportsBasicProfiling = false;
2311 - this._rootSupportsTimelineProfiling = false;
2284 this._rootSupportsPerformanceTracks = false;
2285 this._rootIDToCapabilities.forEach(
2286 ({
@@ -2322,9 +2294,6 @@ export default class Store extends EventEmitter<{
2294 if (hasOwnerMetadata) {
2295 this._hasOwnerMetadata = true;
2296 }
2325 - if (supportsAdvancedProfiling === ADVANCED_PROFILING_TIMELINE) {
2326 - this._rootSupportsTimelineProfiling = true;
2327 - }
2297 if (
2298 supportsAdvancedProfiling === ADVANCED_PROFILING_PERFORMANCE_TRACKS
2299 ) {
@@ -2339,12 +2308,6 @@ export default class Store extends EventEmitter<{
2308 this.emit('rootSupportsBasicProfiling');
2309 }
2310
2342 - if (
2343 - this._rootSupportsTimelineProfiling !==
2344 - prevRootSupportsTimelineProfiling
2345 - ) {
2346 - this.emit('rootSupportsTimelineProfiling');
2347 - }
2311 if (
2312 this._rootSupportsPerformanceTracks !==
2313 prevRootSupportsPerformanceTracks
packages/react-devtools-shared/src/devtools/views/DevTools.js
+59 -67
@@ -35,7 +35,6 @@ import {InspectedElementContextController} from './Components/InspectedElementCo
35 import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
36 import {ProfilerContextController} from './Profiler/ProfilerContext';
37 import {SuspenseTreeContextController} from './SuspenseTab/SuspenseTreeContext';
38 -import {TimelineContextController} from 'react-devtools-timeline/src/TimelineContext';
38 import {ModalDialogContextController} from './ModalDialog';
39 import ReactLogo from './ReactLogo';
40 import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog';
@@ -301,77 +300,70 @@ export default function DevTools({
300 value={fetchFileWithCaching || null}>
301 <TreeContextController>
302 <ProfilerContextController>
304 - <TimelineContextController>
305 - <InspectedElementContextController>
306 - <SuspenseTreeContextController>
307 - <ThemeProvider>
308 - <div
309 - className={styles.DevTools}
310 - ref={devToolsRef}
311 - data-react-devtools-portal-root={true}>
312 - {showTabBar && (
313 - <div className={styles.TabBar}>
314 - <ReactLogo />
315 - <span
316 - className={styles.DevToolsVersion}>
317 - {process.env.DEVTOOLS_VERSION}
318 - </span>
319 - <div className={styles.Spacer} />
320 - <TabBar
321 - currentTab={tab}
322 - id="DevTools"
323 - selectTab={selectTab}
324 - tabs={tabs}
325 - type="navigation"
326 - />
327 - </div>
328 - )}
329 - <div
330 - className={styles.TabContent}
331 - hidden={tab !== 'components'}>
332 - <Components
333 - portalContainer={
334 - componentsPortalContainer
335 - }
336 - />
337 - </div>
338 - <div
339 - className={styles.TabContent}
340 - hidden={tab !== 'profiler'}>
341 - <Profiler
342 - portalContainer={
343 - profilerPortalContainer
344 - }
345 - />
346 - </div>
347 - <div
348 - className={styles.TabContent}
349 - hidden={tab !== 'suspense'}>
350 - <SuspenseTab
351 - portalContainer={
352 - suspensePortalContainer
353 - }
303 + <InspectedElementContextController>
304 + <SuspenseTreeContextController>
305 + <ThemeProvider>
306 + <div
307 + className={styles.DevTools}
308 + ref={devToolsRef}
309 + data-react-devtools-portal-root={true}>
310 + {showTabBar && (
311 + <div className={styles.TabBar}>
312 + <ReactLogo />
313 + <span className={styles.DevToolsVersion}>
314 + {process.env.DEVTOOLS_VERSION}
315 + </span>
316 + <div className={styles.Spacer} />
317 + <TabBar
318 + currentTab={tab}
319 + id="DevTools"
320 + selectTab={selectTab}
321 + tabs={tabs}
322 + type="navigation"
323 />
324 </div>
356 - </div>
357 - {editorPortalContainer ? (
358 - <EditorPane
359 - selectedSource={currentSelectedSource}
360 - portalContainer={editorPortalContainer}
361 - />
362 - ) : null}
363 - {inspectedElementPortalContainer ? (
364 - <InspectedElementPane
365 - selectedSource={currentSelectedSource}
325 + )}
326 + <div
327 + className={styles.TabContent}
328 + hidden={tab !== 'components'}>
329 + <Components
330 portalContainer={
367 - inspectedElementPortalContainer
331 + componentsPortalContainer
332 }
333 />
370 - ) : null}
371 - </ThemeProvider>
372 - </SuspenseTreeContextController>
373 - </InspectedElementContextController>
374 - </TimelineContextController>
334 + </div>
335 + <div
336 + className={styles.TabContent}
337 + hidden={tab !== 'profiler'}>
338 + <Profiler
339 + portalContainer={profilerPortalContainer}
340 + />
341 + </div>
342 + <div
343 + className={styles.TabContent}
344 + hidden={tab !== 'suspense'}>
345 + <SuspenseTab
346 + portalContainer={suspensePortalContainer}
347 + />
348 + </div>
349 + </div>
350 + {editorPortalContainer ? (
351 + <EditorPane
352 + selectedSource={currentSelectedSource}
353 + portalContainer={editorPortalContainer}
354 + />
355 + ) : null}
356 + {inspectedElementPortalContainer ? (
357 + <InspectedElementPane
358 + selectedSource={currentSelectedSource}
359 + portalContainer={
360 + inspectedElementPortalContainer
361 + }
362 + />
363 + ) : null}
364 + </ThemeProvider>
365 + </SuspenseTreeContextController>
366 + </InspectedElementContextController>
367 </ProfilerContextController>
368 </TreeContextController>
369 </FetchFileWithCachingContext.Provider>
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
+1 -1
@@ -69,7 +69,7 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
69 rejectCallbacks.add(reject);
70 },
71
72 - // Optional property used by Timeline:
72 + // Optional property, read by React to name this I/O in async debug info:
73 displayName: `Searching GitHub issues for error "${errorMessage}"`,
74 };
75 const wake = () => {
packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js
+1 -2
@@ -56,9 +56,8 @@ export default function ReloadAndProfileButton({
56
57 bridge.send('reloadAndProfile', {
58 recordChangeDescriptions,
59 - recordTimeline: store.supportsTimeline,
59 });
61 - }, [bridge, recordChangeDescriptions, store]);
60 + }, [bridge, recordChangeDescriptions]);
61
62 if (!supportsReloadAndProfile) {
63 return null;
packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.css deleted
-66
@@ -1,66 +0,0 @@
1 -.Toolbar {
2 - height: 2.25rem;
3 - padding: 0 0.5rem;
4 - flex: 0 0 auto;
5 - display: flex;
6 - align-items: center;
7 - border-bottom: 1px solid var(--color-border);
8 -}
9 -
10 -.Content {
11 - padding: 0.5rem;
12 - user-select: none;
13 - overflow: auto;
14 -}
15 -
16 -.List {
17 - list-style: none;
18 - margin: 0;
19 - padding: 0;
20 -}
21 -
22 -.ListItem {
23 - flex: 1 1;
24 - margin: 0 0 0.5rem;
25 -}
26 -
27 -.Label {
28 - overflow: hidden;
29 - text-overflow: ellipsis;
30 - font-weight: bold;
31 - flex: 1 1;
32 -}
33 -
34 -.Value {
35 - font-family: var(--font-family-monospace);
36 - font-size: var(--font-size-monospace-normal);
37 -}
38 -
39 -.Row {
40 - display: flex;
41 - flex-direction: row;
42 - align-items: center;
43 - border-top: 1px solid var(--color-border);
44 -}
45 -
46 -.UnclickableSource,
47 -.ClickableSource {
48 - width: 100%;
49 - overflow: hidden;
50 - text-overflow: ellipsis;
51 - font-family: var(--font-family-sans);
52 - font-size: var(--font-size-sans-normal);
53 -}
54 -
55 -.UnclickableSource {
56 - color: var(--color-dim);
57 -}
58 -
59 -.ClickableSource {
60 - color: var(--color-text);
61 -}
62 -
63 -.ClickableSource:focus,
64 -.ClickableSource:hover {
65 - background-color: var(--color-background-hover);
66 -}
packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js deleted
-126
@@ -1,126 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {SchedulingEvent} from 'react-devtools-timeline/src/types';
11 -import type {ReactFunctionLocation} from 'shared/ReactTypes';
12 -
13 -import * as React from 'react';
14 -import Button from '../Button';
15 -import ButtonIcon from '../ButtonIcon';
16 -import {useContext} from 'react';
17 -import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
18 -import {
19 - formatTimestamp,
20 - getSchedulingEventLabel,
21 -} from 'react-devtools-timeline/src/utils/formatting';
22 -import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils';
23 -import {copy} from 'clipboard-js';
24 -import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
25 -import useOpenResource from '../useOpenResource';
26 -
27 -import styles from './SidebarEventInfo.css';
28 -
29 -export type Props = {};
30 -
31 -type FunctionLocationProps = {
32 - location: ReactFunctionLocation,
33 - displayName: string,
34 -};
35 -function FunctionLocation({location, displayName}: FunctionLocationProps) {
36 - // TODO: We should support symbolication here as well, but
37 - // symbolicating the whole stack can be expensive
38 - const [canViewSource, viewSource] = useOpenResource(location, null);
39 - return (
40 - <li>
41 - <Button
42 - className={
43 - canViewSource ? styles.ClickableSource : styles.UnclickableSource
44 - }
45 - disabled={!canViewSource}
46 - onClick={viewSource}>
47 - {displayName}
48 - </Button>
49 - </li>
50 - );
51 -}
52 -
53 -type SchedulingEventProps = {
54 - eventInfo: SchedulingEvent,
55 -};
56 -
57 -function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
58 - const {componentName, timestamp} = eventInfo;
59 - const componentStack = eventInfo.componentStack || null;
60 -
61 - return (
62 - <>
63 - <div className={styles.Toolbar}>
64 - {componentName} {getSchedulingEventLabel(eventInfo)}
65 - </div>
66 - <div className={styles.Content} tabIndex={0}>
67 - <ul className={styles.List}>
68 - <li className={styles.ListItem}>
69 - <label className={styles.Label}>Timestamp</label>:{' '}
70 - <span className={styles.Value}>{formatTimestamp(timestamp)}</span>
71 - </li>
72 - {componentStack && (
73 - <li className={styles.ListItem}>
74 - <div className={styles.Row}>
75 - <label className={styles.Label}>Rendered by</label>
76 - <Button
77 - onClick={withPermissionsCheck(
78 - {permissions: ['clipboardWrite']},
79 - () => copy(componentStack),
80 - )}
81 - title="Copy component stack to clipboard">
82 - <ButtonIcon type="copy" />
83 - </Button>
84 - </div>
85 - <ul className={styles.List}>
86 - {stackToComponentLocations(componentStack).map(
87 - ([displayName, location], index) => {
88 - if (location == null) {
89 - return (
90 - <li key={index}>
91 - <Button
92 - className={styles.UnclickableSource}
93 - disabled={true}>
94 - {displayName}
95 - </Button>
96 - </li>
97 - );
98 - }
99 -
100 - return (
101 - <FunctionLocation
102 - key={index}
103 - displayName={displayName}
104 - location={location}
105 - />
106 - );
107 - },
108 - )}
109 - </ul>
110 - </li>
111 - )}
112 - </ul>
113 - </div>
114 - </>
115 - );
116 -}
117 -
118 -export default function SidebarEventInfo(_: Props): React.Node {
119 - const {selectedEvent} = useContext(TimelineContext);
120 - // (TODO) Refactor in next PR so this supports multiple types of events
121 - if (selectedEvent && selectedEvent.schedulingEvent) {
122 - return <SchedulingEventInfo eventInfo={selectedEvent.schedulingEvent} />;
123 - }
124 -
125 - return null;
126 -}
packages/react-devtools-shared/src/devtools/views/Profiler/types.js
-11
@@ -11,10 +11,6 @@ import type {
11 ElementType,
12 SerializedElement,
13 } from 'react-devtools-shared/src/frontend/types';
14 -import type {
15 - TimelineData,
16 - TimelineDataExport,
17 -} from 'react-devtools-timeline/src/types';
14
15 export type CommitTreeNode = {
16 id: number,
@@ -118,9 +114,6 @@ export type ProfilingDataFrontend = {
114 // Legacy profiling data is per renderer + root.
115 dataForRoots: Map<number, ProfilingDataForRootFrontend>,
116
121 - // Timeline data is per rederer.
122 - timelineData: Array<TimelineData>,
123 -
117 // Some functionality should be disabled for imported data.
118 // e.g. DevTools should not try to sync selection between Components and Profiler tabs,
119 // even if there are Fibers with the same IDs.
@@ -157,8 +150,4 @@ export type ProfilingDataExport = {
150
151 // Legacy profiling data is per renderer + root.
152 dataForRoots: Array<ProfilingDataForRootExport>,
160 -
161 - // Timeline data is per rederer.
162 - // Note that old exported profiles won't contain this key.
163 - timelineData?: Array<TimelineDataExport>,
153 };
packages/react-devtools-shared/src/devtools/views/Profiler/utils.js
+1 -124
@@ -18,10 +18,6 @@ import type {
18 ProfilingDataFrontend,
19 SnapshotNode,
20 } from './types';
21 -import type {
22 - TimelineData,
23 - TimelineDataExport,
24 -} from 'react-devtools-timeline/src/types';
21
22 const commitGradient = [
23 'var(--color-commit-gradient-0)',
@@ -45,31 +41,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
41 ): ProfilingDataFrontend {
42 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
43
48 - const timelineDataArray = [];
49 -
44 dataBackends.forEach(dataBackend => {
51 - const {timelineData} = dataBackend;
52 - if (timelineData != null) {
53 - const {
54 - batchUIDToMeasuresKeyValueArray,
55 - internalModuleSourceToRanges,
56 - laneToLabelKeyValueArray,
57 - laneToReactMeasureKeyValueArray,
58 - ...rest
59 - } = timelineData;
60 -
61 - timelineDataArray.push({
62 - ...rest,
63 -
64 - // Most of the data is safe to parse as-is,
65 - // but we need to convert the nested Arrays back to Maps.
66 - batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
67 - internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
68 - laneToLabelMap: new Map(laneToLabelKeyValueArray),
69 - laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
70 - });
71 - }
72 -
45 dataBackend.dataForRoots.forEach(
46 ({commitData, displayName, initialTreeBaseDurations, rootID}) => {
47 const operations = operationsByRootID.get(rootID);
@@ -125,7 +97,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
97 );
98 });
99
128 - return {dataForRoots, imported: false, timelineData: timelineDataArray};
100 + return {dataForRoots, imported: false};
101 }
102
103 // Converts a Profiling data export into the format required by the Store.
@@ -140,50 +112,6 @@ export function prepareProfilingDataFrontendFromExport(
112 );
113 }
114
143 - const timelineData: Array<TimelineData> = profilingDataExport.timelineData
144 - ? profilingDataExport.timelineData.map(
145 - ({
146 - batchUIDToMeasuresKeyValueArray,
147 - componentMeasures,
148 - duration,
149 - flamechart,
150 - internalModuleSourceToRanges,
151 - laneToLabelKeyValueArray,
152 - laneToReactMeasureKeyValueArray,
153 - nativeEvents,
154 - networkMeasures,
155 - otherUserTimingMarks,
156 - reactVersion,
157 - schedulingEvents,
158 - snapshots,
159 - snapshotHeight,
160 - startTime,
161 - suspenseEvents,
162 - thrownErrors,
163 - }) => ({
164 - // Most of the data is safe to parse as-is,
165 - // but we need to convert the nested Arrays back to Maps.
166 - batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
167 - componentMeasures,
168 - duration,
169 - flamechart,
170 - internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
171 - laneToLabelMap: new Map(laneToLabelKeyValueArray),
172 - laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
173 - nativeEvents,
174 - networkMeasures,
175 - otherUserTimingMarks,
176 - reactVersion,
177 - schedulingEvents,
178 - snapshots,
179 - snapshotHeight,
180 - startTime,
181 - suspenseEvents,
182 - thrownErrors,
183 - }),
184 - )
185 - : [];
186 -
115 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
116 profilingDataExport.dataForRoots.forEach(
117 ({
@@ -231,7 +159,6 @@ export function prepareProfilingDataFrontendFromExport(
159 return {
160 dataForRoots,
161 imported: true,
234 - timelineData,
162 };
163 }
164
@@ -239,55 +166,6 @@ export function prepareProfilingDataFrontendFromExport(
166 export function prepareProfilingDataExport(
167 profilingDataFrontend: ProfilingDataFrontend,
168 ): ProfilingDataExport {
242 - const timelineData: Array<TimelineDataExport> =
243 - profilingDataFrontend.timelineData.map(
244 - ({
245 - batchUIDToMeasuresMap,
246 - componentMeasures,
247 - duration,
248 - flamechart,
249 - internalModuleSourceToRanges,
250 - laneToLabelMap,
251 - laneToReactMeasureMap,
252 - nativeEvents,
253 - networkMeasures,
254 - otherUserTimingMarks,
255 - reactVersion,
256 - schedulingEvents,
257 - snapshots,
258 - snapshotHeight,
259 - startTime,
260 - suspenseEvents,
261 - thrownErrors,
262 - }) => ({
263 - // Most of the data is safe to serialize as-is,
264 - // but we need to convert the Maps to nested Arrays.
265 - batchUIDToMeasuresKeyValueArray: Array.from(
266 - batchUIDToMeasuresMap.entries(),
267 - ),
268 - componentMeasures: componentMeasures,
269 - duration,
270 - flamechart,
271 - internalModuleSourceToRanges: Array.from(
272 - internalModuleSourceToRanges.entries(),
273 - ),
274 - laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
275 - laneToReactMeasureKeyValueArray: Array.from(
276 - laneToReactMeasureMap.entries(),
277 - ),
278 - nativeEvents,
279 - networkMeasures,
280 - otherUserTimingMarks,
281 - reactVersion,
282 - schedulingEvents,
283 - snapshots,
284 - snapshotHeight,
285 - startTime,
286 - suspenseEvents,
287 - thrownErrors,
288 - }),
289 - );
290 -
169 const dataForRoots: Array<ProfilingDataForRootExport> = [];
170 profilingDataFrontend.dataForRoots.forEach(
171 ({
@@ -339,7 +217,6 @@ export function prepareProfilingDataExport(
217 return {
218 version: PROFILER_EXPORT_VERSION,
219 dataForRoots,
342 - timelineData,
220 };
221 }
222
packages/react-devtools-shared/src/dynamicImportCache.js
+1 -1
@@ -71,7 +71,7 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
71 rejectCallbacks.add(reject);
72 },
73
74 - // Optional property used by Timeline:
74 + // Optional property, read by React to name this I/O in async debug info:
75 displayName: `Loading module "${moduleLoaderFunction.name}"`,
76 };
77
packages/react-devtools-shared/src/hook.js
-43
@@ -54,7 +54,6 @@ const targetConsole: Object = console;
54
55 const defaultProfilingSettings: ProfilingSettings = {
56 recordChangeDescriptions: false,
57 - recordTimeline: false,
57 };
58
59 export function installHook(
@@ -404,41 +403,6 @@ export function installHook(
403 unpatchConsoleCallbacks.length = 0;
404 }
405
407 - type StackFrameString = string;
408 -
409 - const openModuleRangesStack: Array<StackFrameString> = [];
410 - const moduleRanges: Array<[StackFrameString, StackFrameString]> = [];
411 -
412 - function getTopStackFrameString(error: Error): StackFrameString | null {
413 - const frames = error.stack.split('\n');
414 - const frame = frames.length > 1 ? frames[1] : null;
415 - return frame;
416 - }
417 -
418 - function getInternalModuleRanges(): Array<
419 - [StackFrameString, StackFrameString],
420 - > {
421 - return moduleRanges;
422 - }
423 -
424 - function registerInternalModuleStart(error: Error) {
425 - const startStackFrame = getTopStackFrameString(error);
426 - if (startStackFrame !== null) {
427 - openModuleRangesStack.push(startStackFrame);
428 - }
429 - }
430 -
431 - function registerInternalModuleStop(error: Error) {
432 - if (openModuleRangesStack.length > 0) {
433 - const startStackFrame = openModuleRangesStack.pop();
434 - const stopStackFrame = getTopStackFrameString(error);
435 - if (stopStackFrame !== null) {
436 - // $FlowFixMe[incompatible-type]
437 - moduleRanges.push([startStackFrame, stopStackFrame]);
438 - }
439 - }
440 - }
441 -
406 // For Errors and Warnings we only patch console once
407 function patchConsoleForErrorsAndWarnings() {
408 // Don't patch console in case settings were not injected
@@ -665,13 +629,6 @@ export function installHook(
629 // React v18.0+
630 onPostCommitFiberRoot,
631 setStrictMode,
668 -
669 - // Schedule Profiler runtime helpers.
670 - // These internal React modules to report their own boundaries
671 - // which in turn enables the profiler to dim or filter internal frames.
672 - getInternalModuleRanges,
673 - registerInternalModuleStart,
674 - registerInternalModuleStop,
632 };
633
634 if (maybeSettingsOrSettingsPromise == null) {
packages/react-devtools-shared/src/hookNamesCache.js
+1 -1
@@ -99,7 +99,7 @@ export function loadHookNames(
99 rejectCallbacks.add(reject);
100 },
101
102 - // Optional property used by Timeline:
102 + // Optional property, read by React to name this I/O in async debug info:
103 displayName: `Loading hook names for ${element.displayName || 'Unknown'}`,
104 };
105
packages/react-devtools-shared/src/inspectedElementCache.js
+1 -1
@@ -95,7 +95,7 @@ export function inspectElement(
95 rejectCallbacks.add(reject);
96 },
97
98 - // Optional property used by Timeline:
98 + // Optional property, read by React to name this I/O in async debug info:
99 displayName: `Inspecting ${element.displayName || 'Unknown'}`,
100 };
101
packages/react-devtools-shared/src/utils.js
+1 -12
@@ -39,7 +39,6 @@ import {
39 LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
40 SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
41 SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
42 - SESSION_STORAGE_RECORD_TIMELINE_KEY,
42 SUSPENSE_TREE_OPERATION_ADD,
43 SUSPENSE_TREE_OPERATION_REMOVE,
44 SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
@@ -1295,30 +1294,20 @@ export function getProfilingSettings(): ProfilingSettings {
1294 recordChangeDescriptions:
1295 sessionStorageGetItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY) ===
1296 'true',
1298 - recordTimeline:
1299 - sessionStorageGetItem(SESSION_STORAGE_RECORD_TIMELINE_KEY) === 'true',
1297 };
1298 }
1299
1303 -export function onReloadAndProfile(
1304 - recordChangeDescriptions: boolean,
1305 - recordTimeline: boolean,
1306 -): void {
1300 +export function onReloadAndProfile(recordChangeDescriptions: boolean): void {
1301 sessionStorageSetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
1302 sessionStorageSetItem(
1303 SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
1304 recordChangeDescriptions ? 'true' : 'false',
1305 );
1312 - sessionStorageSetItem(
1313 - SESSION_STORAGE_RECORD_TIMELINE_KEY,
1314 - recordTimeline ? 'true' : 'false',
1315 - );
1306 }
1307
1308 export function onReloadAndProfileFlagsReset(): void {
1309 sessionStorageRemoveItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY);
1310 sessionStorageRemoveItem(SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY);
1321 - sessionStorageRemoveItem(SESSION_STORAGE_RECORD_TIMELINE_KEY);
1311 }
1312
1313 export function unionOfTwoArrays<T>(a: Array<T>, b: Array<T>): Array<T> {
packages/react-devtools-timeline/README.md deleted
-3
@@ -1,3 +0,0 @@
1 -# React Concurrent Mode Profiler
2 -
3 -This package contains the new/experimental "timeline" 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-timeline/package.json deleted
-29
@@ -1,29 +0,0 @@
1 -{
2 - "private": true,
3 - "name": "react-devtools-timeline",
4 - "version": "7.0.1",
5 - "license": "MIT",
6 - "dependencies": {
7 - "@elg/speedscope": "1.9.0-a6f84db",
8 - "clipboard-js": "^0.3.6",
9 - "memoize-one": "^5.1.1",
10 - "nullthrows": "^1.1.1",
11 - "pretty-ms": "^7.0.0",
12 - "react-virtualized-auto-sizer": "^1.0.23",
13 - "regenerator-runtime": "^0.13.7"
14 - },
15 - "devDependencies": {
16 - "@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
17 - "@reach/menu-button": "^0.16.1",
18 - "@reach/tooltip": "^0.16.0",
19 - "babel-loader": "^8.1.0",
20 - "css-loader": "^4.2.1",
21 - "file-loader": "^6.0.0",
22 - "style-loader": "^1.2.1",
23 - "url-loader": "^4.1.0",
24 - "vercel": "^20.1.0",
25 - "webpack": "^5.82.1",
26 - "webpack-cli": "^5.1.1",
27 - "webpack-dev-server": "^4.15.0"
28 - }
29 -}
packages/react-devtools-timeline/src/CanvasPage.css deleted
-7
@@ -1,7 +0,0 @@
1 -.CanvasPage {
2 - position: absolute;
3 - top: 0;
4 - bottom: 0;
5 - left: 0;
6 - right: 0;
7 -}
packages/react-devtools-timeline/src/CanvasPage.js deleted
-729
@@ -1,729 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Interaction, Point} from './view-base';
11 -import type {ReactEventInfo, TimelineData, ViewState} from './types';
12 -
13 -import * as React from 'react';
14 -import {
15 - Fragment,
16 - useContext,
17 - useEffect,
18 - useLayoutEffect,
19 - useRef,
20 - useState,
21 - useCallback,
22 -} from 'react';
23 -import AutoSizer from 'react-virtualized-auto-sizer';
24 -
25 -import {
26 - HorizontalPanAndZoomView,
27 - ResizableView,
28 - VerticalScrollOverflowView,
29 - Surface,
30 - VerticalScrollView,
31 - View,
32 - useCanvasInteraction,
33 - verticallyStackedLayout,
34 - zeroPoint,
35 -} from './view-base';
36 -import {
37 - ComponentMeasuresView,
38 - FlamechartView,
39 - NativeEventsView,
40 - NetworkMeasuresView,
41 - ReactMeasuresView,
42 - SchedulingEventsView,
43 - SnapshotsView,
44 - SuspenseEventsView,
45 - ThrownErrorsView,
46 - TimeAxisMarkersView,
47 - UserTimingMarksView,
48 -} from './content-views';
49 -import {COLORS} from './content-views/constants';
50 -import {clampState, moveStateToRange} from './view-base/utils/scrollState';
51 -import EventTooltip from './EventTooltip';
52 -import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
53 -import {TimelineSearchContext} from './TimelineSearchContext';
54 -import {TimelineContext} from './TimelineContext';
55 -import CanvasPageContextMenu from './CanvasPageContextMenu';
56 -
57 -import type {ContextMenuRef} from 'react-devtools-shared/src/devtools/ContextMenu/types';
58 -
59 -import styles from './CanvasPage.css';
60 -
61 -type Props = {
62 - profilerData: TimelineData,
63 - viewState: ViewState,
64 -};
65 -
66 -function CanvasPage({profilerData, viewState}: Props): React.Node {
67 - return (
68 - <div
69 - className={styles.CanvasPage}
70 - style={{backgroundColor: COLORS.BACKGROUND}}>
71 - <AutoSizer>
72 - {({height, width}: {height: number, width: number}) => (
73 - <AutoSizedCanvas
74 - data={profilerData}
75 - height={height}
76 - viewState={viewState}
77 - width={width}
78 - />
79 - )}
80 - </AutoSizer>
81 - </div>
82 - );
83 -}
84 -
85 -const EMPTY_CONTEXT_INFO: ReactEventInfo = {
86 - componentMeasure: null,
87 - flamechartStackFrame: null,
88 - measure: null,
89 - nativeEvent: null,
90 - networkMeasure: null,
91 - schedulingEvent: null,
92 - snapshot: null,
93 - suspenseEvent: null,
94 - thrownError: null,
95 - userTimingMark: null,
96 -};
97 -
98 -type AutoSizedCanvasProps = {
99 - data: TimelineData,
100 - height: number,
101 - viewState: ViewState,
102 - width: number,
103 -};
104 -
105 -function AutoSizedCanvas({
106 - data,
107 - height,
108 - viewState,
109 - width,
110 -}: AutoSizedCanvasProps) {
111 - const canvasRef = useRef<HTMLCanvasElement | null>(null);
112 -
113 - const [mouseLocation, setMouseLocation] = useState<Point>(zeroPoint); // DOM coordinates
114 - const [hoveredEvent, setHoveredEvent] = useState<ReactEventInfo | null>(null);
115 - const [lastHoveredEvent, setLastHoveredEvent] =
116 - useState<ReactEventInfo | null>(null);
117 -
118 - const contextMenuRef: ContextMenuRef = useRef(null);
119 -
120 - const resetHoveredEvent = useCallback(
121 - () => setHoveredEvent(EMPTY_CONTEXT_INFO),
122 - [],
123 - );
124 - const updateHoveredEvent = useCallback(
125 - (event: ReactEventInfo) => {
126 - setHoveredEvent(event);
127 -
128 - // If menu is already open, don't update the hovered event data
129 - // So the same set of menu items is preserved until the current context menu is closed
130 - if (contextMenuRef.current?.isShown()) {
131 - return;
132 - }
133 -
134 - const {
135 - componentMeasure,
136 - flamechartStackFrame,
137 - measure,
138 - networkMeasure,
139 - schedulingEvent,
140 - suspenseEvent,
141 - } = event;
142 -
143 - // We have to keep track of last non-empty hovered event, since this will be the input for context menu items
144 - // We can't just pass hoveredEvent to ContextMenuContainer,
145 - // since it will be reset each time user moves mouse away from event object on the canvas
146 - if (
147 - componentMeasure != null ||
148 - flamechartStackFrame != null ||
149 - measure != null ||
150 - networkMeasure != null ||
151 - schedulingEvent != null ||
152 - suspenseEvent != null
153 - ) {
154 - setLastHoveredEvent(event);
155 - }
156 - },
157 - [contextMenuRef],
158 - );
159 -
160 - const {searchIndex, searchRegExp, searchResults} = useContext(
161 - TimelineSearchContext,
162 - );
163 -
164 - // This effect searches timeline data and scrolls to the next match wen search criteria change.
165 - useLayoutEffect(() => {
166 - viewState.updateSearchRegExpState(searchRegExp);
167 -
168 - const componentMeasureSearchResult =
169 - searchResults.length > 0 ? searchResults[searchIndex] : null;
170 - if (componentMeasureSearchResult != null) {
171 - const scrollState = moveStateToRange({
172 - state: viewState.horizontalScrollState,
173 - rangeStart: componentMeasureSearchResult.timestamp,
174 - rangeEnd:
175 - componentMeasureSearchResult.timestamp +
176 - componentMeasureSearchResult.duration,
177 - contentLength: data.duration,
178 - minContentLength: data.duration * MIN_ZOOM_LEVEL,
179 - maxContentLength: data.duration * MAX_ZOOM_LEVEL,
180 - containerLength: width,
181 - });
182 -
183 - viewState.updateHorizontalScrollState(scrollState);
184 - }
185 -
186 - surfaceRef.current.displayIfNeeded();
187 - }, [searchIndex, searchRegExp, searchResults, viewState]);
188 -
189 - const surfaceRef = useRef(new Surface(resetHoveredEvent));
190 - const userTimingMarksViewRef = useRef<null | UserTimingMarksView>(null);
191 - const nativeEventsViewRef = useRef<null | NativeEventsView>(null);
192 - const schedulingEventsViewRef = useRef<null | SchedulingEventsView>(null);
193 - const suspenseEventsViewRef = useRef<null | SuspenseEventsView>(null);
194 - const componentMeasuresViewRef = useRef<null | ComponentMeasuresView>(null);
195 - const reactMeasuresViewRef = useRef<null | ReactMeasuresView>(null);
196 - const flamechartViewRef = useRef<null | FlamechartView>(null);
197 - const networkMeasuresViewRef = useRef<null | NetworkMeasuresView>(null);
198 - const snapshotsViewRef = useRef<null | SnapshotsView>(null);
199 - const thrownErrorsViewRef = useRef<null | ThrownErrorsView>(null);
200 -
201 - useLayoutEffect(() => {
202 - const surface = surfaceRef.current;
203 - const defaultFrame = {origin: zeroPoint, size: {width, height}};
204 -
205 - // Auto hide context menu when panning.
206 - viewState.onHorizontalScrollStateChange(scrollState => {
207 - contextMenuRef.current?.hide();
208 - });
209 -
210 - // Initialize horizontal view state
211 - viewState.updateHorizontalScrollState(
212 - clampState({
213 - state: viewState.horizontalScrollState,
214 - minContentLength: data.duration * MIN_ZOOM_LEVEL,
215 - maxContentLength: data.duration * MAX_ZOOM_LEVEL,
216 - containerLength: defaultFrame.size.width,
217 - }),
218 - );
219 -
220 - function createViewHelper(
221 - view: View,
222 - label: string,
223 - shouldScrollVertically: boolean = false,
224 - shouldResizeVertically: boolean = false,
225 - ): View {
226 - let verticalScrollView = null;
227 - if (shouldScrollVertically) {
228 - verticalScrollView = new VerticalScrollView(
229 - surface,
230 - defaultFrame,
231 - view,
232 - viewState,
233 - label,
234 - );
235 - }
236 -
237 - const horizontalPanAndZoomView = new HorizontalPanAndZoomView(
238 - surface,
239 - defaultFrame,
240 - verticalScrollView !== null ? verticalScrollView : view,
241 - data.duration,
242 - viewState,
243 - );
244 -
245 - let resizableView = null;
246 - if (shouldResizeVertically) {
247 - resizableView = new ResizableView(
248 - surface,
249 - defaultFrame,
250 - horizontalPanAndZoomView,
251 - viewState,
252 - canvasRef,
253 - label,
254 - );
255 - }
256 -
257 - return resizableView || horizontalPanAndZoomView;
258 - }
259 -
260 - const axisMarkersView = new TimeAxisMarkersView(
261 - surface,
262 - defaultFrame,
263 - data.duration,
264 - );
265 - const axisMarkersViewWrapper = createViewHelper(axisMarkersView, 'time');
266 -
267 - let userTimingMarksViewWrapper = null;
268 - if (data.otherUserTimingMarks.length > 0) {
269 - const userTimingMarksView = new UserTimingMarksView(
270 - surface,
271 - defaultFrame,
272 - data.otherUserTimingMarks,
273 - data.duration,
274 - );
275 - userTimingMarksViewRef.current = userTimingMarksView;
276 - userTimingMarksViewWrapper = createViewHelper(
277 - userTimingMarksView,
278 - 'user timing api',
279 - );
280 - }
281 -
282 - let nativeEventsViewWrapper = null;
283 - if (data.nativeEvents.length > 0) {
284 - const nativeEventsView = new NativeEventsView(
285 - surface,
286 - defaultFrame,
287 - data,
288 - );
289 - nativeEventsViewRef.current = nativeEventsView;
290 - nativeEventsViewWrapper = createViewHelper(
291 - nativeEventsView,
292 - 'events',
293 - true,
294 - true,
295 - );
296 - }
297 -
298 - let thrownErrorsViewWrapper = null;
299 - if (data.thrownErrors.length > 0) {
300 - const thrownErrorsView = new ThrownErrorsView(
301 - surface,
302 - defaultFrame,
303 - data,
304 - );
305 - thrownErrorsViewRef.current = thrownErrorsView;
306 - thrownErrorsViewWrapper = createViewHelper(
307 - thrownErrorsView,
308 - 'thrown errors',
309 - );
310 - }
311 -
312 - let schedulingEventsViewWrapper = null;
313 - if (data.schedulingEvents.length > 0) {
314 - const schedulingEventsView = new SchedulingEventsView(
315 - surface,
316 - defaultFrame,
317 - data,
318 - );
319 - schedulingEventsViewRef.current = schedulingEventsView;
320 - schedulingEventsViewWrapper = createViewHelper(
321 - schedulingEventsView,
322 - 'react updates',
323 - );
324 - }
325 -
326 - let suspenseEventsViewWrapper = null;
327 - if (data.suspenseEvents.length > 0) {
328 - const suspenseEventsView = new SuspenseEventsView(
329 - surface,
330 - defaultFrame,
331 - data,
332 - );
333 - suspenseEventsViewRef.current = suspenseEventsView;
334 - suspenseEventsViewWrapper = createViewHelper(
335 - suspenseEventsView,
336 - 'suspense',
337 - true,
338 - true,
339 - );
340 - }
341 -
342 - const reactMeasuresView = new ReactMeasuresView(
343 - surface,
344 - defaultFrame,
345 - data,
346 - );
347 - reactMeasuresViewRef.current = reactMeasuresView;
348 - const reactMeasuresViewWrapper = createViewHelper(
349 - reactMeasuresView,
350 - 'react scheduling',
351 - true,
352 - true,
353 - );
354 -
355 - let componentMeasuresViewWrapper = null;
356 - if (data.componentMeasures.length > 0) {
357 - const componentMeasuresView = new ComponentMeasuresView(
358 - surface,
359 - defaultFrame,
360 - data,
361 - viewState,
362 - );
363 - componentMeasuresViewRef.current = componentMeasuresView;
364 - componentMeasuresViewWrapper = createViewHelper(
365 - componentMeasuresView,
366 - 'react components',
367 - );
368 - }
369 -
370 - let snapshotsViewWrapper = null;
371 - if (data.snapshots.length > 0) {
372 - const snapshotsView = new SnapshotsView(surface, defaultFrame, data);
373 - snapshotsViewRef.current = snapshotsView;
374 - snapshotsViewWrapper = createViewHelper(
375 - snapshotsView,
376 - 'snapshots',
377 - true,
378 - true,
379 - );
380 - }
381 -
382 - let networkMeasuresViewWrapper = null;
383 - if (data.snapshots.length > 0) {
384 - const networkMeasuresView = new NetworkMeasuresView(
385 - surface,
386 - defaultFrame,
387 - data,
388 - );
389 - networkMeasuresViewRef.current = networkMeasuresView;
390 - networkMeasuresViewWrapper = createViewHelper(
391 - networkMeasuresView,
392 - 'network',
393 - true,
394 - true,
395 - );
396 - }
397 -
398 - let flamechartViewWrapper = null;
399 - if (data.flamechart.length > 0) {
400 - const flamechartView = new FlamechartView(
401 - surface,
402 - defaultFrame,
403 - data.flamechart,
404 - data.internalModuleSourceToRanges,
405 - data.duration,
406 - );
407 - flamechartViewRef.current = flamechartView;
408 - flamechartViewWrapper = createViewHelper(
409 - flamechartView,
410 - 'flamechart',
411 - true,
412 - true,
413 - );
414 - }
415 -
416 - // Root view contains all of the sub views defined above.
417 - // The order we add them below determines their vertical position.
418 - const rootView = new View(
419 - surface,
420 - defaultFrame,
421 - verticallyStackedLayout,
422 - defaultFrame,
423 - COLORS.BACKGROUND,
424 - );
425 - rootView.addSubview(axisMarkersViewWrapper);
426 - if (userTimingMarksViewWrapper !== null) {
427 - rootView.addSubview(userTimingMarksViewWrapper);
428 - }
429 - if (nativeEventsViewWrapper !== null) {
430 - rootView.addSubview(nativeEventsViewWrapper);
431 - }
432 - if (schedulingEventsViewWrapper !== null) {
433 - rootView.addSubview(schedulingEventsViewWrapper);
434 - }
435 - if (thrownErrorsViewWrapper !== null) {
436 - rootView.addSubview(thrownErrorsViewWrapper);
437 - }
438 - if (suspenseEventsViewWrapper !== null) {
439 - rootView.addSubview(suspenseEventsViewWrapper);
440 - }
441 - // $FlowFixMe[invalid-compare]
442 - if (reactMeasuresViewWrapper !== null) {
443 - rootView.addSubview(reactMeasuresViewWrapper);
444 - }
445 - if (componentMeasuresViewWrapper !== null) {
446 - rootView.addSubview(componentMeasuresViewWrapper);
447 - }
448 - if (snapshotsViewWrapper !== null) {
449 - rootView.addSubview(snapshotsViewWrapper);
450 - }
451 - if (networkMeasuresViewWrapper !== null) {
452 - rootView.addSubview(networkMeasuresViewWrapper);
453 - }
454 - if (flamechartViewWrapper !== null) {
455 - rootView.addSubview(flamechartViewWrapper);
456 - }
457 -
458 - const verticalScrollOverflowView = new VerticalScrollOverflowView(
459 - surface,
460 - defaultFrame,
461 - rootView,
462 - viewState,
463 - );
464 -
465 - surfaceRef.current.rootView = verticalScrollOverflowView;
466 - }, [data]);
467 -
468 - useLayoutEffect(() => {
469 - if (canvasRef.current) {
470 - surfaceRef.current.setCanvas(canvasRef.current, {width, height});
471 - }
472 - }, [width, height]);
473 -
474 - const interactor = useCallback((interaction: Interaction) => {
475 - const canvas = canvasRef.current;
476 - if (canvas === null) {
477 - return;
478 - }
479 -
480 - const surface = surfaceRef.current;
481 - surface.handleInteraction(interaction);
482 -
483 - // Flush any display work that got queued up as part of the previous interaction.
484 - // Typically there should be no work, but certain interactions may need a second pass.
485 - // For example, the ResizableView may collapse/expand its contents,
486 - // which requires a second layout pass for an ancestor VerticalScrollOverflowView.
487 - //
488 - // TODO It would be nice to remove this call for performance reasons.
489 - // To do that, we'll need to address the UX bug with VerticalScrollOverflowView.
490 - // For more info see: https://github.com/facebook/react/pull/22005#issuecomment-896953399
491 - surface.displayIfNeeded();
492 -
493 - canvas.style.cursor = surface.getCurrentCursor() || 'default';
494 -
495 - // Defer drawing to canvas until React's commit phase, to avoid drawing
496 - // twice and to ensure that both the canvas and DOM elements managed by
497 - // React are in sync.
498 - setMouseLocation({
499 - x: interaction.payload.event.x,
500 - y: interaction.payload.event.y,
501 - });
502 - }, []);
503 -
504 - useCanvasInteraction(canvasRef, interactor);
505 -
506 - const {selectEvent} = useContext(TimelineContext);
507 -
508 - useEffect(() => {
509 - const {current: userTimingMarksView} = userTimingMarksViewRef;
510 - if (userTimingMarksView) {
511 - userTimingMarksView.onHover = userTimingMark => {
512 - if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
513 - updateHoveredEvent({
514 - ...EMPTY_CONTEXT_INFO,
515 - userTimingMark,
516 - });
517 - }
518 - };
519 - }
520 -
521 - const {current: nativeEventsView} = nativeEventsViewRef;
522 - if (nativeEventsView) {
523 - nativeEventsView.onHover = nativeEvent => {
524 - if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
525 - updateHoveredEvent({
526 - ...EMPTY_CONTEXT_INFO,
527 - nativeEvent,
528 - });
529 - }
530 - };
531 - }
532 -
533 - const {current: schedulingEventsView} = schedulingEventsViewRef;
534 - if (schedulingEventsView) {
535 - schedulingEventsView.onHover = schedulingEvent => {
536 - if (!hoveredEvent || hoveredEvent.schedulingEvent !== schedulingEvent) {
537 - updateHoveredEvent({
538 - ...EMPTY_CONTEXT_INFO,
539 - schedulingEvent,
540 - });
541 - }
542 - };
543 - schedulingEventsView.onClick = schedulingEvent => {
544 - selectEvent({
545 - ...EMPTY_CONTEXT_INFO,
546 - schedulingEvent,
547 - });
548 - };
549 - }
550 -
551 - const {current: suspenseEventsView} = suspenseEventsViewRef;
552 - if (suspenseEventsView) {
553 - suspenseEventsView.onHover = suspenseEvent => {
554 - if (!hoveredEvent || hoveredEvent.suspenseEvent !== suspenseEvent) {
555 - updateHoveredEvent({
556 - ...EMPTY_CONTEXT_INFO,
557 - suspenseEvent,
558 - });
559 - }
560 - };
561 - }
562 -
563 - const {current: reactMeasuresView} = reactMeasuresViewRef;
564 - if (reactMeasuresView) {
565 - reactMeasuresView.onHover = measure => {
566 - if (!hoveredEvent || hoveredEvent.measure !== measure) {
567 - updateHoveredEvent({
568 - ...EMPTY_CONTEXT_INFO,
569 - measure,
570 - });
571 - }
572 - };
573 - }
574 -
575 - const {current: componentMeasuresView} = componentMeasuresViewRef;
576 - if (componentMeasuresView) {
577 - componentMeasuresView.onHover = componentMeasure => {
578 - if (
579 - !hoveredEvent ||
580 - hoveredEvent.componentMeasure !== componentMeasure
581 - ) {
582 - updateHoveredEvent({
583 - ...EMPTY_CONTEXT_INFO,
584 - componentMeasure,
585 - });
586 - }
587 - };
588 - }
589 -
590 - const {current: snapshotsView} = snapshotsViewRef;
591 - if (snapshotsView) {
592 - snapshotsView.onHover = snapshot => {
593 - if (!hoveredEvent || hoveredEvent.snapshot !== snapshot) {
594 - updateHoveredEvent({
595 - ...EMPTY_CONTEXT_INFO,
596 - snapshot,
597 - });
598 - }
599 - };
600 - }
601 -
602 - const {current: flamechartView} = flamechartViewRef;
603 - if (flamechartView) {
604 - flamechartView.setOnHover(flamechartStackFrame => {
605 - if (
606 - !hoveredEvent ||
607 - hoveredEvent.flamechartStackFrame !== flamechartStackFrame
608 - ) {
609 - updateHoveredEvent({
610 - ...EMPTY_CONTEXT_INFO,
611 - flamechartStackFrame,
612 - });
613 - }
614 - });
615 - }
616 -
617 - const {current: networkMeasuresView} = networkMeasuresViewRef;
618 - if (networkMeasuresView) {
619 - networkMeasuresView.onHover = networkMeasure => {
620 - if (!hoveredEvent || hoveredEvent.networkMeasure !== networkMeasure) {
621 - updateHoveredEvent({
622 - ...EMPTY_CONTEXT_INFO,
623 - networkMeasure,
624 - });
625 - }
626 - };
627 - }
628 -
629 - const {current: thrownErrorsView} = thrownErrorsViewRef;
630 - if (thrownErrorsView) {
631 - thrownErrorsView.onHover = thrownError => {
632 - if (!hoveredEvent || hoveredEvent.thrownError !== thrownError) {
633 - updateHoveredEvent({
634 - ...EMPTY_CONTEXT_INFO,
635 - thrownError,
636 - });
637 - }
638 - };
639 - }
640 - }, [
641 - hoveredEvent,
642 - data, // Attach onHover callbacks when views are re-created on data change
643 - ]);
644 -
645 - useLayoutEffect(() => {
646 - const userTimingMarksView = userTimingMarksViewRef.current;
647 - if (userTimingMarksView) {
648 - userTimingMarksView.setHoveredMark(
649 - hoveredEvent ? hoveredEvent.userTimingMark : null,
650 - );
651 - }
652 -
653 - const nativeEventsView = nativeEventsViewRef.current;
654 - if (nativeEventsView) {
655 - nativeEventsView.setHoveredEvent(
656 - hoveredEvent ? hoveredEvent.nativeEvent : null,
657 - );
658 - }
659 -
660 - const schedulingEventsView = schedulingEventsViewRef.current;
661 - if (schedulingEventsView) {
662 - schedulingEventsView.setHoveredEvent(
663 - hoveredEvent ? hoveredEvent.schedulingEvent : null,
664 - );
665 - }
666 -
667 - const suspenseEventsView = suspenseEventsViewRef.current;
668 - if (suspenseEventsView) {
669 - suspenseEventsView.setHoveredEvent(
670 - hoveredEvent ? hoveredEvent.suspenseEvent : null,
671 - );
672 - }
673 -
674 - const reactMeasuresView = reactMeasuresViewRef.current;
675 - if (reactMeasuresView) {
676 - reactMeasuresView.setHoveredMeasure(
677 - hoveredEvent ? hoveredEvent.measure : null,
678 - );
679 - }
680 -
681 - const flamechartView = flamechartViewRef.current;
682 - if (flamechartView) {
683 - flamechartView.setHoveredFlamechartStackFrame(
684 - hoveredEvent ? hoveredEvent.flamechartStackFrame : null,
685 - );
686 - }
687 -
688 - const networkMeasuresView = networkMeasuresViewRef.current;
689 - if (networkMeasuresView) {
690 - networkMeasuresView.setHoveredEvent(
691 - hoveredEvent ? hoveredEvent.networkMeasure : null,
692 - );
693 - }
694 - }, [hoveredEvent]);
695 -
696 - // Draw to canvas in React's commit phase
697 - useLayoutEffect(() => {
698 - surfaceRef.current.displayIfNeeded();
699 - });
700 -
701 - return (
702 - <Fragment>
703 - <canvas ref={canvasRef} height={height} width={width} />
704 -
705 - <CanvasPageContextMenu
706 - canvasRef={canvasRef}
707 - hoveredEvent={lastHoveredEvent}
708 - timelineData={data}
709 - viewState={viewState}
710 - canvasWidth={width}
711 - closedMenuStub={
712 - !surfaceRef.current.hasActiveView() ? (
713 - <EventTooltip
714 - canvasRef={canvasRef}
715 - data={data}
716 - height={height}
717 - hoveredEvent={hoveredEvent}
718 - origin={mouseLocation}
719 - width={width}
720 - />
721 - ) : null
722 - }
723 - ref={contextMenuRef}
724 - />
725 - </Fragment>
726 - );
727 -}
728 -
729 -export default CanvasPage;
packages/react-devtools-timeline/src/CanvasPageContextMenu.js deleted
-182
@@ -1,182 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {useMemo} from 'react';
12 -import {copy} from 'clipboard-js';
13 -import prettyMilliseconds from 'pretty-ms';
14 -
15 -import ContextMenuContainer from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenuContainer';
16 -import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
17 -
18 -import {getBatchRange} from './utils/getBatchRange';
19 -import {moveStateToRange} from './view-base/utils/scrollState';
20 -import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
21 -
22 -import type {
23 - ContextMenuItem,
24 - ContextMenuRef,
25 -} from 'react-devtools-shared/src/devtools/ContextMenu/types';
26 -import type {
27 - ReactEventInfo,
28 - ReactMeasure,
29 - TimelineData,
30 - ViewState,
31 -} from './types';
32 -
33 -function zoomToBatch(
34 - data: TimelineData,
35 - measure: ReactMeasure,
36 - viewState: ViewState,
37 - width: number,
38 -) {
39 - const {batchUID} = measure;
40 - const [rangeStart, rangeEnd] = getBatchRange(batchUID, data);
41 -
42 - // Convert from time range to ScrollState
43 - const scrollState = moveStateToRange({
44 - state: viewState.horizontalScrollState,
45 - rangeStart,
46 - rangeEnd,
47 - contentLength: data.duration,
48 -
49 - minContentLength: data.duration * MIN_ZOOM_LEVEL,
50 - maxContentLength: data.duration * MAX_ZOOM_LEVEL,
51 - containerLength: width,
52 - });
53 -
54 - viewState.updateHorizontalScrollState(scrollState);
55 -}
56 -
57 -function copySummary(data: TimelineData, measure: ReactMeasure) {
58 - const {batchUID, duration, timestamp, type} = measure;
59 -
60 - const [startTime, stopTime] = getBatchRange(batchUID, data);
61 -
62 - copy(
63 - JSON.stringify({
64 - type,
65 - timestamp: prettyMilliseconds(timestamp),
66 - duration: prettyMilliseconds(duration),
67 - batchDuration: prettyMilliseconds(stopTime - startTime),
68 - }),
69 - );
70 -}
71 -
72 -type Props = {
73 - canvasRef: {current: HTMLCanvasElement | null},
74 - hoveredEvent: ReactEventInfo | null,
75 - timelineData: TimelineData,
76 - viewState: ViewState,
77 - canvasWidth: number,
78 - closedMenuStub: React.Node,
79 - ref: ContextMenuRef,
80 -};
81 -
82 -export default function CanvasPageContextMenu({
83 - canvasRef,
84 - timelineData,
85 - hoveredEvent,
86 - viewState,
87 - canvasWidth,
88 - closedMenuStub,
89 - ref,
90 -}: Props): React.Node {
91 - const menuItems = useMemo<ContextMenuItem[]>(() => {
92 - if (hoveredEvent == null) {
93 - return [];
94 - }
95 -
96 - const {
97 - componentMeasure,
98 - flamechartStackFrame,
99 - measure,
100 - networkMeasure,
101 - schedulingEvent,
102 - suspenseEvent,
103 - } = hoveredEvent;
104 - const items: ContextMenuItem[] = [];
105 -
106 - if (componentMeasure != null) {
107 - items.push({
108 - onClick: () => copy(componentMeasure.componentName),
109 - content: 'Copy component name',
110 - });
111 - }
112 -
113 - if (networkMeasure != null) {
114 - items.push({
115 - onClick: () => copy(networkMeasure.url),
116 - content: 'Copy URL',
117 - });
118 - }
119 -
120 - if (schedulingEvent != null) {
121 - items.push({
122 - onClick: () => copy(schedulingEvent.componentName),
123 - content: 'Copy component name',
124 - });
125 - }
126 -
127 - if (suspenseEvent != null) {
128 - items.push({
129 - onClick: () => copy(suspenseEvent.componentName),
130 - content: 'Copy component name',
131 - });
132 - }
133 -
134 - if (measure != null) {
135 - items.push(
136 - {
137 - onClick: () =>
138 - zoomToBatch(timelineData, measure, viewState, canvasWidth),
139 - content: 'Zoom to batch',
140 - },
141 - {
142 - onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
143 - copySummary(timelineData, measure),
144 - ),
145 - content: 'Copy summary',
146 - },
147 - );
148 - }
149 -
150 - if (flamechartStackFrame != null) {
151 - items.push(
152 - {
153 - onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
154 - copy(flamechartStackFrame.scriptUrl),
155 - ),
156 - content: 'Copy file path',
157 - },
158 - {
159 - onClick: withPermissionsCheck({permissions: ['clipboardWrite']}, () =>
160 - copy(
161 - `line ${flamechartStackFrame.locationLine ?? ''}, column ${
162 - flamechartStackFrame.locationColumn ?? ''
163 - }`,
164 - ),
165 - ),
166 - content: 'Copy location',
167 - },
168 - );
169 - }
170 -
171 - return items;
172 - }, [hoveredEvent, viewState, canvasWidth]);
173 -
174 - return (
175 - <ContextMenuContainer
176 - anchorElementRef={canvasRef}
177 - items={menuItems}
178 - closedMenuStub={closedMenuStub}
179 - ref={ref}
180 - />
181 - );
182 -}
packages/react-devtools-timeline/src/EventTooltip.css deleted
-88
@@ -1,88 +0,0 @@
1 -.Tooltip {
2 - position: fixed;
3 -}
4 -
5 -.TooltipSection,
6 -.TooltipWarningSection,
7 -.SingleLineTextSection {
8 - display: block;
9 - border-radius: 0.125rem;
10 - padding: 0.25rem;
11 - user-select: none;
12 - pointer-events: none;
13 - background-color: var(--color-tooltip-background);
14 - box-shadow: 1px 1px 2px var(--color-shadow);
15 - color: var(--color-tooltip-text);
16 - font-size: 11px;
17 -}
18 -.TooltipWarningSection {
19 - margin-top: 0.25rem;
20 - background-color: var(--color-warning-background);
21 -}
22 -.TooltipSection,
23 -.TooltipWarningSection {
24 - max-width: 300px;
25 -}
26 -.SingleLineTextSection {
27 - white-space: nowrap;
28 -}
29 -
30 -.Divider {
31 - height: 1px;
32 - background-color: #aaa;
33 - margin: 0.25rem 0;
34 -}
35 -
36 -.DetailsGrid {
37 - display: grid;
38 - padding-top: 5px;
39 - grid-gap: 2px 5px;
40 - grid-template-columns: min-content auto;
41 -}
42 -
43 -.DetailsGridLabel {
44 - color: var(--color-dim);
45 - text-align: right;
46 - white-space: nowrap;
47 -}
48 -
49 -.DetailsGridLongValue {
50 - word-break: break-all;
51 - max-height: 50vh;
52 - overflow: hidden;
53 -}
54 -
55 -.FlamechartStackFrameName {
56 - word-break: break-word;
57 -}
58 -
59 -.ComponentName {
60 - font-weight: bold;
61 - word-break: break-word;
62 - margin-right: 0.25rem;
63 -}
64 -
65 -.ReactMeasureLabel {
66 -}
67 -
68 -.UserTimingLabel {
69 - word-break: break-word;
70 -}
71 -
72 -.NativeEventName {
73 - font-weight: bold;
74 - word-break: break-word;
75 - margin-right: 0.25rem;
76 -}
77 -
78 -.WarningText {
79 - color: var(--color-warning-text-color);
80 -}
81 -
82 -.Image {
83 - border: 1px solid var(--color-border);
84 -}
85 -
86 -.DimText {
87 - color: var(--color-dim);
88 -}
\ No newline at end of file
packages/react-devtools-timeline/src/EventTooltip.js deleted
-513
@@ -1,513 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Point} from './view-base';
11 -import type {
12 - FlamechartStackFrame,
13 - NativeEvent,
14 - NetworkMeasure,
15 - ReactComponentMeasure,
16 - ReactEventInfo,
17 - ReactMeasure,
18 - ReactMeasureType,
19 - SchedulingEvent,
20 - Snapshot,
21 - SuspenseEvent,
22 - ThrownError,
23 - TimelineData,
24 - UserTimingMark,
25 -} from './types';
26 -
27 -import * as React from 'react';
28 -import {
29 - formatDuration,
30 - formatTimestamp,
31 - trimString,
32 - getSchedulingEventLabel,
33 -} from './utils/formatting';
34 -import {getBatchRange} from './utils/getBatchRange';
35 -import useSmartTooltip from './utils/useSmartTooltip';
36 -import styles from './EventTooltip.css';
37 -
38 -const MAX_TOOLTIP_TEXT_LENGTH = 60;
39 -
40 -type Props = {
41 - canvasRef: {current: HTMLCanvasElement | null},
42 - data: TimelineData,
43 - height: number,
44 - hoveredEvent: ReactEventInfo | null,
45 - origin: Point,
46 - width: number,
47 -};
48 -
49 -function getReactMeasureLabel(type: ReactMeasureType): string | null {
50 - switch (type) {
51 - case 'commit':
52 - return 'react commit';
53 - case 'render-idle':
54 - return 'react idle';
55 - case 'render':
56 - return 'react render';
57 - case 'layout-effects':
58 - return 'react layout effects';
59 - case 'passive-effects':
60 - return 'react passive effects';
61 - default:
62 - return null;
63 - }
64 -}
65 -
66 -export default function EventTooltip({
67 - canvasRef,
68 - data,
69 - height,
70 - hoveredEvent,
71 - origin,
72 - width,
73 -}: Props): React.Node {
74 - const ref = useSmartTooltip({
75 - canvasRef,
76 - mouseX: origin.x,
77 - mouseY: origin.y,
78 - });
79 -
80 - if (hoveredEvent === null) {
81 - return null;
82 - }
83 -
84 - const {
85 - componentMeasure,
86 - flamechartStackFrame,
87 - measure,
88 - nativeEvent,
89 - networkMeasure,
90 - schedulingEvent,
91 - snapshot,
92 - suspenseEvent,
93 - thrownError,
94 - userTimingMark,
95 - } = hoveredEvent;
96 -
97 - let content = null;
98 - if (componentMeasure !== null) {
99 - content = (
100 - <TooltipReactComponentMeasure componentMeasure={componentMeasure} />
101 - );
102 - } else if (nativeEvent !== null) {
103 - content = <TooltipNativeEvent nativeEvent={nativeEvent} />;
104 - } else if (networkMeasure !== null) {
105 - content = <TooltipNetworkMeasure networkMeasure={networkMeasure} />;
106 - } else if (schedulingEvent !== null) {
107 - content = (
108 - <TooltipSchedulingEvent data={data} schedulingEvent={schedulingEvent} />
109 - );
110 - } else if (snapshot !== null) {
111 - content = (
112 - <TooltipSnapshot height={height} snapshot={snapshot} width={width} />
113 - );
114 - } else if (suspenseEvent !== null) {
115 - content = <TooltipSuspenseEvent suspenseEvent={suspenseEvent} />;
116 - } else if (measure !== null) {
117 - content = <TooltipReactMeasure data={data} measure={measure} />;
118 - } else if (flamechartStackFrame !== null) {
119 - content = <TooltipFlamechartNode stackFrame={flamechartStackFrame} />;
120 - } else if (userTimingMark !== null) {
121 - content = <TooltipUserTimingMark mark={userTimingMark} />;
122 - } else if (thrownError !== null) {
123 - content = <TooltipThrownError thrownError={thrownError} />;
124 - }
125 -
126 - if (content !== null) {
127 - return (
128 - <div className={styles.Tooltip} ref={ref}>
129 - {content}
130 - </div>
131 - );
132 - } else {
133 - return null;
134 - }
135 -}
136 -
137 -const TooltipReactComponentMeasure = ({
138 - componentMeasure,
139 -}: {
140 - componentMeasure: ReactComponentMeasure,
141 -}) => {
142 - const {componentName, duration, timestamp, type, warning} = componentMeasure;
143 -
144 - let label = componentName;
145 - switch (type) {
146 - case 'render':
147 - label += ' rendered';
148 - break;
149 - case 'layout-effect-mount':
150 - label += ' mounted layout effect';
151 - break;
152 - case 'layout-effect-unmount':
153 - label += ' unmounted layout effect';
154 - break;
155 - case 'passive-effect-mount':
156 - label += ' mounted passive effect';
157 - break;
158 - case 'passive-effect-unmount':
159 - label += ' unmounted passive effect';
160 - break;
161 - }
162 -
163 - return (
164 - <>
165 - <div className={styles.TooltipSection}>
166 - {trimString(label, 768)}
167 - <div className={styles.Divider} />
168 - <div className={styles.DetailsGrid}>
169 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
170 - <div>{formatTimestamp(timestamp)}</div>
171 - <div className={styles.DetailsGridLabel}>Duration:</div>
172 - <div>{formatDuration(duration)}</div>
173 - </div>
174 - </div>
175 - {warning !== null && (
176 - <div className={styles.TooltipWarningSection}>
177 - <div className={styles.WarningText}>{warning}</div>
178 - </div>
179 - )}
180 - </>
181 - );
182 -};
183 -
184 -const TooltipFlamechartNode = ({
185 - stackFrame,
186 -}: {
187 - stackFrame: FlamechartStackFrame,
188 -}) => {
189 - const {name, timestamp, duration, locationLine, locationColumn} = stackFrame;
190 - return (
191 - <div className={styles.TooltipSection}>
192 - <span className={styles.FlamechartStackFrameName}>{name}</span>
193 - <div className={styles.DetailsGrid}>
194 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
195 - <div>{formatTimestamp(timestamp)}</div>
196 - <div className={styles.DetailsGridLabel}>Duration:</div>
197 - <div>{formatDuration(duration)}</div>
198 - {(locationLine !== undefined || locationColumn !== undefined) && (
199 - <>
200 - <div className={styles.DetailsGridLabel}>Location:</div>
201 - <div>
202 - line {locationLine}, column {locationColumn}
203 - </div>
204 - </>
205 - )}
206 - </div>
207 - </div>
208 - );
209 -};
210 -
211 -const TooltipNativeEvent = ({nativeEvent}: {nativeEvent: NativeEvent}) => {
212 - const {duration, timestamp, type, warning} = nativeEvent;
213 -
214 - return (
215 - <>
216 - <div className={styles.TooltipSection}>
217 - <span className={styles.NativeEventName}>{trimString(type, 768)}</span>
218 - event
219 - <div className={styles.Divider} />
220 - <div className={styles.DetailsGrid}>
221 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
222 - <div>{formatTimestamp(timestamp)}</div>
223 - <div className={styles.DetailsGridLabel}>Duration:</div>
224 - <div>{formatDuration(duration)}</div>
225 - </div>
226 - </div>
227 - {warning !== null && (
228 - <div className={styles.TooltipWarningSection}>
229 - <div className={styles.WarningText}>{warning}</div>
230 - </div>
231 - )}
232 - </>
233 - );
234 -};
235 -
236 -const TooltipNetworkMeasure = ({
237 - networkMeasure,
238 -}: {
239 - networkMeasure: NetworkMeasure,
240 -}) => {
241 - const {
242 - finishTimestamp,
243 - lastReceivedDataTimestamp,
244 - priority,
245 - sendRequestTimestamp,
246 - url,
247 - } = networkMeasure;
248 -
249 - let urlToDisplay = url;
250 - if (urlToDisplay.length > MAX_TOOLTIP_TEXT_LENGTH) {
251 - const half = Math.floor(MAX_TOOLTIP_TEXT_LENGTH / 2);
252 - urlToDisplay = url.slice(0, half) + '…' + url.slice(url.length - half);
253 - }
254 -
255 - const timestampBegin = sendRequestTimestamp;
256 - const timestampEnd = finishTimestamp || lastReceivedDataTimestamp;
257 - const duration =
258 - timestampEnd > 0
259 - ? formatDuration(finishTimestamp - timestampBegin)
260 - : '(incomplete)';
261 -
262 - return (
263 - <div className={styles.SingleLineTextSection}>
264 - {duration} <span className={styles.DimText}>{priority}</span>{' '}
265 - {urlToDisplay}
266 - </div>
267 - );
268 -};
269 -
270 -const TooltipSchedulingEvent = ({
271 - data,
272 - schedulingEvent,
273 -}: {
274 - data: TimelineData,
275 - schedulingEvent: SchedulingEvent,
276 -}) => {
277 - const label = getSchedulingEventLabel(schedulingEvent);
278 - if (!label) {
279 - if (__DEV__) {
280 - console.warn(
281 - 'Unexpected schedulingEvent type "%s"',
282 - schedulingEvent.type,
283 - );
284 - }
285 - return null;
286 - }
287 -
288 - let laneLabels = null;
289 - let lanes = null;
290 - switch (schedulingEvent.type) {
291 - case 'schedule-render':
292 - case 'schedule-state-update':
293 - case 'schedule-force-update':
294 - lanes = schedulingEvent.lanes;
295 - laneLabels = lanes.map(
296 - lane => data.laneToLabelMap.get(lane) as any as string,
297 - );
298 - break;
299 - }
300 -
301 - const {componentName, timestamp, warning} = schedulingEvent;
302 -
303 - return (
304 - <>
305 - <div className={styles.TooltipSection}>
306 - {componentName && (
307 - <span className={styles.ComponentName}>
308 - {trimString(componentName, 100)}
309 - </span>
310 - )}
311 - {label}
312 - <div className={styles.Divider} />
313 - <div className={styles.DetailsGrid}>
314 - {laneLabels !== null && lanes !== null && (
315 - <>
316 - <div className={styles.DetailsGridLabel}>Lanes:</div>
317 - <div>
318 - {laneLabels.join(', ')} ({lanes.join(', ')})
319 - </div>
320 - </>
321 - )}
322 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
323 - <div>{formatTimestamp(timestamp)}</div>
324 - </div>
325 - </div>
326 - {warning !== null && (
327 - <div className={styles.TooltipWarningSection}>
328 - <div className={styles.WarningText}>{warning}</div>
329 - </div>
330 - )}
331 - </>
332 - );
333 -};
334 -
335 -const TooltipSnapshot = ({
336 - height,
337 - snapshot,
338 - width,
339 -}: {
340 - height: number,
341 - snapshot: Snapshot,
342 - width: number,
343 -}) => {
344 - const aspectRatio = snapshot.width / snapshot.height;
345 -
346 - // Zoomed in view should not be any bigger than the DevTools viewport.
347 - let safeWidth = snapshot.width;
348 - let safeHeight = snapshot.height;
349 - if (safeWidth > width) {
350 - safeWidth = width;
351 - safeHeight = safeWidth / aspectRatio;
352 - }
353 - if (safeHeight > height) {
354 - safeHeight = height;
355 - safeWidth = safeHeight * aspectRatio;
356 - }
357 -
358 - return (
359 - <img
360 - className={styles.Image}
361 - src={snapshot.imageSource}
362 - style={{height: safeHeight, width: safeWidth}}
363 - />
364 - );
365 -};
366 -
367 -const TooltipSuspenseEvent = ({
368 - suspenseEvent,
369 -}: {
370 - suspenseEvent: SuspenseEvent,
371 -}) => {
372 - const {
373 - componentName,
374 - duration,
375 - phase,
376 - promiseName,
377 - resolution,
378 - timestamp,
379 - warning,
380 - } = suspenseEvent;
381 -
382 - let label = 'suspended';
383 - if (phase !== null) {
384 - label += ` during ${phase}`;
385 - }
386 -
387 - return (
388 - <>
389 - <div className={styles.TooltipSection}>
390 - {componentName && (
391 - <span className={styles.ComponentName}>
392 - {trimString(componentName, 100)}
393 - </span>
394 - )}
395 - {label}
396 - <div className={styles.Divider} />
397 - <div className={styles.DetailsGrid}>
398 - {promiseName !== null && (
399 - <>
400 - <div className={styles.DetailsGridLabel}>Resource:</div>
401 - <div className={styles.DetailsGridLongValue}>{promiseName}</div>
402 - </>
403 - )}
404 - <div className={styles.DetailsGridLabel}>Status:</div>
405 - <div>{resolution}</div>
406 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
407 - <div>{formatTimestamp(timestamp)}</div>
408 - {duration !== null && (
409 - <>
410 - <div className={styles.DetailsGridLabel}>Duration:</div>
411 - <div>{formatDuration(duration)}</div>
412 - </>
413 - )}
414 - </div>
415 - </div>
416 - {warning !== null && (
417 - <div className={styles.TooltipWarningSection}>
418 - <div className={styles.WarningText}>{warning}</div>
419 - </div>
420 - )}
421 - </>
422 - );
423 -};
424 -
425 -const TooltipReactMeasure = ({
426 - data,
427 - measure,
428 -}: {
429 - data: TimelineData,
430 - measure: ReactMeasure,
431 -}) => {
432 - const label = getReactMeasureLabel(measure.type);
433 - if (!label) {
434 - if (__DEV__) {
435 - console.warn('Unexpected measure type "%s"', measure.type);
436 - }
437 - return null;
438 - }
439 -
440 - const {batchUID, duration, timestamp, lanes} = measure;
441 - const [startTime, stopTime] = getBatchRange(batchUID, data);
442 -
443 - const laneLabels = lanes.map(
444 - lane => data.laneToLabelMap.get(lane) as any as string,
445 - );
446 -
447 - return (
448 - <div className={styles.TooltipSection}>
449 - <span className={styles.ReactMeasureLabel}>{label}</span>
450 - <div className={styles.Divider} />
451 - <div className={styles.DetailsGrid}>
452 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
453 - <div>{formatTimestamp(timestamp)}</div>
454 - {measure.type !== 'render-idle' && (
455 - <>
456 - <div className={styles.DetailsGridLabel}>Duration:</div>
457 - <div>{formatDuration(duration)}</div>
458 - </>
459 - )}
460 - <div className={styles.DetailsGridLabel}>Batch duration:</div>
461 - <div>{formatDuration(stopTime - startTime)}</div>
462 - <div className={styles.DetailsGridLabel}>
463 - Lane{lanes.length === 1 ? '' : 's'}:
464 - </div>
465 - <div>
466 - {laneLabels.length > 0
467 - ? `${laneLabels.join(', ')} (${lanes.join(', ')})`
468 - : lanes.join(', ')}
469 - </div>
470 - </div>
471 - </div>
472 - );
473 -};
474 -
475 -const TooltipUserTimingMark = ({mark}: {mark: UserTimingMark}) => {
476 - const {name, timestamp} = mark;
477 - return (
478 - <div className={styles.TooltipSection}>
479 - <span className={styles.UserTimingLabel}>{name}</span>
480 - <div className={styles.Divider} />
481 - <div className={styles.DetailsGrid}>
482 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
483 - <div>{formatTimestamp(timestamp)}</div>
484 - </div>
485 - </div>
486 - );
487 -};
488 -
489 -const TooltipThrownError = ({thrownError}: {thrownError: ThrownError}) => {
490 - const {componentName, message, phase, timestamp} = thrownError;
491 - const label = `threw an error during ${phase}`;
492 - return (
493 - <div className={styles.TooltipSection}>
494 - {componentName && (
495 - <span className={styles.ComponentName}>
496 - {trimString(componentName, 100)}
497 - </span>
498 - )}
499 - <span className={styles.UserTimingLabel}>{label}</span>
500 - <div className={styles.Divider} />
501 - <div className={styles.DetailsGrid}>
502 - <div className={styles.DetailsGridLabel}>Timestamp:</div>
503 - <div>{formatTimestamp(timestamp)}</div>
504 - {message !== '' && (
505 - <>
506 - <div className={styles.DetailsGridLabel}>Error:</div>
507 - <div>{message}</div>
508 - </>
509 - )}
510 - </div>
511 - </div>
512 - );
513 -};
packages/react-devtools-timeline/src/Timeline.css deleted
-34
@@ -1,34 +0,0 @@
1 -.Content {
2 - width: 100%;
3 - position: relative;
4 - flex: 1 1 auto;
5 - display: flex;
6 - flex-direction: column;
7 - align-items: center;
8 - justify-content: center;
9 -}
10 -
11 -
12 -.ErrorMessage {
13 - margin: 0.5rem 0;
14 - color: var(--color-dim);
15 - font-family: var(--font-family-monospace);
16 - font-size: var(--font-size-monospace-normal);
17 -}
18 -
19 -.Row {
20 - display: flex;
21 - flex-direction: row;
22 - align-items: center;
23 - justify-content: center;
24 - flex-flow: wrap;
25 -}
26 -
27 -.EmptyStateContainer {
28 - text-align: center;
29 -}
30 -
31 -.Header {
32 - font-size: var(--font-size-sans-large);
33 - margin-bottom: 0.5rem;
34 -}
packages/react-devtools-timeline/src/Timeline.js deleted
-176
@@ -1,176 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ViewState} from './types';
11 -
12 -import * as React from 'react';
13 -import {
14 - Suspense,
15 - useContext,
16 - useDeferredValue,
17 - useLayoutEffect,
18 - useRef,
19 - useState,
20 -} from 'react';
21 -import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
22 -import {ProfilerContext} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext';
23 -import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
24 -import RecordingInProgress from 'react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress';
25 -import {updateColorsToMatchTheme} from './content-views/constants';
26 -import {TimelineContext} from './TimelineContext';
27 -import CanvasPage from './CanvasPage';
28 -import {importFile} from './timelineCache';
29 -import TimelineSearchInput from './TimelineSearchInput';
30 -import TimelineNotSupported from './TimelineNotSupported';
31 -import {TimelineSearchContextController} from './TimelineSearchContext';
32 -
33 -import styles from './Timeline.css';
34 -
35 -export function Timeline(_: {}): React.Node {
36 - const {
37 - file,
38 - inMemoryTimelineData,
39 - isPerformanceTracksSupported,
40 - isTimelineSupported,
41 - setFile,
42 - viewState,
43 - } = useContext(TimelineContext);
44 - const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
45 -
46 - const ref = useRef(null);
47 -
48 - // HACK: Canvas rendering uses an imperative API,
49 - // but DevTools colors are stored in CSS variables (see root.css and SettingsContext).
50 - // When the theme changes, we need to trigger update the imperative colors and re-draw the Canvas.
51 - const {theme} = useContext(SettingsContext);
52 - // HACK: SettingsContext also uses a useLayoutEffect to update styles;
53 - // make sure the theme context in SettingsContext updates before this code.
54 - const deferredTheme = useDeferredValue(theme);
55 - // HACK: Schedule a re-render of the Canvas once colors have been updated.
56 - // The easiest way to guarangee this happens is to recreate the inner Canvas component.
57 - const [key, setKey] = useState<string>(theme);
58 - useLayoutEffect(() => {
59 - const pollForTheme = () => {
60 - if (updateColorsToMatchTheme(ref.current as any as HTMLDivElement)) {
61 - clearInterval(intervalID);
62 - setKey(deferredTheme);
63 - }
64 - };
65 -
66 - const intervalID = setInterval(pollForTheme, 50);
67 -
68 - return () => {
69 - clearInterval(intervalID);
70 - };
71 - }, [deferredTheme]);
72 -
73 - let content = null;
74 - if (isProfiling) {
75 - content = <RecordingInProgress />;
76 - } else if (inMemoryTimelineData && inMemoryTimelineData.length > 0) {
77 - // TODO (timeline) Support multiple renderers.
78 - const timelineData = inMemoryTimelineData[0];
79 -
80 - content = (
81 - <TimelineSearchContextController
82 - profilerData={timelineData}
83 - viewState={viewState}>
84 - <TimelineSearchInput />
85 - <CanvasPage profilerData={timelineData} viewState={viewState} />
86 - </TimelineSearchContextController>
87 - );
88 - } else if (file) {
89 - content = (
90 - <Suspense fallback={<ProcessingData />}>
91 - <FileLoader
92 - file={file}
93 - key={key}
94 - onFileSelect={setFile}
95 - viewState={viewState}
96 - />
97 - </Suspense>
98 - );
99 - } else if (didRecordCommits) {
100 - content = <NoTimelineData />;
101 - } else if (isTimelineSupported) {
102 - content = <NoProfilingData />;
103 - } else {
104 - content = (
105 - <TimelineNotSupported
106 - isPerformanceTracksSupported={isPerformanceTracksSupported}
107 - />
108 - );
109 - }
110 -
111 - return (
112 - <div className={styles.Content} ref={ref}>
113 - {content}
114 - </div>
115 - );
116 -}
117 -
118 -const ProcessingData = () => (
119 - <div className={styles.EmptyStateContainer}>
120 - <div className={styles.Header}>Processing data...</div>
121 - <div className={styles.Row}>This should only take a minute.</div>
122 - </div>
123 -);
124 -
125 -// $FlowFixMe[missing-local-annot]
126 -const CouldNotLoadProfile = ({error, onFileSelect}) => (
127 - <div className={styles.EmptyStateContainer}>
128 - <div className={styles.Header}>Could not load profile</div>
129 - {error.message && (
130 - <div className={styles.Row}>
131 - <div className={styles.ErrorMessage}>{error.message}</div>
132 - </div>
133 - )}
134 - <div className={styles.Row}>
135 - Try importing another Chrome performance profile.
136 - </div>
137 - </div>
138 -);
139 -
140 -const NoTimelineData = () => (
141 - <div className={styles.EmptyStateContainer}>
142 - <div className={styles.Row}>
143 - This current profile does not contain timeline data.
144 - </div>
145 - </div>
146 -);
147 -
148 -const FileLoader = ({
149 - file,
150 - onFileSelect,
151 - viewState,
152 -}: {
153 - file: File | null,
154 - onFileSelect: (file: File) => void,
155 - viewState: ViewState,
156 -}) => {
157 - if (file === null) {
158 - return null;
159 - }
160 -
161 - const dataOrError = importFile(file);
162 - if (dataOrError instanceof Error) {
163 - return (
164 - <CouldNotLoadProfile error={dataOrError} onFileSelect={onFileSelect} />
165 - );
166 - }
167 -
168 - return (
169 - <TimelineSearchContextController
170 - profilerData={dataOrError}
171 - viewState={viewState}>
172 - <TimelineSearchInput />
173 - <CanvasPage profilerData={dataOrError} viewState={viewState} />
174 - </TimelineSearchContextController>
175 - );
176 -};
packages/react-devtools-timeline/src/TimelineContext.js deleted
-178
@@ -1,178 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ReactContext, RefObject} from 'shared/ReactTypes';
11 -
12 -import * as React from 'react';
13 -import {
14 - createContext,
15 - useContext,
16 - useMemo,
17 - useRef,
18 - useState,
19 - useSyncExternalStore,
20 -} from 'react';
21 -import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
22 -
23 -import type {
24 - HorizontalScrollStateChangeCallback,
25 - TimelineData,
26 - SearchRegExpStateChangeCallback,
27 - ViewState,
28 - ReactEventInfo,
29 -} from './types';
30 -
31 -export type Context = {
32 - file: File | null,
33 - inMemoryTimelineData: Array<TimelineData> | null,
34 - isPerformanceTracksSupported: boolean,
35 - isTimelineSupported: boolean,
36 - searchInputContainerRef: RefObject,
37 - setFile: (file: File | null) => void,
38 - viewState: ViewState,
39 - selectEvent: ReactEventInfo => void,
40 - selectedEvent: ReactEventInfo,
41 -};
42 -
43 -const TimelineContext: ReactContext<Context> = createContext<Context>(
44 - null as any as Context,
45 -);
46 -TimelineContext.displayName = 'TimelineContext';
47 -
48 -type Props = {
49 - children: React$Node,
50 -};
51 -
52 -function TimelineContextController({children}: Props): React.Node {
53 - const searchInputContainerRef = useRef(null);
54 - const [file, setFile] = useState<string | null>(null);
55 -
56 - const store = useContext(StoreContext);
57 -
58 - const isTimelineSupported = useSyncExternalStore<boolean>(
59 - function subscribe(callback) {
60 - store.addListener('rootSupportsTimelineProfiling', callback);
61 - return function unsubscribe() {
62 - store.removeListener('rootSupportsTimelineProfiling', callback);
63 - };
64 - },
65 - function getState() {
66 - return store.rootSupportsTimelineProfiling;
67 - },
68 - );
69 -
70 - const isPerformanceTracksSupported = useSyncExternalStore<boolean>(
71 - function subscribe(callback) {
72 - store.addListener('rootSupportsPerformanceTracks', callback);
73 - return function unsubscribe() {
74 - store.removeListener('rootSupportsPerformanceTracks', callback);
75 - };
76 - },
77 - function getState() {
78 - return store.rootSupportsPerformanceTracks;
79 - },
80 - );
81 -
82 - const inMemoryTimelineData = useSyncExternalStore<Array<TimelineData> | null>(
83 - function subscribe(callback) {
84 - store.profilerStore.addListener('isProcessingData', callback);
85 - store.profilerStore.addListener('profilingData', callback);
86 - return function unsubscribe() {
87 - store.profilerStore.removeListener('isProcessingData', callback);
88 - store.profilerStore.removeListener('profilingData', callback);
89 - };
90 - },
91 - function getState() {
92 - return store.profilerStore.profilingData?.timelineData || null;
93 - },
94 - );
95 -
96 - // Recreate view state any time new profiling data is imported.
97 - const viewState = useMemo<ViewState>(() => {
98 - const horizontalScrollStateChangeCallbacks: Set<HorizontalScrollStateChangeCallback> =
99 - new Set();
100 - const searchRegExpStateChangeCallbacks: Set<SearchRegExpStateChangeCallback> =
101 - new Set();
102 -
103 - const horizontalScrollState = {
104 - offset: 0,
105 - length: 0,
106 - };
107 -
108 - const state: ViewState = {
109 - horizontalScrollState,
110 - onHorizontalScrollStateChange: callback => {
111 - horizontalScrollStateChangeCallbacks.add(callback);
112 - },
113 - onSearchRegExpStateChange: callback => {
114 - searchRegExpStateChangeCallbacks.add(callback);
115 - },
116 - searchRegExp: null,
117 - updateHorizontalScrollState: scrollState => {
118 - if (
119 - horizontalScrollState.offset === scrollState.offset &&
120 - horizontalScrollState.length === scrollState.length
121 - ) {
122 - return;
123 - }
124 -
125 - horizontalScrollState.offset = scrollState.offset;
126 - horizontalScrollState.length = scrollState.length;
127 -
128 - horizontalScrollStateChangeCallbacks.forEach(callback => {
129 - callback(scrollState);
130 - });
131 - },
132 - updateSearchRegExpState: (searchRegExp: RegExp | null) => {
133 - state.searchRegExp = searchRegExp;
134 -
135 - searchRegExpStateChangeCallbacks.forEach(callback => {
136 - callback(searchRegExp);
137 - });
138 - },
139 - viewToMutableViewStateMap: new Map(),
140 - };
141 -
142 - return state;
143 - }, [file]);
144 -
145 - const [selectedEvent, selectEvent] = useState<ReactEventInfo | null>(null);
146 -
147 - const value = useMemo(
148 - () => ({
149 - file,
150 - inMemoryTimelineData,
151 - isPerformanceTracksSupported,
152 - isTimelineSupported,
153 - searchInputContainerRef,
154 - setFile,
155 - viewState,
156 - selectEvent,
157 - selectedEvent,
158 - }),
159 - [
160 - file,
161 - inMemoryTimelineData,
162 - isPerformanceTracksSupported,
163 - isTimelineSupported,
164 - setFile,
165 - viewState,
166 - selectEvent,
167 - selectedEvent,
168 - ],
169 - );
170 -
171 - return (
172 - <TimelineContext.Provider value={value}>
173 - {children}
174 - </TimelineContext.Provider>
175 - );
176 -}
177 -
178 -export {TimelineContext, TimelineContextController};
packages/react-devtools-timeline/src/TimelineNotSupported.css deleted
-38
@@ -1,38 +0,0 @@
1 -.Column {
2 - display: flex;
3 - flex-direction: column;
4 - align-items: center;
5 - justify-content: center;
6 - padding: 0 1rem;
7 -}
8 -
9 -.Header {
10 - font-size: var(--font-size-sans-large);
11 - margin-bottom: 0.5rem;
12 -}
13 -
14 -.Paragraph {
15 - text-align: center;
16 - margin: 0;
17 -}
18 -
19 -.Link {
20 - color: var(--color-link);
21 -}
22 -
23 -.LearnMoreRow {
24 - margin-top: 1rem;
25 - color: var(--color-dim);
26 - font-size: var(--font-size-sans-small);
27 -}
28 -
29 -.Code {
30 - color: var(--color-bridge-version-number);
31 -}
32 -
33 -.MetaGKRow {
34 - background: var(--color-background-hover);
35 - padding: 0.25rem 0.5rem;
36 - border-radius: 0.25rem;
37 - margin-top: 1rem;
38 -}
\ No newline at end of file
packages/react-devtools-timeline/src/TimelineNotSupported.js deleted
-100
@@ -1,100 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {isInternalFacebookBuild} from 'react-devtools-feature-flags';
12 -
13 -import styles from './TimelineNotSupported.css';
14 -
15 -type Props = {
16 - isPerformanceTracksSupported: boolean,
17 -};
18 -
19 -function PerformanceTracksSupported() {
20 - return (
21 - <>
22 - <p className={styles.Paragraph}>
23 - <span>
24 - Please use{' '}
25 - <a
26 - className={styles.Link}
27 - href="https://react.dev/reference/dev-tools/react-performance-tracks"
28 - rel="noopener noreferrer"
29 - target="_blank">
30 - React Performance tracks
31 - </a>{' '}
32 - instead of the Timeline profiler.
33 - </span>
34 - </p>
35 - </>
36 - );
37 -}
38 -
39 -function UnknownUnsupportedReason() {
40 - return (
41 - <>
42 - <p className={styles.Paragraph}>
43 - Timeline profiler requires a development or profiling build of{' '}
44 - <code className={styles.Code}>react-dom@{'>='}18</code>.
45 - </p>
46 - <p className={styles.Paragraph}>
47 - In React 19.2 and above{' '}
48 - <a
49 - className={styles.Link}
50 - href="https://react.dev/reference/dev-tools/react-performance-tracks"
51 - rel="noopener noreferrer"
52 - target="_blank">
53 - React Performance tracks
54 - </a>{' '}
55 - can be used instead.
56 - </p>
57 - <div className={styles.LearnMoreRow}>
58 - Click{' '}
59 - <a
60 - className={styles.Link}
61 - href="https://fb.me/react-devtools-profiling"
62 - rel="noopener noreferrer"
63 - target="_blank">
64 - here
65 - </a>{' '}
66 - to learn more about profiling.
67 - </div>
68 - </>
69 - );
70 -}
71 -
72 -export default function TimelineNotSupported({
73 - isPerformanceTracksSupported,
74 -}: Props): React.Node {
75 - return (
76 - <div className={styles.Column}>
77 - <div className={styles.Header}>Timeline profiling not supported.</div>
78 -
79 - {isPerformanceTracksSupported ? (
80 - <PerformanceTracksSupported />
81 - ) : (
82 - <UnknownUnsupportedReason />
83 - )}
84 -
85 - {isInternalFacebookBuild && (
86 - <div className={styles.MetaGKRow}>
87 - <strong>Meta only</strong>: Enable the{' '}
88 - <a
89 - className={styles.Link}
90 - href="https://fburl.com/react-devtools-scheduling-profiler-gk"
91 - rel="noopener noreferrer"
92 - target="_blank">
93 - react_enable_scheduling_profiler GK
94 - </a>
95 - .
96 - </div>
97 - )}
98 - </div>
99 - );
100 -}
packages/react-devtools-timeline/src/TimelineSearchContext.js deleted
-166
@@ -1,166 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ReactContext} from 'shared/ReactTypes';
11 -
12 -import * as React from 'react';
13 -import {createContext, useMemo, useReducer} from 'react';
14 -
15 -import type {ReactComponentMeasure, TimelineData, ViewState} from './types';
16 -
17 -type State = {
18 - profilerData: TimelineData,
19 - searchIndex: number,
20 - searchRegExp: RegExp | null,
21 - searchResults: Array<ReactComponentMeasure>,
22 - searchText: string,
23 -};
24 -
25 -type ACTION_GO_TO_NEXT_SEARCH_RESULT = {
26 - type: 'GO_TO_NEXT_SEARCH_RESULT',
27 -};
28 -type ACTION_GO_TO_PREVIOUS_SEARCH_RESULT = {
29 - type: 'GO_TO_PREVIOUS_SEARCH_RESULT',
30 -};
31 -type ACTION_SET_SEARCH_TEXT = {
32 - type: 'SET_SEARCH_TEXT',
33 - payload: string,
34 -};
35 -
36 -type Action =
37 - | ACTION_GO_TO_NEXT_SEARCH_RESULT
38 - | ACTION_GO_TO_PREVIOUS_SEARCH_RESULT
39 - | ACTION_SET_SEARCH_TEXT;
40 -
41 -type Dispatch = (action: Action) => void;
42 -
43 -const EMPTY_ARRAY: Array<ReactComponentMeasure> = [];
44 -
45 -function reducer(state: State, action: Action): State {
46 - let {searchIndex, searchRegExp, searchResults, searchText} = state;
47 -
48 - switch (action.type) {
49 - case 'GO_TO_NEXT_SEARCH_RESULT':
50 - if (searchResults.length > 0) {
51 - if (searchIndex === -1 || searchIndex + 1 === searchResults.length) {
52 - searchIndex = 0;
53 - } else {
54 - searchIndex++;
55 - }
56 - }
57 - break;
58 - case 'GO_TO_PREVIOUS_SEARCH_RESULT':
59 - if (searchResults.length > 0) {
60 - if (searchIndex === -1 || searchIndex === 0) {
61 - searchIndex = searchResults.length - 1;
62 - } else {
63 - searchIndex--;
64 - }
65 - }
66 - break;
67 - case 'SET_SEARCH_TEXT':
68 - searchText = action.payload;
69 - searchRegExp = null;
70 - searchResults = [];
71 -
72 - if (searchText !== '') {
73 - const safeSearchText = searchText.replace(
74 - /[.*+?^${}()|[\]\\]/g,
75 - '\\$&',
76 - );
77 - searchRegExp = new RegExp(`^${safeSearchText}`, 'i');
78 -
79 - // If something is zoomed in on already, and the new search still contains it,
80 - // don't change the selection (even if overall search results set changes).
81 - let prevSelectedMeasure = null;
82 - if (searchIndex >= 0 && searchResults.length > searchIndex) {
83 - prevSelectedMeasure = searchResults[searchIndex];
84 - }
85 -
86 - const componentMeasures = state.profilerData.componentMeasures;
87 -
88 - let prevSelectedMeasureIndex = -1;
89 -
90 - for (let i = 0; i < componentMeasures.length; i++) {
91 - const componentMeasure = componentMeasures[i];
92 - if (componentMeasure.componentName.match(searchRegExp)) {
93 - searchResults.push(componentMeasure);
94 -
95 - if (componentMeasure === prevSelectedMeasure) {
96 - prevSelectedMeasureIndex = searchResults.length - 1;
97 - }
98 - }
99 - }
100 -
101 - searchIndex =
102 - prevSelectedMeasureIndex >= 0 ? prevSelectedMeasureIndex : 0;
103 - }
104 - break;
105 - }
106 -
107 - return {
108 - profilerData: state.profilerData,
109 - searchIndex,
110 - searchRegExp,
111 - searchResults,
112 - searchText,
113 - };
114 -}
115 -
116 -export type Context = {
117 - profilerData: TimelineData,
118 -
119 - // Search state
120 - dispatch: Dispatch,
121 - searchIndex: number,
122 - searchRegExp: null,
123 - searchResults: Array<ReactComponentMeasure>,
124 - searchText: string,
125 -};
126 -
127 -const TimelineSearchContext: ReactContext<Context> = createContext<Context>(
128 - null as any as Context,
129 -);
130 -TimelineSearchContext.displayName = 'TimelineSearchContext';
131 -
132 -type Props = {
133 - children: React$Node,
134 - profilerData: TimelineData,
135 - viewState: ViewState,
136 -};
137 -
138 -function TimelineSearchContextController({
139 - children,
140 - profilerData,
141 - viewState,
142 -}: Props): React.Node {
143 - const [state, dispatch] = useReducer<State, State, Action>(reducer, {
144 - profilerData,
145 - searchIndex: -1,
146 - searchRegExp: null,
147 - searchResults: EMPTY_ARRAY,
148 - searchText: '',
149 - });
150 -
151 - const value = useMemo(
152 - () => ({
153 - ...state,
154 - dispatch,
155 - }),
156 - [state],
157 - );
158 -
159 - return (
160 - <TimelineSearchContext.Provider value={value}>
161 - {children}
162 - </TimelineSearchContext.Provider>
163 - );
164 -}
165 -
166 -export {TimelineSearchContext, TimelineSearchContextController};
packages/react-devtools-timeline/src/TimelineSearchInput.js deleted
-47
@@ -1,47 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {useContext} from 'react';
12 -import {createPortal} from 'react-dom';
13 -import SearchInput from 'react-devtools-shared/src/devtools/views/SearchInput';
14 -import {TimelineContext} from './TimelineContext';
15 -import {TimelineSearchContext} from './TimelineSearchContext';
16 -
17 -type Props = {};
18 -
19 -export default function TimelineSearchInput(props: Props): React.Node {
20 - const {searchInputContainerRef} = useContext(TimelineContext);
21 - const {dispatch, searchIndex, searchResults, searchText} = useContext(
22 - TimelineSearchContext,
23 - );
24 -
25 - if (searchInputContainerRef.current === null) {
26 - return null;
27 - }
28 -
29 - const search = (text: string) =>
30 - dispatch({type: 'SET_SEARCH_TEXT', payload: text});
31 - const goToNextResult = () => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'});
32 - const goToPreviousResult = () =>
33 - dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'});
34 -
35 - return createPortal(
36 - <SearchInput
37 - goToNextResult={goToNextResult}
38 - goToPreviousResult={goToPreviousResult}
39 - placeholder="Search components by name"
40 - search={search}
41 - searchIndex={searchIndex}
42 - searchResultsCount={searchResults.length}
43 - searchText={searchText}
44 - />,
45 - searchInputContainerRef.current,
46 - );
47 -}
packages/react-devtools-timeline/src/constants.js deleted
-20
@@ -1,20 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export {
11 - COMFORTABLE_LINE_HEIGHT,
12 - COMPACT_LINE_HEIGHT,
13 -} from 'react-devtools-shared/src/devtools/constants.js';
14 -
15 -export const REACT_TOTAL_NUM_LANES = 31;
16 -
17 -// Increment this number any time a backwards breaking change is made to the profiler metadata.
18 -export const SCHEDULING_PROFILER_VERSION = 1;
19 -
20 -export const SNAPSHOT_MAX_HEIGHT = 60;
packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js deleted
-301
@@ -1,301 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ReactComponentMeasure, TimelineData, ViewState} from '../types';
11 -import type {
12 - Interaction,
13 - IntrinsicSize,
14 - MouseMoveInteraction,
15 - Rect,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - durationToWidth,
21 - positioningScaleFactor,
22 - positionToTimestamp,
23 - timestampToPosition,
24 -} from './utils/positioning';
25 -import {drawText} from './utils/text';
26 -import {formatDuration} from '../utils/formatting';
27 -import {
28 - View,
29 - Surface,
30 - rectContainsPoint,
31 - rectIntersectsRect,
32 - intersectionOfRects,
33 -} from '../view-base';
34 -import {BORDER_SIZE, COLORS, NATIVE_EVENT_HEIGHT} from './constants';
35 -
36 -const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
37 -
38 -export class ComponentMeasuresView extends View {
39 - _cachedSearchMatches: Map<string, boolean>;
40 - _cachedSearchRegExp: RegExp | null = null;
41 - _hoveredComponentMeasure: ReactComponentMeasure | null = null;
42 - _intrinsicSize: IntrinsicSize;
43 - _profilerData: TimelineData;
44 - _viewState: ViewState;
45 -
46 - onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
47 -
48 - constructor(
49 - surface: Surface,
50 - frame: Rect,
51 - profilerData: TimelineData,
52 - viewState: ViewState,
53 - ) {
54 - super(surface, frame);
55 -
56 - this._profilerData = profilerData;
57 - this._viewState = viewState;
58 -
59 - this._cachedSearchMatches = new Map();
60 - this._cachedSearchRegExp = null;
61 -
62 - viewState.onSearchRegExpStateChange(() => {
63 - this.setNeedsDisplay();
64 - });
65 -
66 - this._intrinsicSize = {
67 - width: profilerData.duration,
68 - height: ROW_WITH_BORDER_HEIGHT,
69 - };
70 - }
71 -
72 - desiredSize(): IntrinsicSize {
73 - return this._intrinsicSize;
74 - }
75 -
76 - setHoveredEvent(hoveredEvent: ReactComponentMeasure | null) {
77 - if (this._hoveredComponentMeasure === hoveredEvent) {
78 - return;
79 - }
80 - this._hoveredComponentMeasure = hoveredEvent;
81 - this.setNeedsDisplay();
82 - }
83 -
84 - /**
85 - * Draw a single `ReactComponentMeasure` as a box/span with text inside of it.
86 - */
87 - _drawSingleReactComponentMeasure(
88 - context: CanvasRenderingContext2D,
89 - rect: Rect,
90 - componentMeasure: ReactComponentMeasure,
91 - scaleFactor: number,
92 - showHoverHighlight: boolean,
93 - ): boolean {
94 - const {frame} = this;
95 - const {componentName, duration, timestamp, type, warning} =
96 - componentMeasure;
97 -
98 - const xStart = timestampToPosition(timestamp, scaleFactor, frame);
99 - const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
100 - const componentMeasureRect: Rect = {
101 - origin: {
102 - x: xStart,
103 - y: frame.origin.y,
104 - },
105 - size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
106 - };
107 - if (!rectIntersectsRect(componentMeasureRect, rect)) {
108 - return false; // Not in view
109 - }
110 -
111 - const width = durationToWidth(duration, scaleFactor);
112 - if (width < 1) {
113 - return false; // Too small to render at this zoom level
114 - }
115 -
116 - let textFillStyle = null as any as string;
117 - let typeLabel = null as any as string;
118 -
119 - const drawableRect = intersectionOfRects(componentMeasureRect, rect);
120 - context.beginPath();
121 - if (warning !== null) {
122 - context.fillStyle = showHoverHighlight
123 - ? COLORS.WARNING_BACKGROUND_HOVER
124 - : COLORS.WARNING_BACKGROUND;
125 - } else {
126 - switch (type) {
127 - case 'render':
128 - context.fillStyle = showHoverHighlight
129 - ? COLORS.REACT_RENDER_HOVER
130 - : COLORS.REACT_RENDER;
131 - textFillStyle = COLORS.REACT_RENDER_TEXT;
132 - typeLabel = 'rendered';
133 - break;
134 - case 'layout-effect-mount':
135 - context.fillStyle = showHoverHighlight
136 - ? COLORS.REACT_LAYOUT_EFFECTS_HOVER
137 - : COLORS.REACT_LAYOUT_EFFECTS;
138 - textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
139 - typeLabel = 'mounted layout effect';
140 - break;
141 - case 'layout-effect-unmount':
142 - context.fillStyle = showHoverHighlight
143 - ? COLORS.REACT_LAYOUT_EFFECTS_HOVER
144 - : COLORS.REACT_LAYOUT_EFFECTS;
145 - textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
146 - typeLabel = 'unmounted layout effect';
147 - break;
148 - case 'passive-effect-mount':
149 - context.fillStyle = showHoverHighlight
150 - ? COLORS.REACT_PASSIVE_EFFECTS_HOVER
151 - : COLORS.REACT_PASSIVE_EFFECTS;
152 - textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
153 - typeLabel = 'mounted passive effect';
154 - break;
155 - case 'passive-effect-unmount':
156 - context.fillStyle = showHoverHighlight
157 - ? COLORS.REACT_PASSIVE_EFFECTS_HOVER
158 - : COLORS.REACT_PASSIVE_EFFECTS;
159 - textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
160 - typeLabel = 'unmounted passive effect';
161 - break;
162 - }
163 - }
164 -
165 - let isMatch = false;
166 - const cachedSearchRegExp = this._cachedSearchRegExp;
167 - if (cachedSearchRegExp !== null) {
168 - const cachedSearchMatches = this._cachedSearchMatches;
169 - const cachedValue = cachedSearchMatches.get(componentName);
170 - if (cachedValue != null) {
171 - isMatch = cachedValue;
172 - } else {
173 - isMatch = componentName.match(cachedSearchRegExp) !== null;
174 - cachedSearchMatches.set(componentName, isMatch);
175 - }
176 - }
177 -
178 - if (isMatch) {
179 - context.fillStyle = COLORS.SEARCH_RESULT_FILL;
180 - }
181 -
182 - context.fillRect(
183 - drawableRect.origin.x,
184 - drawableRect.origin.y,
185 - drawableRect.size.width,
186 - drawableRect.size.height,
187 - );
188 -
189 - const label = `${componentName} ${typeLabel} - ${formatDuration(duration)}`;
190 -
191 - drawText(label, context, componentMeasureRect, drawableRect, {
192 - fillStyle: textFillStyle,
193 - });
194 -
195 - return true;
196 - }
197 -
198 - draw(context: CanvasRenderingContext2D) {
199 - const {
200 - frame,
201 - _profilerData: {componentMeasures},
202 - _hoveredComponentMeasure,
203 - visibleArea,
204 - } = this;
205 -
206 - const searchRegExp = this._viewState.searchRegExp;
207 - if (this._cachedSearchRegExp !== searchRegExp) {
208 - this._cachedSearchMatches = new Map();
209 - this._cachedSearchRegExp = searchRegExp;
210 - }
211 -
212 - context.fillStyle = COLORS.BACKGROUND;
213 - context.fillRect(
214 - visibleArea.origin.x,
215 - visibleArea.origin.y,
216 - visibleArea.size.width,
217 - visibleArea.size.height,
218 - );
219 -
220 - // Draw events
221 - const scaleFactor = positioningScaleFactor(
222 - this._intrinsicSize.width,
223 - frame,
224 - );
225 -
226 - let didDrawMeasure = false;
227 - componentMeasures.forEach(componentMeasure => {
228 - didDrawMeasure =
229 - this._drawSingleReactComponentMeasure(
230 - context,
231 - visibleArea,
232 - componentMeasure,
233 - scaleFactor,
234 - componentMeasure === _hoveredComponentMeasure,
235 - ) || didDrawMeasure;
236 - });
237 -
238 - if (!didDrawMeasure) {
239 - drawText(
240 - '(zoom or pan to see React components)',
241 - context,
242 - visibleArea,
243 - visibleArea,
244 - {fillStyle: COLORS.TEXT_DIM_COLOR, textAlign: 'center'},
245 - );
246 - }
247 -
248 - context.fillStyle = COLORS.PRIORITY_BORDER;
249 - context.fillRect(
250 - visibleArea.origin.x,
251 - visibleArea.origin.y + ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
252 - visibleArea.size.width,
253 - BORDER_SIZE,
254 - );
255 - }
256 -
257 - /**
258 - * @private
259 - */
260 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
261 - const {frame, _intrinsicSize, onHover, visibleArea} = this;
262 - if (!onHover) {
263 - return;
264 - }
265 -
266 - const {location} = interaction.payload;
267 - if (!rectContainsPoint(location, visibleArea)) {
268 - onHover(null);
269 - return;
270 - }
271 -
272 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
273 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
274 -
275 - const componentMeasures = this._profilerData.componentMeasures;
276 - for (let index = componentMeasures.length - 1; index >= 0; index--) {
277 - const componentMeasure = componentMeasures[index];
278 - const {duration, timestamp} = componentMeasure;
279 -
280 - if (
281 - hoverTimestamp >= timestamp &&
282 - hoverTimestamp <= timestamp + duration
283 - ) {
284 - this.currentCursor = 'context-menu';
285 - viewRefs.hoveredView = this;
286 - onHover(componentMeasure);
287 - return;
288 - }
289 - }
290 -
291 - onHover(null);
292 - }
293 -
294 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
295 - switch (interaction.type) {
296 - case 'mousemove':
297 - this._handleMouseMove(interaction, viewRefs);
298 - break;
299 - }
300 - }
301 -}
packages/react-devtools-timeline/src/content-views/FlamechartView.js deleted
-383
@@ -1,383 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - Flamechart,
12 - FlamechartStackFrame,
13 - FlamechartStackLayer,
14 - InternalModuleSourceToRanges,
15 -} from '../types';
16 -import type {
17 - Interaction,
18 - MouseMoveInteraction,
19 - Rect,
20 - Size,
21 - ViewRefs,
22 -} from '../view-base';
23 -
24 -import {
25 - BackgroundColorView,
26 - Surface,
27 - View,
28 - layeredLayout,
29 - rectContainsPoint,
30 - intersectionOfRects,
31 - rectIntersectsRect,
32 - verticallyStackedLayout,
33 -} from '../view-base';
34 -import {isInternalModule} from './utils/moduleFilters';
35 -import {
36 - durationToWidth,
37 - positioningScaleFactor,
38 - timestampToPosition,
39 -} from './utils/positioning';
40 -import {drawText} from './utils/text';
41 -import {
42 - COLORS,
43 - FLAMECHART_FRAME_HEIGHT,
44 - COLOR_HOVER_DIM_DELTA,
45 - BORDER_SIZE,
46 -} from './constants';
47 -import {ColorGenerator, dimmedColor, hslaColorToString} from './utils/colors';
48 -
49 -// Source: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/timeline/TimelineUIUtils.js;l=2109;drc=fb32e928d79707a693351b806b8710b2f6b7d399
50 -const colorGenerator = new ColorGenerator(
51 - {min: 30, max: 330},
52 - {min: 50, max: 80, count: 3},
53 - 85,
54 -);
55 -colorGenerator.setColorForID('', {h: 43.6, s: 45.8, l: 90.6, a: 100});
56 -
57 -function defaultHslaColorForStackFrame({scriptUrl}: FlamechartStackFrame) {
58 - return colorGenerator.colorForID(scriptUrl ?? '');
59 -}
60 -
61 -function defaultColorForStackFrame(stackFrame: FlamechartStackFrame): string {
62 - const color = defaultHslaColorForStackFrame(stackFrame);
63 - return hslaColorToString(color);
64 -}
65 -
66 -function hoverColorForStackFrame(stackFrame: FlamechartStackFrame): string {
67 - const color = dimmedColor(
68 - defaultHslaColorForStackFrame(stackFrame),
69 - COLOR_HOVER_DIM_DELTA,
70 - );
71 - return hslaColorToString(color);
72 -}
73 -
74 -class FlamechartStackLayerView extends View {
75 - /** Layer to display */
76 - _stackLayer: FlamechartStackLayer;
77 -
78 - /** A set of `stackLayer`'s frames, for efficient lookup. */
79 - _stackFrameSet: Set<FlamechartStackFrame>;
80 -
81 - _internalModuleSourceToRanges: InternalModuleSourceToRanges;
82 -
83 - _intrinsicSize: Size;
84 -
85 - _hoveredStackFrame: FlamechartStackFrame | null = null;
86 - _onHover: ((node: FlamechartStackFrame | null) => void) | null = null;
87 -
88 - constructor(
89 - surface: Surface,
90 - frame: Rect,
91 - stackLayer: FlamechartStackLayer,
92 - internalModuleSourceToRanges: InternalModuleSourceToRanges,
93 - duration: number,
94 - ) {
95 - super(surface, frame);
96 - this._stackLayer = stackLayer;
97 - this._stackFrameSet = new Set(stackLayer);
98 - this._internalModuleSourceToRanges = internalModuleSourceToRanges;
99 - this._intrinsicSize = {
100 - width: duration,
101 - height: FLAMECHART_FRAME_HEIGHT,
102 - };
103 - }
104 -
105 - desiredSize(): Size {
106 - return this._intrinsicSize;
107 - }
108 -
109 - setHoveredFlamechartStackFrame(
110 - hoveredStackFrame: FlamechartStackFrame | null,
111 - ) {
112 - if (this._hoveredStackFrame === hoveredStackFrame) {
113 - return; // We're already hovering over this frame
114 - }
115 -
116 - // Only care about frames displayed by this view.
117 - const stackFrameToSet =
118 - hoveredStackFrame && this._stackFrameSet.has(hoveredStackFrame)
119 - ? hoveredStackFrame
120 - : null;
121 - if (this._hoveredStackFrame === stackFrameToSet) {
122 - return; // Resulting state is unchanged
123 - }
124 - this._hoveredStackFrame = stackFrameToSet;
125 - this.setNeedsDisplay();
126 - }
127 -
128 - draw(context: CanvasRenderingContext2D) {
129 - const {
130 - frame,
131 - _stackLayer,
132 - _hoveredStackFrame,
133 - _intrinsicSize,
134 - visibleArea,
135 - } = this;
136 -
137 - context.fillStyle = COLORS.PRIORITY_BACKGROUND;
138 - context.fillRect(
139 - visibleArea.origin.x,
140 - visibleArea.origin.y,
141 - visibleArea.size.width,
142 - visibleArea.size.height,
143 - );
144 -
145 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
146 -
147 - for (let i = 0; i < _stackLayer.length; i++) {
148 - const stackFrame = _stackLayer[i];
149 - const {name, timestamp, duration} = stackFrame;
150 -
151 - const width = durationToWidth(duration, scaleFactor);
152 - if (width < 1) {
153 - continue; // Too small to render at this zoom level
154 - }
155 -
156 - const x = Math.floor(timestampToPosition(timestamp, scaleFactor, frame));
157 - const nodeRect: Rect = {
158 - origin: {x, y: frame.origin.y},
159 - size: {
160 - width: Math.floor(width - BORDER_SIZE),
161 - height: Math.floor(FLAMECHART_FRAME_HEIGHT - BORDER_SIZE),
162 - },
163 - };
164 - if (!rectIntersectsRect(nodeRect, visibleArea)) {
165 - continue; // Not in view
166 - }
167 -
168 - const showHoverHighlight = _hoveredStackFrame === _stackLayer[i];
169 -
170 - let textFillStyle;
171 - if (isInternalModule(this._internalModuleSourceToRanges, stackFrame)) {
172 - context.fillStyle = showHoverHighlight
173 - ? COLORS.INTERNAL_MODULE_FRAME_HOVER
174 - : COLORS.INTERNAL_MODULE_FRAME;
175 - textFillStyle = COLORS.INTERNAL_MODULE_FRAME_TEXT;
176 - } else {
177 - context.fillStyle = showHoverHighlight
178 - ? hoverColorForStackFrame(stackFrame)
179 - : defaultColorForStackFrame(stackFrame);
180 - textFillStyle = COLORS.TEXT_COLOR;
181 - }
182 -
183 - const drawableRect = intersectionOfRects(nodeRect, visibleArea);
184 - context.fillRect(
185 - drawableRect.origin.x,
186 - drawableRect.origin.y,
187 - drawableRect.size.width,
188 - drawableRect.size.height,
189 - );
190 -
191 - drawText(name, context, nodeRect, drawableRect, {
192 - fillStyle: textFillStyle,
193 - });
194 - }
195 -
196 - // Render bottom border.
197 - const borderFrame: Rect = {
198 - origin: {
199 - x: frame.origin.x,
200 - y: frame.origin.y + FLAMECHART_FRAME_HEIGHT - BORDER_SIZE,
201 - },
202 - size: {
203 - width: frame.size.width,
204 - height: BORDER_SIZE,
205 - },
206 - };
207 - if (rectIntersectsRect(borderFrame, visibleArea)) {
208 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
209 - context.fillStyle = COLORS.PRIORITY_BORDER;
210 - context.fillRect(
211 - borderDrawableRect.origin.x,
212 - borderDrawableRect.origin.y,
213 - borderDrawableRect.size.width,
214 - borderDrawableRect.size.height,
215 - );
216 - }
217 - }
218 -
219 - /**
220 - * @private
221 - */
222 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
223 - const {_stackLayer, frame, _intrinsicSize, _onHover, visibleArea} = this;
224 - const {location} = interaction.payload;
225 - if (!_onHover || !rectContainsPoint(location, visibleArea)) {
226 - return;
227 - }
228 -
229 - // Find the node being hovered over.
230 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
231 - let startIndex = 0;
232 - let stopIndex = _stackLayer.length - 1;
233 - while (startIndex <= stopIndex) {
234 - const currentIndex = Math.floor((startIndex + stopIndex) / 2);
235 - const flamechartStackFrame = _stackLayer[currentIndex];
236 - const {timestamp, duration} = flamechartStackFrame;
237 -
238 - const x = Math.floor(timestampToPosition(timestamp, scaleFactor, frame));
239 - const width = durationToWidth(duration, scaleFactor);
240 -
241 - // Don't show tooltips for nodes that are too small to render at this zoom level.
242 - if (Math.floor(width - BORDER_SIZE) >= 1) {
243 - if (x <= location.x && x + width >= location.x) {
244 - this.currentCursor = 'context-menu';
245 - viewRefs.hoveredView = this;
246 - _onHover(flamechartStackFrame);
247 - return;
248 - }
249 - }
250 -
251 - if (x > location.x) {
252 - stopIndex = currentIndex - 1;
253 - } else {
254 - startIndex = currentIndex + 1;
255 - }
256 - }
257 -
258 - _onHover(null);
259 - }
260 -
261 - _didGrab: boolean = false;
262 -
263 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
264 - switch (interaction.type) {
265 - case 'mousemove':
266 - this._handleMouseMove(interaction, viewRefs);
267 - break;
268 - }
269 - }
270 -}
271 -
272 -export class FlamechartView extends View {
273 - _flamechartRowViews: FlamechartStackLayerView[] = [];
274 -
275 - /** Container view that vertically stacks flamechart rows */
276 - _verticalStackView: View;
277 -
278 - _hoveredStackFrame: FlamechartStackFrame | null = null;
279 - _onHover: ((node: FlamechartStackFrame | null) => void) | null = null;
280 -
281 - constructor(
282 - surface: Surface,
283 - frame: Rect,
284 - flamechart: Flamechart,
285 - internalModuleSourceToRanges: InternalModuleSourceToRanges,
286 - duration: number,
287 - ) {
288 - super(surface, frame, layeredLayout);
289 - this.setDataAndUpdateSubviews(
290 - flamechart,
291 - internalModuleSourceToRanges,
292 - duration,
293 - );
294 - }
295 -
296 - setDataAndUpdateSubviews(
297 - flamechart: Flamechart,
298 - internalModuleSourceToRanges: InternalModuleSourceToRanges,
299 - duration: number,
300 - ) {
301 - const {surface, frame, _onHover, _hoveredStackFrame} = this;
302 -
303 - // Clear existing rows on data update
304 - if (this._verticalStackView) {
305 - this.removeAllSubviews();
306 - this._flamechartRowViews = [];
307 - }
308 -
309 - this._verticalStackView = new View(surface, frame, verticallyStackedLayout);
310 - this._flamechartRowViews = flamechart.map(stackLayer => {
311 - const rowView = new FlamechartStackLayerView(
312 - surface,
313 - frame,
314 - stackLayer,
315 - internalModuleSourceToRanges,
316 - duration,
317 - );
318 - this._verticalStackView.addSubview(rowView);
319 -
320 - // Update states
321 - rowView._onHover = _onHover;
322 - rowView.setHoveredFlamechartStackFrame(_hoveredStackFrame);
323 - return rowView;
324 - });
325 -
326 - // Add a plain background view to prevent gaps from appearing between flamechartRowViews.
327 - this.addSubview(new BackgroundColorView(surface, frame));
328 - this.addSubview(this._verticalStackView);
329 - }
330 -
331 - setHoveredFlamechartStackFrame(
332 - hoveredStackFrame: FlamechartStackFrame | null,
333 - ) {
334 - this._hoveredStackFrame = hoveredStackFrame;
335 - this._flamechartRowViews.forEach(rowView =>
336 - rowView.setHoveredFlamechartStackFrame(hoveredStackFrame),
337 - );
338 - }
339 -
340 - setOnHover(onHover: (node: FlamechartStackFrame | null) => void) {
341 - this._onHover = onHover;
342 - this._flamechartRowViews.forEach(rowView => (rowView._onHover = onHover));
343 - }
344 -
345 - desiredSize(): {
346 - height: number,
347 - hideScrollBarIfLessThanHeight?: number,
348 - maxInitialHeight?: number,
349 - width: number,
350 - } {
351 - // Ignore the wishes of the background color view
352 - const intrinsicSize = this._verticalStackView.desiredSize();
353 - return {
354 - ...intrinsicSize,
355 - // Collapsed by default
356 - maxInitialHeight: 0,
357 - };
358 - }
359 -
360 - /**
361 - * @private
362 - */
363 - _handleMouseMove(interaction: MouseMoveInteraction) {
364 - const {_onHover, visibleArea} = this;
365 - if (!_onHover) {
366 - return;
367 - }
368 -
369 - const {location} = interaction.payload;
370 - if (!rectContainsPoint(location, visibleArea)) {
371 - // Clear out any hovered flamechart stack frame
372 - _onHover(null);
373 - }
374 - }
375 -
376 - handleInteraction(interaction: Interaction) {
377 - switch (interaction.type) {
378 - case 'mousemove':
379 - this._handleMouseMove(interaction);
380 - break;
381 - }
382 - }
383 -}
packages/react-devtools-timeline/src/content-views/NativeEventsView.js deleted
-259
@@ -1,259 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {NativeEvent, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - IntrinsicSize,
14 - MouseMoveInteraction,
15 - Rect,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - durationToWidth,
21 - positioningScaleFactor,
22 - positionToTimestamp,
23 - timestampToPosition,
24 -} from './utils/positioning';
25 -import {drawText} from './utils/text';
26 -import {formatDuration} from '../utils/formatting';
27 -import {
28 - View,
29 - Surface,
30 - rectContainsPoint,
31 - rectIntersectsRect,
32 - intersectionOfRects,
33 -} from '../view-base';
34 -import {COLORS, NATIVE_EVENT_HEIGHT, BORDER_SIZE} from './constants';
35 -
36 -const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
37 -
38 -export class NativeEventsView extends View {
39 - _depthToNativeEvent: Map<number, NativeEvent[]>;
40 - _hoveredEvent: NativeEvent | null = null;
41 - _intrinsicSize: IntrinsicSize;
42 - _maxDepth: number = 0;
43 - _profilerData: TimelineData;
44 -
45 - onHover: ((event: NativeEvent | null) => void) | null = null;
46 -
47 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
48 - super(surface, frame);
49 -
50 - this._profilerData = profilerData;
51 -
52 - this._performPreflightComputations();
53 - }
54 -
55 - _performPreflightComputations() {
56 - this._depthToNativeEvent = new Map();
57 -
58 - const {duration, nativeEvents} = this._profilerData;
59 -
60 - nativeEvents.forEach(event => {
61 - const depth = event.depth;
62 -
63 - this._maxDepth = Math.max(this._maxDepth, depth);
64 -
65 - if (!this._depthToNativeEvent.has(depth)) {
66 - this._depthToNativeEvent.set(depth, [event]);
67 - } else {
68 - // $FlowFixMe[incompatible-use] This is unnecessary.
69 - this._depthToNativeEvent.get(depth).push(event);
70 - }
71 - });
72 -
73 - this._intrinsicSize = {
74 - width: duration,
75 - height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
76 - hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
77 - };
78 - }
79 -
80 - desiredSize(): IntrinsicSize {
81 - return this._intrinsicSize;
82 - }
83 -
84 - setHoveredEvent(hoveredEvent: NativeEvent | null) {
85 - if (this._hoveredEvent === hoveredEvent) {
86 - return;
87 - }
88 - this._hoveredEvent = hoveredEvent;
89 - this.setNeedsDisplay();
90 - }
91 -
92 - /**
93 - * Draw a single `NativeEvent` as a box/span with text inside of it.
94 - */
95 - _drawSingleNativeEvent(
96 - context: CanvasRenderingContext2D,
97 - rect: Rect,
98 - event: NativeEvent,
99 - baseY: number,
100 - scaleFactor: number,
101 - showHoverHighlight: boolean,
102 - ) {
103 - const {frame} = this;
104 - const {depth, duration, timestamp, type, warning} = event;
105 -
106 - baseY += depth * ROW_WITH_BORDER_HEIGHT;
107 -
108 - const xStart = timestampToPosition(timestamp, scaleFactor, frame);
109 - const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
110 - const eventRect: Rect = {
111 - origin: {
112 - x: xStart,
113 - y: baseY,
114 - },
115 - size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
116 - };
117 - if (!rectIntersectsRect(eventRect, rect)) {
118 - return; // Not in view
119 - }
120 -
121 - const width = durationToWidth(duration, scaleFactor);
122 - if (width < 1) {
123 - return; // Too small to render at this zoom level
124 - }
125 -
126 - const drawableRect = intersectionOfRects(eventRect, rect);
127 - context.beginPath();
128 - if (warning !== null) {
129 - context.fillStyle = showHoverHighlight
130 - ? COLORS.WARNING_BACKGROUND_HOVER
131 - : COLORS.WARNING_BACKGROUND;
132 - } else {
133 - context.fillStyle = showHoverHighlight
134 - ? COLORS.NATIVE_EVENT_HOVER
135 - : COLORS.NATIVE_EVENT;
136 - }
137 - context.fillRect(
138 - drawableRect.origin.x,
139 - drawableRect.origin.y,
140 - drawableRect.size.width,
141 - drawableRect.size.height,
142 - );
143 -
144 - const label = `${type} - ${formatDuration(duration)}`;
145 -
146 - drawText(label, context, eventRect, drawableRect);
147 - }
148 -
149 - draw(context: CanvasRenderingContext2D) {
150 - const {
151 - frame,
152 - _profilerData: {nativeEvents},
153 - _hoveredEvent,
154 - visibleArea,
155 - } = this;
156 -
157 - context.fillStyle = COLORS.PRIORITY_BACKGROUND;
158 - context.fillRect(
159 - visibleArea.origin.x,
160 - visibleArea.origin.y,
161 - visibleArea.size.width,
162 - visibleArea.size.height,
163 - );
164 -
165 - // Draw events
166 - const scaleFactor = positioningScaleFactor(
167 - this._intrinsicSize.width,
168 - frame,
169 - );
170 -
171 - nativeEvents.forEach(event => {
172 - this._drawSingleNativeEvent(
173 - context,
174 - visibleArea,
175 - event,
176 - frame.origin.y,
177 - scaleFactor,
178 - event === _hoveredEvent,
179 - );
180 - });
181 -
182 - // Render bottom borders.
183 - for (let i = 0; i <= this._maxDepth; i++) {
184 - const borderFrame: Rect = {
185 - origin: {
186 - x: frame.origin.x,
187 - y: frame.origin.y + NATIVE_EVENT_HEIGHT,
188 - },
189 - size: {
190 - width: frame.size.width,
191 - height: BORDER_SIZE,
192 - },
193 - };
194 - if (rectIntersectsRect(borderFrame, visibleArea)) {
195 - const borderDrawableRect = intersectionOfRects(
196 - borderFrame,
197 - visibleArea,
198 - );
199 - context.fillStyle = COLORS.PRIORITY_BORDER;
200 - context.fillRect(
201 - borderDrawableRect.origin.x,
202 - borderDrawableRect.origin.y,
203 - borderDrawableRect.size.width,
204 - borderDrawableRect.size.height,
205 - );
206 - }
207 - }
208 - }
209 -
210 - /**
211 - * @private
212 - */
213 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
214 - const {frame, _intrinsicSize, onHover, visibleArea} = this;
215 - if (!onHover) {
216 - return;
217 - }
218 -
219 - const {location} = interaction.payload;
220 - if (!rectContainsPoint(location, visibleArea)) {
221 - onHover(null);
222 - return;
223 - }
224 -
225 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
226 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
227 -
228 - const adjustedCanvasMouseY = location.y - frame.origin.y;
229 - const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
230 - const nativeEventsAtDepth = this._depthToNativeEvent.get(depth);
231 -
232 - if (nativeEventsAtDepth) {
233 - // Find the event being hovered over.
234 - for (let index = nativeEventsAtDepth.length - 1; index >= 0; index--) {
235 - const nativeEvent = nativeEventsAtDepth[index];
236 - const {duration, timestamp} = nativeEvent;
237 -
238 - if (
239 - hoverTimestamp >= timestamp &&
240 - hoverTimestamp <= timestamp + duration
241 - ) {
242 - viewRefs.hoveredView = this;
243 - onHover(nativeEvent);
244 - return;
245 - }
246 - }
247 - }
248 -
249 - onHover(null);
250 - }
251 -
252 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
253 - switch (interaction.type) {
254 - case 'mousemove':
255 - this._handleMouseMove(interaction, viewRefs);
256 - break;
257 - }
258 - }
259 -}
packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js deleted
-337
@@ -1,337 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {NetworkMeasure, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - IntrinsicSize,
14 - MouseMoveInteraction,
15 - Rect,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - durationToWidth,
21 - positioningScaleFactor,
22 - positionToTimestamp,
23 - timestampToPosition,
24 -} from './utils/positioning';
25 -import {drawText} from './utils/text';
26 -import {formatDuration} from '../utils/formatting';
27 -import {
28 - View,
29 - Surface,
30 - rectContainsPoint,
31 - rectIntersectsRect,
32 - intersectionOfRects,
33 -} from '../view-base';
34 -import {BORDER_SIZE, COLORS, SUSPENSE_EVENT_HEIGHT} from './constants';
35 -
36 -const HEIGHT = SUSPENSE_EVENT_HEIGHT; // TODO Constant name
37 -const ROW_WITH_BORDER_HEIGHT = HEIGHT + BORDER_SIZE;
38 -
39 -const BASE_URL_REGEX = /([^:]+:\/\/[^\/]+)/;
40 -
41 -export class NetworkMeasuresView extends View {
42 - _depthToNetworkMeasure: Map<number, NetworkMeasure[]>;
43 - _hoveredNetworkMeasure: NetworkMeasure | null = null;
44 - _intrinsicSize: IntrinsicSize;
45 - _maxDepth: number = 0;
46 - _profilerData: TimelineData;
47 -
48 - onHover: ((event: NetworkMeasure | null) => void) | null = null;
49 -
50 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
51 - super(surface, frame);
52 -
53 - this._profilerData = profilerData;
54 -
55 - this._performPreflightComputations();
56 - }
57 -
58 - _performPreflightComputations() {
59 - this._depthToNetworkMeasure = new Map();
60 -
61 - const {duration, networkMeasures} = this._profilerData;
62 -
63 - networkMeasures.forEach(event => {
64 - const depth = event.depth;
65 -
66 - this._maxDepth = Math.max(this._maxDepth, depth);
67 -
68 - if (!this._depthToNetworkMeasure.has(depth)) {
69 - this._depthToNetworkMeasure.set(depth, [event]);
70 - } else {
71 - // $FlowFixMe[incompatible-use] This is unnecessary.
72 - this._depthToNetworkMeasure.get(depth).push(event);
73 - }
74 - });
75 -
76 - this._intrinsicSize = {
77 - width: duration,
78 - height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
79 - // Collapsed by default
80 - maxInitialHeight: 0,
81 - };
82 - }
83 -
84 - desiredSize(): IntrinsicSize {
85 - return this._intrinsicSize;
86 - }
87 -
88 - setHoveredEvent(networkMeasure: NetworkMeasure | null) {
89 - if (this._hoveredNetworkMeasure === networkMeasure) {
90 - return;
91 - }
92 - this._hoveredNetworkMeasure = networkMeasure;
93 - this.setNeedsDisplay();
94 - }
95 -
96 - /**
97 - * Draw a single `NetworkMeasure` as a box/span with text inside of it.
98 - */
99 - _drawSingleNetworkMeasure(
100 - context: CanvasRenderingContext2D,
101 - networkMeasure: NetworkMeasure,
102 - baseY: number,
103 - scaleFactor: number,
104 - showHoverHighlight: boolean,
105 - ) {
106 - const {frame, visibleArea} = this;
107 - const {
108 - depth,
109 - finishTimestamp,
110 - firstReceivedDataTimestamp,
111 - lastReceivedDataTimestamp,
112 - receiveResponseTimestamp,
113 - sendRequestTimestamp,
114 - url,
115 - } = networkMeasure;
116 -
117 - // Account for requests that did not complete while we were profiling.
118 - // As well as requests that did not receive data before finish (cached?).
119 - const duration = this._profilerData.duration;
120 - const timestampBegin = sendRequestTimestamp;
121 - const timestampEnd =
122 - finishTimestamp || lastReceivedDataTimestamp || duration;
123 - const timestampMiddle =
124 - receiveResponseTimestamp || firstReceivedDataTimestamp || timestampEnd;
125 -
126 - // Convert all timestamps to x coordinates.
127 - const xStart = timestampToPosition(timestampBegin, scaleFactor, frame);
128 - const xMiddle = timestampToPosition(timestampMiddle, scaleFactor, frame);
129 - const xStop = timestampToPosition(timestampEnd, scaleFactor, frame);
130 -
131 - const width = durationToWidth(xStop - xStart, scaleFactor);
132 - if (width < 1) {
133 - return; // Too small to render at this zoom level
134 - }
135 -
136 - baseY += depth * ROW_WITH_BORDER_HEIGHT;
137 -
138 - const outerRect: Rect = {
139 - origin: {
140 - x: xStart,
141 - y: baseY,
142 - },
143 - size: {
144 - width: xStop - xStart,
145 - height: HEIGHT,
146 - },
147 - };
148 - if (!rectIntersectsRect(outerRect, visibleArea)) {
149 - return; // Not in view
150 - }
151 -
152 - // Draw the secondary rect first (since it also shows as a thin border around the primary rect).
153 - let rect = {
154 - origin: {
155 - x: xStart,
156 - y: baseY,
157 - },
158 - size: {
159 - width: xStop - xStart,
160 - height: HEIGHT,
161 - },
162 - };
163 - if (rectIntersectsRect(rect, visibleArea)) {
164 - context.beginPath();
165 - context.fillStyle =
166 - this._hoveredNetworkMeasure === networkMeasure
167 - ? COLORS.NETWORK_SECONDARY_HOVER
168 - : COLORS.NETWORK_SECONDARY;
169 - context.fillRect(
170 - rect.origin.x,
171 - rect.origin.y,
172 - rect.size.width,
173 - rect.size.height,
174 - );
175 - }
176 -
177 - rect = {
178 - origin: {
179 - x: xStart + BORDER_SIZE,
180 - y: baseY + BORDER_SIZE,
181 - },
182 - size: {
183 - width: xMiddle - xStart - BORDER_SIZE,
184 - height: HEIGHT - BORDER_SIZE * 2,
185 - },
186 - };
187 - if (rectIntersectsRect(rect, visibleArea)) {
188 - context.beginPath();
189 - context.fillStyle =
190 - this._hoveredNetworkMeasure === networkMeasure
191 - ? COLORS.NETWORK_PRIMARY_HOVER
192 - : COLORS.NETWORK_PRIMARY;
193 - context.fillRect(
194 - rect.origin.x,
195 - rect.origin.y,
196 - rect.size.width,
197 - rect.size.height,
198 - );
199 - }
200 -
201 - const baseUrl = url.match(BASE_URL_REGEX);
202 - const displayUrl = baseUrl !== null ? baseUrl[1] : url;
203 -
204 - const durationLabel =
205 - finishTimestamp !== 0
206 - ? `${formatDuration(finishTimestamp - sendRequestTimestamp)} - `
207 - : '';
208 -
209 - const label = durationLabel + displayUrl;
210 -
211 - drawText(label, context, outerRect, visibleArea);
212 - }
213 -
214 - draw(context: CanvasRenderingContext2D) {
215 - const {
216 - frame,
217 - _profilerData: {networkMeasures},
218 - _hoveredNetworkMeasure,
219 - visibleArea,
220 - } = this;
221 -
222 - context.fillStyle = COLORS.PRIORITY_BACKGROUND;
223 - context.fillRect(
224 - visibleArea.origin.x,
225 - visibleArea.origin.y,
226 - visibleArea.size.width,
227 - visibleArea.size.height,
228 - );
229 -
230 - const scaleFactor = positioningScaleFactor(
231 - this._intrinsicSize.width,
232 - frame,
233 - );
234 -
235 - networkMeasures.forEach(networkMeasure => {
236 - this._drawSingleNetworkMeasure(
237 - context,
238 - networkMeasure,
239 - frame.origin.y,
240 - scaleFactor,
241 - networkMeasure === _hoveredNetworkMeasure,
242 - );
243 - });
244 -
245 - // Render bottom borders.
246 - for (let i = 0; i <= this._maxDepth; i++) {
247 - const borderFrame: Rect = {
248 - origin: {
249 - x: frame.origin.x,
250 - y: frame.origin.y + (i + 1) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
251 - },
252 - size: {
253 - width: frame.size.width,
254 - height: BORDER_SIZE,
255 - },
256 - };
257 - if (rectIntersectsRect(borderFrame, visibleArea)) {
258 - const borderDrawableRect = intersectionOfRects(
259 - borderFrame,
260 - visibleArea,
261 - );
262 - context.fillStyle = COLORS.PRIORITY_BORDER;
263 - context.fillRect(
264 - borderDrawableRect.origin.x,
265 - borderDrawableRect.origin.y,
266 - borderDrawableRect.size.width,
267 - borderDrawableRect.size.height,
268 - );
269 - }
270 - }
271 - }
272 -
273 - /**
274 - * @private
275 - */
276 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
277 - const {frame, _intrinsicSize, onHover, visibleArea} = this;
278 - if (!onHover) {
279 - return;
280 - }
281 -
282 - const {location} = interaction.payload;
283 - if (!rectContainsPoint(location, visibleArea)) {
284 - onHover(null);
285 - return;
286 - }
287 -
288 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
289 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
290 -
291 - const adjustedCanvasMouseY = location.y - frame.origin.y;
292 - const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
293 - const networkMeasuresAtDepth = this._depthToNetworkMeasure.get(depth);
294 -
295 - const duration = this._profilerData.duration;
296 -
297 - if (networkMeasuresAtDepth) {
298 - // Find the event being hovered over.
299 - for (let index = networkMeasuresAtDepth.length - 1; index >= 0; index--) {
300 - const networkMeasure = networkMeasuresAtDepth[index];
301 - const {
302 - finishTimestamp,
303 - lastReceivedDataTimestamp,
304 - sendRequestTimestamp,
305 - } = networkMeasure;
306 -
307 - const timestampBegin = sendRequestTimestamp;
308 - const timestampEnd =
309 - finishTimestamp || lastReceivedDataTimestamp || duration;
310 -
311 - if (
312 - hoverTimestamp >= timestampBegin &&
313 - hoverTimestamp <= timestampEnd
314 - ) {
315 - this.currentCursor = 'context-menu';
316 - viewRefs.hoveredView = this;
317 - onHover(networkMeasure);
318 - return;
319 - }
320 - }
321 - }
322 -
323 - if (viewRefs.hoveredView === this) {
324 - viewRefs.hoveredView = null;
325 - }
326 -
327 - onHover(null);
328 - }
329 -
330 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
331 - switch (interaction.type) {
332 - case 'mousemove':
333 - this._handleMouseMove(interaction, viewRefs);
334 - break;
335 - }
336 - }
337 -}
packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js deleted
-370
@@ -1,370 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ReactLane, ReactMeasure, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - IntrinsicSize,
14 - MouseMoveInteraction,
15 - Rect,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {formatDuration} from '../utils/formatting';
20 -import {drawText} from './utils/text';
21 -import {
22 - durationToWidth,
23 - positioningScaleFactor,
24 - positionToTimestamp,
25 - timestampToPosition,
26 -} from './utils/positioning';
27 -import {
28 - View,
29 - Surface,
30 - rectContainsPoint,
31 - rectIntersectsRect,
32 - intersectionOfRects,
33 -} from '../view-base';
34 -
35 -import {COLORS, BORDER_SIZE, REACT_MEASURE_HEIGHT} from './constants';
36 -
37 -const REACT_LANE_HEIGHT = REACT_MEASURE_HEIGHT + BORDER_SIZE;
38 -const MAX_ROWS_TO_SHOW_INITIALLY = 5;
39 -
40 -export class ReactMeasuresView extends View {
41 - _intrinsicSize: IntrinsicSize;
42 - _lanesToRender: ReactLane[];
43 - _profilerData: TimelineData;
44 - _hoveredMeasure: ReactMeasure | null = null;
45 -
46 - onHover: ((measure: ReactMeasure | null) => void) | null = null;
47 -
48 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
49 - super(surface, frame);
50 - this._profilerData = profilerData;
51 - this._performPreflightComputations();
52 - }
53 -
54 - _performPreflightComputations() {
55 - this._lanesToRender = [];
56 -
57 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
58 - for (const [lane, measuresForLane] of this._profilerData
59 - .laneToReactMeasureMap) {
60 - // Only show lanes with measures
61 - if (measuresForLane.length > 0) {
62 - this._lanesToRender.push(lane);
63 - }
64 - }
65 -
66 - this._intrinsicSize = {
67 - width: this._profilerData.duration,
68 - height: this._lanesToRender.length * REACT_LANE_HEIGHT,
69 - hideScrollBarIfLessThanHeight: REACT_LANE_HEIGHT,
70 - maxInitialHeight: MAX_ROWS_TO_SHOW_INITIALLY * REACT_LANE_HEIGHT,
71 - };
72 - }
73 -
74 - desiredSize(): IntrinsicSize {
75 - return this._intrinsicSize;
76 - }
77 -
78 - setHoveredMeasure(hoveredMeasure: ReactMeasure | null) {
79 - if (this._hoveredMeasure === hoveredMeasure) {
80 - return;
81 - }
82 - this._hoveredMeasure = hoveredMeasure;
83 - this.setNeedsDisplay();
84 - }
85 -
86 - /**
87 - * Draw a single `ReactMeasure` as a bar in the canvas.
88 - */
89 - _drawSingleReactMeasure(
90 - context: CanvasRenderingContext2D,
91 - rect: Rect,
92 - measure: ReactMeasure,
93 - nextMeasure: ReactMeasure | null,
94 - baseY: number,
95 - scaleFactor: number,
96 - showGroupHighlight: boolean,
97 - showHoverHighlight: boolean,
98 - ): void {
99 - const {frame, visibleArea} = this;
100 - const {timestamp, type, duration} = measure;
101 -
102 - let fillStyle = null;
103 - let hoveredFillStyle = null;
104 - let groupSelectedFillStyle = null;
105 - let textFillStyle = null;
106 -
107 - // We could change the max to 0 and just skip over rendering anything that small,
108 - // but this has the effect of making the chart look very empty when zoomed out.
109 - // So long as perf is okay- it might be best to err on the side of showing things.
110 - const width = durationToWidth(duration, scaleFactor);
111 - if (width <= 0) {
112 - return; // Too small to render at this zoom level
113 - }
114 -
115 - const x = timestampToPosition(timestamp, scaleFactor, frame);
116 - const measureRect: Rect = {
117 - origin: {x, y: baseY},
118 - size: {width, height: REACT_MEASURE_HEIGHT},
119 - };
120 - if (!rectIntersectsRect(measureRect, rect)) {
121 - return; // Not in view
122 - }
123 -
124 - const drawableRect = intersectionOfRects(measureRect, rect);
125 - let textRect = measureRect;
126 -
127 - switch (type) {
128 - case 'commit':
129 - fillStyle = COLORS.REACT_COMMIT;
130 - hoveredFillStyle = COLORS.REACT_COMMIT_HOVER;
131 - groupSelectedFillStyle = COLORS.REACT_COMMIT_HOVER;
132 - textFillStyle = COLORS.REACT_COMMIT_TEXT;
133 -
134 - // Commit phase rects are overlapped by layout and passive rects,
135 - // and it looks bad if text flows underneath/behind these overlayed rects.
136 - if (nextMeasure != null) {
137 - // This clipping shouldn't apply for measures that don't overlap though,
138 - // like passive effects that are processed after a delay,
139 - // or if there are now layout or passive effects and the next measure is render or idle.
140 - if (nextMeasure.timestamp < measure.timestamp + measure.duration) {
141 - textRect = {
142 - ...measureRect,
143 - size: {
144 - width:
145 - timestampToPosition(
146 - nextMeasure.timestamp,
147 - scaleFactor,
148 - frame,
149 - ) - x,
150 - height: REACT_MEASURE_HEIGHT,
151 - },
152 - };
153 - }
154 - }
155 - break;
156 - case 'render-idle':
157 - // We could render idle time as diagonal hashes.
158 - // This looks nicer when zoomed in, but not so nice when zoomed out.
159 - // color = context.createPattern(getIdlePattern(), 'repeat');
160 - fillStyle = COLORS.REACT_IDLE;
161 - hoveredFillStyle = COLORS.REACT_IDLE_HOVER;
162 - groupSelectedFillStyle = COLORS.REACT_IDLE_HOVER;
163 - break;
164 - case 'render':
165 - fillStyle = COLORS.REACT_RENDER;
166 - hoveredFillStyle = COLORS.REACT_RENDER_HOVER;
167 - groupSelectedFillStyle = COLORS.REACT_RENDER_HOVER;
168 - textFillStyle = COLORS.REACT_RENDER_TEXT;
169 - break;
170 - case 'layout-effects':
171 - fillStyle = COLORS.REACT_LAYOUT_EFFECTS;
172 - hoveredFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
173 - groupSelectedFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
174 - textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
175 - break;
176 - case 'passive-effects':
177 - fillStyle = COLORS.REACT_PASSIVE_EFFECTS;
178 - hoveredFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
179 - groupSelectedFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
180 - textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
181 - break;
182 - default:
183 - throw new Error(`Unexpected measure type "${type}"`);
184 - }
185 -
186 - context.fillStyle = showHoverHighlight
187 - ? hoveredFillStyle
188 - : showGroupHighlight
189 - ? groupSelectedFillStyle
190 - : fillStyle;
191 - context.fillRect(
192 - drawableRect.origin.x,
193 - drawableRect.origin.y,
194 - drawableRect.size.width,
195 - drawableRect.size.height,
196 - );
197 -
198 - if (textFillStyle !== null) {
199 - drawText(formatDuration(duration), context, textRect, visibleArea, {
200 - fillStyle: textFillStyle,
201 - });
202 - }
203 - }
204 -
205 - draw(context: CanvasRenderingContext2D): void {
206 - const {frame, _hoveredMeasure, _lanesToRender, _profilerData, visibleArea} =
207 - this;
208 -
209 - context.fillStyle = COLORS.PRIORITY_BACKGROUND;
210 - context.fillRect(
211 - visibleArea.origin.x,
212 - visibleArea.origin.y,
213 - visibleArea.size.width,
214 - visibleArea.size.height,
215 - );
216 -
217 - const scaleFactor = positioningScaleFactor(
218 - this._intrinsicSize.width,
219 - frame,
220 - );
221 -
222 - for (let i = 0; i < _lanesToRender.length; i++) {
223 - const lane = _lanesToRender[i];
224 - const baseY = frame.origin.y + i * REACT_LANE_HEIGHT;
225 - const measuresForLane = _profilerData.laneToReactMeasureMap.get(lane);
226 -
227 - if (!measuresForLane) {
228 - throw new Error(
229 - 'No measures found for a React lane! This is a bug in this profiler tool. Please file an issue.',
230 - );
231 - }
232 -
233 - // Render lane labels
234 - const label = _profilerData.laneToLabelMap.get(lane);
235 - if (label == null) {
236 - console.warn(`Could not find label for lane ${lane}.`);
237 - } else {
238 - const labelRect = {
239 - origin: {
240 - x: visibleArea.origin.x,
241 - y: baseY,
242 - },
243 - size: {
244 - width: visibleArea.size.width,
245 - height: REACT_LANE_HEIGHT,
246 - },
247 - };
248 -
249 - drawText(label, context, labelRect, visibleArea, {
250 - fillStyle: COLORS.TEXT_DIM_COLOR,
251 - });
252 - }
253 -
254 - // Draw measures
255 - for (let j = 0; j < measuresForLane.length; j++) {
256 - const measure = measuresForLane[j];
257 - const showHoverHighlight = _hoveredMeasure === measure;
258 - const showGroupHighlight =
259 - !!_hoveredMeasure && _hoveredMeasure.batchUID === measure.batchUID;
260 -
261 - this._drawSingleReactMeasure(
262 - context,
263 - visibleArea,
264 - measure,
265 - measuresForLane[j + 1] || null,
266 - baseY,
267 - scaleFactor,
268 - showGroupHighlight,
269 - showHoverHighlight,
270 - );
271 - }
272 -
273 - // Render bottom border
274 - const borderFrame: Rect = {
275 - origin: {
276 - x: frame.origin.x,
277 - y: frame.origin.y + (i + 1) * REACT_LANE_HEIGHT - BORDER_SIZE,
278 - },
279 - size: {
280 - width: frame.size.width,
281 - height: BORDER_SIZE,
282 - },
283 - };
284 - if (rectIntersectsRect(borderFrame, visibleArea)) {
285 - const borderDrawableRect = intersectionOfRects(
286 - borderFrame,
287 - visibleArea,
288 - );
289 - context.fillStyle = COLORS.PRIORITY_BORDER;
290 - context.fillRect(
291 - borderDrawableRect.origin.x,
292 - borderDrawableRect.origin.y,
293 - borderDrawableRect.size.width,
294 - borderDrawableRect.size.height,
295 - );
296 - }
297 - }
298 - }
299 -
300 - /**
301 - * @private
302 - */
303 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
304 - const {
305 - frame,
306 - _intrinsicSize,
307 - _lanesToRender,
308 - onHover,
309 - _profilerData,
310 - visibleArea,
311 - } = this;
312 - if (!onHover) {
313 - return;
314 - }
315 -
316 - const {location} = interaction.payload;
317 - if (!rectContainsPoint(location, visibleArea)) {
318 - onHover(null);
319 - return;
320 - }
321 -
322 - // Identify the lane being hovered over
323 - const adjustedCanvasMouseY = location.y - frame.origin.y;
324 - const renderedLaneIndex = Math.floor(
325 - adjustedCanvasMouseY / REACT_LANE_HEIGHT,
326 - );
327 - if (renderedLaneIndex < 0 || renderedLaneIndex >= _lanesToRender.length) {
328 - onHover(null);
329 - return;
330 - }
331 - const lane = _lanesToRender[renderedLaneIndex];
332 -
333 - // Find the measure in `lane` being hovered over.
334 - //
335 - // Because data ranges may overlap, we want to find the last intersecting item.
336 - // This will always be the one on "top" (the one the user is hovering over).
337 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
338 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
339 - const measures = _profilerData.laneToReactMeasureMap.get(lane);
340 - if (!measures) {
341 - onHover(null);
342 - return;
343 - }
344 -
345 - for (let index = measures.length - 1; index >= 0; index--) {
346 - const measure = measures[index];
347 - const {duration, timestamp} = measure;
348 -
349 - if (
350 - hoverTimestamp >= timestamp &&
351 - hoverTimestamp <= timestamp + duration
352 - ) {
353 - this.currentCursor = 'context-menu';
354 - viewRefs.hoveredView = this;
355 - onHover(measure);
356 - return;
357 - }
358 - }
359 -
360 - onHover(null);
361 - }
362 -
363 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
364 - switch (interaction.type) {
365 - case 'mousemove':
366 - this._handleMouseMove(interaction, viewRefs);
367 - break;
368 - }
369 - }
370 -}
packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js deleted
-288
@@ -1,288 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {SchedulingEvent, TimelineData} from '../types';
11 -import type {
12 - ClickInteraction,
13 - Interaction,
14 - MouseMoveInteraction,
15 - Rect,
16 - Size,
17 - ViewRefs,
18 -} from '../view-base';
19 -
20 -import {
21 - positioningScaleFactor,
22 - timestampToPosition,
23 - positionToTimestamp,
24 - widthToDuration,
25 -} from './utils/positioning';
26 -import {
27 - View,
28 - Surface,
29 - rectContainsPoint,
30 - rectIntersectsRect,
31 - intersectionOfRects,
32 -} from '../view-base';
33 -import {
34 - COLORS,
35 - TOP_ROW_PADDING,
36 - REACT_EVENT_DIAMETER,
37 - BORDER_SIZE,
38 -} from './constants';
39 -
40 -const EVENT_ROW_HEIGHT_FIXED =
41 - TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
42 -
43 -export class SchedulingEventsView extends View {
44 - _profilerData: TimelineData;
45 - _intrinsicSize: Size;
46 -
47 - _hoveredEvent: SchedulingEvent | null = null;
48 - onHover: ((event: SchedulingEvent | null) => void) | null = null;
49 - onClick:
50 - | ((event: SchedulingEvent | null, eventIndex: number | null) => void)
51 - | null = null;
52 -
53 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
54 - super(surface, frame);
55 - this._profilerData = profilerData;
56 -
57 - this._intrinsicSize = {
58 - width: this._profilerData.duration,
59 - height: EVENT_ROW_HEIGHT_FIXED,
60 - };
61 - }
62 -
63 - desiredSize(): Size {
64 - return this._intrinsicSize;
65 - }
66 -
67 - setHoveredEvent(hoveredEvent: SchedulingEvent | null) {
68 - if (this._hoveredEvent === hoveredEvent) {
69 - return;
70 - }
71 - this._hoveredEvent = hoveredEvent;
72 - this.setNeedsDisplay();
73 - }
74 -
75 - /**
76 - * Draw a single `SchedulingEvent` as a circle in the canvas.
77 - */
78 - _drawSingleSchedulingEvent(
79 - context: CanvasRenderingContext2D,
80 - rect: Rect,
81 - event: SchedulingEvent,
82 - baseY: number,
83 - scaleFactor: number,
84 - showHoverHighlight: boolean,
85 - ) {
86 - const {frame} = this;
87 - const {timestamp, type, warning} = event;
88 -
89 - const x = timestampToPosition(timestamp, scaleFactor, frame);
90 - const radius = REACT_EVENT_DIAMETER / 2;
91 - const eventRect: Rect = {
92 - origin: {
93 - x: x - radius,
94 - y: baseY,
95 - },
96 - size: {width: REACT_EVENT_DIAMETER, height: REACT_EVENT_DIAMETER},
97 - };
98 - if (!rectIntersectsRect(eventRect, rect)) {
99 - return; // Not in view
100 - }
101 -
102 - let fillStyle = null;
103 -
104 - if (warning !== null) {
105 - fillStyle = showHoverHighlight
106 - ? COLORS.WARNING_BACKGROUND_HOVER
107 - : COLORS.WARNING_BACKGROUND;
108 - } else {
109 - switch (type) {
110 - case 'schedule-render':
111 - case 'schedule-state-update':
112 - case 'schedule-force-update':
113 - fillStyle = showHoverHighlight
114 - ? COLORS.REACT_SCHEDULE_HOVER
115 - : COLORS.REACT_SCHEDULE;
116 - break;
117 - default:
118 - if (__DEV__) {
119 - console.warn('Unexpected event type "%s"', type);
120 - }
121 - break;
122 - }
123 - }
124 -
125 - if (fillStyle !== null) {
126 - const y = eventRect.origin.y + radius;
127 -
128 - context.beginPath();
129 - context.fillStyle = fillStyle;
130 - context.arc(x, y, radius, 0, 2 * Math.PI);
131 - context.fill();
132 - }
133 - }
134 -
135 - draw(context: CanvasRenderingContext2D) {
136 - const {
137 - frame,
138 - _profilerData: {schedulingEvents},
139 - _hoveredEvent,
140 - visibleArea,
141 - } = this;
142 -
143 - context.fillStyle = COLORS.BACKGROUND;
144 - context.fillRect(
145 - visibleArea.origin.x,
146 - visibleArea.origin.y,
147 - visibleArea.size.width,
148 - visibleArea.size.height,
149 - );
150 -
151 - // Draw events
152 - const baseY = frame.origin.y + TOP_ROW_PADDING;
153 - const scaleFactor = positioningScaleFactor(
154 - this._intrinsicSize.width,
155 - frame,
156 - );
157 -
158 - const highlightedEvents: SchedulingEvent[] = [];
159 -
160 - schedulingEvents.forEach(event => {
161 - if (event === _hoveredEvent) {
162 - highlightedEvents.push(event);
163 - return;
164 - }
165 - this._drawSingleSchedulingEvent(
166 - context,
167 - visibleArea,
168 - event,
169 - baseY,
170 - scaleFactor,
171 - false,
172 - );
173 - });
174 -
175 - // Draw the highlighted items on top so they stand out.
176 - // This is helpful if there are multiple (overlapping) items close to each other.
177 - highlightedEvents.forEach(event => {
178 - this._drawSingleSchedulingEvent(
179 - context,
180 - visibleArea,
181 - event,
182 - baseY,
183 - scaleFactor,
184 - true,
185 - );
186 - });
187 -
188 - // Render bottom border.
189 - // Propose border rect, check if intersects with `rect`, draw intersection.
190 - const borderFrame: Rect = {
191 - origin: {
192 - x: frame.origin.x,
193 - y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE,
194 - },
195 - size: {
196 - width: frame.size.width,
197 - height: BORDER_SIZE,
198 - },
199 - };
200 - if (rectIntersectsRect(borderFrame, visibleArea)) {
201 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
202 - context.fillStyle = COLORS.REACT_WORK_BORDER;
203 - context.fillRect(
204 - borderDrawableRect.origin.x,
205 - borderDrawableRect.origin.y,
206 - borderDrawableRect.size.width,
207 - borderDrawableRect.size.height,
208 - );
209 - }
210 - }
211 -
212 - /**
213 - * @private
214 - */
215 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
216 - const {frame, onHover, visibleArea} = this;
217 - if (!onHover) {
218 - return;
219 - }
220 -
221 - const {location} = interaction.payload;
222 - if (!rectContainsPoint(location, visibleArea)) {
223 - onHover(null);
224 - return;
225 - }
226 -
227 - const {
228 - _profilerData: {schedulingEvents},
229 - } = this;
230 - const scaleFactor = positioningScaleFactor(
231 - this._intrinsicSize.width,
232 - frame,
233 - );
234 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
235 - const eventTimestampAllowance = widthToDuration(
236 - REACT_EVENT_DIAMETER / 2,
237 - scaleFactor,
238 - );
239 -
240 - // Because data ranges may overlap, we want to find the last intersecting item.
241 - // This will always be the one on "top" (the one the user is hovering over).
242 - for (let index = schedulingEvents.length - 1; index >= 0; index--) {
243 - const event = schedulingEvents[index];
244 - const {timestamp} = event;
245 -
246 - if (
247 - timestamp - eventTimestampAllowance <= hoverTimestamp &&
248 - hoverTimestamp <= timestamp + eventTimestampAllowance
249 - ) {
250 - this.currentCursor = 'pointer';
251 - viewRefs.hoveredView = this;
252 - onHover(event);
253 - return;
254 - }
255 - }
256 -
257 - onHover(null);
258 - }
259 -
260 - /**
261 - * @private
262 - */
263 - _handleClick(interaction: ClickInteraction) {
264 - const {onClick} = this;
265 - if (onClick) {
266 - const {
267 - _profilerData: {schedulingEvents},
268 - } = this;
269 - const eventIndex = schedulingEvents.findIndex(
270 - event => event === this._hoveredEvent,
271 - );
272 - // onHover is going to take care of all the difficult logic here of
273 - // figuring out which event when they're proximity is close.
274 - onClick(this._hoveredEvent, eventIndex >= 0 ? eventIndex : null);
275 - }
276 - }
277 -
278 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
279 - switch (interaction.type) {
280 - case 'mousemove':
281 - this._handleMouseMove(interaction, viewRefs);
282 - break;
283 - case 'click':
284 - this._handleClick(interaction);
285 - break;
286 - }
287 - }
288 -}
packages/react-devtools-timeline/src/content-views/SnapshotsView.js deleted
-260
@@ -1,260 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Snapshot, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - Point,
14 - Rect,
15 - Size,
16 - Surface,
17 - ViewRefs,
18 -} from '../view-base';
19 -
20 -import {positioningScaleFactor, timestampToPosition} from './utils/positioning';
21 -import {
22 - intersectionOfRects,
23 - rectContainsPoint,
24 - rectEqualToRect,
25 - View,
26 -} from '../view-base';
27 -import {BORDER_SIZE, COLORS, SNAPSHOT_SCRUBBER_SIZE} from './constants';
28 -
29 -type OnHover = (node: Snapshot | null) => void;
30 -
31 -export class SnapshotsView extends View {
32 - _hoverLocation: Point | null = null;
33 - _intrinsicSize: Size;
34 - _profilerData: TimelineData;
35 -
36 - onHover: OnHover | null = null;
37 -
38 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
39 - super(surface, frame);
40 -
41 - this._intrinsicSize = {
42 - width: profilerData.duration,
43 - height: profilerData.snapshotHeight,
44 - };
45 - this._profilerData = profilerData;
46 - }
47 -
48 - desiredSize(): Size {
49 - return this._intrinsicSize;
50 - }
51 -
52 - draw(context: CanvasRenderingContext2D) {
53 - const snapshotHeight = this._profilerData.snapshotHeight;
54 - const {visibleArea} = this;
55 -
56 - context.fillStyle = COLORS.BACKGROUND;
57 - context.fillRect(
58 - visibleArea.origin.x,
59 - visibleArea.origin.y,
60 - visibleArea.size.width,
61 - visibleArea.size.height,
62 - );
63 -
64 - const y = visibleArea.origin.y;
65 -
66 - let x = visibleArea.origin.x;
67 -
68 - // Rather than drawing each snapshot where it occurred,
69 - // draw them at fixed intervals and just show the nearest one.
70 - while (x < visibleArea.origin.x + visibleArea.size.width) {
71 - const snapshot = this._findClosestSnapshot(x);
72 - if (snapshot === null) {
73 - // This shold never happen.
74 - break;
75 - }
76 -
77 - const scaledHeight = snapshotHeight;
78 - const scaledWidth = (snapshot.width * snapshotHeight) / snapshot.height;
79 -
80 - const imageRect: Rect = {
81 - origin: {
82 - x,
83 - y,
84 - },
85 - size: {width: scaledWidth, height: scaledHeight},
86 - };
87 -
88 - // Lazily create and cache Image objects as we render a snapsho for the first time.
89 - if (snapshot.image === null) {
90 - const img = (snapshot.image = new Image());
91 - img.onload = () => {
92 - this._drawSnapshotImage(context, snapshot, imageRect);
93 - };
94 - img.src = snapshot.imageSource;
95 - } else {
96 - this._drawSnapshotImage(context, snapshot, imageRect);
97 - }
98 -
99 - x += scaledWidth + BORDER_SIZE;
100 - }
101 -
102 - const hoverLocation = this._hoverLocation;
103 - if (hoverLocation !== null) {
104 - const scrubberWidth = SNAPSHOT_SCRUBBER_SIZE + BORDER_SIZE * 2;
105 - const scrubberOffset = scrubberWidth / 2;
106 -
107 - context.fillStyle = COLORS.SCRUBBER_BORDER;
108 - context.fillRect(
109 - hoverLocation.x - scrubberOffset,
110 - visibleArea.origin.y,
111 - scrubberWidth,
112 - visibleArea.size.height,
113 - );
114 -
115 - context.fillStyle = COLORS.SCRUBBER_BACKGROUND;
116 - context.fillRect(
117 - hoverLocation.x - scrubberOffset + BORDER_SIZE,
118 - visibleArea.origin.y,
119 - SNAPSHOT_SCRUBBER_SIZE,
120 - visibleArea.size.height,
121 - );
122 - }
123 - }
124 -
125 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
126 - switch (interaction.type) {
127 - case 'mousemove':
128 - case 'wheel-control':
129 - case 'wheel-meta':
130 - case 'wheel-plain':
131 - case 'wheel-shift':
132 - this._updateHover(interaction.payload.location, viewRefs);
133 - break;
134 - }
135 - }
136 -
137 - _drawSnapshotImage(
138 - context: CanvasRenderingContext2D,
139 - snapshot: Snapshot,
140 - imageRect: Rect,
141 - ) {
142 - const visibleArea = this.visibleArea;
143 -
144 - // Prevent snapshot from visibly overflowing its container when clipped.
145 - // View clips by default, but since this view may draw async (on Image load) we re-clip.
146 - const shouldClip = !rectEqualToRect(imageRect, visibleArea);
147 - if (shouldClip) {
148 - const clippedRect = intersectionOfRects(imageRect, visibleArea);
149 - context.save();
150 - context.beginPath();
151 - context.rect(
152 - clippedRect.origin.x,
153 - clippedRect.origin.y,
154 - clippedRect.size.width,
155 - clippedRect.size.height,
156 - );
157 - context.closePath();
158 - context.clip();
159 - }
160 -
161 - context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
162 - context.fillRect(
163 - imageRect.origin.x,
164 - imageRect.origin.y,
165 - imageRect.size.width,
166 - imageRect.size.height,
167 - );
168 -
169 - // $FlowFixMe[incompatible-type] Flow doesn't know about the 9 argument variant of drawImage()
170 - context.drawImage(
171 - snapshot.image,
172 -
173 - // Image coordinates
174 - 0,
175 - 0,
176 -
177 - // Native image size
178 - snapshot.width,
179 - snapshot.height,
180 -
181 - // Canvas coordinates
182 - imageRect.origin.x + BORDER_SIZE,
183 - imageRect.origin.y + BORDER_SIZE,
184 -
185 - // Scaled image size
186 - imageRect.size.width - BORDER_SIZE * 2,
187 - imageRect.size.height - BORDER_SIZE * 2,
188 - );
189 -
190 - if (shouldClip) {
191 - context.restore();
192 - }
193 - }
194 -
195 - _findClosestSnapshot(x: number): Snapshot | null {
196 - const frame = this.frame;
197 - const scaleFactor = positioningScaleFactor(
198 - this._intrinsicSize.width,
199 - frame,
200 - );
201 -
202 - const snapshots = this._profilerData.snapshots;
203 -
204 - let startIndex = 0;
205 - let stopIndex = snapshots.length - 1;
206 - while (startIndex <= stopIndex) {
207 - const currentIndex = Math.floor((startIndex + stopIndex) / 2);
208 - const snapshot = snapshots[currentIndex];
209 - const {timestamp} = snapshot;
210 -
211 - const snapshotX = Math.floor(
212 - timestampToPosition(timestamp, scaleFactor, frame),
213 - );
214 -
215 - if (x < snapshotX) {
216 - stopIndex = currentIndex - 1;
217 - } else {
218 - startIndex = currentIndex + 1;
219 - }
220 - }
221 -
222 - return snapshots[stopIndex] || null;
223 - }
224 -
225 - /**
226 - * @private
227 - */
228 - _updateHover(location: Point, viewRefs: ViewRefs) {
229 - const {onHover, visibleArea} = this;
230 - if (!onHover) {
231 - return;
232 - }
233 -
234 - if (!rectContainsPoint(location, visibleArea)) {
235 - if (this._hoverLocation !== null) {
236 - this._hoverLocation = null;
237 -
238 - this.setNeedsDisplay();
239 - }
240 -
241 - onHover(null);
242 - return;
243 - }
244 -
245 - const snapshot = this._findClosestSnapshot(location.x);
246 - if (snapshot !== null) {
247 - this._hoverLocation = location;
248 -
249 - onHover(snapshot);
250 - } else {
251 - this._hoverLocation = null;
252 -
253 - onHover(null);
254 - }
255 -
256 - // Any time the mouse moves within the boundaries of this view, we need to re-render.
257 - // This is because we draw a scrubbing bar that shows the location corresponding to the current tooltip.
258 - this.setNeedsDisplay();
259 - }
260 -}
packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js deleted
-359
@@ -1,359 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {SuspenseEvent, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - IntrinsicSize,
14 - MouseMoveInteraction,
15 - Rect,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - durationToWidth,
21 - positioningScaleFactor,
22 - positionToTimestamp,
23 - timestampToPosition,
24 - widthToDuration,
25 -} from './utils/positioning';
26 -import {drawText} from './utils/text';
27 -import {formatDuration} from '../utils/formatting';
28 -import {
29 - View,
30 - Surface,
31 - rectContainsPoint,
32 - rectIntersectsRect,
33 - intersectionOfRects,
34 -} from '../view-base';
35 -import {
36 - BORDER_SIZE,
37 - COLORS,
38 - PENDING_SUSPENSE_EVENT_SIZE,
39 - SUSPENSE_EVENT_HEIGHT,
40 -} from './constants';
41 -
42 -const ROW_WITH_BORDER_HEIGHT = SUSPENSE_EVENT_HEIGHT + BORDER_SIZE;
43 -const MAX_ROWS_TO_SHOW_INITIALLY = 3;
44 -
45 -export class SuspenseEventsView extends View {
46 - _depthToSuspenseEvent: Map<number, SuspenseEvent[]>;
47 - _hoveredEvent: SuspenseEvent | null = null;
48 - _intrinsicSize: IntrinsicSize;
49 - _maxDepth: number = 0;
50 - _profilerData: TimelineData;
51 -
52 - onHover: ((event: SuspenseEvent | null) => void) | null = null;
53 -
54 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
55 - super(surface, frame);
56 -
57 - this._profilerData = profilerData;
58 -
59 - this._performPreflightComputations();
60 - }
61 -
62 - _performPreflightComputations() {
63 - this._depthToSuspenseEvent = new Map();
64 -
65 - const {duration, suspenseEvents} = this._profilerData;
66 -
67 - suspenseEvents.forEach(event => {
68 - const depth = event.depth;
69 -
70 - this._maxDepth = Math.max(this._maxDepth, depth);
71 -
72 - if (!this._depthToSuspenseEvent.has(depth)) {
73 - this._depthToSuspenseEvent.set(depth, [event]);
74 - } else {
75 - // $FlowFixMe[incompatible-use] This is unnecessary.
76 - this._depthToSuspenseEvent.get(depth).push(event);
77 - }
78 - });
79 -
80 - this._intrinsicSize = {
81 - width: duration,
82 - height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
83 - hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
84 - maxInitialHeight: ROW_WITH_BORDER_HEIGHT * MAX_ROWS_TO_SHOW_INITIALLY,
85 - };
86 - }
87 -
88 - desiredSize(): IntrinsicSize {
89 - return this._intrinsicSize;
90 - }
91 -
92 - setHoveredEvent(hoveredEvent: SuspenseEvent | null) {
93 - if (this._hoveredEvent === hoveredEvent) {
94 - return;
95 - }
96 - this._hoveredEvent = hoveredEvent;
97 - this.setNeedsDisplay();
98 - }
99 -
100 - /**
101 - * Draw a single `SuspenseEvent` as a box/span with text inside of it.
102 - */
103 - _drawSingleSuspenseEvent(
104 - context: CanvasRenderingContext2D,
105 - rect: Rect,
106 - event: SuspenseEvent,
107 - baseY: number,
108 - scaleFactor: number,
109 - showHoverHighlight: boolean,
110 - ) {
111 - const {frame} = this;
112 - const {
113 - componentName,
114 - depth,
115 - duration,
116 - phase,
117 - promiseName,
118 - resolution,
119 - timestamp,
120 - warning,
121 - } = event;
122 -
123 - baseY += depth * ROW_WITH_BORDER_HEIGHT;
124 -
125 - let fillStyle = null as any as string;
126 - if (warning !== null) {
127 - fillStyle = showHoverHighlight
128 - ? COLORS.WARNING_BACKGROUND_HOVER
129 - : COLORS.WARNING_BACKGROUND;
130 - } else {
131 - switch (resolution) {
132 - case 'rejected':
133 - fillStyle = showHoverHighlight
134 - ? COLORS.REACT_SUSPENSE_REJECTED_EVENT_HOVER
135 - : COLORS.REACT_SUSPENSE_REJECTED_EVENT;
136 - break;
137 - case 'resolved':
138 - fillStyle = showHoverHighlight
139 - ? COLORS.REACT_SUSPENSE_RESOLVED_EVENT_HOVER
140 - : COLORS.REACT_SUSPENSE_RESOLVED_EVENT;
141 - break;
142 - case 'unresolved':
143 - fillStyle = showHoverHighlight
144 - ? COLORS.REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER
145 - : COLORS.REACT_SUSPENSE_UNRESOLVED_EVENT;
146 - break;
147 - }
148 - }
149 -
150 - const xStart = timestampToPosition(timestamp, scaleFactor, frame);
151 -
152 - // Pending suspense events (ones that never resolved) won't have durations.
153 - // So instead we draw them as diamonds.
154 - if (duration === null) {
155 - const size = PENDING_SUSPENSE_EVENT_SIZE;
156 - const halfSize = size / 2;
157 -
158 - baseY += (SUSPENSE_EVENT_HEIGHT - PENDING_SUSPENSE_EVENT_SIZE) / 2;
159 -
160 - const y = baseY + halfSize;
161 -
162 - const suspenseRect: Rect = {
163 - origin: {
164 - x: xStart - halfSize,
165 - y: baseY,
166 - },
167 - size: {width: size, height: size},
168 - };
169 - if (!rectIntersectsRect(suspenseRect, rect)) {
170 - return; // Not in view
171 - }
172 -
173 - context.beginPath();
174 - context.fillStyle = fillStyle;
175 - context.moveTo(xStart, y - halfSize);
176 - context.lineTo(xStart + halfSize, y);
177 - context.lineTo(xStart, y + halfSize);
178 - context.lineTo(xStart - halfSize, y);
179 - context.fill();
180 - } else {
181 - const xStop = timestampToPosition(
182 - timestamp + duration,
183 - scaleFactor,
184 - frame,
185 - );
186 - const eventRect: Rect = {
187 - origin: {
188 - x: xStart,
189 - y: baseY,
190 - },
191 - size: {width: xStop - xStart, height: SUSPENSE_EVENT_HEIGHT},
192 - };
193 - if (!rectIntersectsRect(eventRect, rect)) {
194 - return; // Not in view
195 - }
196 -
197 - const width = durationToWidth(duration, scaleFactor);
198 - if (width < 1) {
199 - return; // Too small to render at this zoom level
200 - }
201 -
202 - const drawableRect = intersectionOfRects(eventRect, rect);
203 - context.beginPath();
204 - context.fillStyle = fillStyle;
205 - context.fillRect(
206 - drawableRect.origin.x,
207 - drawableRect.origin.y,
208 - drawableRect.size.width,
209 - drawableRect.size.height,
210 - );
211 -
212 - let label = 'suspended';
213 - if (promiseName != null) {
214 - label = promiseName;
215 - } else if (componentName != null) {
216 - label = `${componentName} ${label}`;
217 - }
218 - if (phase !== null) {
219 - label += ` during ${phase}`;
220 - }
221 - if (resolution !== 'unresolved') {
222 - label += ` - ${formatDuration(duration)}`;
223 - }
224 -
225 - drawText(label, context, eventRect, drawableRect);
226 - }
227 - }
228 -
229 - draw(context: CanvasRenderingContext2D) {
230 - const {
231 - frame,
232 - _profilerData: {suspenseEvents},
233 - _hoveredEvent,
234 - visibleArea,
235 - } = this;
236 -
237 - context.fillStyle = COLORS.PRIORITY_BACKGROUND;
238 - context.fillRect(
239 - visibleArea.origin.x,
240 - visibleArea.origin.y,
241 - visibleArea.size.width,
242 - visibleArea.size.height,
243 - );
244 -
245 - // Draw events
246 - const scaleFactor = positioningScaleFactor(
247 - this._intrinsicSize.width,
248 - frame,
249 - );
250 -
251 - suspenseEvents.forEach(event => {
252 - this._drawSingleSuspenseEvent(
253 - context,
254 - visibleArea,
255 - event,
256 - frame.origin.y,
257 - scaleFactor,
258 - event === _hoveredEvent,
259 - );
260 - });
261 -
262 - // Render bottom borders.
263 - for (let i = 0; i <= this._maxDepth; i++) {
264 - const borderFrame: Rect = {
265 - origin: {
266 - x: frame.origin.x,
267 - y: frame.origin.y + (i + 1) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
268 - },
269 - size: {
270 - width: frame.size.width,
271 - height: BORDER_SIZE,
272 - },
273 - };
274 - if (rectIntersectsRect(borderFrame, visibleArea)) {
275 - const borderDrawableRect = intersectionOfRects(
276 - borderFrame,
277 - visibleArea,
278 - );
279 - context.fillStyle = COLORS.REACT_WORK_BORDER;
280 - context.fillRect(
281 - borderDrawableRect.origin.x,
282 - borderDrawableRect.origin.y,
283 - borderDrawableRect.size.width,
284 - borderDrawableRect.size.height,
285 - );
286 - }
287 - }
288 - }
289 -
290 - /**
291 - * @private
292 - */
293 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
294 - const {frame, _intrinsicSize, onHover, visibleArea} = this;
295 - if (!onHover) {
296 - return;
297 - }
298 -
299 - const {location} = interaction.payload;
300 - if (!rectContainsPoint(location, visibleArea)) {
301 - onHover(null);
302 - return;
303 - }
304 -
305 - const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
306 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
307 -
308 - const adjustedCanvasMouseY = location.y - frame.origin.y;
309 - const depth = Math.floor(adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT);
310 - const suspenseEventsAtDepth = this._depthToSuspenseEvent.get(depth);
311 -
312 - if (suspenseEventsAtDepth) {
313 - // Find the event being hovered over.
314 - for (let index = suspenseEventsAtDepth.length - 1; index >= 0; index--) {
315 - const suspenseEvent = suspenseEventsAtDepth[index];
316 - const {duration, timestamp} = suspenseEvent;
317 -
318 - if (duration === null) {
319 - const timestampAllowance = widthToDuration(
320 - PENDING_SUSPENSE_EVENT_SIZE / 2,
321 - scaleFactor,
322 - );
323 -
324 - if (
325 - timestamp - timestampAllowance <= hoverTimestamp &&
326 - hoverTimestamp <= timestamp + timestampAllowance
327 - ) {
328 - this.currentCursor = 'context-menu';
329 -
330 - viewRefs.hoveredView = this;
331 -
332 - onHover(suspenseEvent);
333 - return;
334 - }
335 - } else if (
336 - hoverTimestamp >= timestamp &&
337 - hoverTimestamp <= timestamp + duration
338 - ) {
339 - this.currentCursor = 'context-menu';
340 -
341 - viewRefs.hoveredView = this;
342 -
343 - onHover(suspenseEvent);
344 - return;
345 - }
346 - }
347 - }
348 -
349 - onHover(null);
350 - }
351 -
352 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
353 - switch (interaction.type) {
354 - case 'mousemove':
355 - this._handleMouseMove(interaction, viewRefs);
356 - break;
357 - }
358 - }
359 -}
packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js deleted
-241
@@ -1,241 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {ThrownError, TimelineData} from '../types';
11 -import type {
12 - Interaction,
13 - MouseMoveInteraction,
14 - Rect,
15 - Size,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - positioningScaleFactor,
21 - timestampToPosition,
22 - positionToTimestamp,
23 - widthToDuration,
24 -} from './utils/positioning';
25 -import {
26 - View,
27 - Surface,
28 - rectContainsPoint,
29 - rectIntersectsRect,
30 - intersectionOfRects,
31 -} from '../view-base';
32 -import {
33 - COLORS,
34 - TOP_ROW_PADDING,
35 - REACT_EVENT_DIAMETER,
36 - BORDER_SIZE,
37 -} from './constants';
38 -
39 -const EVENT_ROW_HEIGHT_FIXED =
40 - TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
41 -
42 -export class ThrownErrorsView extends View {
43 - _profilerData: TimelineData;
44 - _intrinsicSize: Size;
45 - _hoveredEvent: ThrownError | null = null;
46 - onHover: ((event: ThrownError | null) => void) | null = null;
47 -
48 - constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
49 - super(surface, frame);
50 - this._profilerData = profilerData;
51 -
52 - this._intrinsicSize = {
53 - width: this._profilerData.duration,
54 - height: EVENT_ROW_HEIGHT_FIXED,
55 - };
56 - }
57 -
58 - desiredSize(): Size {
59 - return this._intrinsicSize;
60 - }
61 -
62 - setHoveredEvent(hoveredEvent: ThrownError | null) {
63 - if (this._hoveredEvent === hoveredEvent) {
64 - return;
65 - }
66 - this._hoveredEvent = hoveredEvent;
67 - this.setNeedsDisplay();
68 - }
69 -
70 - /**
71 - * Draw a single `ThrownError` as a circle in the canvas.
72 - */
73 - _drawSingleThrownError(
74 - context: CanvasRenderingContext2D,
75 - rect: Rect,
76 - thrownError: ThrownError,
77 - baseY: number,
78 - scaleFactor: number,
79 - showHoverHighlight: boolean,
80 - ) {
81 - const {frame} = this;
82 - const {timestamp} = thrownError;
83 -
84 - const x = timestampToPosition(timestamp, scaleFactor, frame);
85 - const radius = REACT_EVENT_DIAMETER / 2;
86 - const eventRect: Rect = {
87 - origin: {
88 - x: x - radius,
89 - y: baseY,
90 - },
91 - size: {width: REACT_EVENT_DIAMETER, height: REACT_EVENT_DIAMETER},
92 - };
93 - if (!rectIntersectsRect(eventRect, rect)) {
94 - return; // Not in view
95 - }
96 -
97 - const fillStyle = showHoverHighlight
98 - ? COLORS.REACT_THROWN_ERROR_HOVER
99 - : COLORS.REACT_THROWN_ERROR;
100 -
101 - const y = eventRect.origin.y + radius;
102 -
103 - context.beginPath();
104 - context.fillStyle = fillStyle;
105 - context.arc(x, y, radius, 0, 2 * Math.PI);
106 - context.fill();
107 - }
108 -
109 - draw(context: CanvasRenderingContext2D) {
110 - const {
111 - frame,
112 - _profilerData: {thrownErrors},
113 - _hoveredEvent,
114 - visibleArea,
115 - } = this;
116 -
117 - context.fillStyle = COLORS.BACKGROUND;
118 - context.fillRect(
119 - visibleArea.origin.x,
120 - visibleArea.origin.y,
121 - visibleArea.size.width,
122 - visibleArea.size.height,
123 - );
124 -
125 - // Draw events
126 - const baseY = frame.origin.y + TOP_ROW_PADDING;
127 - const scaleFactor = positioningScaleFactor(
128 - this._intrinsicSize.width,
129 - frame,
130 - );
131 -
132 - const highlightedEvents: ThrownError[] = [];
133 -
134 - thrownErrors.forEach(thrownError => {
135 - if (thrownError === _hoveredEvent) {
136 - highlightedEvents.push(thrownError);
137 - return;
138 - }
139 - this._drawSingleThrownError(
140 - context,
141 - visibleArea,
142 - thrownError,
143 - baseY,
144 - scaleFactor,
145 - false,
146 - );
147 - });
148 -
149 - // Draw the highlighted items on top so they stand out.
150 - // This is helpful if there are multiple (overlapping) items close to each other.
151 - highlightedEvents.forEach(thrownError => {
152 - this._drawSingleThrownError(
153 - context,
154 - visibleArea,
155 - thrownError,
156 - baseY,
157 - scaleFactor,
158 - true,
159 - );
160 - });
161 -
162 - // Render bottom borders.
163 - // Propose border rect, check if intersects with `rect`, draw intersection.
164 - const borderFrame: Rect = {
165 - origin: {
166 - x: frame.origin.x,
167 - y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE,
168 - },
169 - size: {
170 - width: frame.size.width,
171 - height: BORDER_SIZE,
172 - },
173 - };
174 - if (rectIntersectsRect(borderFrame, visibleArea)) {
175 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
176 - context.fillStyle = COLORS.REACT_WORK_BORDER;
177 - context.fillRect(
178 - borderDrawableRect.origin.x,
179 - borderDrawableRect.origin.y,
180 - borderDrawableRect.size.width,
181 - borderDrawableRect.size.height,
182 - );
183 - }
184 - }
185 -
186 - /**
187 - * @private
188 - */
189 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
190 - const {frame, onHover, visibleArea} = this;
191 - if (!onHover) {
192 - return;
193 - }
194 -
195 - const {location} = interaction.payload;
196 - if (!rectContainsPoint(location, visibleArea)) {
197 - onHover(null);
198 - return;
199 - }
200 -
201 - const {
202 - _profilerData: {thrownErrors},
203 - } = this;
204 - const scaleFactor = positioningScaleFactor(
205 - this._intrinsicSize.width,
206 - frame,
207 - );
208 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
209 - const eventTimestampAllowance = widthToDuration(
210 - REACT_EVENT_DIAMETER / 2,
211 - scaleFactor,
212 - );
213 -
214 - // Because data ranges may overlap, we want to find the last intersecting item.
215 - // This will always be the one on "top" (the one the user is hovering over).
216 - for (let index = thrownErrors.length - 1; index >= 0; index--) {
217 - const event = thrownErrors[index];
218 - const {timestamp} = event;
219 -
220 - if (
221 - timestamp - eventTimestampAllowance <= hoverTimestamp &&
222 - hoverTimestamp <= timestamp + eventTimestampAllowance
223 - ) {
224 - this.currentCursor = 'context-menu';
225 - viewRefs.hoveredView = this;
226 - onHover(event);
227 - return;
228 - }
229 - }
230 -
231 - onHover(null);
232 - }
233 -
234 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
235 - switch (interaction.type) {
236 - case 'mousemove':
237 - this._handleMouseMove(interaction, viewRefs);
238 - break;
239 - }
240 - }
241 -}
packages/react-devtools-timeline/src/content-views/TimeAxisMarkersView.js deleted
-163
@@ -1,163 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect, Size} from '../view-base';
11 -
12 -import {
13 - durationToWidth,
14 - positioningScaleFactor,
15 - positionToTimestamp,
16 - timestampToPosition,
17 -} from './utils/positioning';
18 -import {
19 - View,
20 - Surface,
21 - rectIntersectsRect,
22 - intersectionOfRects,
23 -} from '../view-base';
24 -import {
25 - COLORS,
26 - INTERVAL_TIMES,
27 - LABEL_SIZE,
28 - FONT_SIZE,
29 - MARKER_HEIGHT,
30 - MARKER_TEXT_PADDING,
31 - MARKER_TICK_HEIGHT,
32 - MIN_INTERVAL_SIZE_PX,
33 - BORDER_SIZE,
34 -} from './constants';
35 -
36 -const HEADER_HEIGHT_FIXED = MARKER_HEIGHT + BORDER_SIZE;
37 -const LABEL_FIXED_WIDTH = LABEL_SIZE + BORDER_SIZE;
38 -
39 -export class TimeAxisMarkersView extends View {
40 - _totalDuration: number;
41 - _intrinsicSize: Size;
42 -
43 - constructor(surface: Surface, frame: Rect, totalDuration: number) {
44 - super(surface, frame);
45 - this._totalDuration = totalDuration;
46 - this._intrinsicSize = {
47 - width: this._totalDuration,
48 - height: HEADER_HEIGHT_FIXED,
49 - };
50 - }
51 -
52 - desiredSize(): Size {
53 - return this._intrinsicSize;
54 - }
55 -
56 - // Time mark intervals vary based on the current zoom range and the time it represents.
57 - // In Chrome, these seem to range from 70-140 pixels wide.
58 - // Time wise, they represent intervals of e.g. 1s, 500ms, 200ms, 100ms, 50ms, 20ms.
59 - // Based on zoom, we should determine which amount to actually show.
60 - _getTimeTickInterval(scaleFactor: number): number {
61 - for (let i = 0; i < INTERVAL_TIMES.length; i++) {
62 - const currentInterval = INTERVAL_TIMES[i];
63 - const intervalWidth = durationToWidth(currentInterval, scaleFactor);
64 - if (intervalWidth > MIN_INTERVAL_SIZE_PX) {
65 - return currentInterval;
66 - }
67 - }
68 - return INTERVAL_TIMES[0];
69 - }
70 -
71 - draw(context: CanvasRenderingContext2D) {
72 - const {frame, _intrinsicSize, visibleArea} = this;
73 - const clippedFrame = {
74 - origin: frame.origin,
75 - size: {
76 - width: frame.size.width,
77 - height: _intrinsicSize.height,
78 - },
79 - };
80 - const drawableRect = intersectionOfRects(clippedFrame, visibleArea);
81 -
82 - // Clear background
83 - context.fillStyle = COLORS.BACKGROUND;
84 - context.fillRect(
85 - drawableRect.origin.x,
86 - drawableRect.origin.y,
87 - drawableRect.size.width,
88 - drawableRect.size.height,
89 - );
90 -
91 - const scaleFactor = positioningScaleFactor(
92 - _intrinsicSize.width,
93 - clippedFrame,
94 - );
95 - const interval = this._getTimeTickInterval(scaleFactor);
96 - const firstIntervalTimestamp =
97 - Math.ceil(
98 - positionToTimestamp(
99 - drawableRect.origin.x - LABEL_FIXED_WIDTH,
100 - scaleFactor,
101 - clippedFrame,
102 - ) / interval,
103 - ) * interval;
104 -
105 - for (
106 - let markerTimestamp = firstIntervalTimestamp;
107 - true;
108 - markerTimestamp += interval
109 - ) {
110 - if (markerTimestamp <= 0) {
111 - continue; // Timestamps < are probably a bug; markers at 0 are ugly.
112 - }
113 -
114 - const x = timestampToPosition(markerTimestamp, scaleFactor, clippedFrame);
115 - if (x > drawableRect.origin.x + drawableRect.size.width) {
116 - break; // Not in view
117 - }
118 -
119 - const markerLabel = Math.round(markerTimestamp);
120 -
121 - context.fillStyle = COLORS.PRIORITY_BORDER;
122 - context.fillRect(
123 - x,
124 - drawableRect.origin.y + MARKER_HEIGHT - MARKER_TICK_HEIGHT,
125 - BORDER_SIZE,
126 - MARKER_TICK_HEIGHT,
127 - );
128 -
129 - context.fillStyle = COLORS.TIME_MARKER_LABEL;
130 - context.textAlign = 'right';
131 - context.textBaseline = 'middle';
132 - context.font = `${FONT_SIZE}px sans-serif`;
133 - context.fillText(
134 - `${markerLabel}ms`,
135 - x - MARKER_TEXT_PADDING,
136 - MARKER_HEIGHT / 2,
137 - );
138 - }
139 -
140 - // Render bottom border.
141 - // Propose border rect, check if intersects with `rect`, draw intersection.
142 - const borderFrame: Rect = {
143 - origin: {
144 - x: clippedFrame.origin.x,
145 - y: clippedFrame.origin.y + clippedFrame.size.height - BORDER_SIZE,
146 - },
147 - size: {
148 - width: clippedFrame.size.width,
149 - height: BORDER_SIZE,
150 - },
151 - };
152 - if (rectIntersectsRect(borderFrame, visibleArea)) {
153 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
154 - context.fillStyle = COLORS.PRIORITY_BORDER;
155 - context.fillRect(
156 - borderDrawableRect.origin.x,
157 - borderDrawableRect.origin.y,
158 - borderDrawableRect.size.width,
159 - borderDrawableRect.size.height,
160 - );
161 - }
162 - }
163 -}
packages/react-devtools-timeline/src/content-views/UserTimingMarksView.js deleted
-244
@@ -1,244 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {UserTimingMark} from '../types';
11 -import type {
12 - Interaction,
13 - MouseMoveInteraction,
14 - Rect,
15 - Size,
16 - ViewRefs,
17 -} from '../view-base';
18 -
19 -import {
20 - positioningScaleFactor,
21 - timestampToPosition,
22 - positionToTimestamp,
23 - widthToDuration,
24 -} from './utils/positioning';
25 -import {
26 - View,
27 - Surface,
28 - rectContainsPoint,
29 - rectIntersectsRect,
30 - intersectionOfRects,
31 -} from '../view-base';
32 -import {
33 - COLORS,
34 - TOP_ROW_PADDING,
35 - USER_TIMING_MARK_SIZE,
36 - BORDER_SIZE,
37 -} from './constants';
38 -
39 -const ROW_HEIGHT_FIXED =
40 - TOP_ROW_PADDING + USER_TIMING_MARK_SIZE + TOP_ROW_PADDING;
41 -
42 -export class UserTimingMarksView extends View {
43 - _marks: UserTimingMark[];
44 - _intrinsicSize: Size;
45 -
46 - _hoveredMark: UserTimingMark | null = null;
47 - onHover: ((mark: UserTimingMark | null) => void) | null = null;
48 -
49 - constructor(
50 - surface: Surface,
51 - frame: Rect,
52 - marks: UserTimingMark[],
53 - duration: number,
54 - ) {
55 - super(surface, frame);
56 - this._marks = marks;
57 -
58 - this._intrinsicSize = {
59 - width: duration,
60 - height: ROW_HEIGHT_FIXED,
61 - };
62 - }
63 -
64 - desiredSize(): Size {
65 - return this._intrinsicSize;
66 - }
67 -
68 - setHoveredMark(hoveredMark: UserTimingMark | null) {
69 - if (this._hoveredMark === hoveredMark) {
70 - return;
71 - }
72 - this._hoveredMark = hoveredMark;
73 - this.setNeedsDisplay();
74 - }
75 -
76 - /**
77 - * Draw a single `UserTimingMark` as a circle in the canvas.
78 - */
79 - _drawSingleMark(
80 - context: CanvasRenderingContext2D,
81 - rect: Rect,
82 - mark: UserTimingMark,
83 - baseY: number,
84 - scaleFactor: number,
85 - showHoverHighlight: boolean,
86 - ) {
87 - const {frame} = this;
88 - const {timestamp} = mark;
89 -
90 - const x = timestampToPosition(timestamp, scaleFactor, frame);
91 - const size = USER_TIMING_MARK_SIZE;
92 - const halfSize = size / 2;
93 -
94 - const markRect: Rect = {
95 - origin: {
96 - x: x - halfSize,
97 - y: baseY,
98 - },
99 - size: {width: size, height: size},
100 - };
101 - if (!rectIntersectsRect(markRect, rect)) {
102 - return; // Not in view
103 - }
104 -
105 - const fillStyle = showHoverHighlight
106 - ? COLORS.USER_TIMING_HOVER
107 - : COLORS.USER_TIMING;
108 -
109 - // $FlowFixMe[invalid-compare]
110 - if (fillStyle !== null) {
111 - const y = baseY + halfSize;
112 -
113 - context.beginPath();
114 - context.fillStyle = fillStyle;
115 - context.moveTo(x, y - halfSize);
116 - context.lineTo(x + halfSize, y);
117 - context.lineTo(x, y + halfSize);
118 - context.lineTo(x - halfSize, y);
119 - context.fill();
120 - }
121 - }
122 -
123 - draw(context: CanvasRenderingContext2D) {
124 - const {frame, _marks, _hoveredMark, visibleArea} = this;
125 -
126 - context.fillStyle = COLORS.BACKGROUND;
127 - context.fillRect(
128 - visibleArea.origin.x,
129 - visibleArea.origin.y,
130 - visibleArea.size.width,
131 - visibleArea.size.height,
132 - );
133 -
134 - // Draw marks
135 - const baseY = frame.origin.y + TOP_ROW_PADDING;
136 - const scaleFactor = positioningScaleFactor(
137 - this._intrinsicSize.width,
138 - frame,
139 - );
140 -
141 - _marks.forEach(mark => {
142 - if (mark === _hoveredMark) {
143 - return;
144 - }
145 - this._drawSingleMark(
146 - context,
147 - visibleArea,
148 - mark,
149 - baseY,
150 - scaleFactor,
151 - false,
152 - );
153 - });
154 -
155 - // Draw the hovered and/or selected items on top so they stand out.
156 - // This is helpful if there are multiple (overlapping) items close to each other.
157 - if (_hoveredMark !== null) {
158 - this._drawSingleMark(
159 - context,
160 - visibleArea,
161 - _hoveredMark,
162 - baseY,
163 - scaleFactor,
164 - true,
165 - );
166 - }
167 -
168 - // Render bottom border.
169 - // Propose border rect, check if intersects with `rect`, draw intersection.
170 - const borderFrame: Rect = {
171 - origin: {
172 - x: frame.origin.x,
173 - y: frame.origin.y + ROW_HEIGHT_FIXED - BORDER_SIZE,
174 - },
175 - size: {
176 - width: frame.size.width,
177 - height: BORDER_SIZE,
178 - },
179 - };
180 - if (rectIntersectsRect(borderFrame, visibleArea)) {
181 - const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
182 - context.fillStyle = COLORS.PRIORITY_BORDER;
183 - context.fillRect(
184 - borderDrawableRect.origin.x,
185 - borderDrawableRect.origin.y,
186 - borderDrawableRect.size.width,
187 - borderDrawableRect.size.height,
188 - );
189 - }
190 - }
191 -
192 - /**
193 - * @private
194 - */
195 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
196 - const {frame, onHover, visibleArea} = this;
197 - if (!onHover) {
198 - return;
199 - }
200 -
201 - const {location} = interaction.payload;
202 - if (!rectContainsPoint(location, visibleArea)) {
203 - onHover(null);
204 - return;
205 - }
206 -
207 - const {_marks} = this;
208 - const scaleFactor = positioningScaleFactor(
209 - this._intrinsicSize.width,
210 - frame,
211 - );
212 - const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
213 - const timestampAllowance = widthToDuration(
214 - USER_TIMING_MARK_SIZE / 2,
215 - scaleFactor,
216 - );
217 -
218 - // Because data ranges may overlap, we want to find the last intersecting item.
219 - // This will always be the one on "top" (the one the user is hovering over).
220 - for (let index = _marks.length - 1; index >= 0; index--) {
221 - const mark = _marks[index];
222 - const {timestamp} = mark;
223 -
224 - if (
225 - timestamp - timestampAllowance <= hoverTimestamp &&
226 - hoverTimestamp <= timestamp + timestampAllowance
227 - ) {
228 - viewRefs.hoveredView = this;
229 - onHover(mark);
230 - return;
231 - }
232 - }
233 -
234 - onHover(null);
235 - }
236 -
237 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
238 - switch (interaction.type) {
239 - case 'mousemove':
240 - this._handleMouseMove(interaction, viewRefs);
241 - break;
242 - }
243 - }
244 -}
packages/react-devtools-timeline/src/content-views/constants.js deleted
-308
@@ -1,308 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export const DPR: number = window.devicePixelRatio || 1;
11 -export const LABEL_SIZE = 80;
12 -export const MARKER_HEIGHT = 20;
13 -export const MARKER_TICK_HEIGHT = 8;
14 -export const FONT_SIZE = 10;
15 -export const MARKER_TEXT_PADDING = 8;
16 -export const COLOR_HOVER_DIM_DELTA = 5;
17 -export const TOP_ROW_PADDING = 4;
18 -export const NATIVE_EVENT_HEIGHT = 14;
19 -export const SUSPENSE_EVENT_HEIGHT: number = 14;
20 -export const PENDING_SUSPENSE_EVENT_SIZE = 8;
21 -export const REACT_EVENT_DIAMETER = 6;
22 -export const USER_TIMING_MARK_SIZE = 8;
23 -export const REACT_MEASURE_HEIGHT = 14;
24 -export const BORDER_SIZE = 1 / DPR;
25 -export const FLAMECHART_FRAME_HEIGHT = 14;
26 -export const TEXT_PADDING = 3;
27 -export const SNAPSHOT_SCRUBBER_SIZE = 3;
28 -
29 -export const INTERVAL_TIMES = [
30 - 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000,
31 -];
32 -export const MIN_INTERVAL_SIZE_PX = 70;
33 -
34 -// TODO Replace this with "export let" vars
35 -export let COLORS: {
36 - BACKGROUND: string,
37 - INTERNAL_MODULE_FRAME: string,
38 - INTERNAL_MODULE_FRAME_HOVER: string,
39 - INTERNAL_MODULE_FRAME_TEXT: string,
40 - NATIVE_EVENT: string,
41 - NATIVE_EVENT_HOVER: string,
42 - NETWORK_PRIMARY: string,
43 - NETWORK_PRIMARY_HOVER: string,
44 - NETWORK_SECONDARY: string,
45 - NETWORK_SECONDARY_HOVER: string,
46 - PRIORITY_BACKGROUND: string,
47 - PRIORITY_BORDER: string,
48 - PRIORITY_LABEL: string,
49 - REACT_COMMIT: string,
50 - REACT_COMMIT_HOVER: string,
51 - REACT_COMMIT_TEXT: string,
52 - REACT_IDLE: string,
53 - REACT_IDLE_HOVER: string,
54 - REACT_LAYOUT_EFFECTS: string,
55 - REACT_LAYOUT_EFFECTS_HOVER: string,
56 - REACT_LAYOUT_EFFECTS_TEXT: string,
57 - REACT_PASSIVE_EFFECTS: string,
58 - REACT_PASSIVE_EFFECTS_HOVER: string,
59 - REACT_PASSIVE_EFFECTS_TEXT: string,
60 - REACT_RENDER: string,
61 - REACT_RENDER_HOVER: string,
62 - REACT_RENDER_TEXT: string,
63 - REACT_RESIZE_BAR: string,
64 - REACT_RESIZE_BAR_ACTIVE: string,
65 - REACT_RESIZE_BAR_BORDER: string,
66 - REACT_RESIZE_BAR_DOT: string,
67 - REACT_SCHEDULE: string,
68 - REACT_SCHEDULE_HOVER: string,
69 - REACT_SUSPENSE_REJECTED_EVENT: string,
70 - REACT_SUSPENSE_REJECTED_EVENT_HOVER: string,
71 - REACT_SUSPENSE_RESOLVED_EVENT: string,
72 - REACT_SUSPENSE_RESOLVED_EVENT_HOVER: string,
73 - REACT_SUSPENSE_UNRESOLVED_EVENT: string,
74 - REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: string,
75 - REACT_THROWN_ERROR: string,
76 - REACT_THROWN_ERROR_HOVER: string,
77 - REACT_WORK_BORDER: string,
78 - SCROLL_CARET: string,
79 - SCRUBBER_BACKGROUND: string,
80 - SCRUBBER_BORDER: string,
81 - SEARCH_RESULT_FILL: string,
82 - TEXT_COLOR: string,
83 - TEXT_DIM_COLOR: string,
84 - TIME_MARKER_LABEL: string,
85 - USER_TIMING: string,
86 - USER_TIMING_HOVER: string,
87 - WARNING_BACKGROUND: string,
88 - WARNING_BACKGROUND_HOVER: string,
89 - WARNING_TEXT: string,
90 - WARNING_TEXT_INVERED: string,
91 -} = {
92 - BACKGROUND: '',
93 - INTERNAL_MODULE_FRAME: '',
94 - INTERNAL_MODULE_FRAME_HOVER: '',
95 - INTERNAL_MODULE_FRAME_TEXT: '',
96 - NATIVE_EVENT: '',
97 - NATIVE_EVENT_HOVER: '',
98 - NETWORK_PRIMARY: '',
99 - NETWORK_PRIMARY_HOVER: '',
100 - NETWORK_SECONDARY: '',
101 - NETWORK_SECONDARY_HOVER: '',
102 - PRIORITY_BACKGROUND: '',
103 - PRIORITY_BORDER: '',
104 - PRIORITY_LABEL: '',
105 - USER_TIMING: '',
106 - USER_TIMING_HOVER: '',
107 - REACT_IDLE: '',
108 - REACT_IDLE_HOVER: '',
109 - REACT_RENDER: '',
110 - REACT_RENDER_HOVER: '',
111 - REACT_RENDER_TEXT: '',
112 - REACT_COMMIT: '',
113 - REACT_COMMIT_HOVER: '',
114 - REACT_COMMIT_TEXT: '',
115 - REACT_LAYOUT_EFFECTS: '',
116 - REACT_LAYOUT_EFFECTS_HOVER: '',
117 - REACT_LAYOUT_EFFECTS_TEXT: '',
118 - REACT_PASSIVE_EFFECTS: '',
119 - REACT_PASSIVE_EFFECTS_HOVER: '',
120 - REACT_PASSIVE_EFFECTS_TEXT: '',
121 - REACT_RESIZE_BAR: '',
122 - REACT_RESIZE_BAR_ACTIVE: '',
123 - REACT_RESIZE_BAR_BORDER: '',
124 - REACT_RESIZE_BAR_DOT: '',
125 - REACT_SCHEDULE: '',
126 - REACT_SCHEDULE_HOVER: '',
127 - REACT_SUSPENSE_REJECTED_EVENT: '',
128 - REACT_SUSPENSE_REJECTED_EVENT_HOVER: '',
129 - REACT_SUSPENSE_RESOLVED_EVENT: '',
130 - REACT_SUSPENSE_RESOLVED_EVENT_HOVER: '',
131 - REACT_SUSPENSE_UNRESOLVED_EVENT: '',
132 - REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: '',
133 - REACT_THROWN_ERROR: '',
134 - REACT_THROWN_ERROR_HOVER: '',
135 - REACT_WORK_BORDER: '',
136 - SCROLL_CARET: '',
137 - SCRUBBER_BACKGROUND: '',
138 - SCRUBBER_BORDER: '',
139 - SEARCH_RESULT_FILL: '',
140 - TEXT_COLOR: '',
141 - TEXT_DIM_COLOR: '',
142 - TIME_MARKER_LABEL: '',
143 - WARNING_BACKGROUND: '',
144 - WARNING_BACKGROUND_HOVER: '',
145 - WARNING_TEXT: '',
146 - WARNING_TEXT_INVERED: '',
147 -};
148 -
149 -export function updateColorsToMatchTheme(element: Element): boolean {
150 - const computedStyle = getComputedStyle(element);
151 -
152 - // Check to see if styles have been initialized...
153 - if (computedStyle.getPropertyValue('--color-background') == null) {
154 - return false;
155 - }
156 -
157 - COLORS = {
158 - BACKGROUND: computedStyle.getPropertyValue('--color-background'),
159 - INTERNAL_MODULE_FRAME: computedStyle.getPropertyValue(
160 - '--color-timeline-internal-module',
161 - ),
162 - INTERNAL_MODULE_FRAME_HOVER: computedStyle.getPropertyValue(
163 - '--color-timeline-internal-module-hover',
164 - ),
165 - INTERNAL_MODULE_FRAME_TEXT: computedStyle.getPropertyValue(
166 - '--color-timeline-internal-module-text',
167 - ),
168 - NATIVE_EVENT: computedStyle.getPropertyValue(
169 - '--color-timeline-native-event',
170 - ),
171 - NATIVE_EVENT_HOVER: computedStyle.getPropertyValue(
172 - '--color-timeline-native-event-hover',
173 - ),
174 - NETWORK_PRIMARY: computedStyle.getPropertyValue(
175 - '--color-timeline-network-primary',
176 - ),
177 - NETWORK_PRIMARY_HOVER: computedStyle.getPropertyValue(
178 - '--color-timeline-network-primary-hover',
179 - ),
180 - NETWORK_SECONDARY: computedStyle.getPropertyValue(
181 - '--color-timeline-network-secondary',
182 - ),
183 - NETWORK_SECONDARY_HOVER: computedStyle.getPropertyValue(
184 - '--color-timeline-network-secondary-hover',
185 - ),
186 - PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
187 - '--color-timeline-priority-background',
188 - ),
189 - PRIORITY_BORDER: computedStyle.getPropertyValue(
190 - '--color-timeline-priority-border',
191 - ),
192 - PRIORITY_LABEL: computedStyle.getPropertyValue('--color-text'),
193 - USER_TIMING: computedStyle.getPropertyValue('--color-timeline-user-timing'),
194 - USER_TIMING_HOVER: computedStyle.getPropertyValue(
195 - '--color-timeline-user-timing-hover',
196 - ),
197 - REACT_IDLE: computedStyle.getPropertyValue('--color-timeline-react-idle'),
198 - REACT_IDLE_HOVER: computedStyle.getPropertyValue(
199 - '--color-timeline-react-idle-hover',
200 - ),
201 - REACT_RENDER: computedStyle.getPropertyValue(
202 - '--color-timeline-react-render',
203 - ),
204 - REACT_RENDER_HOVER: computedStyle.getPropertyValue(
205 - '--color-timeline-react-render-hover',
206 - ),
207 - REACT_RENDER_TEXT: computedStyle.getPropertyValue(
208 - '--color-timeline-react-render-text',
209 - ),
210 - REACT_COMMIT: computedStyle.getPropertyValue(
211 - '--color-timeline-react-commit',
212 - ),
213 - REACT_COMMIT_HOVER: computedStyle.getPropertyValue(
214 - '--color-timeline-react-commit-hover',
215 - ),
216 - REACT_COMMIT_TEXT: computedStyle.getPropertyValue(
217 - '--color-timeline-react-commit-text',
218 - ),
219 - REACT_LAYOUT_EFFECTS: computedStyle.getPropertyValue(
220 - '--color-timeline-react-layout-effects',
221 - ),
222 - REACT_LAYOUT_EFFECTS_HOVER: computedStyle.getPropertyValue(
223 - '--color-timeline-react-layout-effects-hover',
224 - ),
225 - REACT_LAYOUT_EFFECTS_TEXT: computedStyle.getPropertyValue(
226 - '--color-timeline-react-layout-effects-text',
227 - ),
228 - REACT_PASSIVE_EFFECTS: computedStyle.getPropertyValue(
229 - '--color-timeline-react-passive-effects',
230 - ),
231 - REACT_PASSIVE_EFFECTS_HOVER: computedStyle.getPropertyValue(
232 - '--color-timeline-react-passive-effects-hover',
233 - ),
234 - REACT_PASSIVE_EFFECTS_TEXT: computedStyle.getPropertyValue(
235 - '--color-timeline-react-passive-effects-text',
236 - ),
237 - REACT_RESIZE_BAR: computedStyle.getPropertyValue('--color-resize-bar'),
238 - REACT_RESIZE_BAR_ACTIVE: computedStyle.getPropertyValue(
239 - '--color-resize-bar-active',
240 - ),
241 - REACT_RESIZE_BAR_BORDER: computedStyle.getPropertyValue(
242 - '--color-resize-bar-border',
243 - ),
244 - REACT_RESIZE_BAR_DOT: computedStyle.getPropertyValue(
245 - '--color-resize-bar-dot',
246 - ),
247 - REACT_SCHEDULE: computedStyle.getPropertyValue(
248 - '--color-timeline-react-schedule',
249 - ),
250 - REACT_SCHEDULE_HOVER: computedStyle.getPropertyValue(
251 - '--color-timeline-react-schedule-hover',
252 - ),
253 - REACT_SUSPENSE_REJECTED_EVENT: computedStyle.getPropertyValue(
254 - '--color-timeline-react-suspense-rejected',
255 - ),
256 - REACT_SUSPENSE_REJECTED_EVENT_HOVER: computedStyle.getPropertyValue(
257 - '--color-timeline-react-suspense-rejected-hover',
258 - ),
259 - REACT_SUSPENSE_RESOLVED_EVENT: computedStyle.getPropertyValue(
260 - '--color-timeline-react-suspense-resolved',
261 - ),
262 - REACT_SUSPENSE_RESOLVED_EVENT_HOVER: computedStyle.getPropertyValue(
263 - '--color-timeline-react-suspense-resolved-hover',
264 - ),
265 - REACT_SUSPENSE_UNRESOLVED_EVENT: computedStyle.getPropertyValue(
266 - '--color-timeline-react-suspense-unresolved',
267 - ),
268 - REACT_SUSPENSE_UNRESOLVED_EVENT_HOVER: computedStyle.getPropertyValue(
269 - '--color-timeline-react-suspense-unresolved-hover',
270 - ),
271 - REACT_THROWN_ERROR: computedStyle.getPropertyValue(
272 - '--color-timeline-thrown-error',
273 - ),
274 - REACT_THROWN_ERROR_HOVER: computedStyle.getPropertyValue(
275 - '--color-timeline-thrown-error-hover',
276 - ),
277 - REACT_WORK_BORDER: computedStyle.getPropertyValue(
278 - '--color-timeline-react-work-border',
279 - ),
280 - SCROLL_CARET: computedStyle.getPropertyValue('--color-scroll-caret'),
281 - SCRUBBER_BACKGROUND: computedStyle.getPropertyValue(
282 - '--color-timeline-react-suspense-rejected',
283 - ),
284 - SEARCH_RESULT_FILL: computedStyle.getPropertyValue(
285 - '--color-timeline-react-suspense-rejected',
286 - ),
287 - SCRUBBER_BORDER: computedStyle.getPropertyValue(
288 - '--color-timeline-text-color',
289 - ),
290 - TEXT_COLOR: computedStyle.getPropertyValue('--color-timeline-text-color'),
291 - TEXT_DIM_COLOR: computedStyle.getPropertyValue(
292 - '--color-timeline-text-dim-color',
293 - ),
294 - TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
295 - WARNING_BACKGROUND: computedStyle.getPropertyValue(
296 - '--color-warning-background',
297 - ),
298 - WARNING_BACKGROUND_HOVER: computedStyle.getPropertyValue(
299 - '--color-warning-background-hover',
300 - ),
301 - WARNING_TEXT: computedStyle.getPropertyValue('--color-warning-text-color'),
302 - WARNING_TEXT_INVERED: computedStyle.getPropertyValue(
303 - '--color-warning-text-color-inverted',
304 - ),
305 - };
306 -
307 - return true;
308 -}
packages/react-devtools-timeline/src/content-views/index.js deleted
-20
@@ -1,20 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export * from './ComponentMeasuresView';
11 -export * from './FlamechartView';
12 -export * from './NativeEventsView';
13 -export * from './NetworkMeasuresView';
14 -export * from './ReactMeasuresView';
15 -export * from './SchedulingEventsView';
16 -export * from './SnapshotsView';
17 -export * from './SuspenseEventsView';
18 -export * from './ThrownErrorsView';
19 -export * from './TimeAxisMarkersView';
20 -export * from './UserTimingMarksView';
packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-one.js deleted
-16
@@ -1,16 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export const outerErrorA = new Error();
11 -
12 -export const moduleStartError = new Error();
13 -export const innerError = new Error();
14 -export const moduleStopError = new Error();
15 -
16 -export const outerErrorB = new Error();
packages/react-devtools-timeline/src/content-views/utils/__tests__/__modules__/module-two.js deleted
-18
@@ -1,18 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export const moduleAStartError = new Error();
11 -export const innerErrorA = new Error();
12 -export const moduleAStopError = new Error();
13 -
14 -export const outerError = new Error();
15 -
16 -export const moduleBStartError = new Error();
17 -export const innerErrorB = new Error();
18 -export const moduleBStopError = new Error();
packages/react-devtools-timeline/src/content-views/utils/__tests__/colors-test.js deleted
-93
@@ -1,93 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {hslaColorToString, dimmedColor, ColorGenerator} from '../colors';
11 -
12 -describe('hslaColorToString', () => {
13 - it('should transform colors to strings', () => {
14 - expect(hslaColorToString({h: 1, s: 2, l: 3, a: 4})).toEqual(
15 - 'hsl(1deg 2% 3% / 4)',
16 - );
17 - expect(hslaColorToString({h: 3.14, s: 6.28, l: 1.68, a: 100})).toEqual(
18 - 'hsl(3.14deg 6.28% 1.68% / 100)',
19 - );
20 - });
21 -});
22 -
23 -describe('dimmedColor', () => {
24 - it('should dim luminosity using delta', () => {
25 - expect(dimmedColor({h: 1, s: 2, l: 3, a: 4}, 3)).toEqual({
26 - h: 1,
27 - s: 2,
28 - l: 0,
29 - a: 4,
30 - });
31 - expect(dimmedColor({h: 1, s: 2, l: 3, a: 4}, -3)).toEqual({
32 - h: 1,
33 - s: 2,
34 - l: 6,
35 - a: 4,
36 - });
37 - });
38 -});
39 -
40 -describe('ColorGenerator', () => {
41 - describe('colorForID', () => {
42 - it('should generate a color for an ID', () => {
43 - expect(new ColorGenerator().colorForID('123')).toMatchInlineSnapshot(`
44 - {
45 - "a": 1,
46 - "h": 190,
47 - "l": 80,
48 - "s": 67,
49 - }
50 - `);
51 - });
52 -
53 - it('should generate colors deterministically given an ID', () => {
54 - expect(new ColorGenerator().colorForID('id1')).toEqual(
55 - new ColorGenerator().colorForID('id1'),
56 - );
57 - expect(new ColorGenerator().colorForID('id2')).toEqual(
58 - new ColorGenerator().colorForID('id2'),
59 - );
60 - });
61 -
62 - it('should generate different colors for different IDs', () => {
63 - expect(new ColorGenerator().colorForID('id1')).not.toEqual(
64 - new ColorGenerator().colorForID('id2'),
65 - );
66 - });
67 -
68 - it('should return colors that have been set manually', () => {
69 - const generator = new ColorGenerator();
70 - const manualColor = {h: 1, s: 2, l: 3, a: 4};
71 - generator.setColorForID('id with set color', manualColor);
72 - expect(generator.colorForID('id with set color')).toEqual(manualColor);
73 - expect(generator.colorForID('some other id')).not.toEqual(manualColor);
74 - });
75 -
76 - it('should generate colors from fixed color spaces', () => {
77 - const generator = new ColorGenerator(1, 2, 3, 4);
78 - expect(generator.colorForID('123')).toEqual({h: 1, s: 2, l: 3, a: 4});
79 - expect(generator.colorForID('234')).toEqual({h: 1, s: 2, l: 3, a: 4});
80 - });
81 -
82 - it('should generate colors from range color spaces', () => {
83 - const generator = new ColorGenerator(
84 - {min: 0, max: 360, count: 2},
85 - 2,
86 - 3,
87 - 4,
88 - );
89 - expect(generator.colorForID('123')).toEqual({h: 0, s: 2, l: 3, a: 4});
90 - expect(generator.colorForID('234')).toEqual({h: 360, s: 2, l: 3, a: 4});
91 - });
92 - });
93 -});
packages/react-devtools-timeline/src/content-views/utils/__tests__/moduleFilters-test.js deleted
-79
@@ -1,79 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {isInternalModule} from '../moduleFilters';
11 -
12 -describe('isInternalModule', () => {
13 - let map;
14 -
15 - function createFlamechartStackFrame(scriptUrl, locationLine, locationColumn) {
16 - return {
17 - name: 'test',
18 - timestamp: 0,
19 - duration: 1,
20 - scriptUrl,
21 - locationLine,
22 - locationColumn,
23 - };
24 - }
25 -
26 - function createStackFrame(fileName, lineNumber, columnNumber) {
27 - return {
28 - columnNumber: columnNumber,
29 - lineNumber: lineNumber,
30 - fileName: fileName,
31 - functionName: 'test',
32 - source: ` at test (${fileName}:${lineNumber}:${columnNumber})`,
33 - };
34 - }
35 -
36 - beforeEach(() => {
37 - map = new Map();
38 - map.set('foo', [
39 - [createStackFrame('foo', 10, 0), createStackFrame('foo', 15, 100)],
40 - ]);
41 - map.set('bar', [
42 - [createStackFrame('bar', 10, 0), createStackFrame('bar', 15, 100)],
43 - [createStackFrame('bar', 20, 0), createStackFrame('bar', 25, 100)],
44 - ]);
45 - });
46 -
47 - it('should properly identify stack frames within the provided module ranges', () => {
48 - expect(
49 - isInternalModule(map, createFlamechartStackFrame('foo', 10, 0)),
50 - ).toBe(true);
51 - expect(
52 - isInternalModule(map, createFlamechartStackFrame('foo', 12, 35)),
53 - ).toBe(true);
54 - expect(
55 - isInternalModule(map, createFlamechartStackFrame('foo', 15, 100)),
56 - ).toBe(true);
57 - expect(
58 - isInternalModule(map, createFlamechartStackFrame('bar', 12, 0)),
59 - ).toBe(true);
60 - expect(
61 - isInternalModule(map, createFlamechartStackFrame('bar', 22, 125)),
62 - ).toBe(true);
63 - });
64 -
65 - it('should properly identify stack frames outside of the provided module ranges', () => {
66 - expect(isInternalModule(map, createFlamechartStackFrame('foo', 9, 0))).toBe(
67 - false,
68 - );
69 - expect(
70 - isInternalModule(map, createFlamechartStackFrame('foo', 15, 101)),
71 - ).toBe(false);
72 - expect(
73 - isInternalModule(map, createFlamechartStackFrame('bar', 17, 0)),
74 - ).toBe(false);
75 - expect(
76 - isInternalModule(map, createFlamechartStackFrame('baz', 12, 0)),
77 - ).toBe(false);
78 - });
79 -});
packages/react-devtools-timeline/src/content-views/utils/colors.js deleted
-113
@@ -1,113 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -type ColorSpace = number | {min: number, max: number, count?: number};
11 -
12 -// Docstrings from https://www.w3schools.com/css/css_colors_hsl.asp
13 -type HslaColor = $ReadOnly<{
14 - /** Hue is a degree on the color wheel from 0 to 360. 0 is red, 120 is green, and 240 is blue. */
15 - h: number,
16 - /** Saturation is a percentage value, 0% means a shade of gray, and 100% is the full color. */
17 - s: number,
18 - /** Lightness is a percentage, 0% is black, 50% is neither light or dark, 100% is white. */
19 - l: number,
20 - /** Alpha is a percentage, 0% is fully transparent, and 100 is not transparent at all. */
21 - a: number,
22 -}>;
23 -
24 -export function hslaColorToString({h, s, l, a}: HslaColor): string {
25 - return `hsl(${h}deg ${s}% ${l}% / ${a})`;
26 -}
27 -
28 -export function dimmedColor(color: HslaColor, dimDelta: number): HslaColor {
29 - return {
30 - ...color,
31 - l: color.l - dimDelta,
32 - };
33 -}
34 -
35 -// Source: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/platform/utilities.js;l=120
36 -function hashCode(string: string): number {
37 - // Hash algorithm for substrings is described in "Über die Komplexität der Multiplikation in
38 - // eingeschränkten Branchingprogrammmodellen" by Woelfe.
39 - // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
40 - const p = (1 << 30) * 4 - 5; // prime: 2^32 - 5
41 - const z = 0x5033d967; // 32 bits from random.org
42 - const z2 = 0x59d2f15d; // random odd 32 bit number
43 - let s = 0;
44 - let zi = 1;
45 - for (let i = 0; i < string.length; i++) {
46 - const xi = string.charCodeAt(i) * z2;
47 - s = (s + zi * xi) % p;
48 - zi = (zi * z) % p;
49 - }
50 - s = (s + zi * (p - 1)) % p;
51 - return Math.abs(s | 0);
52 -}
53 -
54 -function indexToValueInSpace(index: number, space: ColorSpace): number {
55 - if (typeof space === 'number') {
56 - return space;
57 - }
58 - const count = space.count || space.max - space.min;
59 - index %= count;
60 - return (
61 - space.min + Math.floor((index / (count - 1)) * (space.max - space.min))
62 - );
63 -}
64 -
65 -/**
66 - * Deterministic color generator.
67 - *
68 - * Adapted from: https://source.chromium.org/chromium/chromium/src/+/master:out/Debug/gen/devtools/common/Color.js
69 - */
70 -export class ColorGenerator {
71 - _hueSpace: ColorSpace;
72 - _satSpace: ColorSpace;
73 - _lightnessSpace: ColorSpace;
74 - _alphaSpace: ColorSpace;
75 - _colors: Map<string, HslaColor>;
76 -
77 - constructor(
78 - hueSpace?: ColorSpace,
79 - satSpace?: ColorSpace,
80 - lightnessSpace?: ColorSpace,
81 - alphaSpace?: ColorSpace,
82 - ) {
83 - this._hueSpace = hueSpace || {min: 0, max: 360};
84 - this._satSpace = satSpace || 67;
85 - this._lightnessSpace = lightnessSpace || 80;
86 - this._alphaSpace = alphaSpace || 1;
87 - this._colors = new Map();
88 - }
89 -
90 - setColorForID(id: string, color: HslaColor) {
91 - this._colors.set(id, color);
92 - }
93 -
94 - colorForID(id: string): HslaColor {
95 - const cachedColor = this._colors.get(id);
96 - if (cachedColor) {
97 - return cachedColor;
98 - }
99 - const color = this._generateColorForID(id);
100 - this._colors.set(id, color);
101 - return color;
102 - }
103 -
104 - _generateColorForID(id: string): HslaColor {
105 - const hash = hashCode(id);
106 - return {
107 - h: indexToValueInSpace(hash, this._hueSpace),
108 - s: indexToValueInSpace(hash >> 8, this._satSpace),
109 - l: indexToValueInSpace(hash >> 16, this._lightnessSpace),
110 - a: indexToValueInSpace(hash >> 24, this._alphaSpace),
111 - };
112 - }
113 -}
packages/react-devtools-timeline/src/content-views/utils/moduleFilters.js deleted
-73
@@ -1,73 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - FlamechartStackFrame,
12 - InternalModuleSourceToRanges,
13 -} from '../../types';
14 -
15 -import {
16 - CHROME_WEBSTORE_EXTENSION_ID,
17 - INTERNAL_EXTENSION_ID,
18 - LOCAL_EXTENSION_ID,
19 -} from 'react-devtools-shared/src/constants';
20 -
21 -export function isInternalModule(
22 - internalModuleSourceToRanges: InternalModuleSourceToRanges,
23 - flamechartStackFrame: FlamechartStackFrame,
24 -): boolean {
25 - const {locationColumn, locationLine, scriptUrl} = flamechartStackFrame;
26 -
27 - if (scriptUrl == null || locationColumn == null || locationLine == null) {
28 - // This could indicate a browser-internal API like performance.mark().
29 - return false;
30 - }
31 -
32 - // Internal modules are only registered if DevTools was running when the profile was captured,
33 - // but DevTools should also hide its own frames to avoid over-emphasizing them.
34 - if (
35 - // Handle webpack-internal:// sources
36 - scriptUrl.includes('/react-devtools') ||
37 - scriptUrl.includes('/react_devtools') ||
38 - // Filter out known extension IDs
39 - scriptUrl.includes(CHROME_WEBSTORE_EXTENSION_ID) ||
40 - scriptUrl.includes(INTERNAL_EXTENSION_ID) ||
41 - scriptUrl.includes(LOCAL_EXTENSION_ID)
42 - // Unfortunately this won't get everything, like relatively loaded chunks or Web Worker files.
43 - ) {
44 - return true;
45 - }
46 -
47 - // Filter out React internal packages.
48 - const ranges = internalModuleSourceToRanges.get(scriptUrl);
49 - if (ranges != null) {
50 - for (let i = 0; i < ranges.length; i++) {
51 - const [startStackFrame, stopStackFrame] = ranges[i];
52 -
53 - const isAfterStart =
54 - // $FlowFixMe[invalid-compare] -- TODO: Revealed when adding types to error-stack-parser
55 - locationLine > startStackFrame.lineNumber ||
56 - (locationLine === startStackFrame.lineNumber &&
57 - // $FlowFixMe[invalid-compare]
58 - locationColumn >= startStackFrame.columnNumber);
59 - const isBeforeStop =
60 - // $FlowFixMe[invalid-compare]
61 - locationLine < stopStackFrame.lineNumber ||
62 - (locationLine === stopStackFrame.lineNumber &&
63 - // $FlowFixMe[invalid-compare]
64 - locationColumn <= stopStackFrame.columnNumber);
65 -
66 - if (isAfterStart && isBeforeStop) {
67 - return true;
68 - }
69 - }
70 - }
71 -
72 - return false;
73 -}
packages/react-devtools-timeline/src/content-views/utils/positioning.js deleted
-41
@@ -1,41 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect} from '../../view-base';
11 -
12 -export function positioningScaleFactor(
13 - intrinsicWidth: number,
14 - frame: Rect,
15 -): number {
16 - return frame.size.width / intrinsicWidth;
17 -}
18 -
19 -export function timestampToPosition(
20 - timestamp: number,
21 - scaleFactor: number,
22 - frame: Rect,
23 -): number {
24 - return frame.origin.x + timestamp * scaleFactor;
25 -}
26 -
27 -export function positionToTimestamp(
28 - position: number,
29 - scaleFactor: number,
30 - frame: Rect,
31 -): number {
32 - return (position - frame.origin.x) / scaleFactor;
33 -}
34 -
35 -export function durationToWidth(duration: number, scaleFactor: number): number {
36 - return duration * scaleFactor;
37 -}
38 -
39 -export function widthToDuration(width: number, scaleFactor: number): number {
40 - return width / scaleFactor;
41 -}
packages/react-devtools-timeline/src/content-views/utils/text.js deleted
-134
@@ -1,134 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect} from '../../view-base';
11 -
12 -import {rectEqualToRect} from '../../view-base';
13 -import {COLORS, FONT_SIZE, TEXT_PADDING} from '../constants';
14 -
15 -const cachedTextWidths = new Map<string, number>();
16 -
17 -export function getTextWidth(
18 - context: CanvasRenderingContext2D,
19 - text: string,
20 -): number {
21 - let measuredWidth = cachedTextWidths.get(text);
22 - if (measuredWidth == null) {
23 - measuredWidth = context.measureText(text).width;
24 - cachedTextWidths.set(text, measuredWidth);
25 - }
26 -
27 - return measuredWidth as any as number;
28 -}
29 -
30 -export function trimText(
31 - context: CanvasRenderingContext2D,
32 - text: string,
33 - width: number,
34 -): string | null {
35 - const maxIndex = text.length - 1;
36 -
37 - let startIndex = 0;
38 - let stopIndex = maxIndex;
39 -
40 - let longestValidIndex = 0;
41 - let longestValidText = null;
42 -
43 - // Trimming long text could be really slow if we decrease only 1 character at a time.
44 - // Trimming with more of a binary search approach is faster in the worst cases.
45 - while (startIndex <= stopIndex) {
46 - const currentIndex = Math.floor((startIndex + stopIndex) / 2);
47 - const trimmedText =
48 - currentIndex === maxIndex ? text : text.slice(0, currentIndex) + '…';
49 -
50 - if (getTextWidth(context, trimmedText) <= width) {
51 - if (longestValidIndex < currentIndex) {
52 - longestValidIndex = currentIndex;
53 - longestValidText = trimmedText;
54 - }
55 -
56 - startIndex = currentIndex + 1;
57 - } else {
58 - stopIndex = currentIndex - 1;
59 - }
60 - }
61 -
62 - return longestValidText;
63 -}
64 -
65 -type TextConfig = {
66 - fillStyle?: string,
67 - fontSize?: number,
68 - textAlign?: 'left' | 'center',
69 -};
70 -
71 -export function drawText(
72 - text: string,
73 - context: CanvasRenderingContext2D,
74 - fullRect: Rect,
75 - drawableRect: Rect,
76 - config?: TextConfig,
77 -): void {
78 - const {
79 - fillStyle = COLORS.TEXT_COLOR,
80 - fontSize = FONT_SIZE,
81 - textAlign = 'left',
82 - } = config || {};
83 -
84 - if (fullRect.size.width > TEXT_PADDING * 2) {
85 - context.textAlign = textAlign;
86 - context.textBaseline = 'middle';
87 - context.font = `${fontSize}px sans-serif`;
88 -
89 - const {x, y} = fullRect.origin;
90 -
91 - const trimmedName = trimText(
92 - context,
93 - text,
94 - fullRect.size.width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
95 - );
96 -
97 - if (trimmedName !== null) {
98 - context.fillStyle = fillStyle;
99 -
100 - // Prevent text from visibly overflowing its container when clipped.
101 - const textOverflowsViewableArea = !rectEqualToRect(
102 - drawableRect,
103 - fullRect,
104 - );
105 - if (textOverflowsViewableArea) {
106 - context.save();
107 - context.beginPath();
108 - context.rect(
109 - drawableRect.origin.x,
110 - drawableRect.origin.y,
111 - drawableRect.size.width,
112 - drawableRect.size.height,
113 - );
114 - context.closePath();
115 - context.clip();
116 - }
117 -
118 - let textX;
119 - if (textAlign === 'center') {
120 - textX = x + fullRect.size.width / 2 + TEXT_PADDING - (x < 0 ? x : 0);
121 - } else {
122 - textX = x + TEXT_PADDING - (x < 0 ? x : 0);
123 - }
124 -
125 - const textY = y + fullRect.size.height / 2;
126 -
127 - context.fillText(trimmedName, textX, textY);
128 -
129 - if (textOverflowsViewableArea) {
130 - context.restore();
131 - }
132 - }
133 - }
134 -}
packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js deleted
-46
@@ -1,46 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {TimelineData} from './types';
15 -import type {ImportWorkerOutputData} from './import-worker/index';
16 -
17 -export type DataResource = Resource<void, File, TimelineData | Error>;
18 -
19 -export default function createDataResourceFromImportedFile(
20 - file: File,
21 -): DataResource {
22 - return createResource(
23 - () => {
24 - return new Promise<TimelineData | Error>((resolve, reject) => {
25 - const promise = importFile(
26 - file,
27 - ) as any as 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-timeline/src/import-worker/InvalidProfileError.js deleted
-13
@@ -1,13 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -/**
11 - * An error thrown when an invalid profile could not be processed.
12 - */
13 -export default class InvalidProfileError extends Error {}
packages/react-devtools-timeline/src/import-worker/importFile.js deleted
-46
@@ -1,46 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 type {TimelineEvent} from '@elg/speedscope';
13 -import type {ImportWorkerOutputData} from './index';
14 -
15 -import preprocessData from './preprocessData';
16 -import {readInputData} from './readInputData';
17 -import InvalidProfileError from './InvalidProfileError';
18 -
19 -export async function importFile(file: File): Promise<ImportWorkerOutputData> {
20 - try {
21 - const readFile = await readInputData(file);
22 - const events: TimelineEvent[] = JSON.parse(readFile);
23 - if (events.length === 0) {
24 - throw new InvalidProfileError('No profiling data found in file.');
25 - }
26 -
27 - const processedData = await preprocessData(events);
28 -
29 - return {
30 - status: 'SUCCESS',
31 - processedData,
32 - };
33 - } catch (error) {
34 - if (error instanceof InvalidProfileError) {
35 - return {
36 - status: 'INVALID_PROFILE_ERROR',
37 - error,
38 - };
39 - } else {
40 - return {
41 - status: 'UNEXPECTED_ERROR',
42 - error,
43 - };
44 - }
45 - }
46 -}
packages/react-devtools-timeline/src/import-worker/importFile.worker.js deleted
-10
@@ -1,10 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 importFileModule from './importFile';
9 -
10 -export const importFile = importFileModule.importFile;
packages/react-devtools-timeline/src/import-worker/index.js deleted
-32
@@ -1,32 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {TimelineData} 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: TimelineData}
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): Promise<ImportWorkerOutputData> =>
32 - workerizedImportFile.importFile(file);
packages/react-devtools-timeline/src/import-worker/preprocessData.js deleted
-1178
@@ -1,1178 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 - importFromChromeTimeline,
12 - Flamechart as SpeedscopeFlamechart,
13 -} from '@elg/speedscope';
14 -import type {TimelineEvent} from '@elg/speedscope';
15 -import type {
16 - ErrorStackFrame,
17 - BatchUID,
18 - Flamechart,
19 - Milliseconds,
20 - NativeEvent,
21 - NetworkMeasure,
22 - Phase,
23 - ReactLane,
24 - ReactComponentMeasure,
25 - ReactComponentMeasureType,
26 - ReactMeasure,
27 - ReactMeasureType,
28 - TimelineData,
29 - SchedulingEvent,
30 - SuspenseEvent,
31 - Snapshot,
32 -} from '../types';
33 -import {
34 - REACT_TOTAL_NUM_LANES,
35 - SCHEDULING_PROFILER_VERSION,
36 - SNAPSHOT_MAX_HEIGHT,
37 -} from '../constants';
38 -import InvalidProfileError from './InvalidProfileError';
39 -import {getBatchRange} from '../utils/getBatchRange';
40 -import ErrorStackParser from 'error-stack-parser';
41 -
42 -type MeasureStackElement = {
43 - type: ReactMeasureType,
44 - depth: number,
45 - measure: ReactMeasure,
46 - startTime: Milliseconds,
47 - stopTime?: Milliseconds,
48 -};
49 -
50 -type ProcessorState = {
51 - asyncProcessingPromises: Promise<any>[],
52 - batchUID: BatchUID,
53 - currentReactComponentMeasure: ReactComponentMeasure | null,
54 - internalModuleCurrentStackFrame: ErrorStackFrame | null,
55 - internalModuleStackStringSet: Set<string>,
56 - measureStack: MeasureStackElement[],
57 - nativeEventStack: NativeEvent[],
58 - nextRenderShouldGenerateNewBatchID: boolean,
59 - potentialLongEvents: Array<[NativeEvent, BatchUID]>,
60 - potentialLongNestedUpdate: SchedulingEvent | null,
61 - potentialLongNestedUpdates: Array<[SchedulingEvent, BatchUID]>,
62 - potentialSuspenseEventsOutsideOfTransition: Array<
63 - [SuspenseEvent, ReactLane[]],
64 - >,
65 - requestIdToNetworkMeasureMap: Map<string, NetworkMeasure>,
66 - uidCounter: BatchUID,
67 - unresolvedSuspenseEvents: Map<string, SuspenseEvent>,
68 -};
69 -
70 -const NATIVE_EVENT_DURATION_THRESHOLD = 20;
71 -const NESTED_UPDATE_DURATION_THRESHOLD = 20;
72 -
73 -const WARNING_STRINGS = {
74 - LONG_EVENT_HANDLER:
75 - 'An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work.',
76 - NESTED_UPDATE:
77 - 'A big nested update was scheduled during layout. ' +
78 - 'Nested updates require React to re-render synchronously before the browser can paint. ' +
79 - 'Consider delaying this update by moving it to a passive effect (useEffect).',
80 - SUSPEND_DURING_UPDATE:
81 - 'A component suspended during an update which caused a fallback to be shown. ' +
82 - "Consider using the Transition API to avoid hiding components after they've been mounted.",
83 -};
84 -
85 -// Exported for tests
86 -export function getLanesFromTransportDecimalBitmask(
87 - laneBitmaskString: string,
88 -): ReactLane[] {
89 - const laneBitmask = parseInt(laneBitmaskString, 10);
90 -
91 - // As negative numbers are stored in two's complement format, our bitmask
92 - // checks will be thrown off by them.
93 - if (laneBitmask < 0) {
94 - return [];
95 - }
96 -
97 - const lanes = [];
98 - let powersOfTwo = 0;
99 - while (powersOfTwo <= REACT_TOTAL_NUM_LANES) {
100 - if ((1 << powersOfTwo) & laneBitmask) {
101 - lanes.push(powersOfTwo);
102 - }
103 - powersOfTwo++;
104 - }
105 - return lanes;
106 -}
107 -
108 -function updateLaneToLabelMap(
109 - profilerData: TimelineData,
110 - laneLabelTuplesString: string,
111 -): void {
112 - // These marks appear multiple times in the data;
113 - // We only need to extact them once.
114 - if (profilerData.laneToLabelMap.size === 0) {
115 - const laneLabelTuples = laneLabelTuplesString.split(',');
116 - for (let laneIndex = 0; laneIndex < laneLabelTuples.length; laneIndex++) {
117 - // The numeric lane value (e.g. 64) isn't important.
118 - // The profiler parses and stores the lane's position within the bitmap,
119 - // (e.g. lane 1 is index 0, lane 16 is index 4).
120 - profilerData.laneToLabelMap.set(laneIndex, laneLabelTuples[laneIndex]);
121 - }
122 - }
123 -}
124 -
125 -let profilerVersion = null;
126 -
127 -function getLastType(stack: ProcessorState['measureStack']) {
128 - if (stack.length > 0) {
129 - const {type} = stack[stack.length - 1];
130 - return type;
131 - }
132 - return null;
133 -}
134 -
135 -function getDepth(stack: ProcessorState['measureStack']) {
136 - if (stack.length > 0) {
137 - const {depth, type} = stack[stack.length - 1];
138 - return type === 'render-idle' ? depth : depth + 1;
139 - }
140 - return 0;
141 -}
142 -
143 -function markWorkStarted(
144 - type: ReactMeasureType,
145 - startTime: Milliseconds,
146 - lanes: ReactLane[],
147 - currentProfilerData: TimelineData,
148 - state: ProcessorState,
149 -) {
150 - const {batchUID, measureStack} = state;
151 - const depth = getDepth(measureStack);
152 -
153 - const measure: ReactMeasure = {
154 - type,
155 - batchUID,
156 - depth,
157 - lanes,
158 - timestamp: startTime,
159 - duration: 0,
160 - };
161 -
162 - state.measureStack.push({depth, measure, startTime, type});
163 -
164 - // This array is pre-initialized when the batchUID is generated.
165 - const measures = currentProfilerData.batchUIDToMeasuresMap.get(batchUID);
166 - if (measures != null) {
167 - measures.push(measure);
168 - } else {
169 - currentProfilerData.batchUIDToMeasuresMap.set(state.batchUID, [measure]);
170 - }
171 -
172 - // This array is pre-initialized before processing starts.
173 - lanes.forEach(lane => {
174 - (
175 - currentProfilerData.laneToReactMeasureMap.get(
176 - lane,
177 - ) as any as Array<ReactMeasure>
178 - ).push(measure);
179 - });
180 -}
181 -
182 -function markWorkCompleted(
183 - type: ReactMeasureType,
184 - stopTime: Milliseconds,
185 - currentProfilerData: TimelineData,
186 - stack: ProcessorState['measureStack'],
187 -) {
188 - if (stack.length === 0) {
189 - console.error(
190 - 'Unexpected type "%s" completed at %sms while stack is empty.',
191 - type,
192 - stopTime,
193 - );
194 - // Ignore work "completion" user timing mark that doesn't complete anything
195 - return;
196 - }
197 -
198 - const last = stack[stack.length - 1];
199 - if (last.type !== type) {
200 - console.error(
201 - 'Unexpected type "%s" completed at %sms before "%s" completed.',
202 - type,
203 - stopTime,
204 - last.type,
205 - );
206 - }
207 -
208 - // $FlowFixMe[incompatible-use]
209 - const {measure, startTime} = stack.pop();
210 - if (!measure) {
211 - console.error('Could not find matching measure for type "%s".', type);
212 - }
213 -
214 - // $FlowFixMe[cannot-write] This property should not be writable outside of this function.
215 - measure.duration = stopTime - startTime;
216 -}
217 -
218 -function throwIfIncomplete(
219 - type: ReactMeasureType,
220 - stack: ProcessorState['measureStack'],
221 -) {
222 - const lastIndex = stack.length - 1;
223 - if (lastIndex >= 0) {
224 - const last = stack[lastIndex];
225 - if (last.stopTime === undefined && last.type === type) {
226 - throw new InvalidProfileError(
227 - `Unexpected type "${type}" started before "${last.type}" completed.`,
228 - );
229 - }
230 - }
231 -}
232 -
233 -function processEventDispatch(
234 - event: TimelineEvent,
235 - timestamp: Milliseconds,
236 - profilerData: TimelineData,
237 - state: ProcessorState,
238 -) {
239 - const data = event.args.data;
240 - const type = data.type;
241 -
242 - if (type.startsWith('react-')) {
243 - const stackTrace = data.stackTrace;
244 - if (stackTrace) {
245 - const topFrame = stackTrace[stackTrace.length - 1];
246 - if (topFrame.url.includes('/react-dom.')) {
247 - // Filter out fake React events dispatched by invokeGuardedCallbackDev.
248 - return;
249 - }
250 - }
251 - }
252 -
253 - // Reduce noise from events like DOMActivate, load/unload, etc. which are usually not relevant
254 - if (
255 - type === 'blur' ||
256 - type === 'click' ||
257 - type === 'input' ||
258 - type.startsWith('focus') ||
259 - type.startsWith('key') ||
260 - type.startsWith('mouse') ||
261 - type.startsWith('pointer')
262 - ) {
263 - const duration = event.dur / 1000;
264 -
265 - let depth = 0;
266 -
267 - while (state.nativeEventStack.length > 0) {
268 - const prevNativeEvent =
269 - state.nativeEventStack[state.nativeEventStack.length - 1];
270 - const prevStopTime = prevNativeEvent.timestamp + prevNativeEvent.duration;
271 -
272 - if (timestamp < prevStopTime) {
273 - depth = prevNativeEvent.depth + 1;
274 - break;
275 - } else {
276 - state.nativeEventStack.pop();
277 - }
278 - }
279 -
280 - const nativeEvent = {
281 - depth,
282 - duration,
283 - timestamp,
284 - type,
285 - warning: null,
286 - };
287 -
288 - // $FlowFixMe[incompatible-type]
289 - profilerData.nativeEvents.push(nativeEvent);
290 -
291 - // Keep track of curent event in case future ones overlap.
292 - // We separate them into different vertical lanes in this case.
293 - // $FlowFixMe[incompatible-type]
294 - state.nativeEventStack.push(nativeEvent);
295 - }
296 -}
297 -
298 -function processResourceFinish(
299 - event: TimelineEvent,
300 - timestamp: Milliseconds,
301 - profilerData: TimelineData,
302 - state: ProcessorState,
303 -) {
304 - const requestId = event.args.data.requestId;
305 - const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
306 - if (networkMeasure != null) {
307 - networkMeasure.finishTimestamp = timestamp;
308 - if (networkMeasure.firstReceivedDataTimestamp === 0) {
309 - networkMeasure.firstReceivedDataTimestamp = timestamp;
310 - }
311 - if (networkMeasure.lastReceivedDataTimestamp === 0) {
312 - networkMeasure.lastReceivedDataTimestamp = timestamp;
313 - }
314 -
315 - // Clean up now that the resource is done.
316 - state.requestIdToNetworkMeasureMap.delete(event.args.data.requestId);
317 - }
318 -}
319 -
320 -function processResourceReceivedData(
321 - event: TimelineEvent,
322 - timestamp: Milliseconds,
323 - profilerData: TimelineData,
324 - state: ProcessorState,
325 -) {
326 - const requestId = event.args.data.requestId;
327 - const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
328 - if (networkMeasure != null) {
329 - if (networkMeasure.firstReceivedDataTimestamp === 0) {
330 - networkMeasure.firstReceivedDataTimestamp = timestamp;
331 - }
332 - networkMeasure.lastReceivedDataTimestamp = timestamp;
333 - networkMeasure.finishTimestamp = timestamp;
334 - }
335 -}
336 -
337 -function processResourceReceiveResponse(
338 - event: TimelineEvent,
339 - timestamp: Milliseconds,
340 - profilerData: TimelineData,
341 - state: ProcessorState,
342 -) {
343 - const requestId = event.args.data.requestId;
344 - const networkMeasure = state.requestIdToNetworkMeasureMap.get(requestId);
345 - if (networkMeasure != null) {
346 - networkMeasure.receiveResponseTimestamp = timestamp;
347 - }
348 -}
349 -
350 -function processScreenshot(
351 - event: TimelineEvent,
352 - timestamp: Milliseconds,
353 - profilerData: TimelineData,
354 - state: ProcessorState,
355 -) {
356 - const encodedSnapshot = event.args.snapshot; // Base 64 encoded
357 -
358 - const snapshot: Snapshot = {
359 - height: 0,
360 - image: null,
361 - imageSource: `data:image/png;base64,${encodedSnapshot}`,
362 - timestamp,
363 - width: 0,
364 - };
365 -
366 - // Delay processing until we've extracted snapshot dimensions.
367 - let resolveFn = null as any as Function;
368 - state.asyncProcessingPromises.push(
369 - new Promise(resolve => {
370 - resolveFn = resolve;
371 - }),
372 - );
373 -
374 - // Parse the Base64 image data to determine native size.
375 - // This will be used later to scale for display within the thumbnail strip.
376 - fetch(snapshot.imageSource)
377 - .then(response => response.blob())
378 - .then(blob => {
379 - // $FlowFixMe[cannot-resolve-name] createImageBitmap
380 - createImageBitmap(blob).then(bitmap => {
381 - snapshot.height = bitmap.height;
382 - snapshot.width = bitmap.width;
383 -
384 - resolveFn();
385 - });
386 - });
387 -
388 - profilerData.snapshots.push(snapshot);
389 -}
390 -
391 -function processResourceSendRequest(
392 - event: TimelineEvent,
393 - timestamp: Milliseconds,
394 - profilerData: TimelineData,
395 - state: ProcessorState,
396 -) {
397 - const data = event.args.data;
398 - const requestId = data.requestId;
399 -
400 - const availableDepths = new Array<boolean>(
401 - state.requestIdToNetworkMeasureMap.size + 1,
402 - ).fill(true);
403 - state.requestIdToNetworkMeasureMap.forEach(({depth}) => {
404 - availableDepths[depth] = false;
405 - });
406 -
407 - let depth = 0;
408 - for (let i = 0; i < availableDepths.length; i++) {
409 - if (availableDepths[i]) {
410 - depth = i;
411 - break;
412 - }
413 - }
414 -
415 - const networkMeasure: NetworkMeasure = {
416 - depth,
417 - finishTimestamp: 0,
418 - firstReceivedDataTimestamp: 0,
419 - lastReceivedDataTimestamp: 0,
420 - requestId,
421 - requestMethod: data.requestMethod,
422 - priority: data.priority,
423 - sendRequestTimestamp: timestamp,
424 - receiveResponseTimestamp: 0,
425 - url: data.url,
426 - };
427 -
428 - state.requestIdToNetworkMeasureMap.set(requestId, networkMeasure);
429 -
430 - profilerData.networkMeasures.push(networkMeasure);
431 - networkMeasure.sendRequestTimestamp = timestamp;
432 -}
433 -
434 -function processTimelineEvent(
435 - event: TimelineEvent,
436 - /** Finalized profiler data up to `event`. May be mutated. */
437 - currentProfilerData: TimelineData,
438 - /** Intermediate processor state. May be mutated. */
439 - state: ProcessorState,
440 -) {
441 - const {cat, name, ts, ph} = event;
442 -
443 - const startTime = (ts - currentProfilerData.startTime) / 1000;
444 -
445 - switch (cat) {
446 - case 'disabled-by-default-devtools.screenshot':
447 - processScreenshot(event, startTime, currentProfilerData, state);
448 - break;
449 - case 'devtools.timeline':
450 - switch (name) {
451 - case 'EventDispatch':
452 - processEventDispatch(event, startTime, currentProfilerData, state);
453 - break;
454 - case 'ResourceFinish':
455 - processResourceFinish(event, startTime, currentProfilerData, state);
456 - break;
457 - case 'ResourceReceivedData':
458 - processResourceReceivedData(
459 - event,
460 - startTime,
461 - currentProfilerData,
462 - state,
463 - );
464 - break;
465 - case 'ResourceReceiveResponse':
466 - processResourceReceiveResponse(
467 - event,
468 - startTime,
469 - currentProfilerData,
470 - state,
471 - );
472 - break;
473 - case 'ResourceSendRequest':
474 - processResourceSendRequest(
475 - event,
476 - startTime,
477 - currentProfilerData,
478 - state,
479 - );
480 - break;
481 - }
482 - break;
483 - case 'blink.user_timing':
484 - if (name.startsWith('--react-version-')) {
485 - const [reactVersion] = name.slice(16).split('-');
486 - currentProfilerData.reactVersion = reactVersion;
487 - } else if (name.startsWith('--profiler-version-')) {
488 - const [versionString] = name.slice(19).split('-');
489 - profilerVersion = parseInt(versionString, 10);
490 - if (profilerVersion !== SCHEDULING_PROFILER_VERSION) {
491 - throw new InvalidProfileError(
492 - `This version of profiling data (${versionString}) is not supported by the current profiler.`,
493 - );
494 - }
495 - } else if (name.startsWith('--react-lane-labels-')) {
496 - const [laneLabelTuplesString] = name.slice(20).split('-');
497 - updateLaneToLabelMap(currentProfilerData, laneLabelTuplesString);
498 - } else if (name.startsWith('--component-')) {
499 - processReactComponentMeasure(
500 - name,
501 - startTime,
502 - currentProfilerData,
503 - state,
504 - );
505 - } else if (name.startsWith('--schedule-render-')) {
506 - const [laneBitmaskString] = name.slice(18).split('-');
507 -
508 - currentProfilerData.schedulingEvents.push({
509 - type: 'schedule-render',
510 - lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
511 - timestamp: startTime,
512 - warning: null,
513 - });
514 - } else if (name.startsWith('--schedule-forced-update-')) {
515 - const [laneBitmaskString, componentName] = name.slice(25).split('-');
516 -
517 - const forceUpdateEvent: SchedulingEvent = {
518 - type: 'schedule-force-update',
519 - lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
520 - componentName,
521 - timestamp: startTime,
522 - warning: null,
523 - };
524 -
525 - // If this is a nested update, make a note of it.
526 - // Once we're done processing events, we'll check to see if it was a long update and warn about it.
527 - if (state.measureStack.find(({type}) => type === 'commit')) {
528 - state.potentialLongNestedUpdate = forceUpdateEvent;
529 - }
530 -
531 - currentProfilerData.schedulingEvents.push(forceUpdateEvent);
532 - } else if (name.startsWith('--schedule-state-update-')) {
533 - const [laneBitmaskString, componentName] = name.slice(24).split('-');
534 -
535 - const stateUpdateEvent: SchedulingEvent = {
536 - type: 'schedule-state-update',
537 - lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
538 - componentName,
539 - timestamp: startTime,
540 - warning: null,
541 - };
542 -
543 - // If this is a nested update, make a note of it.
544 - // Once we're done processing events, we'll check to see if it was a long update and warn about it.
545 - if (state.measureStack.find(({type}) => type === 'commit')) {
546 - state.potentialLongNestedUpdate = stateUpdateEvent;
547 - }
548 -
549 - currentProfilerData.schedulingEvents.push(stateUpdateEvent);
550 - } else if (name.startsWith('--error-')) {
551 - const [componentName, phase, message] = name.slice(8).split('-');
552 -
553 - currentProfilerData.thrownErrors.push({
554 - componentName,
555 - message,
556 - phase: phase as any as Phase,
557 - timestamp: startTime,
558 - type: 'thrown-error',
559 - });
560 - } else if (name.startsWith('--suspense-suspend-')) {
561 - const [id, componentName, phase, laneBitmaskString, promiseName] = name
562 - .slice(19)
563 - .split('-');
564 - const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
565 -
566 - const availableDepths = new Array<boolean>(
567 - state.unresolvedSuspenseEvents.size + 1,
568 - ).fill(true);
569 - state.unresolvedSuspenseEvents.forEach(({depth}) => {
570 - availableDepths[depth] = false;
571 - });
572 -
573 - let depth = 0;
574 - for (let i = 0; i < availableDepths.length; i++) {
575 - if (availableDepths[i]) {
576 - depth = i;
577 - break;
578 - }
579 - }
580 -
581 - // TODO (timeline) Maybe we should calculate depth in post,
582 - // so unresolved Suspense requests don't take up space.
583 - // We can't know if they'll be resolved or not at this point.
584 - // We'll just give them a default (fake) duration width.
585 -
586 - const suspenseEvent: SuspenseEvent = {
587 - componentName,
588 - depth,
589 - duration: null,
590 - id,
591 - phase: phase as any as Phase,
592 - promiseName: promiseName || null,
593 - resolution: 'unresolved',
594 - timestamp: startTime,
595 - type: 'suspense',
596 - warning: null,
597 - };
598 -
599 - if (phase === 'update') {
600 - // If a component suspended during an update, we should verify that it was during a transition.
601 - // We need the lane metadata to verify this though.
602 - // Since that data is only logged during commit, we may not have it yet.
603 - // Store these events for post-processing then.
604 - state.potentialSuspenseEventsOutsideOfTransition.push([
605 - suspenseEvent,
606 - lanes,
607 - ]);
608 - }
609 -
610 - currentProfilerData.suspenseEvents.push(suspenseEvent);
611 - state.unresolvedSuspenseEvents.set(id, suspenseEvent);
612 - } else if (name.startsWith('--suspense-resolved-')) {
613 - const [id] = name.slice(20).split('-');
614 - const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
615 - if (suspenseEvent != null) {
616 - state.unresolvedSuspenseEvents.delete(id);
617 -
618 - suspenseEvent.duration = startTime - suspenseEvent.timestamp;
619 - suspenseEvent.resolution = 'resolved';
620 - }
621 - } else if (name.startsWith('--suspense-rejected-')) {
622 - const [id] = name.slice(20).split('-');
623 - const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
624 - if (suspenseEvent != null) {
625 - state.unresolvedSuspenseEvents.delete(id);
626 -
627 - suspenseEvent.duration = startTime - suspenseEvent.timestamp;
628 - suspenseEvent.resolution = 'rejected';
629 - }
630 - } else if (name.startsWith('--render-start-')) {
631 - if (state.nextRenderShouldGenerateNewBatchID) {
632 - state.nextRenderShouldGenerateNewBatchID = false;
633 - state.batchUID = state.uidCounter++ as any as BatchUID;
634 - }
635 -
636 - // If this render is the result of a nested update, make a note of it.
637 - // Once we're done processing events, we'll check to see if it was a long update and warn about it.
638 - if (state.potentialLongNestedUpdate !== null) {
639 - state.potentialLongNestedUpdates.push([
640 - state.potentialLongNestedUpdate,
641 - state.batchUID,
642 - ]);
643 - state.potentialLongNestedUpdate = null;
644 - }
645 -
646 - const [laneBitmaskString] = name.slice(15).split('-');
647 -
648 - throwIfIncomplete('render', state.measureStack);
649 - if (getLastType(state.measureStack) !== 'render-idle') {
650 - markWorkStarted(
651 - 'render-idle',
652 - startTime,
653 - getLanesFromTransportDecimalBitmask(laneBitmaskString),
654 - currentProfilerData,
655 - state,
656 - );
657 - }
658 - markWorkStarted(
659 - 'render',
660 - startTime,
661 - getLanesFromTransportDecimalBitmask(laneBitmaskString),
662 - currentProfilerData,
663 - state,
664 - );
665 -
666 - for (let i = 0; i < state.nativeEventStack.length; i++) {
667 - const nativeEvent = state.nativeEventStack[i];
668 - const stopTime = nativeEvent.timestamp + nativeEvent.duration;
669 -
670 - // If React work was scheduled during an event handler, and the event had a long duration,
671 - // it might be because the React render was long and stretched the event.
672 - // It might also be that the React work was short and that something else stretched the event.
673 - // Make a note of this event for now and we'll examine the batch of React render work later.
674 - // (We can't know until we're done processing the React update anyway.)
675 - if (stopTime > startTime) {
676 - state.potentialLongEvents.push([nativeEvent, state.batchUID]);
677 - }
678 - }
679 - } else if (
680 - name.startsWith('--render-stop') ||
681 - name.startsWith('--render-yield')
682 - ) {
683 - markWorkCompleted(
684 - 'render',
685 - startTime,
686 - currentProfilerData,
687 - state.measureStack,
688 - );
689 - } else if (name.startsWith('--commit-start-')) {
690 - state.nextRenderShouldGenerateNewBatchID = true;
691 - const [laneBitmaskString] = name.slice(15).split('-');
692 -
693 - markWorkStarted(
694 - 'commit',
695 - startTime,
696 - getLanesFromTransportDecimalBitmask(laneBitmaskString),
697 - currentProfilerData,
698 - state,
699 - );
700 - } else if (name.startsWith('--commit-stop')) {
701 - markWorkCompleted(
702 - 'commit',
703 - startTime,
704 - currentProfilerData,
705 - state.measureStack,
706 - );
707 - markWorkCompleted(
708 - 'render-idle',
709 - startTime,
710 - currentProfilerData,
711 - state.measureStack,
712 - );
713 - } else if (name.startsWith('--layout-effects-start-')) {
714 - const [laneBitmaskString] = name.slice(23).split('-');
715 -
716 - markWorkStarted(
717 - 'layout-effects',
718 - startTime,
719 - getLanesFromTransportDecimalBitmask(laneBitmaskString),
720 - currentProfilerData,
721 - state,
722 - );
723 - } else if (name.startsWith('--layout-effects-stop')) {
724 - markWorkCompleted(
725 - 'layout-effects',
726 - startTime,
727 - currentProfilerData,
728 - state.measureStack,
729 - );
730 - } else if (name.startsWith('--passive-effects-start-')) {
731 - const [laneBitmaskString] = name.slice(24).split('-');
732 -
733 - markWorkStarted(
734 - 'passive-effects',
735 - startTime,
736 - getLanesFromTransportDecimalBitmask(laneBitmaskString),
737 - currentProfilerData,
738 - state,
739 - );
740 - } else if (name.startsWith('--passive-effects-stop')) {
741 - markWorkCompleted(
742 - 'passive-effects',
743 - startTime,
744 - currentProfilerData,
745 - state.measureStack,
746 - );
747 - } else if (name.startsWith('--react-internal-module-start-')) {
748 - const stackFrameStart = name.slice(30);
749 -
750 - if (!state.internalModuleStackStringSet.has(stackFrameStart)) {
751 - state.internalModuleStackStringSet.add(stackFrameStart);
752 -
753 - const parsedStackFrameStart = parseStackFrame(stackFrameStart);
754 -
755 - state.internalModuleCurrentStackFrame = parsedStackFrameStart;
756 - }
757 - } else if (name.startsWith('--react-internal-module-stop-')) {
758 - const stackFrameStop = name.slice(29);
759 -
760 - if (!state.internalModuleStackStringSet.has(stackFrameStop)) {
761 - state.internalModuleStackStringSet.add(stackFrameStop);
762 -
763 - const parsedStackFrameStop = parseStackFrame(stackFrameStop);
764 -
765 - if (
766 - parsedStackFrameStop !== null &&
767 - state.internalModuleCurrentStackFrame !== null
768 - ) {
769 - const parsedStackFrameStart = state.internalModuleCurrentStackFrame;
770 -
771 - state.internalModuleCurrentStackFrame = null;
772 -
773 - const range = [parsedStackFrameStart, parsedStackFrameStop];
774 - const ranges = currentProfilerData.internalModuleSourceToRanges.get(
775 - parsedStackFrameStart.fileName,
776 - );
777 - if (ranges == null) {
778 - currentProfilerData.internalModuleSourceToRanges.set(
779 - parsedStackFrameStart.fileName,
780 - [range],
781 - );
782 - } else {
783 - ranges.push(range);
784 - }
785 - }
786 - }
787 - } else if (ph === 'R' || ph === 'n') {
788 - // User Timing mark
789 - currentProfilerData.otherUserTimingMarks.push({
790 - name,
791 - timestamp: startTime,
792 - });
793 - } else if (ph === 'b') {
794 - // TODO: Begin user timing measure
795 - } else if (ph === 'e') {
796 - // TODO: End user timing measure
797 - } else if (ph === 'i' || ph === 'I') {
798 - // Instant events.
799 - // Note that the capital "I" is a deprecated value that exists in Chrome Canary traces.
800 - } else {
801 - throw new InvalidProfileError(
802 - `Unrecognized event ${JSON.stringify(
803 - event,
804 - )}! This is likely a bug in this profiler tool.`,
805 - );
806 - }
807 - break;
808 - }
809 -}
810 -
811 -function assertNoOverlappingComponentMeasure(state: ProcessorState) {
812 - if (state.currentReactComponentMeasure !== null) {
813 - console.error(
814 - 'Component measure started while another measure in progress:',
815 - state.currentReactComponentMeasure,
816 - );
817 - }
818 -}
819 -
820 -function assertCurrentComponentMeasureType(
821 - state: ProcessorState,
822 - type: ReactComponentMeasureType,
823 -): void {
824 - if (state.currentReactComponentMeasure === null) {
825 - console.error(
826 - `Component measure type "${type}" stopped while no measure was in progress`,
827 - );
828 - } else if (state.currentReactComponentMeasure.type !== type) {
829 - console.error(
830 - `Component measure type "${type}" stopped while type ${state.currentReactComponentMeasure.type} in progress`,
831 - );
832 - }
833 -}
834 -
835 -function processReactComponentMeasure(
836 - name: string,
837 - startTime: Milliseconds,
838 - currentProfilerData: TimelineData,
839 - state: ProcessorState,
840 -): void {
841 - if (name.startsWith('--component-render-start-')) {
842 - const [componentName] = name.slice(25).split('-');
843 -
844 - assertNoOverlappingComponentMeasure(state);
845 -
846 - state.currentReactComponentMeasure = {
847 - componentName,
848 - timestamp: startTime,
849 - duration: 0,
850 - type: 'render',
851 - warning: null,
852 - };
853 - } else if (name === '--component-render-stop') {
854 - assertCurrentComponentMeasureType(state, 'render');
855 -
856 - if (state.currentReactComponentMeasure !== null) {
857 - const componentMeasure = state.currentReactComponentMeasure;
858 - componentMeasure.duration = startTime - componentMeasure.timestamp;
859 -
860 - state.currentReactComponentMeasure = null;
861 -
862 - currentProfilerData.componentMeasures.push(componentMeasure);
863 - }
864 - } else if (name.startsWith('--component-layout-effect-mount-start-')) {
865 - const [componentName] = name.slice(38).split('-');
866 -
867 - assertNoOverlappingComponentMeasure(state);
868 -
869 - state.currentReactComponentMeasure = {
870 - componentName,
871 - timestamp: startTime,
872 - duration: 0,
873 - type: 'layout-effect-mount',
874 - warning: null,
875 - };
876 - } else if (name === '--component-layout-effect-mount-stop') {
877 - assertCurrentComponentMeasureType(state, 'layout-effect-mount');
878 -
879 - if (state.currentReactComponentMeasure !== null) {
880 - const componentMeasure = state.currentReactComponentMeasure;
881 - componentMeasure.duration = startTime - componentMeasure.timestamp;
882 -
883 - state.currentReactComponentMeasure = null;
884 -
885 - currentProfilerData.componentMeasures.push(componentMeasure);
886 - }
887 - } else if (name.startsWith('--component-layout-effect-unmount-start-')) {
888 - const [componentName] = name.slice(40).split('-');
889 -
890 - assertNoOverlappingComponentMeasure(state);
891 -
892 - state.currentReactComponentMeasure = {
893 - componentName,
894 - timestamp: startTime,
895 - duration: 0,
896 - type: 'layout-effect-unmount',
897 - warning: null,
898 - };
899 - } else if (name === '--component-layout-effect-unmount-stop') {
900 - assertCurrentComponentMeasureType(state, 'layout-effect-unmount');
901 -
902 - if (state.currentReactComponentMeasure !== null) {
903 - const componentMeasure = state.currentReactComponentMeasure;
904 - componentMeasure.duration = startTime - componentMeasure.timestamp;
905 -
906 - state.currentReactComponentMeasure = null;
907 -
908 - currentProfilerData.componentMeasures.push(componentMeasure);
909 - }
910 - } else if (name.startsWith('--component-passive-effect-mount-start-')) {
911 - const [componentName] = name.slice(39).split('-');
912 -
913 - assertNoOverlappingComponentMeasure(state);
914 -
915 - state.currentReactComponentMeasure = {
916 - componentName,
917 - timestamp: startTime,
918 - duration: 0,
919 - type: 'passive-effect-mount',
920 - warning: null,
921 - };
922 - } else if (name === '--component-passive-effect-mount-stop') {
923 - assertCurrentComponentMeasureType(state, 'passive-effect-mount');
924 -
925 - if (state.currentReactComponentMeasure !== null) {
926 - const componentMeasure = state.currentReactComponentMeasure;
927 - componentMeasure.duration = startTime - componentMeasure.timestamp;
928 -
929 - state.currentReactComponentMeasure = null;
930 -
931 - currentProfilerData.componentMeasures.push(componentMeasure);
932 - }
933 - } else if (name.startsWith('--component-passive-effect-unmount-start-')) {
934 - const [componentName] = name.slice(41).split('-');
935 -
936 - assertNoOverlappingComponentMeasure(state);
937 -
938 - state.currentReactComponentMeasure = {
939 - componentName,
940 - timestamp: startTime,
941 - duration: 0,
942 - type: 'passive-effect-unmount',
943 - warning: null,
944 - };
945 - } else if (name === '--component-passive-effect-unmount-stop') {
946 - assertCurrentComponentMeasureType(state, 'passive-effect-unmount');
947 -
948 - if (state.currentReactComponentMeasure !== null) {
949 - const componentMeasure = state.currentReactComponentMeasure;
950 - componentMeasure.duration = startTime - componentMeasure.timestamp;
951 -
952 - state.currentReactComponentMeasure = null;
953 -
954 - currentProfilerData.componentMeasures.push(componentMeasure);
955 - }
956 - }
957 -}
958 -
959 -function preprocessFlamechart(rawData: TimelineEvent[]): Flamechart {
960 - let parsedData;
961 - try {
962 - parsedData = importFromChromeTimeline(rawData, 'react-devtools');
963 - } catch (error) {
964 - // Assume any Speedscope errors are caused by bad profiles
965 - const errorToRethrow = new InvalidProfileError(error.message);
966 - errorToRethrow.stack = error.stack;
967 - throw errorToRethrow;
968 - }
969 -
970 - const profile = parsedData.profiles[0]; // TODO: Choose the main CPU thread only
971 -
972 - const speedscopeFlamechart = new SpeedscopeFlamechart({
973 - // $FlowFixMe[method-unbinding]
974 - getTotalWeight: profile.getTotalWeight.bind(profile),
975 - // $FlowFixMe[method-unbinding]
976 - forEachCall: profile.forEachCall.bind(profile),
977 - // $FlowFixMe[method-unbinding]
978 - formatValue: profile.formatValue.bind(profile),
979 - getColorBucketForFrame: () => 0,
980 - });
981 -
982 - const flamechart: Flamechart = speedscopeFlamechart.getLayers().map(layer =>
983 - layer.map(
984 - ({
985 - start,
986 - end,
987 - node: {
988 - frame: {name, file, line, col},
989 - },
990 - }) => ({
991 - name,
992 - timestamp: start / 1000,
993 - duration: (end - start) / 1000,
994 - scriptUrl: file,
995 - locationLine: line,
996 - locationColumn: col,
997 - }),
998 - ),
999 - );
1000 -
1001 - return flamechart;
1002 -}
1003 -
1004 -function parseStackFrame(stackFrame: string): ErrorStackFrame | null {
1005 - const error = new Error();
1006 - error.stack = stackFrame;
1007 -
1008 - const frames = ErrorStackParser.parse(error);
1009 -
1010 - return frames.length === 1 ? frames[0] : null;
1011 -}
1012 -
1013 -export default async function preprocessData(
1014 - timeline: TimelineEvent[],
1015 -): Promise<TimelineData> {
1016 - const flamechart = preprocessFlamechart(timeline);
1017 -
1018 - const laneToReactMeasureMap: Map<ReactLane, Array<ReactMeasure>> = new Map();
1019 - for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
1020 - laneToReactMeasureMap.set(lane, []);
1021 - }
1022 -
1023 - const profilerData: TimelineData = {
1024 - batchUIDToMeasuresMap: new Map(),
1025 - componentMeasures: [],
1026 - duration: 0,
1027 - flamechart,
1028 - internalModuleSourceToRanges: new Map(),
1029 - laneToLabelMap: new Map(),
1030 - laneToReactMeasureMap,
1031 - nativeEvents: [],
1032 - networkMeasures: [],
1033 - otherUserTimingMarks: [],
1034 - reactVersion: null,
1035 - schedulingEvents: [],
1036 - snapshots: [],
1037 - snapshotHeight: 0,
1038 - startTime: 0,
1039 - suspenseEvents: [],
1040 - thrownErrors: [],
1041 - };
1042 -
1043 - // Sort `timeline`. JSON Array Format trace events need not be ordered. See:
1044 - // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.f2f0yd51wi15
1045 - timeline = timeline.filter(Boolean).sort((a, b) => (a.ts > b.ts ? 1 : -1));
1046 -
1047 - // Events displayed in flamechart have timestamps relative to the profile
1048 - // event's startTime. Source: https://github.com/v8/v8/blob/44bd8fd7/src/inspector/js_protocol.json#L1486
1049 - //
1050 - // We'll thus expect there to be a 'Profile' event; if there is not one, we
1051 - // can deduce that there are no flame chart events. As we expect React
1052 - // scheduling profiling user timing marks to be recorded together with browser
1053 - // flame chart events, we can futher deduce that the data is invalid and we
1054 - // don't bother finding React events.
1055 - const indexOfProfileEvent = timeline.findIndex(
1056 - event => event.name === 'Profile',
1057 - );
1058 - if (indexOfProfileEvent === -1) {
1059 - return profilerData;
1060 - }
1061 -
1062 - // Use Profile event's `startTime` as the start time to align with flame chart.
1063 - // TODO: Remove assumption that there'll only be 1 'Profile' event. If this
1064 - // assumption does not hold, the chart may start at the wrong time.
1065 - profilerData.startTime = timeline[indexOfProfileEvent].args.data.startTime;
1066 - profilerData.duration =
1067 - (timeline[timeline.length - 1].ts - profilerData.startTime) / 1000;
1068 -
1069 - const state: ProcessorState = {
1070 - asyncProcessingPromises: [],
1071 - batchUID: 0,
1072 - currentReactComponentMeasure: null,
1073 - internalModuleCurrentStackFrame: null,
1074 - internalModuleStackStringSet: new Set(),
1075 - measureStack: [],
1076 - nativeEventStack: [],
1077 - nextRenderShouldGenerateNewBatchID: true,
1078 - potentialLongEvents: [],
1079 - potentialLongNestedUpdate: null,
1080 - potentialLongNestedUpdates: [],
1081 - potentialSuspenseEventsOutsideOfTransition: [],
1082 - requestIdToNetworkMeasureMap: new Map(),
1083 - uidCounter: 0,
1084 - unresolvedSuspenseEvents: new Map(),
1085 - };
1086 -
1087 - timeline.forEach(event => processTimelineEvent(event, profilerData, state));
1088 -
1089 - if (profilerVersion === null) {
1090 - if (
1091 - profilerData.schedulingEvents.length === 0 &&
1092 - profilerData.batchUIDToMeasuresMap.size === 0
1093 - ) {
1094 - // No profiler version could indicate data was logged using an older build of React,
1095 - // before an explicitly profiler version was included in the logging data.
1096 - // But it could also indicate that the website was either not using React, or using a production build.
1097 - // The easiest way to check for this case is to see if the data contains any scheduled updates or render work.
1098 - throw new InvalidProfileError(
1099 - 'No React marks were found in the provided profile.' +
1100 - ' Please provide profiling data from an React application running in development or profiling mode.',
1101 - );
1102 - }
1103 -
1104 - throw new InvalidProfileError(
1105 - `This version of profiling data is not supported by the current profiler.`,
1106 - );
1107 - }
1108 -
1109 - // Validate that all events and measures are complete
1110 - const {measureStack} = state;
1111 - if (measureStack.length > 0) {
1112 - console.error('Incomplete events or measures', measureStack);
1113 - }
1114 -
1115 - // Check for warnings.
1116 - state.potentialLongEvents.forEach(([nativeEvent, batchUID]) => {
1117 - // See how long the subsequent batch of React work was.
1118 - // Ignore any work that was already started.
1119 - const [startTime, stopTime] = getBatchRange(
1120 - batchUID,
1121 - profilerData,
1122 - nativeEvent.timestamp,
1123 - );
1124 - if (stopTime - startTime > NATIVE_EVENT_DURATION_THRESHOLD) {
1125 - nativeEvent.warning = WARNING_STRINGS.LONG_EVENT_HANDLER;
1126 - }
1127 - });
1128 - state.potentialLongNestedUpdates.forEach(([schedulingEvent, batchUID]) => {
1129 - // See how long the subsequent batch of React work was.
1130 - const [startTime, stopTime] = getBatchRange(batchUID, profilerData);
1131 - if (stopTime - startTime > NESTED_UPDATE_DURATION_THRESHOLD) {
1132 - // Don't warn about transition updates scheduled during the commit phase.
1133 - // e.g. useTransition, useDeferredValue
1134 - // These are allowed to be long-running.
1135 - if (
1136 - !schedulingEvent.lanes.some(
1137 - lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
1138 - )
1139 - ) {
1140 - // FIXME: This warning doesn't account for "nested updates" that are
1141 - // spawned by useDeferredValue. Disabling temporarily until we figure
1142 - // out the right way to handle this.
1143 - // schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE;
1144 - }
1145 - }
1146 - });
1147 - state.potentialSuspenseEventsOutsideOfTransition.forEach(
1148 - ([suspenseEvent, lanes]) => {
1149 - // HACK This is a bit gross but the numeric lane value might change between render versions.
1150 - if (
1151 - !lanes.some(
1152 - lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
1153 - )
1154 - ) {
1155 - suspenseEvent.warning = WARNING_STRINGS.SUSPEND_DURING_UPDATE;
1156 - }
1157 - },
1158 - );
1159 -
1160 - // Wait for any async processing to complete before returning.
1161 - // Since processing is done in a worker, async work must complete before data is serialized and returned.
1162 - await Promise.all(state.asyncProcessingPromises);
1163 -
1164 - // Now that all images have been loaded, let's figure out the display size we're going to use for our thumbnails:
1165 - // both the ones rendered to the canvas and the ones shown on hover.
1166 - if (profilerData.snapshots.length > 0) {
1167 - // NOTE We assume a static window size here, which is not necessarily true but should be for most cases.
1168 - // Regardless, Chrome also sets a single size/ratio and stick with it- so we'll do the same.
1169 - const snapshot = profilerData.snapshots[0];
1170 -
1171 - profilerData.snapshotHeight = Math.min(
1172 - snapshot.height,
1173 - SNAPSHOT_MAX_HEIGHT,
1174 - );
1175 - }
1176 -
1177 - return profilerData;
1178 -}
packages/react-devtools-timeline/src/import-worker/readInputData.js deleted
-35
@@ -1,35 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 nullthrows from 'nullthrows';
11 -import InvalidProfileError from './InvalidProfileError';
12 -
13 -export const readInputData = (file: File): Promise<string> => {
14 - if (!file.name.endsWith('.json')) {
15 - throw new InvalidProfileError(
16 - 'Invalid file type. Only JSON performance profiles are supported',
17 - );
18 - }
19 -
20 - const fileReader = new FileReader();
21 -
22 - return new Promise((resolve, reject) => {
23 - fileReader.onload = () => {
24 - const result = nullthrows(fileReader.result);
25 - if (typeof result === 'string') {
26 - resolve(result);
27 - }
28 - reject(new InvalidProfileError('Input file was not read as a string'));
29 - };
30 -
31 - fileReader.onerror = () => reject(fileReader.error);
32 -
33 - fileReader.readAsText(file);
34 - });
35 -};
packages/react-devtools-timeline/src/timelineCache.js deleted
-110
@@ -1,110 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - Thenable,
12 - FulfilledThenable,
13 - RejectedThenable,
14 -} from 'shared/ReactTypes';
15 -import type {TimelineData} from './types';
16 -
17 -import * as React from 'react';
18 -
19 -import {importFile as importFileWorker} from './import-worker';
20 -
21 -// This is intentionally a module-level Map, rather than a React-managed one.
22 -// Otherwise, refreshing the inspected element cache would also clear this cache.
23 -// Profiler file contents are static anyway.
24 -const fileNameToProfilerDataMap: Map<
25 - string,
26 - Thenable<TimelineData>,
27 -> = new Map();
28 -
29 -function readRecord<T>(record: Thenable<T>): T | Error {
30 - if (typeof React.use === 'function') {
31 - try {
32 - // eslint-disable-next-line react-hooks-published/rules-of-hooks
33 - return React.use(record);
34 - } catch (x) {
35 - if (record.status === 'rejected') {
36 - return record.reason as any;
37 - }
38 - throw x;
39 - }
40 - }
41 - if (record.status === 'fulfilled') {
42 - return record.value;
43 - } else if (record.status === 'rejected') {
44 - return record.reason as any;
45 - } else {
46 - throw record;
47 - }
48 -}
49 -
50 -export function importFile(file: File): TimelineData | Error {
51 - const fileName = file.name;
52 - let record = fileNameToProfilerDataMap.get(fileName);
53 -
54 - if (!record) {
55 - const callbacks = new Set<(value: any) => mixed>();
56 - const rejectCallbacks = new Set<(reason: mixed) => mixed>();
57 - const thenable: Thenable<TimelineData> = {
58 - status: 'pending',
59 - value: null,
60 - reason: null,
61 - then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
62 - callbacks.add(callback);
63 - rejectCallbacks.add(reject);
64 - },
65 -
66 - // Optional property used by Timeline:
67 - displayName: `Importing file "${fileName}"`,
68 - };
69 -
70 - const wake = () => {
71 - // This assumes they won't throw.
72 - callbacks.forEach(callback => callback((thenable as any).value));
73 - callbacks.clear();
74 - rejectCallbacks.clear();
75 - };
76 - const wakeRejections = () => {
77 - // This assumes they won't throw.
78 - rejectCallbacks.forEach(callback => callback((thenable as any).reason));
79 - rejectCallbacks.clear();
80 - callbacks.clear();
81 - };
82 -
83 - record = thenable;
84 -
85 - importFileWorker(file).then(data => {
86 - switch (data.status) {
87 - case 'SUCCESS':
88 - const fulfilledThenable: FulfilledThenable<TimelineData> =
89 - thenable as any;
90 - fulfilledThenable.status = 'fulfilled';
91 - fulfilledThenable.value = data.processedData;
92 - wake();
93 - break;
94 - case 'INVALID_PROFILE_ERROR':
95 - case 'UNEXPECTED_ERROR':
96 - const rejectedThenable: RejectedThenable<TimelineData> =
97 - thenable as any;
98 - rejectedThenable.status = 'rejected';
99 - rejectedThenable.reason = data.error;
100 - wakeRejections();
101 - break;
102 - }
103 - });
104 -
105 - fileNameToProfilerDataMap.set(fileName, record);
106 - }
107 -
108 - const response = readRecord(record);
109 - return response;
110 -}
packages/react-devtools-timeline/src/types.js deleted
-249
@@ -1,249 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -import type {StackFrame as ErrorStackFrame} from 'error-stack-parser';
10 -import type {ScrollState} from './view-base/utils/scrollState';
11 -
12 -// Source: https://github.com/facebook/flow/issues/4002#issuecomment-323612798
13 -// eslint-disable-next-line no-unused-vars
14 -type Return_<R, F: (...args: Array<any>) => R> = R;
15 -/** Get return type of a function. */
16 -export type Return<T> = Return_<mixed, T>;
17 -
18 -// Project types
19 -export type {ErrorStackFrame};
20 -
21 -export type Milliseconds = number;
22 -
23 -export type ReactLane = number;
24 -
25 -export type NativeEvent = {
26 - +depth: number,
27 - +duration: Milliseconds,
28 - +timestamp: Milliseconds,
29 - +type: string,
30 - warning: string | null,
31 -};
32 -
33 -type BaseReactEvent = {
34 - +componentName?: string,
35 - +timestamp: Milliseconds,
36 - warning: string | null,
37 -};
38 -
39 -type BaseReactScheduleEvent = {
40 - ...BaseReactEvent,
41 - +lanes: ReactLane[],
42 -};
43 -export type ReactScheduleRenderEvent = {
44 - ...BaseReactScheduleEvent,
45 - +type: 'schedule-render',
46 -};
47 -export type ReactScheduleStateUpdateEvent = {
48 - ...BaseReactScheduleEvent,
49 - +componentStack?: string,
50 - +type: 'schedule-state-update',
51 -};
52 -export type ReactScheduleForceUpdateEvent = {
53 - ...BaseReactScheduleEvent,
54 - +type: 'schedule-force-update',
55 -};
56 -
57 -export type Phase = 'mount' | 'update';
58 -
59 -export type SuspenseEvent = {
60 - ...BaseReactEvent,
61 - depth: number,
62 - duration: number | null,
63 - +id: string,
64 - +phase: Phase | null,
65 - promiseName: string | null,
66 - resolution: 'rejected' | 'resolved' | 'unresolved',
67 - +type: 'suspense',
68 -};
69 -
70 -export type ThrownError = {
71 - +componentName?: string,
72 - +message: string,
73 - +phase: Phase,
74 - +timestamp: Milliseconds,
75 - +type: 'thrown-error',
76 -};
77 -
78 -export type SchedulingEvent =
79 - | ReactScheduleRenderEvent
80 - | ReactScheduleStateUpdateEvent
81 - | ReactScheduleForceUpdateEvent;
82 -export type SchedulingEventType = SchedulingEvent['type'];
83 -
84 -export type ReactMeasureType =
85 - | 'commit'
86 - // render-idle: A measure spanning the time when a render starts, through all
87 - // yields and restarts, and ends when commit stops OR render is cancelled.
88 - | 'render-idle'
89 - | 'render'
90 - | 'layout-effects'
91 - | 'passive-effects';
92 -
93 -export type BatchUID = number;
94 -
95 -export type ReactMeasure = {
96 - +type: ReactMeasureType,
97 - +lanes: ReactLane[],
98 - +timestamp: Milliseconds,
99 - +duration: Milliseconds,
100 - +batchUID: BatchUID,
101 - +depth: number,
102 -};
103 -
104 -export type NetworkMeasure = {
105 - +depth: number,
106 - finishTimestamp: Milliseconds,
107 - firstReceivedDataTimestamp: Milliseconds,
108 - lastReceivedDataTimestamp: Milliseconds,
109 - priority: string,
110 - receiveResponseTimestamp: Milliseconds,
111 - +requestId: string,
112 - requestMethod: string,
113 - sendRequestTimestamp: Milliseconds,
114 - url: string,
115 -};
116 -
117 -export type ReactComponentMeasureType =
118 - | 'render'
119 - | 'layout-effect-mount'
120 - | 'layout-effect-unmount'
121 - | 'passive-effect-mount'
122 - | 'passive-effect-unmount';
123 -
124 -export type ReactComponentMeasure = {
125 - +componentName: string,
126 - duration: Milliseconds,
127 - +timestamp: Milliseconds,
128 - +type: ReactComponentMeasureType,
129 - warning: string | null,
130 -};
131 -
132 -/**
133 - * A flamechart stack frame belonging to a stack trace.
134 - */
135 -export type FlamechartStackFrame = {
136 - name: string,
137 - timestamp: Milliseconds,
138 - duration: Milliseconds,
139 - scriptUrl?: string,
140 - locationLine?: number,
141 - locationColumn?: number,
142 -};
143 -
144 -export type UserTimingMark = {
145 - name: string,
146 - timestamp: Milliseconds,
147 -};
148 -
149 -export type Snapshot = {
150 - height: number,
151 - image: Image | null,
152 - +imageSource: string,
153 - +timestamp: Milliseconds,
154 - width: number,
155 -};
156 -
157 -/**
158 - * A "layer" of stack frames in the profiler UI, i.e. all stack frames of the
159 - * same depth across all stack traces. Displayed as a flamechart row in the UI.
160 - */
161 -export type FlamechartStackLayer = FlamechartStackFrame[];
162 -
163 -export type Flamechart = FlamechartStackLayer[];
164 -
165 -export type HorizontalScrollStateChangeCallback = (
166 - scrollState: ScrollState,
167 -) => void;
168 -export type SearchRegExpStateChangeCallback = (
169 - searchRegExp: RegExp | null,
170 -) => void;
171 -
172 -// Imperative view state that corresponds to profiler data.
173 -// This state lives outside of React's lifecycle
174 -// and should be erased/reset whenever new profiler data is loaded.
175 -export type ViewState = {
176 - horizontalScrollState: ScrollState,
177 - onHorizontalScrollStateChange: (
178 - callback: HorizontalScrollStateChangeCallback,
179 - ) => void,
180 - onSearchRegExpStateChange: (
181 - callback: SearchRegExpStateChangeCallback,
182 - ) => void,
183 - searchRegExp: RegExp | null,
184 - updateHorizontalScrollState: (scrollState: ScrollState) => void,
185 - updateSearchRegExpState: (searchRegExp: RegExp | null) => void,
186 - viewToMutableViewStateMap: Map<string, mixed>,
187 -};
188 -
189 -export type InternalModuleSourceToRanges = Map<
190 - string | void,
191 - Array<[ErrorStackFrame, ErrorStackFrame]>,
192 ->;
193 -
194 -export type LaneToLabelMap = Map<ReactLane, string>;
195 -
196 -export type TimelineData = {
197 - batchUIDToMeasuresMap: Map<BatchUID, ReactMeasure[]>,
198 - componentMeasures: ReactComponentMeasure[],
199 - duration: number,
200 - flamechart: Flamechart,
201 - internalModuleSourceToRanges: InternalModuleSourceToRanges,
202 - laneToLabelMap: LaneToLabelMap,
203 - laneToReactMeasureMap: Map<ReactLane, ReactMeasure[]>,
204 - nativeEvents: NativeEvent[],
205 - networkMeasures: NetworkMeasure[],
206 - otherUserTimingMarks: UserTimingMark[],
207 - reactVersion: string | null,
208 - schedulingEvents: SchedulingEvent[],
209 - snapshots: Snapshot[],
210 - snapshotHeight: number,
211 - startTime: number,
212 - suspenseEvents: SuspenseEvent[],
213 - thrownErrors: ThrownError[],
214 -};
215 -
216 -export type TimelineDataExport = {
217 - batchUIDToMeasuresKeyValueArray: Array<[BatchUID, ReactMeasure[]]>,
218 - componentMeasures: ReactComponentMeasure[],
219 - duration: number,
220 - flamechart: Flamechart,
221 - internalModuleSourceToRanges: Array<
222 - [string | void, Array<[ErrorStackFrame, ErrorStackFrame]>],
223 - >,
224 - laneToLabelKeyValueArray: Array<[ReactLane, string]>,
225 - laneToReactMeasureKeyValueArray: Array<[ReactLane, ReactMeasure[]]>,
226 - nativeEvents: NativeEvent[],
227 - networkMeasures: NetworkMeasure[],
228 - otherUserTimingMarks: UserTimingMark[],
229 - reactVersion: string | null,
230 - schedulingEvents: SchedulingEvent[],
231 - snapshots: Snapshot[],
232 - snapshotHeight: number,
233 - startTime: number,
234 - suspenseEvents: SuspenseEvent[],
235 - thrownErrors: ThrownError[],
236 -};
237 -
238 -export type ReactEventInfo = {
239 - componentMeasure: ReactComponentMeasure | null,
240 - flamechartStackFrame: FlamechartStackFrame | null,
241 - measure: ReactMeasure | null,
242 - nativeEvent: NativeEvent | null,
243 - networkMeasure: NetworkMeasure | null,
244 - schedulingEvent: SchedulingEvent | null,
245 - suspenseEvent: SuspenseEvent | null,
246 - snapshot: Snapshot | null,
247 - thrownError: ThrownError | null,
248 - userTimingMark: UserTimingMark | null,
249 -};
packages/react-devtools-timeline/src/utils/flow.js deleted
-16
@@ -1,16 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -import type {ReactScheduleStateUpdateEvent, SchedulingEvent} from '../types';
10 -
11 -export function isStateUpdateEvent(
12 - event: SchedulingEvent,
13 - // eslint-disable-next-line
14 -): event is ReactScheduleStateUpdateEvent {
15 - return event.type === 'schedule-state-update';
16 -}
packages/react-devtools-timeline/src/utils/formatting.js deleted
-45
@@ -1,45 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {SchedulingEvent} from '../types';
11 -
12 -import prettyMilliseconds from 'pretty-ms';
13 -
14 -export function formatTimestamp(ms: number): string {
15 - return (
16 - ms.toLocaleString(undefined, {
17 - minimumFractionDigits: 1,
18 - maximumFractionDigits: 1,
19 - }) + 'ms'
20 - );
21 -}
22 -
23 -export function formatDuration(ms: number): string {
24 - return prettyMilliseconds(ms, {millisecondsDecimalDigits: 1});
25 -}
26 -
27 -export function trimString(string: string, length: number): string {
28 - if (string.length > length) {
29 - return `${string.slice(0, length - 1)}…`;
30 - }
31 - return string;
32 -}
33 -
34 -export function getSchedulingEventLabel(event: SchedulingEvent): string | null {
35 - switch (event.type) {
36 - case 'schedule-render':
37 - return 'render scheduled';
38 - case 'schedule-state-update':
39 - return 'state update scheduled';
40 - case 'schedule-force-update':
41 - return 'force update scheduled';
42 - default:
43 - return null;
44 - }
45 -}
packages/react-devtools-timeline/src/utils/getBatchRange.js deleted
-52
@@ -1,52 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 memoize from 'memoize-one';
11 -
12 -import type {
13 - BatchUID,
14 - Milliseconds,
15 - ReactMeasure,
16 - TimelineData,
17 -} from '../types';
18 -
19 -function unmemoizedGetBatchRange(
20 - batchUID: BatchUID,
21 - data: TimelineData,
22 - minStartTime?: number = 0,
23 -): [Milliseconds, Milliseconds] {
24 - const measures = data.batchUIDToMeasuresMap.get(batchUID);
25 - if (measures == null || measures.length === 0) {
26 - throw Error(`Could not find measures with batch UID "${batchUID}"`);
27 - }
28 -
29 - const lastMeasure = measures[measures.length - 1] as any as ReactMeasure;
30 - const stopTime = lastMeasure.timestamp + lastMeasure.duration;
31 -
32 - if (stopTime < minStartTime) {
33 - return [0, 0];
34 - }
35 -
36 - let startTime = minStartTime;
37 - for (let index = 0; index < measures.length; index++) {
38 - const measure = measures[index];
39 - if (measure.timestamp >= minStartTime) {
40 - startTime = measure.timestamp;
41 - break;
42 - }
43 - }
44 -
45 - return [startTime, stopTime];
46 -}
47 -
48 -export const getBatchRange: (
49 - batchUID: BatchUID,
50 - data: TimelineData,
51 - minStartTime?: number,
52 -) => [Milliseconds, Milliseconds] = memoize(unmemoizedGetBatchRange);
packages/react-devtools-timeline/src/utils/useSmartTooltip.js deleted
-81
@@ -1,81 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {useLayoutEffect, useRef} from 'react';
11 -
12 -const TOOLTIP_OFFSET_BOTTOM = 10;
13 -const TOOLTIP_OFFSET_TOP = 5;
14 -
15 -export default function useSmartTooltip({
16 - canvasRef,
17 - mouseX,
18 - mouseY,
19 -}: {
20 - canvasRef: {current: HTMLCanvasElement | null},
21 - mouseX: number,
22 - mouseY: number,
23 -}): {current: HTMLElement | null} {
24 - const ref = useRef<HTMLElement | null>(null);
25 -
26 - // HACK: Browser extension reports window.innerHeight of 0,
27 - // so we fallback to using the tooltip target element.
28 - let height = window.innerHeight;
29 - let width = window.innerWidth;
30 - const target = canvasRef.current;
31 - if (target !== null) {
32 - const rect = target.getBoundingClientRect();
33 - height = rect.top + rect.height;
34 - width = rect.left + rect.width;
35 - }
36 -
37 - useLayoutEffect(() => {
38 - const element = ref.current;
39 - if (element !== null) {
40 - // Let's check the vertical position.
41 - if (mouseY + TOOLTIP_OFFSET_BOTTOM + element.offsetHeight >= height) {
42 - // The tooltip doesn't fit below the mouse cursor (which is our
43 - // default strategy). Therefore we try to position it either above the
44 - // mouse cursor or finally aligned with the window's top edge.
45 - if (mouseY - TOOLTIP_OFFSET_TOP - element.offsetHeight > 0) {
46 - // We position the tooltip above the mouse cursor if it fits there.
47 - element.style.top = `${
48 - mouseY - element.offsetHeight - TOOLTIP_OFFSET_TOP
49 - }px`;
50 - } else {
51 - // Otherwise we align the tooltip with the window's top edge.
52 - element.style.top = '0px';
53 - }
54 - } else {
55 - element.style.top = `${mouseY + TOOLTIP_OFFSET_BOTTOM}px`;
56 - }
57 -
58 - // Now let's check the horizontal position.
59 - if (mouseX + TOOLTIP_OFFSET_BOTTOM + element.offsetWidth >= width) {
60 - // The tooltip doesn't fit at the right of the mouse cursor (which is
61 - // our default strategy). Therefore we try to position it either at the
62 - // left of the mouse cursor or finally aligned with the window's left
63 - // edge.
64 - if (mouseX - TOOLTIP_OFFSET_TOP - element.offsetWidth > 0) {
65 - // We position the tooltip at the left of the mouse cursor if it fits
66 - // there.
67 - element.style.left = `${
68 - mouseX - element.offsetWidth - TOOLTIP_OFFSET_TOP
69 - }px`;
70 - } else {
71 - // Otherwise, align the tooltip with the window's left edge.
72 - element.style.left = '0px';
73 - }
74 - } else {
75 - element.style.left = `${mouseX + TOOLTIP_OFFSET_BOTTOM}px`;
76 - }
77 - }
78 - });
79 -
80 - return ref;
81 -}
packages/react-devtools-timeline/src/view-base/BackgroundColorView.js deleted
-28
@@ -1,28 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {View} from './View';
11 -import {COLORS} from '../content-views/constants';
12 -
13 -/**
14 - * View that fills its visible area with a CSS color.
15 - */
16 -export class BackgroundColorView extends View {
17 - draw(context: CanvasRenderingContext2D) {
18 - const {visibleArea} = this;
19 -
20 - context.fillStyle = COLORS.BACKGROUND;
21 - context.fillRect(
22 - visibleArea.origin.x,
23 - visibleArea.origin.y,
24 - visibleArea.size.width,
25 - visibleArea.size.height,
26 - );
27 - }
28 -}
packages/react-devtools-timeline/src/view-base/HorizontalPanAndZoomView.js deleted
-245
@@ -1,245 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Size, IntrinsicSize, Rect} from './geometry';
11 -import type {
12 - Interaction,
13 - MouseDownInteraction,
14 - MouseMoveInteraction,
15 - MouseUpInteraction,
16 - WheelPlainInteraction,
17 - WheelWithShiftInteraction,
18 -} from './useCanvasInteraction';
19 -import type {ScrollState} from './utils/scrollState';
20 -import type {ViewRefs} from './Surface';
21 -import type {ViewState} from '../types';
22 -
23 -import {Surface} from './Surface';
24 -import {View} from './View';
25 -import {rectContainsPoint} from './geometry';
26 -import {
27 - clampState,
28 - moveStateToRange,
29 - areScrollStatesEqual,
30 - translateState,
31 - zoomState,
32 -} from './utils/scrollState';
33 -import {
34 - MAX_ZOOM_LEVEL,
35 - MIN_ZOOM_LEVEL,
36 - MOVE_WHEEL_DELTA_THRESHOLD,
37 -} from './constants';
38 -
39 -export class HorizontalPanAndZoomView extends View {
40 - _contentView: View;
41 - _intrinsicContentWidth: number;
42 - _isPanning: boolean = false;
43 - _viewState: ViewState;
44 -
45 - constructor(
46 - surface: Surface,
47 - frame: Rect,
48 - contentView: View,
49 - intrinsicContentWidth: number,
50 - viewState: ViewState,
51 - ) {
52 - super(surface, frame);
53 -
54 - this._contentView = contentView;
55 - this._intrinsicContentWidth = intrinsicContentWidth;
56 - this._viewState = viewState;
57 -
58 - viewState.onHorizontalScrollStateChange(scrollState => {
59 - this.zoomToRange(scrollState.offset, scrollState.length);
60 - });
61 -
62 - this.addSubview(contentView);
63 - }
64 -
65 - /**
66 - * Just sets scroll state.
67 - * Use `_setStateAndInformCallbacksIfChanged` if this view's callbacks should also be called.
68 - *
69 - * @returns Whether state was changed
70 - * @private
71 - */
72 - setScrollState(proposedState: ScrollState) {
73 - const clampedState = clampState({
74 - state: proposedState,
75 - minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
76 - maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
77 - containerLength: this.frame.size.width,
78 - });
79 - if (
80 - !areScrollStatesEqual(clampedState, this._viewState.horizontalScrollState)
81 - ) {
82 - this.setNeedsDisplay();
83 - }
84 - }
85 -
86 - /**
87 - * Zoom to a specific range of the content specified as a range of the
88 - * content view's intrinsic content size.
89 - *
90 - * Does not inform callbacks of state change since this is a public API.
91 - */
92 - zoomToRange(rangeStart: number, rangeEnd: number) {
93 - const newState = moveStateToRange({
94 - state: this._viewState.horizontalScrollState,
95 - rangeStart,
96 - rangeEnd,
97 - contentLength: this._intrinsicContentWidth,
98 -
99 - minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
100 - maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
101 - containerLength: this.frame.size.width,
102 - });
103 - this.setScrollState(newState);
104 - }
105 -
106 - desiredSize(): Size | IntrinsicSize {
107 - return this._contentView.desiredSize();
108 - }
109 -
110 - layoutSubviews() {
111 - const {offset, length} = this._viewState.horizontalScrollState;
112 - const proposedFrame = {
113 - origin: {
114 - x: this.frame.origin.x + offset,
115 - y: this.frame.origin.y,
116 - },
117 - size: {
118 - width: length,
119 - height: this.frame.size.height,
120 - },
121 - };
122 - this._contentView.setFrame(proposedFrame);
123 - super.layoutSubviews();
124 - }
125 -
126 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
127 - switch (interaction.type) {
128 - case 'mousedown':
129 - this._handleMouseDown(interaction, viewRefs);
130 - break;
131 - case 'mousemove':
132 - this._handleMouseMove(interaction, viewRefs);
133 - break;
134 - case 'mouseup':
135 - this._handleMouseUp(interaction, viewRefs);
136 - break;
137 - case 'wheel-plain':
138 - case 'wheel-shift':
139 - this._handleWheel(interaction);
140 - break;
141 - }
142 - }
143 -
144 - _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
145 - if (rectContainsPoint(interaction.payload.location, this.frame)) {
146 - this._isPanning = true;
147 -
148 - viewRefs.activeView = this;
149 -
150 - this.currentCursor = 'grabbing';
151 - }
152 - }
153 -
154 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
155 - const isHovered = rectContainsPoint(
156 - interaction.payload.location,
157 - this.frame,
158 - );
159 - if (isHovered && viewRefs.hoveredView === null) {
160 - viewRefs.hoveredView = this;
161 - }
162 -
163 - if (viewRefs.activeView === this) {
164 - this.currentCursor = 'grabbing';
165 - } else if (isHovered) {
166 - this.currentCursor = 'grab';
167 - }
168 -
169 - if (!this._isPanning) {
170 - return;
171 - }
172 -
173 - // Don't prevent mouse-move events from bubbling if they are vertical drags.
174 - const {movementX, movementY} = interaction.payload.event;
175 - if (Math.abs(movementX) < Math.abs(movementY)) {
176 - return;
177 - }
178 -
179 - const newState = translateState({
180 - state: this._viewState.horizontalScrollState,
181 - delta: movementX,
182 - containerLength: this.frame.size.width,
183 - });
184 - this._viewState.updateHorizontalScrollState(newState);
185 - }
186 -
187 - _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
188 - if (this._isPanning) {
189 - this._isPanning = false;
190 - }
191 -
192 - if (viewRefs.activeView === this) {
193 - viewRefs.activeView = null;
194 - }
195 - }
196 -
197 - _handleWheel(interaction: WheelPlainInteraction | WheelWithShiftInteraction) {
198 - const {
199 - location,
200 - delta: {deltaX, deltaY},
201 - } = interaction.payload;
202 -
203 - if (!rectContainsPoint(location, this.frame)) {
204 - return; // Not scrolling on view
205 - }
206 -
207 - const absDeltaX = Math.abs(deltaX);
208 - const absDeltaY = Math.abs(deltaY);
209 -
210 - // Vertical scrolling zooms in and out (unless the SHIFT modifier is used).
211 - // Horizontal scrolling pans.
212 - if (absDeltaY > absDeltaX) {
213 - if (absDeltaY < MOVE_WHEEL_DELTA_THRESHOLD) {
214 - return;
215 - }
216 -
217 - if (interaction.type === 'wheel-shift') {
218 - // Shift modifier is for scrolling, not zooming.
219 - return;
220 - }
221 -
222 - const newState = zoomState({
223 - state: this._viewState.horizontalScrollState,
224 - multiplier: 1 + 0.005 * -deltaY,
225 - fixedPoint: location.x - this._viewState.horizontalScrollState.offset,
226 -
227 - minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
228 - maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
229 - containerLength: this.frame.size.width,
230 - });
231 - this._viewState.updateHorizontalScrollState(newState);
232 - } else {
233 - if (absDeltaX < MOVE_WHEEL_DELTA_THRESHOLD) {
234 - return;
235 - }
236 -
237 - const newState = translateState({
238 - state: this._viewState.horizontalScrollState,
239 - delta: -deltaX,
240 - containerLength: this.frame.size.width,
241 - });
242 - this._viewState.updateHorizontalScrollState(newState);
243 - }
244 - }
245 -}
packages/react-devtools-timeline/src/view-base/Surface.js deleted
-154
@@ -1,154 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Interaction} from './useCanvasInteraction';
11 -import type {Size} from './geometry';
12 -
13 -import memoize from 'memoize-one';
14 -
15 -import {View} from './View';
16 -import {zeroPoint} from './geometry';
17 -import {DPR} from '../content-views/constants';
18 -
19 -export type ViewRefs = {
20 - activeView: View | null,
21 - hoveredView: View | null,
22 -};
23 -
24 -// hidpi canvas: https://web.dev/articles/canvas-hidipi
25 -function configureRetinaCanvas(
26 - canvas: HTMLCanvasElement,
27 - height: number,
28 - width: number,
29 -) {
30 - canvas.width = width * DPR;
31 - canvas.height = height * DPR;
32 - canvas.style.width = `${width}px`;
33 - canvas.style.height = `${height}px`;
34 -}
35 -
36 -const getCanvasContext = memoize(
37 - (
38 - canvas: HTMLCanvasElement,
39 - height: number,
40 - width: number,
41 - scaleCanvas: boolean = true,
42 - ): CanvasRenderingContext2D => {
43 - const context = canvas.getContext('2d', {alpha: false});
44 - if (scaleCanvas) {
45 - configureRetinaCanvas(canvas, height, width);
46 -
47 - // Scale all drawing operations by the dpr, so you don't have to worry about the difference.
48 - context.scale(DPR, DPR);
49 - }
50 - return context;
51 - },
52 -);
53 -
54 -type ResetHoveredEventFn = () => void;
55 -
56 -/**
57 - * Represents the canvas surface and a view heirarchy. A surface is also the
58 - * place where all interactions enter the view heirarchy.
59 - */
60 -export class Surface {
61 - rootView: ?View;
62 -
63 - _context: ?CanvasRenderingContext2D;
64 - _canvasSize: ?Size;
65 -
66 - _resetHoveredEvent: ResetHoveredEventFn;
67 -
68 - _viewRefs: ViewRefs = {
69 - activeView: null,
70 - hoveredView: null,
71 - };
72 -
73 - constructor(resetHoveredEvent: ResetHoveredEventFn) {
74 - this._resetHoveredEvent = resetHoveredEvent;
75 - }
76 -
77 - hasActiveView(): boolean {
78 - return this._viewRefs.activeView !== null;
79 - }
80 -
81 - setCanvas(canvas: HTMLCanvasElement, canvasSize: Size) {
82 - this._context = getCanvasContext(
83 - canvas,
84 - canvasSize.height,
85 - canvasSize.width,
86 - );
87 - this._canvasSize = canvasSize;
88 -
89 - if (this.rootView) {
90 - this.rootView.setNeedsDisplay();
91 - }
92 - }
93 -
94 - displayIfNeeded() {
95 - const {rootView, _canvasSize, _context} = this;
96 - if (!rootView || !_context || !_canvasSize) {
97 - return;
98 - }
99 - rootView.setFrame({
100 - origin: zeroPoint,
101 - size: _canvasSize,
102 - });
103 - rootView.setVisibleArea({
104 - origin: zeroPoint,
105 - size: _canvasSize,
106 - });
107 - rootView.displayIfNeeded(_context, this._viewRefs);
108 - }
109 -
110 - getCurrentCursor(): string | null {
111 - const {activeView, hoveredView} = this._viewRefs;
112 - if (activeView !== null) {
113 - return activeView.currentCursor;
114 - } else if (hoveredView !== null) {
115 - return hoveredView.currentCursor;
116 - } else {
117 - return null;
118 - }
119 - }
120 -
121 - handleInteraction(interaction: Interaction) {
122 - const rootView = this.rootView;
123 - if (rootView != null) {
124 - const viewRefs = this._viewRefs;
125 - switch (interaction.type) {
126 - case 'mousemove':
127 - case 'wheel-control':
128 - case 'wheel-meta':
129 - case 'wheel-plain':
130 - case 'wheel-shift':
131 - // Clean out the hovered view before processing this type of interaction.
132 - const hoveredView = viewRefs.hoveredView;
133 - viewRefs.hoveredView = null;
134 -
135 - rootView.handleInteractionAndPropagateToSubviews(
136 - interaction,
137 - viewRefs,
138 - );
139 -
140 - // If a previously hovered view is no longer hovered, update the outer state.
141 - if (hoveredView !== null && viewRefs.hoveredView === null) {
142 - this._resetHoveredEvent();
143 - }
144 - break;
145 - default:
146 - rootView.handleInteractionAndPropagateToSubviews(
147 - interaction,
148 - viewRefs,
149 - );
150 - break;
151 - }
152 - }
153 - }
154 -}
packages/react-devtools-timeline/src/view-base/VerticalScrollView.js deleted
-297
@@ -1,297 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Size, IntrinsicSize, Rect} from './geometry';
11 -import type {
12 - Interaction,
13 - MouseDownInteraction,
14 - MouseMoveInteraction,
15 - MouseUpInteraction,
16 - WheelWithShiftInteraction,
17 -} from './useCanvasInteraction';
18 -import type {ScrollState} from './utils/scrollState';
19 -import type {ViewRefs} from './Surface';
20 -import type {ViewState} from '../types';
21 -
22 -import {Surface} from './Surface';
23 -import {View} from './View';
24 -import {rectContainsPoint} from './geometry';
25 -import {
26 - clampState,
27 - areScrollStatesEqual,
28 - translateState,
29 -} from './utils/scrollState';
30 -import {MOVE_WHEEL_DELTA_THRESHOLD} from './constants';
31 -import {COLORS} from '../content-views/constants';
32 -
33 -const CARET_MARGIN = 3;
34 -const CARET_WIDTH = 5;
35 -const CARET_HEIGHT = 3;
36 -
37 -type OnChangeCallback = (
38 - scrollState: ScrollState,
39 - containerLength: number,
40 -) => void;
41 -
42 -export class VerticalScrollView extends View {
43 - _contentView: View;
44 - _isPanning: boolean;
45 - _mutableViewStateKey: string;
46 - _onChangeCallback: OnChangeCallback | null;
47 - _scrollState: ScrollState;
48 - _viewState: ViewState;
49 -
50 - constructor(
51 - surface: Surface,
52 - frame: Rect,
53 - contentView: View,
54 - viewState: ViewState,
55 - label: string,
56 - ) {
57 - super(surface, frame);
58 -
59 - this._contentView = contentView;
60 - this._isPanning = false;
61 - this._mutableViewStateKey = label + ':VerticalScrollView';
62 - this._onChangeCallback = null;
63 - this._scrollState = {
64 - offset: 0,
65 - length: 0,
66 - };
67 - this._viewState = viewState;
68 -
69 - this.addSubview(contentView);
70 -
71 - this._restoreMutableViewState();
72 - }
73 -
74 - setFrame(newFrame: Rect) {
75 - super.setFrame(newFrame);
76 -
77 - // Revalidate scrollState
78 - this._setScrollState(this._scrollState);
79 - }
80 -
81 - desiredSize(): Size | IntrinsicSize {
82 - return this._contentView.desiredSize();
83 - }
84 -
85 - draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
86 - super.draw(context, viewRefs);
87 -
88 - // Show carets if there's scroll overflow above or below the viewable area.
89 - if (this.frame.size.height > CARET_HEIGHT * 2 + CARET_MARGIN * 3) {
90 - const offset = this._scrollState.offset;
91 - const desiredSize = this._contentView.desiredSize();
92 -
93 - const above = offset;
94 - const below = this.frame.size.height - desiredSize.height - offset;
95 -
96 - if (above < 0 || below < 0) {
97 - const {visibleArea} = this;
98 - const {x, y} = visibleArea.origin;
99 - const {width, height} = visibleArea.size;
100 - const horizontalCenter = x + width / 2;
101 -
102 - const halfWidth = CARET_WIDTH;
103 - const left = horizontalCenter + halfWidth;
104 - const right = horizontalCenter - halfWidth;
105 -
106 - if (above < 0) {
107 - const topY = y + CARET_MARGIN;
108 -
109 - context.beginPath();
110 - context.moveTo(horizontalCenter, topY);
111 - context.lineTo(left, topY + CARET_HEIGHT);
112 - context.lineTo(right, topY + CARET_HEIGHT);
113 - context.closePath();
114 - context.fillStyle = COLORS.SCROLL_CARET;
115 - context.fill();
116 - }
117 -
118 - if (below < 0) {
119 - const bottomY = y + height - CARET_MARGIN;
120 -
121 - context.beginPath();
122 - context.moveTo(horizontalCenter, bottomY);
123 - context.lineTo(left, bottomY - CARET_HEIGHT);
124 - context.lineTo(right, bottomY - CARET_HEIGHT);
125 - context.closePath();
126 - context.fillStyle = COLORS.SCROLL_CARET;
127 - context.fill();
128 - }
129 - }
130 - }
131 - }
132 -
133 - layoutSubviews() {
134 - const {offset} = this._scrollState;
135 - const desiredSize = this._contentView.desiredSize();
136 -
137 - const minimumHeight = this.frame.size.height;
138 - const desiredHeight = desiredSize ? desiredSize.height : 0;
139 - // Force view to take up at least all remaining vertical space.
140 - const height = Math.max(desiredHeight, minimumHeight);
141 -
142 - const proposedFrame = {
143 - origin: {
144 - x: this.frame.origin.x,
145 - y: this.frame.origin.y + offset,
146 - },
147 - size: {
148 - width: this.frame.size.width,
149 - height,
150 - },
151 - };
152 - this._contentView.setFrame(proposedFrame);
153 - super.layoutSubviews();
154 - }
155 -
156 - handleInteraction(interaction: Interaction): ?boolean {
157 - switch (interaction.type) {
158 - case 'mousedown':
159 - return this._handleMouseDown(interaction);
160 - case 'mousemove':
161 - return this._handleMouseMove(interaction);
162 - case 'mouseup':
163 - return this._handleMouseUp(interaction);
164 - case 'wheel-shift':
165 - return this._handleWheelShift(interaction);
166 - }
167 - }
168 -
169 - onChange(callback: OnChangeCallback) {
170 - this._onChangeCallback = callback;
171 - }
172 -
173 - scrollBy(deltaY: number): boolean {
174 - const newState = translateState({
175 - state: this._scrollState,
176 - delta: -deltaY,
177 - containerLength: this.frame.size.height,
178 - });
179 -
180 - // If the state is updated by this wheel scroll,
181 - // return true to prevent the interaction from bubbling.
182 - // For instance, this prevents the outermost container from also scrolling.
183 - return this._setScrollState(newState);
184 - }
185 -
186 - _handleMouseDown(interaction: MouseDownInteraction) {
187 - if (rectContainsPoint(interaction.payload.location, this.frame)) {
188 - const frameHeight = this.frame.size.height;
189 - const contentHeight = this._contentView.desiredSize().height;
190 - // Don't claim drag operations if the content is not tall enough to be scrollable.
191 - // This would block any outer scroll views from working.
192 - if (frameHeight < contentHeight) {
193 - this._isPanning = true;
194 - }
195 - }
196 - }
197 -
198 - _handleMouseMove(interaction: MouseMoveInteraction): void | boolean {
199 - if (!this._isPanning) {
200 - return;
201 - }
202 -
203 - // Don't prevent mouse-move events from bubbling if they are horizontal drags.
204 - const {movementX, movementY} = interaction.payload.event;
205 - if (Math.abs(movementX) > Math.abs(movementY)) {
206 - return;
207 - }
208 -
209 - const newState = translateState({
210 - state: this._scrollState,
211 - delta: interaction.payload.event.movementY,
212 - containerLength: this.frame.size.height,
213 - });
214 - this._setScrollState(newState);
215 -
216 - return true;
217 - }
218 -
219 - _handleMouseUp(interaction: MouseUpInteraction) {
220 - if (this._isPanning) {
221 - this._isPanning = false;
222 - }
223 - }
224 -
225 - _handleWheelShift(interaction: WheelWithShiftInteraction): boolean {
226 - const {
227 - location,
228 - delta: {deltaX, deltaY},
229 - } = interaction.payload;
230 -
231 - if (!rectContainsPoint(location, this.frame)) {
232 - return false; // Not scrolling on view
233 - }
234 -
235 - const absDeltaX = Math.abs(deltaX);
236 - const absDeltaY = Math.abs(deltaY);
237 - if (absDeltaX > absDeltaY) {
238 - return false; // Scrolling horizontally
239 - }
240 -
241 - if (absDeltaY < MOVE_WHEEL_DELTA_THRESHOLD) {
242 - return false; // Movement was too small and should be ignored.
243 - }
244 -
245 - return this.scrollBy(deltaY);
246 - }
247 -
248 - _restoreMutableViewState() {
249 - if (
250 - this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
251 - ) {
252 - this._scrollState = this._viewState.viewToMutableViewStateMap.get(
253 - this._mutableViewStateKey,
254 - ) as any as ScrollState;
255 - } else {
256 - this._viewState.viewToMutableViewStateMap.set(
257 - this._mutableViewStateKey,
258 - this._scrollState,
259 - );
260 - }
261 -
262 - this.setNeedsDisplay();
263 - }
264 -
265 - _setScrollState(proposedState: ScrollState): boolean {
266 - const contentHeight = this._contentView.frame.size.height;
267 - const containerHeight = this.frame.size.height;
268 -
269 - const clampedState = clampState({
270 - state: proposedState,
271 - minContentLength: contentHeight,
272 - maxContentLength: contentHeight,
273 - containerLength: containerHeight,
274 - });
275 - if (!areScrollStatesEqual(clampedState, this._scrollState)) {
276 - this._scrollState.offset = clampedState.offset;
277 - this._scrollState.length = clampedState.length;
278 -
279 - this.setNeedsDisplay();
280 -
281 - if (this._onChangeCallback !== null) {
282 - this._onChangeCallback(clampedState, this.frame.size.height);
283 - }
284 -
285 - return true;
286 - }
287 -
288 - // Don't allow wheel events to bubble past this view even if we've scrolled to the edge.
289 - // It just feels bad to have the scrolling jump unexpectedly from in a container to the outer page.
290 - // The only exception is when the container fits the content (no scrolling).
291 - if (contentHeight === containerHeight) {
292 - return false;
293 - }
294 -
295 - return true;
296 - }
297 -}
packages/react-devtools-timeline/src/view-base/View.js deleted
-345
@@ -1,345 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Interaction} from './useCanvasInteraction';
11 -import type {IntrinsicSize, Rect, Size} from './geometry';
12 -import type {Layouter} from './layouter';
13 -import type {ViewRefs} from './Surface';
14 -
15 -import {Surface} from './Surface';
16 -import {
17 - rectEqualToRect,
18 - intersectionOfRects,
19 - rectIntersectsRect,
20 - sizeIsEmpty,
21 - sizeIsValid,
22 - unionOfRects,
23 - zeroRect,
24 -} from './geometry';
25 -import {noopLayout, viewsToLayout, collapseLayoutIntoViews} from './layouter';
26 -
27 -/**
28 - * Base view class that can be subclassed to draw custom content or manage
29 - * subclasses.
30 - */
31 -export class View {
32 - _backgroundColor: string | null;
33 -
34 - currentCursor: string | null = null;
35 -
36 - surface: Surface;
37 -
38 - frame: Rect;
39 - visibleArea: Rect;
40 -
41 - superview: ?View;
42 - subviews: View[] = [];
43 -
44 - /**
45 - * An injected function that lays out our subviews.
46 - * @private
47 - */
48 - _layouter: Layouter;
49 -
50 - /**
51 - * Whether this view needs to be drawn.
52 - *
53 - * NOTE: Do not set directly! Use `setNeedsDisplay`.
54 - *
55 - * @see setNeedsDisplay
56 - * @private
57 - */
58 - _needsDisplay: boolean = true;
59 -
60 - /**
61 - * Whether the hierarchy below this view has subviews that need display.
62 - *
63 - * NOTE: Do not set directly! Use `setSubviewsNeedDisplay`.
64 - *
65 - * @see setSubviewsNeedDisplay
66 - * @private
67 - */
68 - _subviewsNeedDisplay: boolean = false;
69 -
70 - constructor(
71 - surface: Surface,
72 - frame: Rect,
73 - layouter: Layouter = noopLayout,
74 - visibleArea: Rect = frame,
75 - backgroundColor?: string | null = null,
76 - ) {
77 - this._backgroundColor = backgroundColor || null;
78 - this.surface = surface;
79 - this.frame = frame;
80 - this._layouter = layouter;
81 - this.visibleArea = visibleArea;
82 - }
83 -
84 - /**
85 - * Invalidates view's contents.
86 - *
87 - * Downward propagating; once called, all subviews of this view should also
88 - * be invalidated.
89 - */
90 - setNeedsDisplay() {
91 - this._needsDisplay = true;
92 - if (this.superview) {
93 - this.superview._setSubviewsNeedDisplay();
94 - }
95 - this.subviews.forEach(subview => subview.setNeedsDisplay());
96 - }
97 -
98 - /**
99 - * Informs superview that it has subviews that need to be drawn.
100 - *
101 - * Upward propagating; once called, all superviews of this view should also
102 - * have `subviewsNeedDisplay` = true.
103 - *
104 - * @private
105 - */
106 - _setSubviewsNeedDisplay() {
107 - this._subviewsNeedDisplay = true;
108 - if (this.superview) {
109 - this.superview._setSubviewsNeedDisplay();
110 - }
111 - }
112 -
113 - setFrame(newFrame: Rect) {
114 - if (!rectEqualToRect(this.frame, newFrame)) {
115 - this.frame = newFrame;
116 - if (sizeIsValid(newFrame.size)) {
117 - this.frame = newFrame;
118 - } else {
119 - this.frame = zeroRect;
120 - }
121 - this.setNeedsDisplay();
122 - }
123 - }
124 -
125 - setVisibleArea(newVisibleArea: Rect) {
126 - if (!rectEqualToRect(this.visibleArea, newVisibleArea)) {
127 - if (sizeIsValid(newVisibleArea.size)) {
128 - this.visibleArea = newVisibleArea;
129 - } else {
130 - this.visibleArea = zeroRect;
131 - }
132 - this.setNeedsDisplay();
133 - }
134 - }
135 -
136 - /**
137 - * A size that can be used as a hint by layout functions.
138 - *
139 - * Implementations should typically return the intrinsic content size or a
140 - * size that fits all the view's content.
141 - *
142 - * The default implementation returns a size that fits all the view's
143 - * subviews.
144 - *
145 - * Can be overridden by subclasses.
146 - */
147 - desiredSize(): Size | IntrinsicSize {
148 - if (this._needsDisplay) {
149 - this.layoutSubviews();
150 - }
151 - const frames = this.subviews.map(subview => subview.frame);
152 - return unionOfRects(...frames).size;
153 - }
154 -
155 - /**
156 - * Appends `view` to the list of this view's `subviews`.
157 - */
158 - addSubview(view: View) {
159 - if (this.subviews.includes(view)) {
160 - return;
161 - }
162 - this.subviews.push(view);
163 - view.superview = this;
164 - }
165 -
166 - /**
167 - * Breaks the subview-superview relationship between `view` and this view, if
168 - * `view` is a subview of this view.
169 - */
170 - removeSubview(view: View) {
171 - const subviewIndex = this.subviews.indexOf(view);
172 - if (subviewIndex === -1) {
173 - return;
174 - }
175 - view.superview = undefined;
176 - this.subviews.splice(subviewIndex, 1);
177 - }
178 -
179 - /**
180 - * Removes all subviews from this view.
181 - */
182 - removeAllSubviews() {
183 - this.subviews.forEach(subview => (subview.superview = undefined));
184 - this.subviews = [];
185 - }
186 -
187 - /**
188 - * Executes the display flow if this view needs to be drawn.
189 - *
190 - * 1. Lays out subviews with `layoutSubviews`.
191 - * 2. Draws content with `draw`.
192 - */
193 - displayIfNeeded(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
194 - if (
195 - (this._needsDisplay || this._subviewsNeedDisplay) &&
196 - rectIntersectsRect(this.frame, this.visibleArea) &&
197 - !sizeIsEmpty(this.visibleArea.size)
198 - ) {
199 - this.layoutSubviews();
200 - if (this._needsDisplay) {
201 - this._needsDisplay = false;
202 - }
203 - if (this._subviewsNeedDisplay) this._subviewsNeedDisplay = false;
204 -
205 - // Clip anything drawn by the view to prevent it from overflowing its visible area.
206 - const visibleArea = this.visibleArea;
207 - const region = new Path2D();
208 - region.rect(
209 - visibleArea.origin.x,
210 - visibleArea.origin.y,
211 - visibleArea.size.width,
212 - visibleArea.size.height,
213 - );
214 - context.save();
215 - context.clip(region);
216 - context.beginPath();
217 -
218 - this.draw(context, viewRefs);
219 -
220 - // Stop clipping
221 - context.restore();
222 - }
223 - }
224 -
225 - /**
226 - * Layout self and subviews.
227 - *
228 - * Implementations should call `setNeedsDisplay` if a draw is required.
229 - *
230 - * The default implementation uses `this.layouter` to lay out subviews.
231 - *
232 - * Can be overwritten by subclasses that wish to manually manage their
233 - * subviews' layout.
234 - *
235 - * NOTE: Do not call directly! Use `displayIfNeeded`.
236 - *
237 - * @see displayIfNeeded
238 - */
239 - layoutSubviews() {
240 - const {frame, _layouter, subviews, visibleArea} = this;
241 - const existingLayout = viewsToLayout(subviews);
242 - const newLayout = _layouter(existingLayout, frame);
243 - collapseLayoutIntoViews(newLayout);
244 -
245 - subviews.forEach((subview, subviewIndex) => {
246 - if (rectIntersectsRect(visibleArea, subview.frame)) {
247 - subview.setVisibleArea(intersectionOfRects(visibleArea, subview.frame));
248 - } else {
249 - subview.setVisibleArea(zeroRect);
250 - }
251 - });
252 - }
253 -
254 - /**
255 - * Draw the contents of this view in the given canvas `context`.
256 - *
257 - * Defaults to drawing this view's `subviews`.
258 - *
259 - * To be overwritten by subclasses that wish to draw custom content.
260 - *
261 - * NOTE: Do not call directly! Use `displayIfNeeded`.
262 - *
263 - * @see displayIfNeeded
264 - */
265 - draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
266 - const {subviews, visibleArea} = this;
267 - subviews.forEach(subview => {
268 - if (rectIntersectsRect(visibleArea, subview.visibleArea)) {
269 - subview.displayIfNeeded(context, viewRefs);
270 - }
271 - });
272 -
273 - const backgroundColor = this._backgroundColor;
274 - if (backgroundColor !== null) {
275 - const desiredSize = this.desiredSize();
276 - if (visibleArea.size.height > desiredSize.height) {
277 - context.fillStyle = backgroundColor;
278 - context.fillRect(
279 - visibleArea.origin.x,
280 - visibleArea.origin.y + desiredSize.height,
281 - visibleArea.size.width,
282 - visibleArea.size.height - desiredSize.height,
283 - );
284 - }
285 - }
286 - }
287 -
288 - /**
289 - * Handle an `interaction`.
290 - *
291 - * To be overwritten by subclasses that wish to handle interactions.
292 - *
293 - * NOTE: Do not call directly! Use `handleInteractionAndPropagateToSubviews`
294 - */
295 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs): ?boolean {}
296 -
297 - /**
298 - * Handle an `interaction` and propagates it to all of this view's
299 - * `subviews`.
300 - *
301 - * NOTE: Should not be overridden! Subclasses should override
302 - * `handleInteraction` instead.
303 - *
304 - * @see handleInteraction
305 - * @protected
306 - */
307 - handleInteractionAndPropagateToSubviews(
308 - interaction: Interaction,
309 - viewRefs: ViewRefs,
310 - ): boolean {
311 - const {subviews, visibleArea} = this;
312 -
313 - if (visibleArea.size.height === 0) {
314 - return false;
315 - }
316 -
317 - // Pass the interaction to subviews first,
318 - // so they have the opportunity to claim it before it bubbles.
319 - //
320 - // Views are painted first to last,
321 - // so they should process interactions last to first,
322 - // so views in front (on top) can claim the interaction first.
323 - for (let i = subviews.length - 1; i >= 0; i--) {
324 - const subview = subviews[i];
325 - if (rectIntersectsRect(visibleArea, subview.visibleArea)) {
326 - const didSubviewHandle =
327 - subview.handleInteractionAndPropagateToSubviews(
328 - interaction,
329 - viewRefs,
330 - ) === true;
331 - if (didSubviewHandle) {
332 - return true;
333 - }
334 - }
335 - }
336 -
337 - const didSelfHandle =
338 - this.handleInteraction(interaction, viewRefs) === true;
339 - if (didSelfHandle) {
340 - return true;
341 - }
342 -
343 - return false;
344 - }
345 -}
packages/react-devtools-timeline/src/view-base/__tests__/geometry-test.js deleted
-272
@@ -1,272 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 - pointEqualToPoint,
12 - sizeEqualToSize,
13 - rectEqualToRect,
14 - sizeIsValid,
15 - sizeIsEmpty,
16 - rectIntersectsRect,
17 - intersectionOfRects,
18 - rectContainsPoint,
19 - unionOfRects,
20 -} from '../geometry';
21 -
22 -describe('pointEqualToPoint', () => {
23 - it('should return true when 2 points have the same values', () => {
24 - expect(pointEqualToPoint({x: 1, y: 1}, {x: 1, y: 1})).toBe(true);
25 - expect(pointEqualToPoint({x: -1, y: 2}, {x: -1, y: 2})).toBe(true);
26 - expect(
27 - pointEqualToPoint({x: 3.14159, y: 0.26535}, {x: 3.14159, y: 0.26535}),
28 - ).toBe(true);
29 - });
30 -
31 - it('should return false when 2 points have different values', () => {
32 - expect(pointEqualToPoint({x: 1, y: 1}, {x: 1, y: 0})).toBe(false);
33 - expect(pointEqualToPoint({x: -1, y: 2}, {x: 0, y: 1})).toBe(false);
34 - expect(
35 - pointEqualToPoint({x: 3.1416, y: 0.26534}, {x: 3.14159, y: 0.26535}),
36 - ).toBe(false);
37 - });
38 -});
39 -
40 -describe('sizeEqualToSize', () => {
41 - it('should return true when 2 sizes have the same values', () => {
42 - expect(sizeEqualToSize({width: 1, height: 1}, {width: 1, height: 1})).toBe(
43 - true,
44 - );
45 - expect(
46 - sizeEqualToSize({width: -1, height: 2}, {width: -1, height: 2}),
47 - ).toBe(true);
48 - expect(
49 - sizeEqualToSize(
50 - {width: 3.14159, height: 0.26535},
51 - {width: 3.14159, height: 0.26535},
52 - ),
53 - ).toBe(true);
54 - });
55 -
56 - it('should return false when 2 sizes have different values', () => {
57 - expect(sizeEqualToSize({width: 1, height: 1}, {width: 1, height: 0})).toBe(
58 - false,
59 - );
60 - expect(sizeEqualToSize({width: -1, height: 2}, {width: 0, height: 1})).toBe(
61 - false,
62 - );
63 - expect(
64 - sizeEqualToSize(
65 - {width: 3.1416, height: 0.26534},
66 - {width: 3.14159, height: 0.26535},
67 - ),
68 - ).toBe(false);
69 - });
70 -});
71 -
72 -describe('rectEqualToRect', () => {
73 - it('should return true when 2 rects have the same values', () => {
74 - expect(
75 - rectEqualToRect(
76 - {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
77 - {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
78 - ),
79 - ).toBe(true);
80 - expect(
81 - rectEqualToRect(
82 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
83 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
84 - ),
85 - ).toBe(true);
86 - });
87 -
88 - it('should return false when 2 rects have different values', () => {
89 - expect(
90 - rectEqualToRect(
91 - {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
92 - {origin: {x: 0, y: 1}, size: {width: 1, height: 1}},
93 - ),
94 - ).toBe(false);
95 - expect(
96 - rectEqualToRect(
97 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
98 - {origin: {x: 1, y: 2}, size: {width: 3.15, height: 4}},
99 - ),
100 - ).toBe(false);
101 - });
102 -});
103 -
104 -describe('sizeIsValid', () => {
105 - it('should return true when the size has non-negative width and height', () => {
106 - expect(sizeIsValid({width: 1, height: 1})).toBe(true);
107 - expect(sizeIsValid({width: 0, height: 0})).toBe(true);
108 - });
109 -
110 - it('should return false when the size has negative width or height', () => {
111 - expect(sizeIsValid({width: 0, height: -1})).toBe(false);
112 - expect(sizeIsValid({width: -1, height: 0})).toBe(false);
113 - expect(sizeIsValid({width: -1, height: -1})).toBe(false);
114 - });
115 -});
116 -
117 -describe('sizeIsEmpty', () => {
118 - it('should return true when the size has negative area', () => {
119 - expect(sizeIsEmpty({width: 1, height: -1})).toBe(true);
120 - expect(sizeIsEmpty({width: -1, height: -1})).toBe(true);
121 - });
122 -
123 - it('should return true when the size has zero area', () => {
124 - expect(sizeIsEmpty({width: 0, height: 0})).toBe(true);
125 - expect(sizeIsEmpty({width: 0, height: 1})).toBe(true);
126 - expect(sizeIsEmpty({width: 1, height: 0})).toBe(true);
127 - });
128 -
129 - it('should return false when the size has positive area', () => {
130 - expect(sizeIsEmpty({width: 1, height: 1})).toBe(false);
131 - expect(sizeIsEmpty({width: 2, height: 1})).toBe(false);
132 - });
133 -});
134 -
135 -describe('rectIntersectsRect', () => {
136 - it('should return true when 2 rects intersect', () => {
137 - // Rects touch
138 - expect(
139 - rectIntersectsRect(
140 - {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
141 - {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
142 - ),
143 - ).toEqual(true);
144 -
145 - // Rects overlap
146 - expect(
147 - rectIntersectsRect(
148 - {origin: {x: 0, y: 0}, size: {width: 2, height: 1}},
149 - {origin: {x: 1, y: -2}, size: {width: 0.5, height: 5}},
150 - ),
151 - ).toEqual(true);
152 -
153 - // Rects are equal
154 - expect(
155 - rectIntersectsRect(
156 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
157 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
158 - ),
159 - ).toEqual(true);
160 - });
161 -
162 - it('should return false when 2 rects do not intersect', () => {
163 - expect(
164 - rectIntersectsRect(
165 - {origin: {x: 0, y: 1}, size: {width: 1, height: 1}},
166 - {origin: {x: 0, y: 10}, size: {width: 1, height: 1}},
167 - ),
168 - ).toBe(false);
169 - expect(
170 - rectIntersectsRect(
171 - {origin: {x: 1, y: 2}, size: {width: 3.14, height: 4}},
172 - {origin: {x: -4, y: 2}, size: {width: 3.15, height: 4}},
173 - ),
174 - ).toBe(false);
175 - });
176 -});
177 -
178 -describe('intersectionOfRects', () => {
179 - // NOTE: Undefined behavior if rects do not intersect
180 -
181 - it('should return intersection when 2 rects intersect', () => {
182 - // Rects touch
183 - expect(
184 - intersectionOfRects(
185 - {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
186 - {origin: {x: 1, y: 1}, size: {width: 1, height: 1}},
187 - ),
188 - ).toEqual({origin: {x: 1, y: 1}, size: {width: 0, height: 0}});
189 -
190 - // Rects overlap
191 - expect(
192 - intersectionOfRects(
193 - {origin: {x: 0, y: 0}, size: {width: 2, height: 1}},
194 - {origin: {x: 1, y: -2}, size: {width: 0.5, height: 5}},
195 - ),
196 - ).toEqual({origin: {x: 1, y: 0}, size: {width: 0.5, height: 1}});
197 -
198 - // Rects are equal
199 - expect(
200 - intersectionOfRects(
201 - {origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}},
202 - {origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}},
203 - ),
204 - ).toEqual({origin: {x: 1, y: 2}, size: {width: 9.24, height: 4}});
205 - });
206 -});
207 -
208 -describe('rectContainsPoint', () => {
209 - it("should return true if point is on the rect's edge", () => {
210 - expect(
211 - rectContainsPoint(
212 - {x: 0, y: 0},
213 - {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
214 - ),
215 - ).toBe(true);
216 - expect(
217 - rectContainsPoint(
218 - {x: 5, y: 0},
219 - {origin: {x: 0, y: 0}, size: {width: 10, height: 1}},
220 - ),
221 - ).toBe(true);
222 - expect(
223 - rectContainsPoint(
224 - {x: 1, y: 1},
225 - {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
226 - ),
227 - ).toBe(true);
228 - });
229 -
230 - it('should return true if point is in rect', () => {
231 - expect(
232 - rectContainsPoint(
233 - {x: 5, y: 50},
234 - {origin: {x: 0, y: 0}, size: {width: 10, height: 100}},
235 - ),
236 - ).toBe(true);
237 - });
238 -
239 - it('should return false if point is not in rect', () => {
240 - expect(
241 - rectContainsPoint(
242 - {x: -1, y: 0},
243 - {origin: {x: 0, y: 0}, size: {width: 1, height: 1}},
244 - ),
245 - ).toBe(false);
246 - });
247 -});
248 -
249 -describe('unionOfRects', () => {
250 - it('should return zero rect if no rects are provided', () => {
251 - expect(unionOfRects()).toEqual({
252 - origin: {x: 0, y: 0},
253 - size: {width: 0, height: 0},
254 - });
255 - });
256 -
257 - it('should return rect if 1 rect is provided', () => {
258 - expect(
259 - unionOfRects({origin: {x: 1, y: 2}, size: {width: 3, height: 4}}),
260 - ).toEqual({origin: {x: 1, y: 2}, size: {width: 3, height: 4}});
261 - });
262 -
263 - it('should return union of rects if more than one rect is provided', () => {
264 - expect(
265 - unionOfRects(
266 - {origin: {x: 1, y: 2}, size: {width: 3, height: 4}},
267 - {origin: {x: 100, y: 200}, size: {width: 3, height: 4}},
268 - {origin: {x: -10, y: -20}, size: {width: 50, height: 60}},
269 - ),
270 - ).toEqual({origin: {x: -10, y: -20}, size: {width: 113, height: 224}});
271 - });
272 -});
packages/react-devtools-timeline/src/view-base/constants.js deleted
-14
@@ -1,14 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export const MOVE_WHEEL_DELTA_THRESHOLD = 1;
11 -export const ZOOM_WHEEL_DELTA_THRESHOLD = 1;
12 -export const MIN_ZOOM_LEVEL = 0.25;
13 -export const MAX_ZOOM_LEVEL = 1000;
14 -export const DEFAULT_ZOOM_LEVEL = 0.25;
packages/react-devtools-timeline/src/view-base/geometry.js deleted
-147
@@ -1,147 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export type Point = $ReadOnly<{x: number, y: number}>;
11 -export type Size = $ReadOnly<{width: number, height: number}>;
12 -export type IntrinsicSize = {
13 - ...Size,
14 -
15 - // If content is this height or less, hide the scrollbar entirely,
16 - // so that it doesn't take up vertical space unnecessarily (e.g. for a single row of content).
17 - hideScrollBarIfLessThanHeight?: number,
18 -
19 - // The initial height should be the height of the content, or this, whichever is less.
20 - maxInitialHeight?: number,
21 -};
22 -export type Rect = $ReadOnly<{origin: Point, size: Size}>;
23 -
24 -/**
25 - * Alternative representation of `Rect`.
26 - * A tuple of (`top`, `right`, `bottom`, `left`) coordinates.
27 - */
28 -type Box = [number, number, number, number];
29 -
30 -export const zeroPoint: Point = Object.freeze({x: 0, y: 0});
31 -export const zeroSize: Size = Object.freeze({width: 0, height: 0});
32 -export const zeroRect: Rect = Object.freeze({
33 - origin: zeroPoint,
34 - size: zeroSize,
35 -});
36 -
37 -export function pointEqualToPoint(point1: Point, point2: Point): boolean {
38 - return point1.x === point2.x && point1.y === point2.y;
39 -}
40 -
41 -export function sizeEqualToSize(size1: Size, size2: Size): boolean {
42 - return size1.width === size2.width && size1.height === size2.height;
43 -}
44 -
45 -export function rectEqualToRect(rect1: Rect, rect2: Rect): boolean {
46 - return (
47 - pointEqualToPoint(rect1.origin, rect2.origin) &&
48 - sizeEqualToSize(rect1.size, rect2.size)
49 - );
50 -}
51 -
52 -export function sizeIsValid({width, height}: Size): boolean {
53 - return width >= 0 && height >= 0;
54 -}
55 -
56 -export function sizeIsEmpty({width, height}: Size): boolean {
57 - return width <= 0 || height <= 0;
58 -}
59 -
60 -function rectToBox(rect: Rect): Box {
61 - const top = rect.origin.y;
62 - const right = rect.origin.x + rect.size.width;
63 - const bottom = rect.origin.y + rect.size.height;
64 - const left = rect.origin.x;
65 - return [top, right, bottom, left];
66 -}
67 -
68 -function boxToRect(box: Box): Rect {
69 - const [top, right, bottom, left] = box;
70 - return {
71 - origin: {
72 - x: left,
73 - y: top,
74 - },
75 - size: {
76 - width: right - left,
77 - height: bottom - top,
78 - },
79 - };
80 -}
81 -
82 -export function rectIntersectsRect(rect1: Rect, rect2: Rect): boolean {
83 - if (
84 - rect1.size.width === 0 ||
85 - rect1.size.height === 0 ||
86 - rect2.size.width === 0 ||
87 - rect2.size.height === 0
88 - ) {
89 - return false;
90 - }
91 -
92 - const [top1, right1, bottom1, left1] = rectToBox(rect1);
93 - const [top2, right2, bottom2, left2] = rectToBox(rect2);
94 - return !(
95 - right1 < left2 ||
96 - right2 < left1 ||
97 - bottom1 < top2 ||
98 - bottom2 < top1
99 - );
100 -}
101 -
102 -/**
103 - * Returns the intersection of the 2 rectangles.
104 - *
105 - * Prerequisite: `rect1` must intersect with `rect2`.
106 - */
107 -export function intersectionOfRects(rect1: Rect, rect2: Rect): Rect {
108 - const [top1, right1, bottom1, left1] = rectToBox(rect1);
109 - const [top2, right2, bottom2, left2] = rectToBox(rect2);
110 - return boxToRect([
111 - Math.max(top1, top2),
112 - Math.min(right1, right2),
113 - Math.min(bottom1, bottom2),
114 - Math.max(left1, left2),
115 - ]);
116 -}
117 -
118 -export function rectContainsPoint({x, y}: Point, rect: Rect): boolean {
119 - const [top, right, bottom, left] = rectToBox(rect);
120 - return left <= x && x <= right && top <= y && y <= bottom;
121 -}
122 -
123 -/**
124 - * Returns the smallest rectangle that contains all provided rects.
125 - *
126 - * @returns Union of `rects`. If `rects` is empty, returns `zeroRect`.
127 - */
128 -export function unionOfRects(...rects: Rect[]): Rect {
129 - if (rects.length === 0) {
130 - return zeroRect;
131 - }
132 -
133 - const [firstRect, ...remainingRects] = rects;
134 - const boxUnion = remainingRects
135 - .map(rectToBox)
136 - .reduce((intermediateUnion, nextBox): Box => {
137 - const [unionTop, unionRight, unionBottom, unionLeft] = intermediateUnion;
138 - const [nextTop, nextRight, nextBottom, nextLeft] = nextBox;
139 - return [
140 - Math.min(unionTop, nextTop),
141 - Math.max(unionRight, nextRight),
142 - Math.max(unionBottom, nextBottom),
143 - Math.min(unionLeft, nextLeft),
144 - ];
145 - }, rectToBox(firstRect));
146 - return boxToRect(boxUnion);
147 -}
packages/react-devtools-timeline/src/view-base/index.js deleted
-19
@@ -1,19 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export * from './BackgroundColorView';
11 -export * from './HorizontalPanAndZoomView';
12 -export * from './Surface';
13 -export * from './VerticalScrollView';
14 -export * from './View';
15 -export * from './geometry';
16 -export * from './layouter';
17 -export * from './resizable';
18 -export * from './useCanvasInteraction';
19 -export * from './vertical-scroll-overflow';
packages/react-devtools-timeline/src/view-base/layouter.js deleted
-225
@@ -1,225 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect} from './geometry';
11 -import type {View} from './View';
12 -
13 -export type LayoutInfo = {view: View, frame: Rect};
14 -export type Layout = LayoutInfo[];
15 -
16 -/**
17 - * A function that takes a list of subviews, currently laid out in
18 - * `existingLayout`, and lays them out into `containingFrame`.
19 - */
20 -export type Layouter = (
21 - existingLayout: Layout,
22 - containingFrame: Rect,
23 -) => Layout;
24 -
25 -function viewToLayoutInfo(view: View): LayoutInfo {
26 - return {view, frame: view.frame};
27 -}
28 -
29 -export function viewsToLayout(views: View[]): Layout {
30 - return views.map(viewToLayoutInfo);
31 -}
32 -
33 -/**
34 - * Applies `layout`'s `frame`s to its corresponding `view`.
35 - */
36 -export function collapseLayoutIntoViews(layout: Layout) {
37 - layout.forEach(({view, frame}) => view.setFrame(frame));
38 -}
39 -
40 -/**
41 - * A no-operation layout; does not modify the layout.
42 - */
43 -export const noopLayout: Layouter = layout => layout;
44 -
45 -/**
46 - * Layer views on top of each other. All views' frames will be set to `containerFrame`.
47 - *
48 - * Equivalent to composing:
49 - * - `alignToContainerXLayout`,
50 - * - `alignToContainerYLayout`,
51 - * - `containerWidthLayout`, and
52 - * - `containerHeightLayout`.
53 - */
54 -export const layeredLayout: Layouter = (layout, containerFrame) => {
55 - return layout.map(layoutInfo => ({...layoutInfo, frame: containerFrame}));
56 -};
57 -
58 -/**
59 - * Stacks `views` vertically in `frame`.
60 - * All views in `views` will have their widths set to the frame's width.
61 - */
62 -export const verticallyStackedLayout: Layouter = (layout, containerFrame) => {
63 - let currentY = containerFrame.origin.y;
64 - return layout.map(layoutInfo => {
65 - const desiredSize = layoutInfo.view.desiredSize();
66 - const height = desiredSize
67 - ? desiredSize.height
68 - : containerFrame.origin.y + containerFrame.size.height - currentY;
69 - const proposedFrame = {
70 - origin: {x: containerFrame.origin.x, y: currentY},
71 - size: {width: containerFrame.size.width, height},
72 - };
73 - currentY += height;
74 - return {
75 - ...layoutInfo,
76 - frame: proposedFrame,
77 - };
78 - });
79 -};
80 -
81 -/**
82 - * A layouter that aligns all frames' lefts to the container frame's left.
83 - */
84 -export const alignToContainerXLayout: Layouter = (layout, containerFrame) => {
85 - return layout.map(layoutInfo => ({
86 - ...layoutInfo,
87 - frame: {
88 - origin: {
89 - x: containerFrame.origin.x,
90 - y: layoutInfo.frame.origin.y,
91 - },
92 - size: layoutInfo.frame.size,
93 - },
94 - }));
95 -};
96 -
97 -/**
98 - * A layouter that aligns all frames' tops to the container frame's top.
99 - */
100 -export const alignToContainerYLayout: Layouter = (layout, containerFrame) => {
101 - return layout.map(layoutInfo => ({
102 - ...layoutInfo,
103 - frame: {
104 - origin: {
105 - x: layoutInfo.frame.origin.x,
106 - y: containerFrame.origin.y,
107 - },
108 - size: layoutInfo.frame.size,
109 - },
110 - }));
111 -};
112 -
113 -/**
114 - * A layouter that sets all frames' widths to `containerFrame.size.width`.
115 - */
116 -export const containerWidthLayout: Layouter = (layout, containerFrame) => {
117 - return layout.map(layoutInfo => ({
118 - ...layoutInfo,
119 - frame: {
120 - origin: layoutInfo.frame.origin,
121 - size: {
122 - width: containerFrame.size.width,
123 - height: layoutInfo.frame.size.height,
124 - },
125 - },
126 - }));
127 -};
128 -
129 -/**
130 - * A layouter that sets all frames' heights to `containerFrame.size.height`.
131 - */
132 -export const containerHeightLayout: Layouter = (layout, containerFrame) => {
133 - return layout.map(layoutInfo => ({
134 - ...layoutInfo,
135 - frame: {
136 - origin: layoutInfo.frame.origin,
137 - size: {
138 - width: layoutInfo.frame.size.width,
139 - height: containerFrame.size.height,
140 - },
141 - },
142 - }));
143 -};
144 -
145 -/**
146 - * A layouter that sets all frames' heights to the desired height of its view.
147 - * If the view has no desired size, the frame's height is set to 0.
148 - */
149 -export const desiredHeightLayout: Layouter = layout => {
150 - return layout.map(layoutInfo => {
151 - const desiredSize = layoutInfo.view.desiredSize();
152 - const height = desiredSize ? desiredSize.height : 0;
153 - return {
154 - ...layoutInfo,
155 - frame: {
156 - origin: layoutInfo.frame.origin,
157 - size: {
158 - width: layoutInfo.frame.size.width,
159 - height,
160 - },
161 - },
162 - };
163 - });
164 -};
165 -
166 -/**
167 - * A layouter that sets all frames' heights to the height of the tallest frame.
168 - */
169 -export const uniformMaxSubviewHeightLayout: Layouter = layout => {
170 - const maxHeight = Math.max(
171 - ...layout.map(layoutInfo => layoutInfo.frame.size.height),
172 - );
173 - return layout.map(layoutInfo => ({
174 - ...layoutInfo,
175 - frame: {
176 - origin: layoutInfo.frame.origin,
177 - size: {
178 - width: layoutInfo.frame.size.width,
179 - height: maxHeight,
180 - },
181 - },
182 - }));
183 -};
184 -
185 -/**
186 - * A layouter that sets heights in this fashion:
187 - * - If a frame's height >= `containerFrame.size.height`, the frame is left unchanged.
188 - * - Otherwise, sets the frame's height to `containerFrame.size.height`.
189 - */
190 -export const atLeastContainerHeightLayout: Layouter = (
191 - layout,
192 - containerFrame,
193 -) => {
194 - return layout.map(layoutInfo => ({
195 - ...layoutInfo,
196 - frame: {
197 - origin: layoutInfo.frame.origin,
198 - size: {
199 - width: layoutInfo.frame.size.width,
200 - height: Math.max(
201 - containerFrame.size.height,
202 - layoutInfo.frame.size.height,
203 - ),
204 - },
205 - },
206 - }));
207 -};
208 -
209 -/**
210 - * Create a layouter that applies each layouter in `layouters` in sequence.
211 - */
212 -export function createComposedLayout(...layouters: Layouter[]): Layouter {
213 - if (layouters.length === 0) {
214 - return noopLayout;
215 - }
216 -
217 - const composedLayout: Layouter = (layout, containerFrame) => {
218 - return layouters.reduce(
219 - (intermediateLayout, layouter) =>
220 - layouter(intermediateLayout, containerFrame),
221 - layout,
222 - );
223 - };
224 - return composedLayout;
225 -}
packages/react-devtools-timeline/src/view-base/resizable/ResizableView.js deleted
-299
@@ -1,299 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - ClickInteraction,
12 - DoubleClickInteraction,
13 - Interaction,
14 - MouseDownInteraction,
15 - MouseMoveInteraction,
16 - MouseUpInteraction,
17 -} from '../useCanvasInteraction';
18 -import type {Rect} from '../geometry';
19 -import type {ViewRefs} from '../Surface';
20 -import type {ViewState} from '../../types';
21 -
22 -import {ResizeBarView} from './ResizeBarView';
23 -import {Surface} from '../Surface';
24 -import {View} from '../View';
25 -import {rectContainsPoint} from '../geometry';
26 -import {noopLayout} from '../layouter';
27 -import {clamp} from '../utils/clamp';
28 -
29 -type ResizingState = $ReadOnly<{
30 - /** Distance between top of resize bar and mouseY */
31 - cursorOffsetInBarFrame: number,
32 - /** Mouse's vertical coordinates relative to canvas */
33 - mouseY: number,
34 -}>;
35 -
36 -type LayoutState = {
37 - /** Resize bar's vertical position relative to resize view's frame.origin.y */
38 - barOffsetY: number,
39 -};
40 -
41 -const RESIZE_BAR_HEIGHT = 8;
42 -const RESIZE_BAR_WITH_LABEL_HEIGHT = 16;
43 -
44 -const HIDDEN_RECT = {
45 - origin: {x: 0, y: 0},
46 - size: {width: 0, height: 0},
47 -};
48 -
49 -export class ResizableView extends View {
50 - _canvasRef: {current: HTMLCanvasElement | null};
51 - _layoutState: LayoutState;
52 - _mutableViewStateKey: string;
53 - _resizeBar: ResizeBarView;
54 - _resizingState: ResizingState | null = null;
55 - _subview: View;
56 - _viewState: ViewState;
57 -
58 - constructor(
59 - surface: Surface,
60 - frame: Rect,
61 - subview: View,
62 - viewState: ViewState,
63 - canvasRef: {current: HTMLCanvasElement | null},
64 - label: string,
65 - ) {
66 - super(surface, frame, noopLayout);
67 -
68 - this._canvasRef = canvasRef;
69 - this._layoutState = {barOffsetY: 0};
70 - this._mutableViewStateKey = label + ':ResizableView';
71 - this._subview = subview;
72 - this._resizeBar = new ResizeBarView(surface, frame, label);
73 - this._viewState = viewState;
74 -
75 - this.addSubview(this._subview);
76 - this.addSubview(this._resizeBar);
77 -
78 - this._restoreMutableViewState();
79 - }
80 -
81 - desiredSize(): {+height: number, +width: number} {
82 - const subviewDesiredSize = this._subview.desiredSize();
83 -
84 - if (this._shouldRenderResizeBar()) {
85 - const resizeBarDesiredSize = this._resizeBar.desiredSize();
86 -
87 - return {
88 - width: this.frame.size.width,
89 - height: this._layoutState.barOffsetY + resizeBarDesiredSize.height,
90 - };
91 - } else {
92 - return {
93 - width: this.frame.size.width,
94 - height: subviewDesiredSize.height,
95 - };
96 - }
97 - }
98 -
99 - layoutSubviews() {
100 - this._updateLayoutState();
101 - this._updateSubviewFrames();
102 -
103 - super.layoutSubviews();
104 - }
105 -
106 - _restoreMutableViewState() {
107 - if (
108 - this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
109 - ) {
110 - this._layoutState = this._viewState.viewToMutableViewStateMap.get(
111 - this._mutableViewStateKey,
112 - ) as any as LayoutState;
113 -
114 - this._updateLayoutStateAndResizeBar(this._layoutState.barOffsetY);
115 - } else {
116 - this._viewState.viewToMutableViewStateMap.set(
117 - this._mutableViewStateKey,
118 - this._layoutState,
119 - );
120 -
121 - const subviewDesiredSize = this._subview.desiredSize();
122 - this._updateLayoutStateAndResizeBar(
123 - subviewDesiredSize.maxInitialHeight != null
124 - ? Math.min(
125 - subviewDesiredSize.maxInitialHeight,
126 - subviewDesiredSize.height,
127 - )
128 - : subviewDesiredSize.height,
129 - );
130 - }
131 -
132 - this.setNeedsDisplay();
133 - }
134 -
135 - _shouldRenderResizeBar(): boolean {
136 - const subviewDesiredSize = this._subview.desiredSize();
137 - return subviewDesiredSize.hideScrollBarIfLessThanHeight != null
138 - ? subviewDesiredSize.height >
139 - subviewDesiredSize.hideScrollBarIfLessThanHeight
140 - : true;
141 - }
142 -
143 - _updateLayoutStateAndResizeBar(barOffsetY: number) {
144 - if (barOffsetY <= RESIZE_BAR_WITH_LABEL_HEIGHT - RESIZE_BAR_HEIGHT) {
145 - barOffsetY = 0;
146 - }
147 -
148 - this._layoutState.barOffsetY = barOffsetY;
149 -
150 - this._resizeBar.showLabel = barOffsetY === 0;
151 - }
152 -
153 - _updateLayoutState() {
154 - const {frame, _resizingState} = this;
155 -
156 - // Allow bar to travel to bottom of the visible area of this view but no further
157 - const subviewDesiredSize = this._subview.desiredSize();
158 - const maxBarOffset = subviewDesiredSize.height;
159 -
160 - let proposedBarOffsetY = this._layoutState.barOffsetY;
161 - // Update bar offset if dragging bar
162 - if (_resizingState) {
163 - const {mouseY, cursorOffsetInBarFrame} = _resizingState;
164 - proposedBarOffsetY = mouseY - frame.origin.y - cursorOffsetInBarFrame;
165 - }
166 -
167 - this._updateLayoutStateAndResizeBar(
168 - clamp(0, maxBarOffset, proposedBarOffsetY),
169 - );
170 - }
171 -
172 - _updateSubviewFrames() {
173 - const {
174 - frame: {
175 - origin: {x, y},
176 - size: {width},
177 - },
178 - _layoutState: {barOffsetY},
179 - } = this;
180 -
181 - const resizeBarDesiredSize = this._resizeBar.desiredSize();
182 -
183 - if (barOffsetY === 0) {
184 - this._subview.setFrame(HIDDEN_RECT);
185 - } else {
186 - this._subview.setFrame({
187 - origin: {x, y},
188 - size: {width, height: barOffsetY},
189 - });
190 - }
191 -
192 - this._resizeBar.setFrame({
193 - origin: {x, y: y + barOffsetY},
194 - size: {width, height: resizeBarDesiredSize.height},
195 - });
196 - }
197 -
198 - _handleClick(interaction: ClickInteraction): void | boolean {
199 - if (!this._shouldRenderResizeBar()) {
200 - return;
201 - }
202 -
203 - const cursorInView = rectContainsPoint(
204 - interaction.payload.location,
205 - this.frame,
206 - );
207 - if (cursorInView) {
208 - if (this._layoutState.barOffsetY === 0) {
209 - // Clicking on the collapsed label should expand.
210 - const subviewDesiredSize = this._subview.desiredSize();
211 - this._updateLayoutStateAndResizeBar(subviewDesiredSize.height);
212 - this.setNeedsDisplay();
213 -
214 - return true;
215 - }
216 - }
217 - }
218 -
219 - _handleDoubleClick(interaction: DoubleClickInteraction): void | boolean {
220 - if (!this._shouldRenderResizeBar()) {
221 - return;
222 - }
223 -
224 - const cursorInView = rectContainsPoint(
225 - interaction.payload.location,
226 - this.frame,
227 - );
228 - if (cursorInView) {
229 - if (this._layoutState.barOffsetY > 0) {
230 - // Double clicking on the expanded view should collapse.
231 - this._updateLayoutStateAndResizeBar(0);
232 - this.setNeedsDisplay();
233 -
234 - return true;
235 - }
236 - }
237 - }
238 -
239 - _handleMouseDown(interaction: MouseDownInteraction): void | boolean {
240 - const cursorLocation = interaction.payload.location;
241 - const resizeBarFrame = this._resizeBar.frame;
242 - if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
243 - const mouseY = cursorLocation.y;
244 - this._resizingState = {
245 - cursorOffsetInBarFrame: mouseY - resizeBarFrame.origin.y,
246 - mouseY,
247 - };
248 -
249 - return true;
250 - }
251 - }
252 -
253 - _handleMouseMove(interaction: MouseMoveInteraction): void | boolean {
254 - const {_resizingState} = this;
255 - if (_resizingState) {
256 - this._resizingState = {
257 - ..._resizingState,
258 - mouseY: interaction.payload.location.y,
259 - };
260 - this.setNeedsDisplay();
261 -
262 - return true;
263 - }
264 - }
265 -
266 - _handleMouseUp(interaction: MouseUpInteraction) {
267 - if (this._resizingState) {
268 - this._resizingState = null;
269 - }
270 - }
271 -
272 - getCursorActiveSubView(interaction: Interaction): View | null {
273 - const cursorLocation = interaction.payload.location;
274 - const resizeBarFrame = this._resizeBar.frame;
275 - if (rectContainsPoint(cursorLocation, resizeBarFrame)) {
276 - return this;
277 - } else {
278 - return null;
279 - }
280 - }
281 -
282 - handleInteraction(
283 - interaction: Interaction,
284 - viewRefs: ViewRefs,
285 - ): void | boolean {
286 - switch (interaction.type) {
287 - case 'click':
288 - return this._handleClick(interaction);
289 - case 'double-click':
290 - return this._handleDoubleClick(interaction);
291 - case 'mousedown':
292 - return this._handleMouseDown(interaction);
293 - case 'mousemove':
294 - return this._handleMouseMove(interaction);
295 - case 'mouseup':
296 - return this._handleMouseUp(interaction);
297 - }
298 - }
299 -}
packages/react-devtools-timeline/src/view-base/resizable/ResizeBarView.js deleted
-193
@@ -1,193 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {
11 - Interaction,
12 - MouseDownInteraction,
13 - MouseMoveInteraction,
14 - MouseUpInteraction,
15 -} from '../useCanvasInteraction';
16 -import type {Rect, Size} from '../geometry';
17 -import type {ViewRefs} from '../Surface';
18 -
19 -import {BORDER_SIZE, COLORS} from '../../content-views/constants';
20 -import {drawText} from '../../content-views/utils/text';
21 -import {Surface} from '../Surface';
22 -import {View} from '../View';
23 -import {rectContainsPoint} from '../geometry';
24 -import {noopLayout} from '../layouter';
25 -
26 -type ResizeBarState = 'normal' | 'hovered' | 'dragging';
27 -
28 -const RESIZE_BAR_DOT_RADIUS = 1;
29 -const RESIZE_BAR_DOT_SPACING = 4;
30 -const RESIZE_BAR_HEIGHT = 8;
31 -const RESIZE_BAR_WITH_LABEL_HEIGHT = 16;
32 -
33 -export class ResizeBarView extends View {
34 - _interactionState: ResizeBarState = 'normal';
35 - _label: string;
36 -
37 - showLabel: boolean = false;
38 -
39 - constructor(surface: Surface, frame: Rect, label: string) {
40 - super(surface, frame, noopLayout);
41 -
42 - this._label = label;
43 - }
44 -
45 - desiredSize(): Size {
46 - return this.showLabel
47 - ? {height: RESIZE_BAR_WITH_LABEL_HEIGHT, width: 0}
48 - : {height: RESIZE_BAR_HEIGHT, width: 0};
49 - }
50 -
51 - draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
52 - const {frame} = this;
53 - const {x, y} = frame.origin;
54 - const {width, height} = frame.size;
55 -
56 - const isActive =
57 - this._interactionState === 'dragging' ||
58 - (this._interactionState === 'hovered' && viewRefs.activeView === null);
59 -
60 - context.fillStyle = isActive
61 - ? COLORS.REACT_RESIZE_BAR_ACTIVE
62 - : COLORS.REACT_RESIZE_BAR;
63 - context.fillRect(x, y, width, height);
64 -
65 - context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
66 - context.fillRect(x, y, width, BORDER_SIZE);
67 - context.fillRect(x, y + height - BORDER_SIZE, width, BORDER_SIZE);
68 -
69 - const horizontalCenter = x + width / 2;
70 - const verticalCenter = y + height / 2;
71 -
72 - if (this.showLabel) {
73 - // When the resize view is collapsed entirely,
74 - // rather than showing a resize bar– this view displays a label.
75 - const labelRect: Rect = {
76 - origin: {
77 - x: 0,
78 - y: y + height - RESIZE_BAR_WITH_LABEL_HEIGHT,
79 - },
80 - size: {
81 - width: frame.size.width,
82 - height: RESIZE_BAR_WITH_LABEL_HEIGHT,
83 - },
84 - };
85 -
86 - drawText(this._label, context, labelRect, frame, {
87 - fillStyle: COLORS.REACT_RESIZE_BAR_DOT,
88 - textAlign: 'center',
89 - });
90 - } else {
91 - // Otherwise draw horizontally centered resize bar dots
92 - context.beginPath();
93 - context.fillStyle = COLORS.REACT_RESIZE_BAR_DOT;
94 - context.arc(
95 - horizontalCenter,
96 - verticalCenter,
97 - RESIZE_BAR_DOT_RADIUS,
98 - 0,
99 - 2 * Math.PI,
100 - );
101 - context.arc(
102 - horizontalCenter + RESIZE_BAR_DOT_SPACING,
103 - verticalCenter,
104 - RESIZE_BAR_DOT_RADIUS,
105 - 0,
106 - 2 * Math.PI,
107 - );
108 - context.arc(
109 - horizontalCenter - RESIZE_BAR_DOT_SPACING,
110 - verticalCenter,
111 - RESIZE_BAR_DOT_RADIUS,
112 - 0,
113 - 2 * Math.PI,
114 - );
115 - context.fill();
116 - }
117 - }
118 -
119 - _setInteractionState(state: ResizeBarState) {
120 - if (this._interactionState === state) {
121 - return;
122 - }
123 - this._interactionState = state;
124 - this.setNeedsDisplay();
125 - }
126 -
127 - _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
128 - const cursorInView = rectContainsPoint(
129 - interaction.payload.location,
130 - this.frame,
131 - );
132 - if (cursorInView) {
133 - this._setInteractionState('dragging');
134 - viewRefs.activeView = this;
135 - }
136 - }
137 -
138 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
139 - const cursorInView = rectContainsPoint(
140 - interaction.payload.location,
141 - this.frame,
142 - );
143 -
144 - if (viewRefs.activeView === this) {
145 - // If we're actively dragging this resize bar,
146 - // show the cursor even if the pointer isn't hovering over this view.
147 - this.currentCursor = 'ns-resize';
148 - } else if (cursorInView) {
149 - if (this.showLabel) {
150 - this.currentCursor = 'pointer';
151 - } else {
152 - this.currentCursor = 'ns-resize';
153 - }
154 - }
155 -
156 - if (cursorInView) {
157 - viewRefs.hoveredView = this;
158 - }
159 -
160 - if (this._interactionState === 'dragging') {
161 - return;
162 - }
163 - this._setInteractionState(cursorInView ? 'hovered' : 'normal');
164 - }
165 -
166 - _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
167 - const cursorInView = rectContainsPoint(
168 - interaction.payload.location,
169 - this.frame,
170 - );
171 - if (this._interactionState === 'dragging') {
172 - this._setInteractionState(cursorInView ? 'hovered' : 'normal');
173 - }
174 -
175 - if (viewRefs.activeView === this) {
176 - viewRefs.activeView = null;
177 - }
178 - }
179 -
180 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
181 - switch (interaction.type) {
182 - case 'mousedown':
183 - this._handleMouseDown(interaction, viewRefs);
184 - break;
185 - case 'mousemove':
186 - this._handleMouseMove(interaction, viewRefs);
187 - break;
188 - case 'mouseup':
189 - this._handleMouseUp(interaction, viewRefs);
190 - break;
191 - }
192 - }
193 -}
packages/react-devtools-timeline/src/view-base/resizable/index.js deleted
-11
@@ -1,11 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export * from './ResizableView';
11 -export * from './ResizeBarView';
packages/react-devtools-timeline/src/view-base/useCanvasInteraction.js deleted
-254
@@ -1,254 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {NormalizedWheelDelta} from './utils/normalizeWheel';
11 -import type {Point} from './geometry';
12 -
13 -import {useEffect, useRef} from 'react';
14 -import {normalizeWheel} from './utils/normalizeWheel';
15 -
16 -export type ClickInteraction = {
17 - type: 'click',
18 - payload: {
19 - event: MouseEvent,
20 - location: Point,
21 - },
22 -};
23 -export type DoubleClickInteraction = {
24 - type: 'double-click',
25 - payload: {
26 - event: MouseEvent,
27 - location: Point,
28 - },
29 -};
30 -export type MouseDownInteraction = {
31 - type: 'mousedown',
32 - payload: {
33 - event: MouseEvent,
34 - location: Point,
35 - },
36 -};
37 -export type MouseMoveInteraction = {
38 - type: 'mousemove',
39 - payload: {
40 - event: MouseEvent,
41 - location: Point,
42 - },
43 -};
44 -export type MouseUpInteraction = {
45 - type: 'mouseup',
46 - payload: {
47 - event: MouseEvent,
48 - location: Point,
49 - },
50 -};
51 -export type WheelPlainInteraction = {
52 - type: 'wheel-plain',
53 - payload: {
54 - event: WheelEvent,
55 - location: Point,
56 - delta: NormalizedWheelDelta,
57 - },
58 -};
59 -export type WheelWithShiftInteraction = {
60 - type: 'wheel-shift',
61 - payload: {
62 - event: WheelEvent,
63 - location: Point,
64 - delta: NormalizedWheelDelta,
65 - },
66 -};
67 -export type WheelWithControlInteraction = {
68 - type: 'wheel-control',
69 - payload: {
70 - event: WheelEvent,
71 - location: Point,
72 - delta: NormalizedWheelDelta,
73 - },
74 -};
75 -export type WheelWithMetaInteraction = {
76 - type: 'wheel-meta',
77 - payload: {
78 - event: WheelEvent,
79 - location: Point,
80 - delta: NormalizedWheelDelta,
81 - },
82 -};
83 -
84 -export type Interaction =
85 - | ClickInteraction
86 - | DoubleClickInteraction
87 - | MouseDownInteraction
88 - | MouseMoveInteraction
89 - | MouseUpInteraction
90 - | WheelPlainInteraction
91 - | WheelWithShiftInteraction
92 - | WheelWithControlInteraction
93 - | WheelWithMetaInteraction;
94 -
95 -let canvasBoundingRectCache = null;
96 -function cacheFirstGetCanvasBoundingRect(
97 - canvas: HTMLCanvasElement,
98 -): ClientRect {
99 - if (
100 - canvasBoundingRectCache &&
101 - canvas.width === canvasBoundingRectCache.width &&
102 - canvas.height === canvasBoundingRectCache.height
103 - ) {
104 - return canvasBoundingRectCache.rect;
105 - }
106 - canvasBoundingRectCache = {
107 - width: canvas.width,
108 - height: canvas.height,
109 - rect: canvas.getBoundingClientRect(),
110 - };
111 - return canvasBoundingRectCache.rect;
112 -}
113 -
114 -export function useCanvasInteraction(
115 - canvasRef: {current: HTMLCanvasElement | null},
116 - interactor: (interaction: Interaction) => void,
117 -) {
118 - const isMouseDownRef = useRef<boolean>(false);
119 - const didMouseMoveWhileDownRef = useRef<boolean>(false);
120 -
121 - useEffect(() => {
122 - const canvas = canvasRef.current;
123 - if (!canvas) {
124 - return;
125 - }
126 -
127 - function localToCanvasCoordinates(localCoordinates: Point): Point {
128 - // $FlowFixMe[incompatible-type] found when upgrading Flow
129 - const canvasRect = cacheFirstGetCanvasBoundingRect(canvas);
130 - return {
131 - x: localCoordinates.x - canvasRect.left,
132 - y: localCoordinates.y - canvasRect.top,
133 - };
134 - }
135 -
136 - const onCanvasClick: MouseEventHandler = event => {
137 - if (didMouseMoveWhileDownRef.current) {
138 - return;
139 - }
140 -
141 - interactor({
142 - type: 'click',
143 - payload: {
144 - event,
145 - location: localToCanvasCoordinates({x: event.x, y: event.y}),
146 - },
147 - });
148 - };
149 -
150 - const onCanvasDoubleClick: MouseEventHandler = event => {
151 - if (didMouseMoveWhileDownRef.current) {
152 - return;
153 - }
154 -
155 - interactor({
156 - type: 'double-click',
157 - payload: {
158 - event,
159 - location: localToCanvasCoordinates({x: event.x, y: event.y}),
160 - },
161 - });
162 - };
163 -
164 - const onCanvasMouseDown: MouseEventHandler = event => {
165 - didMouseMoveWhileDownRef.current = false;
166 - isMouseDownRef.current = true;
167 -
168 - interactor({
169 - type: 'mousedown',
170 - payload: {
171 - event,
172 - location: localToCanvasCoordinates({x: event.x, y: event.y}),
173 - },
174 - });
175 - };
176 -
177 - const onDocumentMouseMove: MouseEventHandler = event => {
178 - if (isMouseDownRef.current) {
179 - didMouseMoveWhileDownRef.current = true;
180 - }
181 -
182 - interactor({
183 - type: 'mousemove',
184 - payload: {
185 - event,
186 - location: localToCanvasCoordinates({x: event.x, y: event.y}),
187 - },
188 - });
189 - };
190 -
191 - const onDocumentMouseUp: MouseEventHandler = event => {
192 - isMouseDownRef.current = false;
193 -
194 - interactor({
195 - type: 'mouseup',
196 - payload: {
197 - event,
198 - location: localToCanvasCoordinates({x: event.x, y: event.y}),
199 - },
200 - });
201 - };
202 -
203 - const onCanvasWheel: WheelEventHandler = event => {
204 - event.preventDefault();
205 - event.stopPropagation();
206 -
207 - const location = localToCanvasCoordinates({x: event.x, y: event.y});
208 - const delta = normalizeWheel(event);
209 -
210 - if (event.shiftKey) {
211 - interactor({
212 - type: 'wheel-shift',
213 - payload: {event, location, delta},
214 - });
215 - } else if (event.ctrlKey) {
216 - interactor({
217 - type: 'wheel-control',
218 - payload: {event, location, delta},
219 - });
220 - } else if (event.metaKey) {
221 - interactor({
222 - type: 'wheel-meta',
223 - payload: {event, location, delta},
224 - });
225 - } else {
226 - interactor({
227 - type: 'wheel-plain',
228 - payload: {event, location, delta},
229 - });
230 - }
231 -
232 - return false;
233 - };
234 -
235 - const ownerDocument = canvas.ownerDocument;
236 - ownerDocument.addEventListener('mousemove', onDocumentMouseMove);
237 - ownerDocument.addEventListener('mouseup', onDocumentMouseUp);
238 -
239 - canvas.addEventListener('click', onCanvasClick);
240 - canvas.addEventListener('dblclick', onCanvasDoubleClick);
241 - canvas.addEventListener('mousedown', onCanvasMouseDown);
242 - canvas.addEventListener('wheel', onCanvasWheel);
243 -
244 - return () => {
245 - ownerDocument.removeEventListener('mousemove', onDocumentMouseMove);
246 - ownerDocument.removeEventListener('mouseup', onDocumentMouseUp);
247 -
248 - canvas.removeEventListener('click', onCanvasClick);
249 - canvas.removeEventListener('dblclick', onCanvasDoubleClick);
250 - canvas.removeEventListener('mousedown', onCanvasMouseDown);
251 - canvas.removeEventListener('wheel', onCanvasWheel);
252 - };
253 - }, [canvasRef, interactor]);
254 -}
packages/react-devtools-timeline/src/view-base/utils/__tests__/clamp-test.js deleted
-29
@@ -1,29 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {clamp} from '../clamp';
11 -
12 -describe('clamp', () => {
13 - it('should return min if value < min', () => {
14 - expect(clamp(0, 1, -1)).toBe(0);
15 - expect(clamp(0.1, 1.1, 0.05)).toBe(0.1);
16 - });
17 -
18 - it('should return value if min <= value <= max', () => {
19 - expect(clamp(0, 1, 0)).toBe(0);
20 - expect(clamp(0, 1, 0.5)).toBe(0.5);
21 - expect(clamp(0, 1, 1)).toBe(1);
22 - expect(clamp(0.1, 1.1, 0.15)).toBe(0.15);
23 - });
24 -
25 - it('should return max if max < value', () => {
26 - expect(clamp(0, 1, 2)).toBe(1);
27 - expect(clamp(0.1, 1.1, 1.15)).toBe(1.1);
28 - });
29 -});
packages/react-devtools-timeline/src/view-base/utils/__tests__/scrollState-test.js deleted
-261
@@ -1,261 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 - clampState,
12 - moveStateToRange,
13 - areScrollStatesEqual,
14 - translateState,
15 - zoomState,
16 -} from '../scrollState';
17 -
18 -describe('clampState', () => {
19 - it('should passthrough offset if state fits within container', () => {
20 - expect(
21 - clampState({
22 - state: {offset: 0, length: 50},
23 - minContentLength: 0,
24 - maxContentLength: 100,
25 - containerLength: 50,
26 - }).offset,
27 - ).toBeCloseTo(0, 10);
28 - expect(
29 - clampState({
30 - state: {offset: -20, length: 100},
31 - minContentLength: 0,
32 - maxContentLength: 100,
33 - containerLength: 50,
34 - }).offset,
35 - ).toBeCloseTo(-20, 10);
36 - });
37 -
38 - it('should clamp offset if offset causes content to go out of container', () => {
39 - expect(
40 - clampState({
41 - state: {offset: -1, length: 50},
42 - minContentLength: 0,
43 - maxContentLength: 100,
44 - containerLength: 50,
45 - }).offset,
46 - ).toBeCloseTo(0, 10);
47 - expect(
48 - clampState({
49 - state: {offset: 1, length: 50},
50 - minContentLength: 0,
51 - maxContentLength: 100,
52 - containerLength: 50,
53 - }).offset,
54 - ).toBeCloseTo(0, 10);
55 -
56 - expect(
57 - clampState({
58 - state: {offset: -51, length: 100},
59 - minContentLength: 0,
60 - maxContentLength: 100,
61 - containerLength: 50,
62 - }).offset,
63 - ).toBeCloseTo(-50, 10);
64 - expect(
65 - clampState({
66 - state: {offset: 1, length: 100},
67 - minContentLength: 0,
68 - maxContentLength: 100,
69 - containerLength: 50,
70 - }).offset,
71 - ).toBeCloseTo(0, 10);
72 - });
73 -
74 - it('should passthrough length if container fits in content', () => {
75 - expect(
76 - clampState({
77 - state: {offset: 0, length: 70},
78 - minContentLength: 0,
79 - maxContentLength: 100,
80 - containerLength: 50,
81 - }).length,
82 - ).toBeCloseTo(70, 10);
83 - expect(
84 - clampState({
85 - state: {offset: 0, length: 50},
86 - minContentLength: 0,
87 - maxContentLength: 100,
88 - containerLength: 50,
89 - }).length,
90 - ).toBeCloseTo(50, 10);
91 - expect(
92 - clampState({
93 - state: {offset: 0, length: 100},
94 - minContentLength: 0,
95 - maxContentLength: 100,
96 - containerLength: 50,
97 - }).length,
98 - ).toBeCloseTo(100, 10);
99 - });
100 -
101 - it('should clamp length to minimum of max(minContentLength, containerLength)', () => {
102 - expect(
103 - clampState({
104 - state: {offset: -20, length: 0},
105 - minContentLength: 20,
106 - maxContentLength: 100,
107 - containerLength: 50,
108 - }).length,
109 - ).toBeCloseTo(50, 10);
110 - expect(
111 - clampState({
112 - state: {offset: -20, length: 0},
113 - minContentLength: 50,
114 - maxContentLength: 100,
115 - containerLength: 20,
116 - }).length,
117 - ).toBeCloseTo(50, 10);
118 - });
119 -
120 - it('should clamp length to maximum of max(containerLength, maxContentLength)', () => {
121 - expect(
122 - clampState({
123 - state: {offset: -20, length: 100},
124 - minContentLength: 0,
125 - maxContentLength: 40,
126 - containerLength: 50,
127 - }).length,
128 - ).toBeCloseTo(50, 10);
129 - expect(
130 - clampState({
131 - state: {offset: -20, length: 100},
132 - minContentLength: 0,
133 - maxContentLength: 50,
134 - containerLength: 40,
135 - }).length,
136 - ).toBeCloseTo(50, 10);
137 - });
138 -});
139 -
140 -describe('translateState', () => {
141 - it('should translate state by delta and leave length unchanged', () => {
142 - expect(
143 - translateState({
144 - state: {offset: 0, length: 100},
145 - delta: -3.14,
146 - containerLength: 50,
147 - }),
148 - ).toEqual({offset: -3.14, length: 100});
149 - });
150 -
151 - it('should clamp resulting offset', () => {
152 - expect(
153 - translateState({
154 - state: {offset: 0, length: 50},
155 - delta: -3.14,
156 - containerLength: 50,
157 - }).offset,
158 - ).toBeCloseTo(0, 10);
159 - expect(
160 - translateState({
161 - state: {offset: 0, length: 53},
162 - delta: -100,
163 - containerLength: 50,
164 - }).offset,
165 - ).toBeCloseTo(-3, 10);
166 - });
167 -});
168 -
169 -describe('zoomState', () => {
170 - it('should scale width by multiplier', () => {
171 - expect(
172 - zoomState({
173 - state: {offset: 0, length: 100},
174 - multiplier: 1.5,
175 - fixedPoint: 0,
176 -
177 - minContentLength: 0,
178 - maxContentLength: 1000,
179 - containerLength: 50,
180 - }),
181 - ).toEqual({offset: 0, length: 150});
182 - });
183 -
184 - it('should clamp zoomed state', () => {
185 - const zoomedState = zoomState({
186 - state: {offset: -20, length: 100},
187 - multiplier: 0.1,
188 - fixedPoint: 5,
189 -
190 - minContentLength: 50,
191 - maxContentLength: 100,
192 - containerLength: 50,
193 - });
194 - expect(zoomedState.offset).toBeCloseTo(0, 10);
195 - expect(zoomedState.length).toBeCloseTo(50, 10);
196 - });
197 -
198 - it('should maintain containerStart<->fixedPoint distance', () => {
199 - const offset = -20;
200 - const fixedPointFromContainer = 10;
201 -
202 - const zoomedState = zoomState({
203 - state: {offset, length: 100},
204 - multiplier: 2,
205 - fixedPoint: fixedPointFromContainer - offset,
206 -
207 - minContentLength: 0,
208 - maxContentLength: 1000,
209 - containerLength: 50,
210 - });
211 -
212 - expect(zoomedState).toMatchInlineSnapshot(`
213 - {
214 - "length": 200,
215 - "offset": -50,
216 - }
217 - `);
218 - });
219 -});
220 -
221 -describe('moveStateToRange', () => {
222 - it('should set [rangeStart, rangeEnd] = container', () => {
223 - const movedState = moveStateToRange({
224 - state: {offset: -20, length: 100},
225 - rangeStart: 50,
226 - rangeEnd: 100,
227 - contentLength: 400,
228 -
229 - minContentLength: 10,
230 - maxContentLength: 1000,
231 - containerLength: 50,
232 - });
233 -
234 - expect(movedState).toMatchInlineSnapshot(`
235 - {
236 - "length": 400,
237 - "offset": -50,
238 - }
239 - `);
240 - });
241 -});
242 -
243 -describe('areScrollStatesEqual', () => {
244 - it('should return true if equal', () => {
245 - expect(
246 - areScrollStatesEqual({offset: 0, length: 0}, {offset: 0, length: 0}),
247 - ).toBe(true);
248 - expect(
249 - areScrollStatesEqual({offset: -1, length: 1}, {offset: -1, length: 1}),
250 - ).toBe(true);
251 - });
252 -
253 - it('should return false if not equal', () => {
254 - expect(
255 - areScrollStatesEqual({offset: 0, length: 0}, {offset: -1, length: 0}),
256 - ).toBe(false);
257 - expect(
258 - areScrollStatesEqual({offset: -1, length: 1}, {offset: -1, length: 0}),
259 - ).toBe(false);
260 - });
261 -});
packages/react-devtools-timeline/src/view-base/utils/clamp.js deleted
-17
@@ -1,17 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export function clamp(min: number, max: number, value: number): number {
11 - if (Number.isNaN(min) || Number.isNaN(max) || Number.isNaN(value)) {
12 - throw new Error(
13 - `Clamp was called with NaN. Args: min: ${min}, max: ${max}, value: ${value}.`,
14 - );
15 - }
16 - return Math.min(max, Math.max(min, value));
17 -}
packages/react-devtools-timeline/src/view-base/utils/normalizeWheel.js deleted
-85
@@ -1,85 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -// Adapted from: https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
11 -
12 -export type NormalizedWheelDelta = {
13 - deltaX: number,
14 - deltaY: number,
15 -};
16 -
17 -// Reasonable defaults
18 -const LINE_HEIGHT = 40;
19 -const PAGE_HEIGHT = 800;
20 -
21 -/**
22 - * Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
23 - * complicated, thus this doc is long and (hopefully) detailed enough to answer
24 - * your questions.
25 - *
26 - * If you need to react to the mouse wheel in a predictable way, this code is
27 - * like your bestest friend. * hugs *
28 - *
29 - * In your event callback, use this code to get sane interpretation of the
30 - * deltas. This code will return an object with properties:
31 - *
32 - * - deltaX -- normalized distance (to pixels) - x plane
33 - * - deltaY -- " - y plane
34 - *
35 - * Wheel values are provided by the browser assuming you are using the wheel to
36 - * scroll a web page by a number of lines or pixels (or pages). Values can vary
37 - * significantly on different platforms and browsers, forgetting that you can
38 - * scroll at different speeds. Some devices (like trackpads) emit more events
39 - * at smaller increments with fine granularity, and some emit massive jumps with
40 - * linear speed or acceleration.
41 - *
42 - * This code does its best to normalize the deltas for you:
43 - *
44 - * - delta* is normalizing the desired scroll delta in pixel units.
45 - *
46 - * - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT. This
47 - * should translate to positive value zooming IN, negative zooming OUT.
48 - * This matches the 'wheel' event.
49 - *
50 - * Implementation info:
51 - *
52 - * The basics of the standard 'wheel' event is that it includes a unit,
53 - * deltaMode (pixels, lines, pages), and deltaX, deltaY and deltaZ.
54 - * See: http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
55 - *
56 - * Examples of 'wheel' event if you scroll slowly (down) by one step with an
57 - * average mouse:
58 - *
59 - * OS X + Chrome (mouse) - 4 pixel delta (wheelDelta -120)
60 - * OS X + Safari (mouse) - N/A pixel delta (wheelDelta -12)
61 - * OS X + Firefox (mouse) - 0.1 line delta (wheelDelta N/A)
62 - * Win8 + Chrome (mouse) - 100 pixel delta (wheelDelta -120)
63 - * Win8 + Firefox (mouse) - 3 line delta (wheelDelta -120)
64 - *
65 - * On the trackpad:
66 - *
67 - * OS X + Chrome (trackpad) - 2 pixel delta (wheelDelta -6)
68 - * OS X + Firefox (trackpad) - 1 pixel delta (wheelDelta N/A)
69 - */
70 -export function normalizeWheel(event: WheelEvent): NormalizedWheelDelta {
71 - let deltaX = event.deltaX;
72 - let deltaY = event.deltaY;
73 -
74 - if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
75 - // delta in LINE units
76 - deltaX *= LINE_HEIGHT;
77 - deltaY *= LINE_HEIGHT;
78 - } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
79 - // delta in PAGE units
80 - deltaX *= PAGE_HEIGHT;
81 - deltaY *= PAGE_HEIGHT;
82 - }
83 -
84 - return {deltaX, deltaY};
85 -}
packages/react-devtools-timeline/src/view-base/utils/scrollState.js deleted
-206
@@ -1,206 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {clamp} from './clamp';
11 -
12 -/**
13 - * Single-axis offset and length state.
14 - *
15 - * ```
16 - * contentStart containerStart containerEnd contentEnd
17 - * |<----------offset| | |
18 - * |<-------------------length------------------->|
19 - * ```
20 - */
21 -export type ScrollState = {
22 - offset: number,
23 - length: number,
24 -};
25 -
26 -function clampOffset(state: ScrollState, containerLength: number): ScrollState {
27 - return {
28 - offset: clamp(-(state.length - containerLength), 0, state.offset),
29 - length: state.length,
30 - };
31 -}
32 -
33 -function clampLength({
34 - state,
35 - minContentLength,
36 - maxContentLength,
37 - containerLength,
38 -}: {
39 - state: ScrollState,
40 - minContentLength: number,
41 - maxContentLength: number,
42 - containerLength: number,
43 -}): ScrollState {
44 - return {
45 - offset: state.offset,
46 - length: clamp(
47 - Math.max(minContentLength, containerLength),
48 - Math.max(containerLength, maxContentLength),
49 - state.length,
50 - ),
51 - };
52 -}
53 -
54 -/**
55 - * Returns `state` clamped such that:
56 - * - `length`: you won't be able to zoom in/out such that the content is
57 - * shorter than the `containerLength`.
58 - * - `offset`: content remains in `containerLength`.
59 - */
60 -export function clampState({
61 - state,
62 - minContentLength,
63 - maxContentLength,
64 - containerLength,
65 -}: {
66 - state: ScrollState,
67 - minContentLength: number,
68 - maxContentLength: number,
69 - containerLength: number,
70 -}): ScrollState {
71 - return clampOffset(
72 - clampLength({
73 - state,
74 - minContentLength,
75 - maxContentLength,
76 - containerLength,
77 - }),
78 - containerLength,
79 - );
80 -}
81 -
82 -export function translateState({
83 - state,
84 - delta,
85 - containerLength,
86 -}: {
87 - state: ScrollState,
88 - delta: number,
89 - containerLength: number,
90 -}): ScrollState {
91 - return clampOffset(
92 - {
93 - offset: state.offset + delta,
94 - length: state.length,
95 - },
96 - containerLength,
97 - );
98 -}
99 -
100 -/**
101 - * Returns a new clamped `state` zoomed by `multiplier`.
102 - *
103 - * The provided fixed point will also remain stationary relative to
104 - * `containerStart`.
105 - *
106 - * ```
107 - * contentStart containerStart fixedPoint containerEnd
108 - * |<---------offset-| x |
109 - * |-fixedPoint-------------------------------->x |
110 - * |-fixedPointFromContainer->x |
111 - * |<----------containerLength----------->|
112 - * ```
113 - */
114 -export function zoomState({
115 - state,
116 - multiplier,
117 - fixedPoint,
118 -
119 - minContentLength,
120 - maxContentLength,
121 - containerLength,
122 -}: {
123 - state: ScrollState,
124 - multiplier: number,
125 - fixedPoint: number,
126 -
127 - minContentLength: number,
128 - maxContentLength: number,
129 - containerLength: number,
130 -}): ScrollState {
131 - // Length and offset must be computed separately, so that if the length is
132 - // clamped the offset will still be correct (unless it gets clamped too).
133 -
134 - const zoomedState = clampLength({
135 - state: {
136 - offset: state.offset,
137 - length: state.length * multiplier,
138 - },
139 - minContentLength,
140 - maxContentLength,
141 - containerLength,
142 - });
143 -
144 - // Adjust offset so that distance between containerStart<->fixedPoint is fixed
145 - const fixedPointFromContainer = fixedPoint + state.offset;
146 - const scaledFixedPoint = fixedPoint * (zoomedState.length / state.length);
147 - const offsetAdjustedState = clampOffset(
148 - {
149 - offset: fixedPointFromContainer - scaledFixedPoint,
150 - length: zoomedState.length,
151 - },
152 - containerLength,
153 - );
154 -
155 - return offsetAdjustedState;
156 -}
157 -
158 -export function moveStateToRange({
159 - state,
160 - rangeStart,
161 - rangeEnd,
162 - contentLength,
163 -
164 - minContentLength,
165 - maxContentLength,
166 - containerLength,
167 -}: {
168 - state: ScrollState,
169 - rangeStart: number,
170 - rangeEnd: number,
171 - contentLength: number,
172 -
173 - minContentLength: number,
174 - maxContentLength: number,
175 - containerLength: number,
176 -}): ScrollState {
177 - // Length and offset must be computed separately, so that if the length is
178 - // clamped the offset will still be correct (unless it gets clamped too).
179 -
180 - const lengthClampedState = clampLength({
181 - state: {
182 - offset: state.offset,
183 - length: contentLength * (containerLength / (rangeEnd - rangeStart)),
184 - },
185 - minContentLength,
186 - maxContentLength,
187 - containerLength,
188 - });
189 -
190 - const offsetAdjustedState = clampOffset(
191 - {
192 - offset: -rangeStart * (lengthClampedState.length / contentLength),
193 - length: lengthClampedState.length,
194 - },
195 - containerLength,
196 - );
197 -
198 - return offsetAdjustedState;
199 -}
200 -
201 -export function areScrollStatesEqual(
202 - state1: ScrollState,
203 - state2: ScrollState,
204 -): boolean {
205 - return state1.offset === state2.offset && state1.length === state2.length;
206 -}
packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollBarView.js deleted
-219
@@ -1,219 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect} from '../geometry';
11 -import type {Surface, ViewRefs} from '../Surface';
12 -import type {
13 - Interaction,
14 - ClickInteraction,
15 - MouseDownInteraction,
16 - MouseMoveInteraction,
17 - MouseUpInteraction,
18 -} from '../useCanvasInteraction';
19 -
20 -import {VerticalScrollOverflowView} from './VerticalScrollOverflowView';
21 -import {rectContainsPoint, rectEqualToRect} from '../geometry';
22 -import {View} from '../View';
23 -import {BORDER_SIZE, COLORS} from '../../content-views/constants';
24 -
25 -const SCROLL_BAR_SIZE = 14;
26 -
27 -const HIDDEN_RECT = {
28 - origin: {
29 - x: 0,
30 - y: 0,
31 - },
32 - size: {
33 - width: 0,
34 - height: 0,
35 - },
36 -};
37 -
38 -export class VerticalScrollBarView extends View {
39 - _contentHeight: number = 0;
40 - _isScrolling: boolean = false;
41 - _scrollBarRect: Rect = HIDDEN_RECT;
42 - _scrollThumbRect: Rect = HIDDEN_RECT;
43 - _verticalScrollOverflowView: VerticalScrollOverflowView;
44 -
45 - constructor(
46 - surface: Surface,
47 - frame: Rect,
48 - verticalScrollOverflowView: VerticalScrollOverflowView,
49 - ) {
50 - super(surface, frame);
51 -
52 - this._verticalScrollOverflowView = verticalScrollOverflowView;
53 - }
54 -
55 - desiredSize(): {+height: number, +width: number} {
56 - return {
57 - width: SCROLL_BAR_SIZE,
58 - height: 0, // No desired height
59 - };
60 - }
61 -
62 - getMaxScrollThumbY(): number {
63 - const {height} = this.frame.size;
64 -
65 - const maxScrollThumbY = height - this._scrollThumbRect.size.height;
66 -
67 - return maxScrollThumbY;
68 - }
69 -
70 - setContentHeight(contentHeight: number) {
71 - this._contentHeight = contentHeight;
72 -
73 - const {height, width} = this.frame.size;
74 -
75 - const proposedScrollThumbRect = {
76 - origin: {
77 - x: this.frame.origin.x,
78 - y: this._scrollThumbRect.origin.y,
79 - },
80 - size: {
81 - width,
82 - height: height * (height / contentHeight),
83 - },
84 - };
85 -
86 - if (!rectEqualToRect(this._scrollThumbRect, proposedScrollThumbRect)) {
87 - this._scrollThumbRect = proposedScrollThumbRect;
88 - this.setNeedsDisplay();
89 - }
90 - }
91 -
92 - setScrollThumbY(value: number) {
93 - const {height} = this.frame.size;
94 -
95 - const maxScrollThumbY = this.getMaxScrollThumbY();
96 - const newScrollThumbY = Math.max(0, Math.min(maxScrollThumbY, value));
97 -
98 - this._scrollThumbRect = {
99 - ...this._scrollThumbRect,
100 - origin: {
101 - x: this.frame.origin.x,
102 - y: newScrollThumbY,
103 - },
104 - };
105 -
106 - const maxContentOffset = this._contentHeight - height;
107 - const contentScrollOffset =
108 - (newScrollThumbY / maxScrollThumbY) * maxContentOffset * -1;
109 -
110 - this._verticalScrollOverflowView.setScrollOffset(
111 - contentScrollOffset,
112 - maxScrollThumbY,
113 - );
114 - }
115 -
116 - draw(context: CanvasRenderingContext2D, viewRefs: ViewRefs) {
117 - const {x, y} = this.frame.origin;
118 - const {width, height} = this.frame.size;
119 -
120 - // TODO Use real color
121 - context.fillStyle = COLORS.REACT_RESIZE_BAR;
122 - context.fillRect(x, y, width, height);
123 -
124 - // TODO Use real color
125 - context.fillStyle = COLORS.SCROLL_CARET;
126 - context.fillRect(
127 - this._scrollThumbRect.origin.x,
128 - this._scrollThumbRect.origin.y,
129 - this._scrollThumbRect.size.width,
130 - this._scrollThumbRect.size.height,
131 - );
132 -
133 - // TODO Use real color
134 - context.fillStyle = COLORS.REACT_RESIZE_BAR_BORDER;
135 - context.fillRect(x, y, BORDER_SIZE, height);
136 - }
137 -
138 - handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
139 - switch (interaction.type) {
140 - case 'click':
141 - this._handleClick(interaction, viewRefs);
142 - break;
143 - case 'mousedown':
144 - this._handleMouseDown(interaction, viewRefs);
145 - break;
146 - case 'mousemove':
147 - this._handleMouseMove(interaction, viewRefs);
148 - break;
149 - case 'mouseup':
150 - this._handleMouseUp(interaction, viewRefs);
151 - break;
152 - }
153 - }
154 -
155 - _handleClick(interaction: ClickInteraction, viewRefs: ViewRefs) {
156 - const {location} = interaction.payload;
157 - if (rectContainsPoint(location, this.frame)) {
158 - if (rectContainsPoint(location, this._scrollThumbRect)) {
159 - // Ignore clicks on the track thumb directly.
160 - return;
161 - }
162 -
163 - const currentScrollThumbY = this._scrollThumbRect.origin.y;
164 - const y = location.y;
165 -
166 - const {height} = this.frame.size;
167 -
168 - // Scroll up or down about one viewport worth of content:
169 - const deltaY = (height / this._contentHeight) * height * 0.8;
170 -
171 - this.setScrollThumbY(
172 - y > currentScrollThumbY
173 - ? this._scrollThumbRect.origin.y + deltaY
174 - : this._scrollThumbRect.origin.y - deltaY,
175 - );
176 - }
177 - }
178 -
179 - _handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
180 - const {location} = interaction.payload;
181 - if (!rectContainsPoint(location, this._scrollThumbRect)) {
182 - return;
183 - }
184 - viewRefs.activeView = this;
185 -
186 - this.currentCursor = 'default';
187 -
188 - this._isScrolling = true;
189 - this.setNeedsDisplay();
190 - }
191 -
192 - _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
193 - const {event, location} = interaction.payload;
194 - if (rectContainsPoint(location, this.frame)) {
195 - if (viewRefs.hoveredView !== this) {
196 - viewRefs.hoveredView = this;
197 - }
198 -
199 - this.currentCursor = 'default';
200 - }
201 -
202 - if (viewRefs.activeView === this) {
203 - this.currentCursor = 'default';
204 -
205 - this.setScrollThumbY(this._scrollThumbRect.origin.y + event.movementY);
206 - }
207 - }
208 -
209 - _handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
210 - if (viewRefs.activeView === this) {
211 - viewRefs.activeView = null;
212 - }
213 -
214 - if (this._isScrolling) {
215 - this._isScrolling = false;
216 - this.setNeedsDisplay();
217 - }
218 - }
219 -}
packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/VerticalScrollOverflowView.js deleted
-91
@@ -1,91 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {Rect} from '../geometry';
11 -import type {ScrollState} from '../utils/scrollState';
12 -import type {Surface} from '../Surface';
13 -import type {ViewState} from '../../types';
14 -
15 -import {VerticalScrollBarView} from './VerticalScrollBarView';
16 -import {withVerticalScrollbarLayout} from './withVerticalScrollbarLayout';
17 -import {View} from '../View';
18 -import {VerticalScrollView} from '../VerticalScrollView';
19 -
20 -export class VerticalScrollOverflowView extends View {
21 - _contentView: View;
22 - _isProcessingOnChange: boolean = false;
23 - _isScrolling: boolean = false;
24 - _scrollOffset: number = 0;
25 - _scrollBarView: VerticalScrollBarView;
26 - _verticalScrollView: VerticalScrollView;
27 -
28 - constructor(
29 - surface: Surface,
30 - frame: Rect,
31 - contentView: View,
32 - viewState: ViewState,
33 - ) {
34 - super(surface, frame, withVerticalScrollbarLayout);
35 -
36 - this._contentView = contentView;
37 - this._verticalScrollView = new VerticalScrollView(
38 - surface,
39 - frame,
40 - contentView,
41 - viewState,
42 - 'VerticalScrollOverflowView',
43 - );
44 - this._verticalScrollView.onChange(this._onVerticalScrollViewChange);
45 -
46 - this._scrollBarView = new VerticalScrollBarView(surface, frame, this);
47 -
48 - this.addSubview(this._verticalScrollView);
49 - this.addSubview(this._scrollBarView);
50 - }
51 -
52 - layoutSubviews() {
53 - super.layoutSubviews();
54 -
55 - const contentSize = this._contentView.desiredSize();
56 -
57 - // This should be done after calling super.layoutSubviews() – calling it
58 - // before somehow causes _contentView to need display on every mousemove
59 - // event when the scroll bar is shown.
60 - this._scrollBarView.setContentHeight(contentSize.height);
61 - }
62 -
63 - setScrollOffset(newScrollOffset: number, maxScrollOffset: number) {
64 - const deltaY = newScrollOffset - this._scrollOffset;
65 -
66 - if (!this._isProcessingOnChange) {
67 - this._verticalScrollView.scrollBy(-deltaY);
68 - }
69 -
70 - this._scrollOffset = newScrollOffset;
71 -
72 - this.setNeedsDisplay();
73 - }
74 -
75 - _onVerticalScrollViewChange: (
76 - scrollState: ScrollState,
77 - containerLength: number,
78 - ) => void = (scrollState: ScrollState, containerLength: number) => {
79 - const maxOffset = scrollState.length - containerLength;
80 - if (maxOffset === 0) {
81 - return;
82 - }
83 -
84 - const percentage = Math.abs(scrollState.offset) / maxOffset;
85 - const maxScrollThumbY = this._scrollBarView.getMaxScrollThumbY();
86 -
87 - this._isProcessingOnChange = true;
88 - this._scrollBarView.setScrollThumbY(percentage * maxScrollThumbY);
89 - this._isProcessingOnChange = false;
90 - };
91 -}
packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/index.js deleted
-11
@@ -1,11 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -export * from './VerticalScrollBarView';
11 -export * from './VerticalScrollOverflowView';
packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js deleted
-55
@@ -1,55 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 {LayoutInfo, Layouter} from '../layouter';
11 -
12 -/**
13 - * Assumes {@param layout} will only contain 2 views.
14 - */
15 -export const withVerticalScrollbarLayout: Layouter = (
16 - layout,
17 - containerFrame,
18 -) => {
19 - const [contentLayoutInfo, scrollbarLayoutInfo] = layout;
20 -
21 - const desiredContentSize = contentLayoutInfo.view.desiredSize();
22 - const shouldShowScrollbar =
23 - desiredContentSize.height > containerFrame.size.height;
24 - const scrollbarWidth = shouldShowScrollbar
25 - ? scrollbarLayoutInfo.view.desiredSize().width
26 - : 0;
27 -
28 - const laidOutContentLayoutInfo: LayoutInfo = {
29 - ...contentLayoutInfo,
30 - frame: {
31 - origin: contentLayoutInfo.view.frame.origin,
32 - size: {
33 - width: containerFrame.size.width - scrollbarWidth,
34 - height: containerFrame.size.height,
35 - },
36 - },
37 - };
38 - const laidOutScrollbarLayoutInfo: LayoutInfo = {
39 - ...scrollbarLayoutInfo,
40 - frame: {
41 - origin: {
42 - x:
43 - laidOutContentLayoutInfo.frame.origin.x +
44 - laidOutContentLayoutInfo.frame.size.width,
45 - y: containerFrame.origin.y,
46 - },
47 - size: {
48 - width: scrollbarWidth,
49 - height: containerFrame.size.height,
50 - },
51 - },
52 - };
53 -
54 - return [laidOutContentLayoutInfo, laidOutScrollbarLayoutInfo];
55 -};
packages/react-devtools/OVERVIEW.md
+2 -8
@@ -293,13 +293,13 @@ To mitigate the performance impact of re-rendering a component, DevTools does th
293
294 ## Profiler
295
296 -DevTools provides a suite of profiling tools for identifying and fixing performance problems. React 16.9+ supports a "legacy" profiler and React 18+ adds the ["timeline" profiler](https://github.com/facebook/react/tree/main/packages/react-devtools-timeline/src) support. These profilers are explained below, but at a high level– the architecture of each profiler aims to minimize the impact (CPU usage) while profiling is active. This can be accomplished by:
296 +DevTools provides profiling tools for identifying and fixing performance problems in React 16.9+. The profiler is explained below, but at a high level– its architecture aims to minimize the impact (CPU usage) while profiling is active. This can be accomplished by:
297 * Minimizing bridge traffic.
298 * Making expensive computations lazy.
299
300 The majority of profiling information is stored in the DevTools backend. The backend push-notifies the frontend of when profiling starts or stops by sending a "_profilingStatus_" message. The frontend also asks for the current status after mounting by sending a "_getProfilingStatus_" message. (This is done to support the reload-and-profile functionality.)
301
302 -### Legacy profiler
302 +### Commit profiler
303
304 When profiling begins, the frontend takes a snapshot/copy of each root. This snapshot includes the id, name, key, and child IDs for each node in the tree. (This information is already present on the frontend, so it does not require any additional bridge traffic.) While profiling is active, each time React commits– the frontend also stores a copy of the "_operations_" message (described above). Once profiling has finished, the frontend can use the original snapshot along with each of the stored "_operations_" messages to reconstruct the tree for each of the profiled commits.
305
@@ -310,12 +310,6 @@ When profiling begins, the backend records the base durations of each fiber curr
310
311 This information will eventually be required by the frontend in order to render its profiling graphs, but it will not be sent across the bridge until profiling has been completed (to minimize the performance impact of profiling).
312
313 -### Timeline profiler
314 -
315 -Timeline profiling data can come from one of two places:
316 -* The React DevTools backend, which injects a [set of profiling hooks](https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/profilingHooks.js) that React calls while rendering. When profiling, these hooks store information in memory which gets passed to DevTools when profiling is stopped.
317 -* A Chrome performance export (JSON) containing React data (as User Timing marks) and other browser data like CPU samples, Network traffic, and native commits. (This method is not as convenient but provides more detailed browser performance data.)
318 -
313 ### Combining profiling data
314
315 Once profiling is finished, the frontend requests profiling data from the backend one renderer at a time by sending a "_getProfilingData_" message. The backend responds with a "_profilingData_" message that contains per-root commit timing and duration information. The frontend then combines this information with its own snapshots to form a complete picture of the profiling session. Using this data, charts and graphs are lazily computed (and incrementally cached) on-demand, based on which commits and views are selected in the Profiler UI.
scripts/devtools/configuration.js
-1
@@ -6,7 +6,6 @@ const PACKAGE_PATHS = [
6 'packages/react-devtools/package.json',
7 'packages/react-devtools-core/package.json',
8 'packages/react-devtools-inline/package.json',
9 - 'packages/react-devtools-timeline/package.json',
9 ];
10
11 const MANIFEST_PATHS = [
scripts/jest/config.build-devtools.js
-3
@@ -85,9 +85,6 @@ module.exports = Object.assign({}, baseConfig, {
85 require.resolve(
86 '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
87 ),
88 - require.resolve(
89 - '../../packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js'
90 - ),
88 require.resolve(
89 '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
90 ),
yarn.lock
+15 -403
@@ -2628,14 +2628,6 @@
2628 optionalDependencies:
2629 global-agent "^3.0.0"
2630
2631 -"@elg/speedscope@1.9.0-a6f84db":
2632 - version "1.9.0-a6f84db"
2633 - resolved "https://registry.yarnpkg.com/@elg/speedscope/-/speedscope-1.9.0-a6f84db.tgz#36079096390b9b396dfec5aeac12e971feb46084"
2634 - integrity sha512-AoGBS1H5s8jrqIh51P8tZnO9i02w7jBJsd0W6+nrF2tLce502zb2rOyot4iv4fnvhmjnM6VFPBLjE2DfElg8Dw==
2635 - dependencies:
2636 - opn "5.3.0"
2637 - react "^16.13.1"
2638 -
2631 "@esbuild/aix-ppc64@0.25.1":
2632 version "0.25.1"
2633 resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz#c33cf6bbee34975626b01b80451cbb72b4c6c91d"
@@ -3446,18 +3438,6 @@
3438 dependencies:
3439 playwright "1.51.1"
3440
3449 -"@pmmmwh/react-refresh-webpack-plugin@^0.4.1":
3450 - version "0.4.1"
3451 - resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.1.tgz#a4db0ed8e58c2f8566161c9a8cdf1d095c9a891b"
3452 - integrity sha512-MzM87WdX2r2KRFfhEho7oGyK1XRE/J9WwjB3v6oLQHN0dzBypBZxSWjnoYx+RWneRCsg8Sin1myf+EjX1fqIbQ==
3453 - dependencies:
3454 - ansi-html "^0.0.7"
3455 - error-stack-parser "^2.0.6"
3456 - html-entities "^1.2.1"
3457 - native-url "^0.2.6"
3458 - schema-utils "^2.6.5"
3459 - source-map "^0.7.3"
3460 -
3441 "@pnpm/config.env-replace@^1.1.0":
3442 version "1.1.0"
3443 resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
@@ -4030,11 +4010,6 @@
4010 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
4011 integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
4012
4033 -"@types/json-schema@^7.0.4":
4034 - version "7.0.5"
4035 - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
4036 - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
4037 -
4013 "@types/json-schema@^7.0.8":
4014 version "7.0.8"
4015 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
@@ -4466,35 +4441,6 @@
4441 resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
4442 integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
4443
4469 -"@vercel/build-utils@2.5.1":
4470 - version "2.5.1"
4471 - resolved "https://registry.yarnpkg.com/@vercel/build-utils/-/build-utils-2.5.1.tgz#2f687c2d82464dd85e0ed8130bc01e5dac9b11a4"
4472 - integrity sha512-689ov8fGrgVnLwPbJUvgUKdSTseZiOMobR4XoZjmSmeF8+gaOpycsdGuVFzNtdFneUiQWO/k5AkY5diZj8fNgg==
4473 -
4474 -"@vercel/go@1.1.6":
4475 - version "1.1.6"
4476 - resolved "https://registry.yarnpkg.com/@vercel/go/-/go-1.1.6.tgz#45ac3a6bd98a15b8bf1028b8c141a51fd971ac15"
4477 - integrity sha512-swA2crS08fkPmw4UkR9yjmoL8FOCzuNHLFDqj8oM1V9ni610ibJ7Xk57jI8uyS7bTecQVh8VUxihb+SF0GT+aw==
4478 -
4479 -"@vercel/node@1.8.1":
4480 - version "1.8.1"
4481 - resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.8.1.tgz#aa0a04f425638874d19be64f7905018ef6f9b1c5"
4482 - integrity sha512-vQsYMrulghMaHEKbqxJCQPfHrGEBmYmQMLYrx+m8kolKDq1LQDWFx2MRh4DyMnGfYSoYoW5PI2HP7NAfbEjzmg==
4483 - dependencies:
4484 - "@types/node" "*"
4485 - ts-node "8.9.1"
4486 - typescript "3.9.3"
4487 -
4488 -"@vercel/python@1.2.3":
4489 - version "1.2.3"
4490 - resolved "https://registry.yarnpkg.com/@vercel/python/-/python-1.2.3.tgz#23ebb71c753fe1cc75fe186c89fc0af04c950191"
4491 - integrity sha512-DJRvL6bmt4m0xrkzSKUbP8mK57YSDdTBWoo0JYyXq/o2golQrv/wQTalbNchd4P8NhVL3mZuk/1JNYuv5u1rKQ==
4492 -
4493 -"@vercel/ruby@1.2.4":
4494 - version "1.2.4"
4495 - resolved "https://registry.yarnpkg.com/@vercel/ruby/-/ruby-1.2.4.tgz#60e0a91d7a1a730a7ecdbc6e095acb95f28f24be"
4496 - integrity sha512-g19vrrmJ4MTJCRB/bvx8DahIsml1iPn7wsdHf5k3QVN6lT0dlDILSBwpERC4hqzndimaApsmWOfjYtY9/L6+tQ==
4497 -
4444 "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
4445 version "1.11.6"
4446 resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
@@ -4811,7 +4757,7 @@ ajv-formats@^2.1.1:
4757 dependencies:
4758 ajv "^8.0.0"
4759
4814 -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
4760 +ajv-keywords@^3.1.0:
4761 version "3.4.1"
4762 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
4763 integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
@@ -4848,7 +4794,7 @@ ajv@^6.1.0:
4794 json-schema-traverse "^0.4.1"
4795 uri-js "^4.2.2"
4796
4851 -ajv@^6.10.0, ajv@^6.12.2:
4797 +ajv@^6.10.0:
4798 version "6.12.4"
4799 resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
4800 integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
@@ -4940,11 +4886,6 @@ ansi-html-community@^0.0.8:
4886 resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
4887 integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
4888
4943 -ansi-html@^0.0.7:
4944 - version "0.0.7"
4945 - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
4946 - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
4947 -
4889 ansi-red@^0.1.1:
4890 version "0.1.1"
4891 resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c"
@@ -5094,11 +5035,6 @@ archiver@~2.1.0:
5035 tar-stream "^1.5.0"
5036 zip-stream "^1.2.0"
5037
5097 -arg@^4.1.0:
5098 - version "4.1.3"
5099 - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
5100 - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
5101 -
5038 argparse@^1.0.7:
5039 version "1.0.10"
5040 resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -5369,17 +5305,6 @@ babel-loader@^8.0.4:
5305 mkdirp "^0.5.1"
5306 pify "^4.0.1"
5307
5372 -babel-loader@^8.1.0:
5373 - version "8.1.0"
5374 - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3"
5375 - integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==
5376 - dependencies:
5377 - find-cache-dir "^2.1.0"
5378 - loader-utils "^1.4.0"
5379 - mkdirp "^0.5.3"
5380 - pify "^4.0.1"
5381 - schema-utils "^2.6.5"
5382 -
5308 babel-plugin-dynamic-import-node@^2.3.3:
5309 version "2.3.3"
5310 resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
@@ -5847,20 +5772,6 @@ boolean@^3.0.0, boolean@^3.0.1:
5772 resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
5773 integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==
5774
5850 -boxen@^4.2.0:
5851 - version "4.2.0"
5852 - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
5853 - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
5854 - dependencies:
5855 - ansi-align "^3.0.0"
5856 - camelcase "^5.3.1"
5857 - chalk "^3.0.0"
5858 - cli-boxes "^2.2.0"
5859 - string-width "^4.1.0"
5860 - term-size "^2.1.0"
5861 - type-fest "^0.8.1"
5862 - widest-line "^3.1.0"
5863 -
5775 boxen@^5.0.0:
5776 version "5.1.2"
5777 resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
@@ -6238,11 +6149,6 @@ camelcase@^5.0.0, camelcase@^5.3.1:
6149 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
6150 integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
6151
6241 -camelcase@^6.0.0:
6242 - version "6.0.0"
6243 - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
6244 - integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
6245 -
6152 camelcase@^6.2.0:
6153 version "6.3.0"
6154 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
@@ -6422,6 +6328,11 @@ chownr@^1.0.1:
6328 resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
6329 integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
6330
6331 +chrome-devtools-mcp@1.3.0:
6332 + version "1.3.0"
6333 + resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.3.0.tgz#7aeb4c8dab5d8dc536ef683b75e7a81b3989ad0e"
6334 + integrity sha512-52NVUwWSL4eW7W9nsDrzYJF96IKVuxEwAn4O7ZfdNRtopS954P9nryJbdYwg7vdqxhLrvioGFlm5e4P41WXsiw==
6335 +
6336 chrome-launch@^1.1.4:
6337 version "1.1.4"
6338 resolved "https://registry.yarnpkg.com/chrome-launch/-/chrome-launch-1.1.4.tgz#c8985bd022635a5cc897099bafd19831838a7252"
@@ -6432,11 +6343,6 @@ chrome-launch@^1.1.4:
6343 rimraf "^2.2.8"
6344 shallow-copy "0.0.1"
6345
6435 -chrome-devtools-mcp@1.3.0:
6436 - version "1.3.0"
6437 - resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.3.0.tgz#7aeb4c8dab5d8dc536ef683b75e7a81b3989ad0e"
6438 - integrity sha512-52NVUwWSL4eW7W9nsDrzYJF96IKVuxEwAn4O7ZfdNRtopS954P9nryJbdYwg7vdqxhLrvioGFlm5e4P41WXsiw==
6439 -
6346 chrome-launcher@0.15.1:
6347 version "0.15.1"
6348 resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.1.tgz#0a0208037063641e2b3613b7e42b0fcb3fa2d399"
@@ -6487,11 +6393,6 @@ class-utils@^0.3.5:
6393 isobject "^3.0.0"
6394 static-extend "^0.1.1"
6395
6490 -cli-boxes@^2.2.0:
6491 - version "2.2.0"
6492 - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
6493 - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
6494 -
6396 cli-boxes@^2.2.1:
6397 version "2.2.1"
6398 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
@@ -7117,25 +7018,6 @@ css-loader@^1.0.1:
7018 postcss-value-parser "^3.3.0"
7019 source-list-map "^2.0.0"
7020
7120 -css-loader@^4.2.1:
7121 - version "4.2.1"
7122 - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.1.tgz#9f48fd7eae1219d629a3f085ba9a9102ca1141a7"
7123 - integrity sha512-MoqmF1if7Z0pZIEXA4ZF9PgtCXxWbfzfJM+3p+OYfhcrwcqhaCRb74DSnfzRl7e024xEiCRn5hCvfUbTf2sgFA==
7124 - dependencies:
7125 - camelcase "^6.0.0"
7126 - cssesc "^3.0.0"
7127 - icss-utils "^4.1.1"
7128 - loader-utils "^2.0.0"
7129 - normalize-path "^3.0.0"
7130 - postcss "^7.0.32"
7131 - postcss-modules-extract-imports "^2.0.0"
7132 - postcss-modules-local-by-default "^3.0.3"
7133 - postcss-modules-scope "^2.2.0"
7134 - postcss-modules-values "^3.0.0"
7135 - postcss-value-parser "^4.1.0"
7136 - schema-utils "^2.7.0"
7137 - semver "^7.3.2"
7138 -
7021 css-loader@^6.9.1:
7022 version "6.10.0"
7023 resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7"
@@ -7600,11 +7482,6 @@ diff@5.1.0:
7482 resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
7483 integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
7484
7603 -diff@^4.0.1:
7604 - version "4.0.2"
7605 - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
7606 - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
7607 -
7485 dir-glob@^3.0.1:
7486 version "3.0.1"
7487 resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -9019,14 +8896,6 @@ file-entry-cache@^8.0.0:
8896 dependencies:
8897 flat-cache "^4.0.0"
8898
9022 -file-loader@^6.0.0:
9023 - version "6.0.0"
9024 - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f"
9025 - integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==
9026 - dependencies:
9027 - loader-utils "^2.0.0"
9028 - schema-utils "^2.6.5"
9029 -
8899 file-loader@^6.1.0:
8900 version "6.2.0"
8901 resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@@ -9134,7 +9003,7 @@ finalhandler@1.3.1:
9003 statuses "2.0.1"
9004 unpipe "~1.0.0"
9005
9137 -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
9006 +find-cache-dir@^2.0.0:
9007 version "2.1.0"
9008 resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
9009 integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@@ -9729,13 +9598,6 @@ global-agent@^3.0.0:
9598 semver "^7.3.2"
9599 serialize-error "^7.0.1"
9600
9732 -global-dirs@^2.0.1:
9733 - version "2.0.1"
9734 - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201"
9735 - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==
9736 - dependencies:
9737 - ini "^1.3.5"
9738 -
9601 global-dirs@^3.0.0:
9602 version "3.0.1"
9603 resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485"
@@ -10192,11 +10054,6 @@ html-encoding-sniffer@^3.0.0:
10054 dependencies:
10055 whatwg-encoding "^2.0.0"
10056
10195 -html-entities@^1.2.1:
10196 - version "1.2.1"
10197 - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
10198 - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
10199 -
10057 html-entities@^2.3.2:
10058 version "2.3.3"
10059 resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
@@ -10393,13 +10250,6 @@ icss-utils@^2.1.0:
10250 dependencies:
10251 postcss "^6.0.1"
10252
10396 -icss-utils@^4.0.0, icss-utils@^4.1.1:
10397 - version "4.1.1"
10398 - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
10399 - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
10400 - dependencies:
10401 - postcss "^7.0.14"
10402 -
10253 icss-utils@^5.0.0, icss-utils@^5.1.0:
10254 version "5.1.0"
10255 resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
@@ -10902,14 +10752,6 @@ is-inside-container@^1.0.0:
10752 dependencies:
10753 is-docker "^3.0.0"
10754
10905 -is-installed-globally@^0.3.1:
10906 - version "0.3.2"
10907 - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
10908 - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
10909 - dependencies:
10910 - global-dirs "^2.0.1"
10911 - is-path-inside "^3.0.1"
10912 -
10755 is-installed-globally@^0.4.0:
10756 version "0.4.0"
10757 resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
@@ -10955,11 +10797,6 @@ is-negative-zero@^2.0.1:
10797 resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
10798 integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
10799
10958 -is-npm@^4.0.0:
10959 - version "4.0.0"
10960 - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
10961 - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
10962 -
10800 is-npm@^5.0.0:
10801 version "5.0.0"
10802 resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
@@ -11002,11 +10839,6 @@ is-object@^1.0.1:
10839 resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
10840 integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
10841
11005 -is-path-inside@^3.0.1:
11006 - version "3.0.2"
11007 - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
11008 - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
11009 -
10842 is-path-inside@^3.0.2, is-path-inside@^3.0.3:
10843 version "3.0.3"
10844 resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
@@ -11182,11 +11014,6 @@ is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
11014 resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
11015 integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
11016
11185 -is-wsl@^1.1.0:
11186 - version "1.1.0"
11187 - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
11188 - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
11189 -
11017 is-wsl@^2.1.1:
11018 version "2.1.1"
11019 resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
@@ -12065,7 +11892,7 @@ kleur@^3.0.3:
11892 resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
11893 integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
11894
12068 -latest-version@^5.0.0, latest-version@^5.1.0:
11895 +latest-version@^5.1.0:
11896 version "5.1.0"
11897 resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
11898 integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
@@ -12186,15 +12013,6 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
12013 emojis-list "^2.0.0"
12014 json5 "^1.0.1"
12015
12189 -loader-utils@^1.4.0:
12190 - version "1.4.0"
12191 - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
12192 - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
12193 - dependencies:
12194 - big.js "^5.2.2"
12195 - emojis-list "^3.0.0"
12196 - json5 "^1.0.1"
12197 -
12016 loader-utils@^2.0.0:
12017 version "2.0.0"
12018 resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
@@ -12485,7 +12303,7 @@ make-dir@^4.0.0:
12303 dependencies:
12304 semver "^7.5.3"
12305
12488 -make-error@^1.1.1, make-error@^1.3.2:
12306 +make-error@^1.3.2:
12307 version "1.3.6"
12308 resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
12309 integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
@@ -12585,11 +12403,6 @@ memfs@^3.4.3:
12403 resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
12404 integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
12405
12588 -memoize-one@^5.1.1:
12589 - version "5.1.1"
12590 - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
12591 - integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
12592 -
12406 meow@^3.3.0:
12407 version "3.7.0"
12408 resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -12666,11 +12479,6 @@ mime-db@1.40.0, "mime-db@>= 1.40.0 < 2":
12479 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
12480 integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
12481
12669 -mime-db@1.44.0:
12670 - version "1.44.0"
12671 - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
12672 - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
12673 -
12482 mime-db@1.52.0:
12483 version "1.52.0"
12484 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
@@ -12688,13 +12496,6 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.34:
12496 dependencies:
12497 mime-db "1.52.0"
12498
12691 -mime-types@^2.1.26:
12692 - version "2.1.27"
12693 - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
12694 - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
12695 - dependencies:
12696 - mime-db "1.44.0"
12697 -
12499 mime-types@~2.1.17, mime-types@~2.1.24:
12500 version "2.1.24"
12501 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
@@ -12820,7 +12621,7 @@ mkdirp@3.0.1:
12621 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
12622 integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
12623
12823 -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
12624 +mkdirp@^0.5.1, mkdirp@~0.5.1:
12625 version "0.5.5"
12626 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
12627 integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@@ -12920,13 +12721,6 @@ nanomatch@^1.2.9:
12721 snapdragon "^0.8.1"
12722 to-regex "^3.0.1"
12723
12923 -native-url@^0.2.6:
12924 - version "0.2.6"
12925 - resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae"
12926 - integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==
12927 - dependencies:
12928 - querystring "^0.2.0"
12929 -
12724 natural-compare@^1.4.0:
12725 version "1.4.0"
12726 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -13200,7 +12994,7 @@ nth-check@^2.0.1:
12994 dependencies:
12995 boolbase "^1.0.0"
12996
13203 -nullthrows@^1.0.0, nullthrows@^1.1.1:
12997 +nullthrows@^1.0.0:
12998 version "1.1.1"
12999 resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
13000 integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
@@ -13374,13 +13168,6 @@ open@^8.0.9:
13168 is-docker "^2.1.1"
13169 is-wsl "^2.2.0"
13170
13377 -opn@5.3.0:
13378 - version "5.3.0"
13379 - resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
13380 - integrity sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==
13381 - dependencies:
13382 - is-wsl "^1.1.0"
13383 -
13171 optionator@^0.9.1:
13172 version "0.9.1"
13173 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@@ -13730,11 +13517,6 @@ parse-link-header@^2.0.0:
13517 dependencies:
13518 xtend "~4.0.1"
13519
13733 -parse-ms@^2.1.0:
13734 - version "2.1.0"
13735 - resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d"
13736 - integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==
13737 -
13520 parse-node-version@^1.0.0:
13521 version "1.0.1"
13522 resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
@@ -14066,13 +13848,6 @@ postcss-modules-extract-imports@^1.2.0:
13848 dependencies:
13849 postcss "^6.0.1"
13850
14069 -postcss-modules-extract-imports@^2.0.0:
14070 - version "2.0.0"
14071 - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
14072 - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==
14073 - dependencies:
14074 - postcss "^7.0.5"
14075 -
13851 postcss-modules-extract-imports@^3.0.0:
13852 version "3.0.0"
13853 resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
@@ -14086,16 +13861,6 @@ postcss-modules-local-by-default@^1.2.0:
13861 css-selector-tokenizer "^0.7.0"
13862 postcss "^6.0.1"
13863
14089 -postcss-modules-local-by-default@^3.0.3:
14090 - version "3.0.3"
14091 - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
14092 - integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==
14093 - dependencies:
14094 - icss-utils "^4.1.1"
14095 - postcss "^7.0.32"
14096 - postcss-selector-parser "^6.0.2"
14097 - postcss-value-parser "^4.1.0"
14098 -
13864 postcss-modules-local-by-default@^4.0.4:
13865 version "4.0.4"
13866 resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6"
@@ -14113,14 +13878,6 @@ postcss-modules-scope@^1.1.0:
13878 css-selector-tokenizer "^0.7.0"
13879 postcss "^6.0.1"
13880
14116 -postcss-modules-scope@^2.2.0:
14117 - version "2.2.0"
14118 - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee"
14119 - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==
14120 - dependencies:
14121 - postcss "^7.0.6"
14122 - postcss-selector-parser "^6.0.0"
14123 -
13881 postcss-modules-scope@^3.1.1:
13882 version "3.1.1"
13883 resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134"
@@ -14136,14 +13893,6 @@ postcss-modules-values@^1.3.0:
13893 icss-replace-symbols "^1.1.0"
13894 postcss "^6.0.1"
13895
14139 -postcss-modules-values@^3.0.0:
14140 - version "3.0.0"
14141 - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
14142 - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
14143 - dependencies:
14144 - icss-utils "^4.0.0"
14145 - postcss "^7.0.6"
14146 -
13896 postcss-modules-values@^4.0.0:
13897 version "4.0.0"
13898 resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
@@ -14151,7 +13900,7 @@ postcss-modules-values@^4.0.0:
13900 dependencies:
13901 icss-utils "^5.0.0"
13902
14154 -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
13903 +postcss-selector-parser@^6.0.2:
13904 version "6.0.2"
13905 resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
13906 integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
@@ -14192,15 +13941,6 @@ postcss@^6.0.1, postcss@^6.0.23:
13941 source-map "^0.6.1"
13942 supports-color "^5.4.0"
13943
14195 -postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
14196 - version "7.0.32"
14197 - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
14198 - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
14199 - dependencies:
14200 - chalk "^2.4.2"
14201 - source-map "^0.6.1"
14202 - supports-color "^6.1.0"
14203 -
13944 postcss@^8.4.33:
13945 version "8.4.35"
13946 resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7"
@@ -14268,13 +14008,6 @@ pretty-format@^29.7.0:
14008 ansi-styles "^5.0.0"
14009 react-is "^18.0.0"
14010
14271 -pretty-ms@^7.0.0:
14272 - version "7.0.0"
14273 - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.0.tgz#45781273110caf35f55cab21a8a9bd403a233dc0"
14274 - integrity sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==
14275 - dependencies:
14276 - parse-ms "^2.1.0"
14277 -
14011 prettyjson@^1.2.1:
14012 version "1.2.1"
14013 resolved "https://registry.yarnpkg.com/prettyjson/-/prettyjson-1.2.1.tgz#fcffab41d19cab4dfae5e575e64246619b12d289"
@@ -14425,13 +14158,6 @@ punycode@^2.1.1, punycode@^2.3.0:
14158 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
14159 integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
14160
14428 -pupa@^2.0.1:
14429 - version "2.0.1"
14430 - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726"
14431 - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==
14432 - dependencies:
14433 - escape-goat "^2.0.0"
14434 -
14161 pupa@^2.1.1:
14162 version "2.1.1"
14163 resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
@@ -14487,7 +14213,7 @@ querystring-es3@~0.2.0:
14213 resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
14214 integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
14215
14490 -querystring@0.2.0, querystring@^0.2.0:
14216 +querystring@0.2.0:
14217 version "0.2.0"
14218 resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
14219 integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
@@ -14683,15 +14409,6 @@ react-window@^1.8.10:
14409 "@babel/runtime" "^7.0.0"
14410 memoize-one ">=3.1.1 <6"
14411
14686 -react@^16.13.1:
14687 - version "16.13.1"
14688 - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
14689 - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
14690 - dependencies:
14691 - loose-envify "^1.1.0"
14692 - object-assign "^4.1.1"
14693 - prop-types "^15.6.2"
14694 -
14412 read-pkg-up@^1.0.1:
14413 version "1.0.1"
14414 resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -14861,11 +14578,6 @@ regenerator-runtime@^0.13.4:
14578 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
14579 integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
14580
14864 -regenerator-runtime@^0.13.7:
14865 - version "0.13.7"
14866 - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
14867 - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
14868 -
14581 regenerator-runtime@^0.13.9:
14582 version "0.13.11"
14583 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
@@ -15414,15 +15126,6 @@ schema-utils@^2.0.1:
15126 ajv "^6.1.0"
15127 ajv-keywords "^3.1.0"
15128
15417 -schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
15418 - version "2.7.0"
15419 - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
15420 - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
15421 - dependencies:
15422 - "@types/json-schema" "^7.0.4"
15423 - ajv "^6.12.2"
15424 - ajv-keywords "^3.4.1"
15425 -
15129 schema-utils@^3.0.0:
15130 version "3.1.1"
15131 resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
@@ -15905,7 +15608,7 @@ source-map-support@0.5.13:
15608 buffer-from "^1.0.0"
15609 source-map "^0.6.0"
15610
15908 -source-map-support@0.5.21, source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@~0.5.20:
15611 +source-map-support@0.5.21, source-map-support@^0.5.16, source-map-support@~0.5.20:
15612 version "0.5.21"
15613 resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
15614 integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -15935,11 +15638,6 @@ source-map@^0.6.0, source-map@^0.6.1:
15638 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
15639 integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
15640
15938 -source-map@^0.7.3:
15939 - version "0.7.3"
15940 - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
15941 - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
15942 -
15641 sourcemap-codec@^1.4.8:
15642 version "1.4.8"
15643 resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@@ -16365,14 +16063,6 @@ style-loader@^0.23.1:
16063 loader-utils "^1.1.0"
16064 schema-utils "^1.0.0"
16065
16368 -style-loader@^1.2.1:
16369 - version "1.2.1"
16370 - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
16371 - integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==
16372 - dependencies:
16373 - loader-utils "^2.0.0"
16374 - schema-utils "^2.6.6"
16375 -
16066 sucrase@^3.35.0:
16067 version "3.35.0"
16068 resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
@@ -16405,13 +16095,6 @@ supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0:
16095 dependencies:
16096 has-flag "^3.0.0"
16097
16408 -supports-color@^6.1.0:
16409 - version "6.1.0"
16410 - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
16411 - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
16412 - dependencies:
16413 - has-flag "^3.0.0"
16414 -
16098 supports-color@^7.1.0:
16099 version "7.2.0"
16100 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -16554,11 +16237,6 @@ tempfile@^2.0.0:
16237 temp-dir "^1.0.0"
16238 uuid "^3.0.1"
16239
16557 -term-size@^2.1.0:
16558 - version "2.2.0"
16559 - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
16560 - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==
16561 -
16240 terser-webpack-plugin@^5.3.7:
16241 version "5.3.8"
16242 resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz#415e03d2508f7de63d59eca85c5d102838f06610"
@@ -16848,17 +16526,6 @@ ts-interface-checker@^0.1.9:
16526 resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
16527 integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
16528
16851 -ts-node@8.9.1:
16852 - version "8.9.1"
16853 - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5"
16854 - integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==
16855 - dependencies:
16856 - arg "^4.1.0"
16857 - diff "^4.0.1"
16858 - make-error "^1.1.1"
16859 - source-map-support "^0.5.17"
16860 - yn "3.1.1"
16861 -
16529 tslib@^1.8.1:
16530 version "1.11.1"
16531 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
@@ -16949,11 +16616,6 @@ type-fest@^0.21.3:
16616 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
16617 integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
16618
16952 -type-fest@^0.8.1:
16953 - version "0.8.1"
16954 - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
16955 - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
16956 -
16619 type-fest@^1.0.1:
16620 version "1.4.0"
16621 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
@@ -16994,11 +16656,6 @@ typescript-compiler@^1.4.1-2:
16656 resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
16657 integrity sha512-EMopKmoAEJqA4XXRFGOb7eSBhmQMbBahW6P1Koayeatp0b4AW2q/bBqYWkpG7QVQc9HGQUiS4trx2ZHcnAaZUg==
16658
16997 -typescript@3.9.3:
16998 - version "3.9.3"
16999 - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
17000 - integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
17001 -
16659 typescript@^5.4.3:
16660 version "5.7.3"
16661 resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
@@ -17211,25 +16868,6 @@ update-browserslist-db@^1.1.1:
16868 escalade "^3.2.0"
16869 picocolors "^1.1.1"
16870
17214 -update-notifier@4.1.0:
17215 - version "4.1.0"
17216 - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3"
17217 - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==
17218 - dependencies:
17219 - boxen "^4.2.0"
17220 - chalk "^3.0.0"
17221 - configstore "^5.0.1"
17222 - has-yarn "^2.1.0"
17223 - import-lazy "^2.1.0"
17224 - is-ci "^2.0.0"
17225 - is-installed-globally "^0.3.1"
17226 - is-npm "^4.0.0"
17227 - is-yarn-global "^0.3.0"
17228 - latest-version "^5.0.0"
17229 - pupa "^2.0.1"
17230 - semver-diff "^3.1.1"
17231 - xdg-basedir "^4.0.0"
17232 -
16871 update-notifier@6.0.2:
16872 version "6.0.2"
16873 resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60"
@@ -17289,15 +16927,6 @@ urix@^0.1.0:
16927 resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
16928 integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
16929
17292 -url-loader@^4.1.0:
17293 - version "4.1.0"
17294 - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.0.tgz#c7d6b0d6b0fccd51ab3ffc58a78d32b8d89a7be2"
17295 - integrity sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==
17296 - dependencies:
17297 - loader-utils "^2.0.0"
17298 - mime-types "^2.1.26"
17299 - schema-utils "^2.6.5"
17300 -
16930 url-parse-lax@^1.0.0:
16931 version "1.0.0"
16932 resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
@@ -17421,18 +17050,6 @@ vary@~1.1.2:
17050 resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
17051 integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
17052
17424 -vercel@^20.1.0:
17425 - version "20.1.0"
17426 - resolved "https://registry.yarnpkg.com/vercel/-/vercel-20.1.0.tgz#366c39892455f5fff9b1d31db6e6ac1dff436453"
17427 - integrity sha512-fOYq2X6o157hOBbyxU9VcM2pCHa6cAxvkA731N9FEw8MyMnPC7zJRgFQ/kYJ0oisjlKsmm2A1ryrLEz3KLIDFw==
17428 - dependencies:
17429 - "@vercel/build-utils" "2.5.1"
17430 - "@vercel/go" "1.1.6"
17431 - "@vercel/node" "1.8.1"
17432 - "@vercel/python" "1.2.3"
17433 - "@vercel/ruby" "1.2.4"
17434 - update-notifier "4.1.0"
17435 -
17053 vinyl-sourcemaps-apply@^0.2.0:
17054 version "0.2.1"
17055 resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
@@ -18072,11 +17689,6 @@ yauzl@2.10.0, yauzl@^2.10.0, yauzl@^2.4.2:
17689 buffer-crc32 "~0.2.3"
17690 fd-slicer "~1.1.0"
17691
18075 -yn@3.1.1:
18076 - version "3.1.1"
18077 - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
18078 - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
18079 -
17692 yocto-queue@^0.1.0:
17693 version "0.1.0"
17694 resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"