@samitouri / QOS-React / commits / 51947a14bb

Refactored how React/DevTools log Timeline performance data (#23102)

Until now, DEV and PROFILING builds of React recorded Timeline profiling data using the User Timing API. This commit changes things so that React records this data by calling methods on the DevTools hook. (For now, DevTools still records that data using the User Timing API, to match previous behavior.) This commit is large but most of it is just moving things around: * New methods have been added to the DevTools hook (in "backend/profilingHooks") for recording the Timeline performance events. * Reconciler's "ReactFiberDevToolsHook" has been updated to call these new methods (when they're present). * User Timing method calls in "SchedulingProfiler" have been moved to DevTools "backend/profilingHooks" (to match previous behavior, for now). * The old reconciler tests, "SchedulingProfiler-test" and "SchedulingProfilerLabels-test", have been moved into DevTools "TimelineProfiler-test" to ensure behavior didn't change unexpectedly. * Two new methods have been added to the injected renderer interface: injectProfilingHooks() and getLaneLabelMap(). Relates to #22529.

Brian Vaughn committed Jan 13, 2022 at 14:55 UTC 51947a14bb24bd151f76f6fc0acdbbc404de13f7
28 files changed +4208 -3593
packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js new
+1124
@@ -0,0 +1,1124 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +describe('Timeline profiler', () => {
13 + let React;
14 + let ReactDOM;
15 + let Scheduler;
16 + let renderHelper;
17 + let renderRootHelper;
18 + let unmountFns;
19 + let utils;
20 +
21 + let clearedMarks;
22 + let featureDetectionMarkName = null;
23 + let marks;
24 + let setPerformanceMock;
25 +
26 + function createUserTimingPolyfill() {
27 + featureDetectionMarkName = null;
28 +
29 + clearedMarks = [];
30 + marks = [];
31 +
32 + // Remove file-system specific bits or version-specific bits of information from the module range marks.
33 + function filterMarkData(markName) {
34 + if (markName.startsWith('--react-internal-module-start')) {
35 + return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
36 + } else if (markName.startsWith('--react-internal-module-stop')) {
37 + return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
38 + } else if (markName.startsWith('--react-version')) {
39 + return `${markName.substr(0, 15)}-<filtered-version>`;
40 + } else {
41 + return markName;
42 + }
43 + }
44 +
45 + // This is not a true polyfill, but it gives us enough to capture marks.
46 + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
47 + return {
48 + clearMarks(markName) {
49 + markName = filterMarkData(markName);
50 +
51 + clearedMarks.push(markName);
52 + marks = marks.filter(mark => mark !== markName);
53 + },
54 + mark(markName, markOptions) {
55 + markName = filterMarkData(markName);
56 +
57 + if (featureDetectionMarkName === null) {
58 + featureDetectionMarkName = markName;
59 + }
60 +
61 + marks.push(markName);
62 +
63 + if (markOptions != null) {
64 + // This is triggers the feature detection.
65 + markOptions.startTime++;
66 + }
67 + },
68 + };
69 + }
70 +
71 + function clearPendingMarks() {
72 + clearedMarks.splice(0);
73 + }
74 +
75 + function dispatchAndSetCurrentEvent(element, event) {
76 + try {
77 + window.event = event;
78 + element.dispatchEvent(event);
79 + } finally {
80 + window.event = undefined;
81 + }
82 + }
83 +
84 + beforeEach(() => {
85 + utils = require('./utils');
86 + utils.beforeEachProfiling();
87 +
88 + unmountFns = [];
89 + renderHelper = element => {
90 + const unmountFn = utils.legacyRender(element);
91 + unmountFns.push(unmountFn);
92 + return unmountFn;
93 + };
94 + renderRootHelper = element => {
95 + const container = document.createElement('div');
96 + const root = ReactDOM.createRoot(container);
97 + root.render(element);
98 + const unmountFn = () => root.unmount();
99 + unmountFns.push(unmountFn);
100 + return unmountFn;
101 + };
102 +
103 + React = require('react');
104 + ReactDOM = require('react-dom');
105 + Scheduler = require('scheduler');
106 +
107 + setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
108 + .setPerformanceMock_ONLY_FOR_TESTING;
109 + setPerformanceMock(createUserTimingPolyfill());
110 + });
111 +
112 + afterEach(() => {
113 + // Verify all logged marks also get cleared.
114 + expect(marks).toHaveLength(0);
115 +
116 + unmountFns.forEach(unmountFn => unmountFn());
117 +
118 + setPerformanceMock(null);
119 + });
120 +
121 + it('should mark sync render without suspends or state updates', () => {
122 + renderHelper(<div />);
123 +
124 + expect(clearedMarks).toMatchInlineSnapshot(`
125 + Array [
126 + "--schedule-render-1",
127 + "--render-start-1",
128 + "--render-stop",
129 + "--commit-start-1",
130 + "--react-version-<filtered-version>",
131 + "--profiler-version-1",
132 + "--react-internal-module-start-<filtered-file-system-path>",
133 + "--react-internal-module-stop-<filtered-file-system-path>",
134 + "--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",
135 + "--layout-effects-start-1",
136 + "--layout-effects-stop",
137 + "--commit-stop",
138 + ]
139 + `);
140 + });
141 +
142 + it('should mark concurrent render without suspends or state updates', () => {
143 + renderRootHelper(<div />);
144 +
145 + expect(clearedMarks).toMatchInlineSnapshot(`
146 + Array [
147 + "--schedule-render-16",
148 + ]
149 + `);
150 +
151 + clearPendingMarks();
152 +
153 + expect(Scheduler).toFlushUntilNextPaint([]);
154 +
155 + expect(clearedMarks).toMatchInlineSnapshot(`
156 + Array [
157 + "--render-start-16",
158 + "--render-stop",
159 + "--commit-start-16",
160 + "--react-version-<filtered-version>",
161 + "--profiler-version-1",
162 + "--react-internal-module-start-<filtered-file-system-path>",
163 + "--react-internal-module-stop-<filtered-file-system-path>",
164 + "--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",
165 + "--layout-effects-start-16",
166 + "--layout-effects-stop",
167 + "--commit-stop",
168 + ]
169 + `);
170 + });
171 +
172 + it('should mark render yields', async () => {
173 + function Bar() {
174 + Scheduler.unstable_yieldValue('Bar');
175 + return null;
176 + }
177 +
178 + function Foo() {
179 + Scheduler.unstable_yieldValue('Foo');
180 + return <Bar />;
181 + }
182 +
183 + React.startTransition(() => {
184 + renderRootHelper(<Foo />);
185 + });
186 +
187 + // Do one step of work.
188 + expect(Scheduler).toFlushAndYieldThrough(['Foo']);
189 +
190 + expect(clearedMarks).toMatchInlineSnapshot(`
191 + Array [
192 + "--schedule-render-64",
193 + "--render-start-64",
194 + "--component-render-start-Foo",
195 + "--component-render-stop",
196 + "--render-yield",
197 + ]
198 + `);
199 + });
200 +
201 + it('should mark sync render with suspense that resolves', async () => {
202 + const fakeSuspensePromise = Promise.resolve(true);
203 + function Example() {
204 + throw fakeSuspensePromise;
205 + }
206 +
207 + renderHelper(
208 + <React.Suspense fallback={null}>
209 + <Example />
210 + </React.Suspense>,
211 + );
212 +
213 + expect(clearedMarks).toMatchInlineSnapshot(`
214 + Array [
215 + "--schedule-render-1",
216 + "--render-start-1",
217 + "--component-render-start-Example",
218 + "--component-render-stop",
219 + "--suspense-suspend-0-Example-mount-1-",
220 + "--render-stop",
221 + "--commit-start-1",
222 + "--react-version-<filtered-version>",
223 + "--profiler-version-1",
224 + "--react-internal-module-start-<filtered-file-system-path>",
225 + "--react-internal-module-stop-<filtered-file-system-path>",
226 + "--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",
227 + "--layout-effects-start-1",
228 + "--layout-effects-stop",
229 + "--commit-stop",
230 + ]
231 + `);
232 +
233 + clearPendingMarks();
234 +
235 + await fakeSuspensePromise;
236 + expect(clearedMarks).toMatchInlineSnapshot(`
237 + Array [
238 + "--suspense-resolved-0-Example",
239 + ]
240 + `);
241 + });
242 +
243 + it('should mark sync render with suspense that rejects', async () => {
244 + const fakeSuspensePromise = Promise.reject(new Error('error'));
245 + function Example() {
246 + throw fakeSuspensePromise;
247 + }
248 +
249 + renderHelper(
250 + <React.Suspense fallback={null}>
251 + <Example />
252 + </React.Suspense>,
253 + );
254 +
255 + expect(clearedMarks).toMatchInlineSnapshot(`
256 + Array [
257 + "--schedule-render-1",
258 + "--render-start-1",
259 + "--component-render-start-Example",
260 + "--component-render-stop",
261 + "--suspense-suspend-0-Example-mount-1-",
262 + "--render-stop",
263 + "--commit-start-1",
264 + "--react-version-<filtered-version>",
265 + "--profiler-version-1",
266 + "--react-internal-module-start-<filtered-file-system-path>",
267 + "--react-internal-module-stop-<filtered-file-system-path>",
268 + "--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",
269 + "--layout-effects-start-1",
270 + "--layout-effects-stop",
271 + "--commit-stop",
272 + ]
273 + `);
274 +
275 + clearPendingMarks();
276 +
277 + await expect(fakeSuspensePromise).rejects.toThrow();
278 + expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
279 + });
280 +
281 + it('should mark concurrent render with suspense that resolves', async () => {
282 + const fakeSuspensePromise = Promise.resolve(true);
283 + function Example() {
284 + throw fakeSuspensePromise;
285 + }
286 +
287 + renderRootHelper(
288 + <React.Suspense fallback={null}>
289 + <Example />
290 + </React.Suspense>,
291 + );
292 +
293 + expect(clearedMarks).toMatchInlineSnapshot(`
294 + Array [
295 + "--schedule-render-16",
296 + ]
297 + `);
298 +
299 + clearPendingMarks();
300 +
301 + expect(Scheduler).toFlushUntilNextPaint([]);
302 +
303 + expect(clearedMarks).toMatchInlineSnapshot(`
304 + Array [
305 + "--render-start-16",
306 + "--component-render-start-Example",
307 + "--component-render-stop",
308 + "--suspense-suspend-0-Example-mount-16-",
309 + "--render-stop",
310 + "--commit-start-16",
311 + "--react-version-<filtered-version>",
312 + "--profiler-version-1",
313 + "--react-internal-module-start-<filtered-file-system-path>",
314 + "--react-internal-module-stop-<filtered-file-system-path>",
315 + "--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",
316 + "--layout-effects-start-16",
317 + "--layout-effects-stop",
318 + "--commit-stop",
319 + ]
320 + `);
321 +
322 + clearPendingMarks();
323 +
324 + await fakeSuspensePromise;
325 + expect(clearedMarks).toMatchInlineSnapshot(`
326 + Array [
327 + "--suspense-resolved-0-Example",
328 + ]
329 + `);
330 + });
331 +
332 + it('should mark concurrent render with suspense that rejects', async () => {
333 + const fakeSuspensePromise = Promise.reject(new Error('error'));
334 + function Example() {
335 + throw fakeSuspensePromise;
336 + }
337 +
338 + renderRootHelper(
339 + <React.Suspense fallback={null}>
340 + <Example />
341 + </React.Suspense>,
342 + );
343 +
344 + expect(clearedMarks).toMatchInlineSnapshot(`
345 + Array [
346 + "--schedule-render-16",
347 + ]
348 + `);
349 +
350 + clearPendingMarks();
351 +
352 + expect(Scheduler).toFlushUntilNextPaint([]);
353 +
354 + expect(clearedMarks).toMatchInlineSnapshot(`
355 + Array [
356 + "--render-start-16",
357 + "--component-render-start-Example",
358 + "--component-render-stop",
359 + "--suspense-suspend-0-Example-mount-16-",
360 + "--render-stop",
361 + "--commit-start-16",
362 + "--react-version-<filtered-version>",
363 + "--profiler-version-1",
364 + "--react-internal-module-start-<filtered-file-system-path>",
365 + "--react-internal-module-stop-<filtered-file-system-path>",
366 + "--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",
367 + "--layout-effects-start-16",
368 + "--layout-effects-stop",
369 + "--commit-stop",
370 + ]
371 + `);
372 +
373 + clearPendingMarks();
374 +
375 + await expect(fakeSuspensePromise).rejects.toThrow();
376 + expect(clearedMarks).toMatchInlineSnapshot(`
377 + Array [
378 + "--suspense-rejected-0-Example",
379 + ]
380 + `);
381 + });
382 +
383 + it('should mark cascading class component state updates', () => {
384 + class Example extends React.Component {
385 + state = {didMount: false};
386 + componentDidMount() {
387 + this.setState({didMount: true});
388 + }
389 + render() {
390 + return null;
391 + }
392 + }
393 +
394 + renderRootHelper(<Example />);
395 +
396 + expect(clearedMarks).toMatchInlineSnapshot(`
397 + Array [
398 + "--schedule-render-16",
399 + ]
400 + `);
401 +
402 + clearPendingMarks();
403 +
404 + expect(Scheduler).toFlushUntilNextPaint([]);
405 +
406 + expect(clearedMarks).toMatchInlineSnapshot(`
407 + Array [
408 + "--render-start-16",
409 + "--component-render-start-Example",
410 + "--component-render-stop",
411 + "--render-stop",
412 + "--commit-start-16",
413 + "--react-version-<filtered-version>",
414 + "--profiler-version-1",
415 + "--react-internal-module-start-<filtered-file-system-path>",
416 + "--react-internal-module-stop-<filtered-file-system-path>",
417 + "--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",
418 + "--layout-effects-start-16",
419 + "--schedule-state-update-1-Example",
420 + "--layout-effects-stop",
421 + "--render-start-1",
422 + "--component-render-start-Example",
423 + "--component-render-stop",
424 + "--render-stop",
425 + "--commit-start-1",
426 + "--react-version-<filtered-version>",
427 + "--profiler-version-1",
428 + "--react-internal-module-start-<filtered-file-system-path>",
429 + "--react-internal-module-stop-<filtered-file-system-path>",
430 + "--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",
431 + "--commit-stop",
432 + "--commit-stop",
433 + ]
434 + `);
435 + });
436 +
437 + it('should mark cascading class component force updates', () => {
438 + class Example extends React.Component {
439 + componentDidMount() {
440 + this.forceUpdate();
441 + }
442 + render() {
443 + return null;
444 + }
445 + }
446 +
447 + renderRootHelper(<Example />);
448 +
449 + expect(clearedMarks).toMatchInlineSnapshot(`
450 + Array [
451 + "--schedule-render-16",
452 + ]
453 + `);
454 +
455 + clearPendingMarks();
456 +
457 + expect(Scheduler).toFlushUntilNextPaint([]);
458 +
459 + expect(clearedMarks).toMatchInlineSnapshot(`
460 + Array [
461 + "--render-start-16",
462 + "--component-render-start-Example",
463 + "--component-render-stop",
464 + "--render-stop",
465 + "--commit-start-16",
466 + "--react-version-<filtered-version>",
467 + "--profiler-version-1",
468 + "--react-internal-module-start-<filtered-file-system-path>",
469 + "--react-internal-module-stop-<filtered-file-system-path>",
470 + "--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",
471 + "--layout-effects-start-16",
472 + "--schedule-forced-update-1-Example",
473 + "--layout-effects-stop",
474 + "--render-start-1",
475 + "--component-render-start-Example",
476 + "--component-render-stop",
477 + "--render-stop",
478 + "--commit-start-1",
479 + "--react-version-<filtered-version>",
480 + "--profiler-version-1",
481 + "--react-internal-module-start-<filtered-file-system-path>",
482 + "--react-internal-module-stop-<filtered-file-system-path>",
483 + "--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",
484 + "--commit-stop",
485 + "--commit-stop",
486 + ]
487 + `);
488 + });
489 +
490 + it('should mark render phase state updates for class component', () => {
491 + class Example extends React.Component {
492 + state = {didRender: false};
493 + render() {
494 + if (this.state.didRender === false) {
495 + this.setState({didRender: true});
496 + }
497 + return null;
498 + }
499 + }
500 +
501 + renderRootHelper(<Example />);
502 +
503 + expect(clearedMarks).toMatchInlineSnapshot(`
504 + Array [
505 + "--schedule-render-16",
506 + ]
507 + `);
508 +
509 + clearPendingMarks();
510 +
511 + let errorMessage;
512 + spyOn(console, 'error').and.callFake(message => {
513 + errorMessage = message;
514 + });
515 +
516 + expect(Scheduler).toFlushUntilNextPaint([]);
517 +
518 + expect(console.error).toHaveBeenCalledTimes(1);
519 + expect(errorMessage).toContain(
520 + 'Cannot update during an existing state transition',
521 + );
522 +
523 + expect(clearedMarks).toMatchInlineSnapshot(`
524 + Array [
525 + "--render-start-16",
526 + "--component-render-start-Example",
527 + "--schedule-state-update-16-Example",
528 + "--component-render-stop",
529 + "--render-stop",
530 + "--commit-start-16",
531 + "--react-version-<filtered-version>",
532 + "--profiler-version-1",
533 + "--react-internal-module-start-<filtered-file-system-path>",
534 + "--react-internal-module-stop-<filtered-file-system-path>",
535 + "--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",
536 + "--layout-effects-start-16",
537 + "--layout-effects-stop",
538 + "--commit-stop",
539 + ]
540 + `);
541 + });
542 +
543 + it('should mark render phase force updates for class component', () => {
544 + class Example extends React.Component {
545 + state = {didRender: false};
546 + render() {
547 + if (this.state.didRender === false) {
548 + this.forceUpdate(() => this.setState({didRender: true}));
549 + }
550 + return null;
551 + }
552 + }
553 +
554 + renderRootHelper(<Example />);
555 +
556 + expect(clearedMarks).toMatchInlineSnapshot(`
557 + Array [
558 + "--schedule-render-16",
559 + ]
560 + `);
561 +
562 + clearPendingMarks();
563 +
564 + let errorMessage;
565 + spyOn(console, 'error').and.callFake(message => {
566 + errorMessage = message;
567 + });
568 +
569 + expect(Scheduler).toFlushUntilNextPaint([]);
570 +
571 + expect(console.error).toHaveBeenCalledTimes(1);
572 + expect(errorMessage).toContain(
573 + 'Cannot update during an existing state transition',
574 + );
575 +
576 + expect(clearedMarks).toMatchInlineSnapshot(`
577 + Array [
578 + "--render-start-16",
579 + "--component-render-start-Example",
580 + "--schedule-forced-update-16-Example",
581 + "--component-render-stop",
582 + "--render-stop",
583 + "--commit-start-16",
584 + "--react-version-<filtered-version>",
585 + "--profiler-version-1",
586 + "--react-internal-module-start-<filtered-file-system-path>",
587 + "--react-internal-module-stop-<filtered-file-system-path>",
588 + "--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",
589 + "--layout-effects-start-16",
590 + "--layout-effects-stop",
591 + "--commit-stop",
592 + ]
593 + `);
594 + });
595 +
596 + it('should mark cascading layout updates', () => {
597 + function Example() {
598 + const [didMount, setDidMount] = React.useState(false);
599 + React.useLayoutEffect(() => {
600 + setDidMount(true);
601 + }, []);
602 + return didMount;
603 + }
604 +
605 + renderRootHelper(<Example />);
606 +
607 + expect(clearedMarks).toMatchInlineSnapshot(`
608 + Array [
609 + "--schedule-render-16",
610 + ]
611 + `);
612 +
613 + clearPendingMarks();
614 +
615 + expect(Scheduler).toFlushUntilNextPaint([]);
616 +
617 + expect(clearedMarks).toMatchInlineSnapshot(`
618 + Array [
619 + "--render-start-16",
620 + "--component-render-start-Example",
621 + "--component-render-stop",
622 + "--render-stop",
623 + "--commit-start-16",
624 + "--react-version-<filtered-version>",
625 + "--profiler-version-1",
626 + "--react-internal-module-start-<filtered-file-system-path>",
627 + "--react-internal-module-stop-<filtered-file-system-path>",
628 + "--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",
629 + "--layout-effects-start-16",
630 + "--component-layout-effect-mount-start-Example",
631 + "--schedule-state-update-1-Example",
632 + "--component-layout-effect-mount-stop",
633 + "--layout-effects-stop",
634 + "--render-start-1",
635 + "--component-render-start-Example",
636 + "--component-render-stop",
637 + "--render-stop",
638 + "--commit-start-1",
639 + "--react-version-<filtered-version>",
640 + "--profiler-version-1",
641 + "--react-internal-module-start-<filtered-file-system-path>",
642 + "--react-internal-module-stop-<filtered-file-system-path>",
643 + "--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",
644 + "--commit-stop",
645 + "--commit-stop",
646 + ]
647 + `);
648 + });
649 +
650 + // This test is coupled to lane implementation details, so I'm disabling it in
651 + // the new fork until it stabilizes so we don't have to repeatedly update it.
652 + it('should mark cascading passive updates', () => {
653 + function Example() {
654 + const [didMount, setDidMount] = React.useState(false);
655 + React.useEffect(() => {
656 + setDidMount(true);
657 + }, []);
658 + return didMount;
659 + }
660 +
661 + renderRootHelper(<Example />);
662 +
663 + expect(Scheduler).toFlushAndYield([]);
664 +
665 + expect(clearedMarks).toMatchInlineSnapshot(`
666 + Array [
667 + "--schedule-render-16",
668 + "--render-start-16",
669 + "--component-render-start-Example",
670 + "--component-render-stop",
671 + "--render-stop",
672 + "--commit-start-16",
673 + "--react-version-<filtered-version>",
674 + "--profiler-version-1",
675 + "--react-internal-module-start-<filtered-file-system-path>",
676 + "--react-internal-module-stop-<filtered-file-system-path>",
677 + "--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",
678 + "--layout-effects-start-16",
679 + "--layout-effects-stop",
680 + "--commit-stop",
681 + "--passive-effects-start-16",
682 + "--component-passive-effect-mount-start-Example",
683 + "--schedule-state-update-16-Example",
684 + "--component-passive-effect-mount-stop",
685 + "--passive-effects-stop",
686 + "--render-start-16",
687 + "--component-render-start-Example",
688 + "--component-render-stop",
689 + "--render-stop",
690 + "--commit-start-16",
691 + "--react-version-<filtered-version>",
692 + "--profiler-version-1",
693 + "--react-internal-module-start-<filtered-file-system-path>",
694 + "--react-internal-module-stop-<filtered-file-system-path>",
695 + "--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",
696 + "--commit-stop",
697 + ]
698 + `);
699 + });
700 +
701 + it('should mark render phase updates', () => {
702 + function Example() {
703 + const [didRender, setDidRender] = React.useState(false);
704 + if (!didRender) {
705 + setDidRender(true);
706 + }
707 + return didRender;
708 + }
709 +
710 + renderRootHelper(<Example />);
711 +
712 + expect(Scheduler).toFlushAndYield([]);
713 +
714 + expect(clearedMarks).toMatchInlineSnapshot(`
715 + Array [
716 + "--schedule-render-16",
717 + "--render-start-16",
718 + "--component-render-start-Example",
719 + "--schedule-state-update-16-Example",
720 + "--component-render-stop",
721 + "--render-stop",
722 + "--commit-start-16",
723 + "--react-version-<filtered-version>",
724 + "--profiler-version-1",
725 + "--react-internal-module-start-<filtered-file-system-path>",
726 + "--react-internal-module-stop-<filtered-file-system-path>",
727 + "--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",
728 + "--layout-effects-start-16",
729 + "--layout-effects-stop",
730 + "--commit-stop",
731 + ]
732 + `);
733 + });
734 +
735 + it('should mark sync render that throws', async () => {
736 + spyOn(console, 'error');
737 +
738 + class ErrorBoundary extends React.Component {
739 + state = {error: null};
740 + componentDidCatch(error) {
741 + this.setState({error});
742 + }
743 + render() {
744 + if (this.state.error) {
745 + return null;
746 + }
747 + return this.props.children;
748 + }
749 + }
750 +
751 + function ExampleThatThrows() {
752 + throw Error('Expected error');
753 + }
754 +
755 + renderHelper(
756 + <ErrorBoundary>
757 + <ExampleThatThrows />
758 + </ErrorBoundary>,
759 + );
760 +
761 + expect(clearedMarks).toMatchInlineSnapshot(`
762 + Array [
763 + "--schedule-render-1",
764 + "--render-start-1",
765 + "--component-render-start-ErrorBoundary",
766 + "--component-render-stop",
767 + "--component-render-start-ExampleThatThrows",
768 + "--component-render-start-ExampleThatThrows",
769 + "--component-render-stop",
770 + "--error-ExampleThatThrows-mount-Expected error",
771 + "--render-stop",
772 + "--commit-start-1",
773 + "--react-version-<filtered-version>",
774 + "--profiler-version-1",
775 + "--react-internal-module-start-<filtered-file-system-path>",
776 + "--react-internal-module-stop-<filtered-file-system-path>",
777 + "--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",
778 + "--layout-effects-start-1",
779 + "--schedule-state-update-1-ErrorBoundary",
780 + "--layout-effects-stop",
781 + "--commit-stop",
782 + "--render-start-1",
783 + "--component-render-start-ErrorBoundary",
784 + "--component-render-stop",
785 + "--render-stop",
786 + "--commit-start-1",
787 + "--react-version-<filtered-version>",
788 + "--profiler-version-1",
789 + "--react-internal-module-start-<filtered-file-system-path>",
790 + "--react-internal-module-stop-<filtered-file-system-path>",
791 + "--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",
792 + "--commit-stop",
793 + ]
794 + `);
795 + });
796 +
797 + it('should mark concurrent render that throws', async () => {
798 + spyOn(console, 'error');
799 +
800 + class ErrorBoundary extends React.Component {
801 + state = {error: null};
802 + componentDidCatch(error) {
803 + this.setState({error});
804 + }
805 + render() {
806 + if (this.state.error) {
807 + return null;
808 + }
809 + return this.props.children;
810 + }
811 + }
812 +
813 + function ExampleThatThrows() {
814 + // eslint-disable-next-line no-throw-literal
815 + throw 'Expected error';
816 + }
817 +
818 + renderRootHelper(
819 + <ErrorBoundary>
820 + <ExampleThatThrows />
821 + </ErrorBoundary>,
822 + );
823 +
824 + expect(clearedMarks).toMatchInlineSnapshot(`
825 + Array [
826 + "--schedule-render-16",
827 + ]
828 + `);
829 +
830 + clearPendingMarks();
831 +
832 + expect(Scheduler).toFlushUntilNextPaint([]);
833 +
834 + expect(clearedMarks).toMatchInlineSnapshot(`
835 + Array [
836 + "--render-start-16",
837 + "--component-render-start-ErrorBoundary",
838 + "--component-render-stop",
839 + "--component-render-start-ExampleThatThrows",
840 + "--component-render-start-ExampleThatThrows",
841 + "--component-render-stop",
842 + "--error-ExampleThatThrows-mount-Expected error",
843 + "--render-stop",
844 + "--render-start-16",
845 + "--component-render-start-ErrorBoundary",
846 + "--component-render-stop",
847 + "--component-render-start-ExampleThatThrows",
848 + "--component-render-start-ExampleThatThrows",
849 + "--component-render-stop",
850 + "--error-ExampleThatThrows-mount-Expected error",
851 + "--render-stop",
852 + "--commit-start-16",
853 + "--react-version-<filtered-version>",
854 + "--profiler-version-1",
855 + "--react-internal-module-start-<filtered-file-system-path>",
856 + "--react-internal-module-stop-<filtered-file-system-path>",
857 + "--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",
858 + "--layout-effects-start-16",
859 + "--schedule-state-update-1-ErrorBoundary",
860 + "--layout-effects-stop",
861 + "--render-start-1",
862 + "--component-render-start-ErrorBoundary",
863 + "--component-render-stop",
864 + "--render-stop",
865 + "--commit-start-1",
866 + "--react-version-<filtered-version>",
867 + "--profiler-version-1",
868 + "--react-internal-module-start-<filtered-file-system-path>",
869 + "--react-internal-module-stop-<filtered-file-system-path>",
870 + "--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",
871 + "--commit-stop",
872 + "--commit-stop",
873 + ]
874 + `);
875 + });
876 +
877 + it('should mark passive and layout effects', async () => {
878 + function ComponentWithEffects() {
879 + React.useLayoutEffect(() => {
880 + Scheduler.unstable_yieldValue('layout 1 mount');
881 + return () => {
882 + Scheduler.unstable_yieldValue('layout 1 unmount');
883 + };
884 + }, []);
885 +
886 + React.useEffect(() => {
887 + Scheduler.unstable_yieldValue('passive 1 mount');
888 + return () => {
889 + Scheduler.unstable_yieldValue('passive 1 unmount');
890 + };
891 + }, []);
892 +
893 + React.useLayoutEffect(() => {
894 + Scheduler.unstable_yieldValue('layout 2 mount');
895 + return () => {
896 + Scheduler.unstable_yieldValue('layout 2 unmount');
897 + };
898 + }, []);
899 +
900 + React.useEffect(() => {
901 + Scheduler.unstable_yieldValue('passive 2 mount');
902 + return () => {
903 + Scheduler.unstable_yieldValue('passive 2 unmount');
904 + };
905 + }, []);
906 +
907 + React.useEffect(() => {
908 + Scheduler.unstable_yieldValue('passive 3 mount');
909 + return () => {
910 + Scheduler.unstable_yieldValue('passive 3 unmount');
911 + };
912 + }, []);
913 +
914 + return null;
915 + }
916 +
917 + const unmount = renderRootHelper(<ComponentWithEffects />);
918 +
919 + expect(Scheduler).toFlushUntilNextPaint([
920 + 'layout 1 mount',
921 + 'layout 2 mount',
922 + ]);
923 +
924 + expect(clearedMarks).toMatchInlineSnapshot(`
925 + Array [
926 + "--schedule-render-16",
927 + "--render-start-16",
928 + "--component-render-start-ComponentWithEffects",
929 + "--component-render-stop",
930 + "--render-stop",
931 + "--commit-start-16",
932 + "--react-version-<filtered-version>",
933 + "--profiler-version-1",
934 + "--react-internal-module-start-<filtered-file-system-path>",
935 + "--react-internal-module-stop-<filtered-file-system-path>",
936 + "--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",
937 + "--layout-effects-start-16",
938 + "--component-layout-effect-mount-start-ComponentWithEffects",
939 + "--component-layout-effect-mount-stop",
940 + "--component-layout-effect-mount-start-ComponentWithEffects",
941 + "--component-layout-effect-mount-stop",
942 + "--layout-effects-stop",
943 + "--commit-stop",
944 + ]
945 + `);
946 +
947 + clearPendingMarks();
948 +
949 + expect(Scheduler).toFlushAndYield([
950 + 'passive 1 mount',
951 + 'passive 2 mount',
952 + 'passive 3 mount',
953 + ]);
954 +
955 + expect(clearedMarks).toMatchInlineSnapshot(`
956 + Array [
957 + "--passive-effects-start-16",
958 + "--component-passive-effect-mount-start-ComponentWithEffects",
959 + "--component-passive-effect-mount-stop",
960 + "--component-passive-effect-mount-start-ComponentWithEffects",
961 + "--component-passive-effect-mount-stop",
962 + "--component-passive-effect-mount-start-ComponentWithEffects",
963 + "--component-passive-effect-mount-stop",
964 + "--passive-effects-stop",
965 + ]
966 + `);
967 +
968 + clearPendingMarks();
969 +
970 + expect(Scheduler).toFlushAndYield([]);
971 +
972 + unmount();
973 +
974 + expect(Scheduler).toHaveYielded([
975 + 'layout 1 unmount',
976 + 'layout 2 unmount',
977 + 'passive 1 unmount',
978 + 'passive 2 unmount',
979 + 'passive 3 unmount',
980 + ]);
981 +
982 + expect(clearedMarks).toMatchInlineSnapshot(`
983 + Array [
984 + "--schedule-render-1",
985 + "--render-start-1",
986 + "--render-stop",
987 + "--commit-start-1",
988 + "--react-version-<filtered-version>",
989 + "--profiler-version-1",
990 + "--react-internal-module-start-<filtered-file-system-path>",
991 + "--react-internal-module-stop-<filtered-file-system-path>",
992 + "--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",
993 + "--component-layout-effect-unmount-start-ComponentWithEffects",
994 + "--component-layout-effect-unmount-stop",
995 + "--component-layout-effect-unmount-start-ComponentWithEffects",
996 + "--component-layout-effect-unmount-stop",
997 + "--layout-effects-start-1",
998 + "--layout-effects-stop",
999 + "--passive-effects-start-1",
1000 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1001 + "--component-passive-effect-unmount-stop",
1002 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1003 + "--component-passive-effect-unmount-stop",
1004 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1005 + "--component-passive-effect-unmount-stop",
1006 + "--passive-effects-stop",
1007 + "--commit-stop",
1008 + ]
1009 + `);
1010 + });
1011 +
1012 + describe('lane labels', () => {
1013 + it('regression test SyncLane', () => {
1014 + renderHelper(<div />);
1015 +
1016 + expect(clearedMarks).toMatchInlineSnapshot(`
1017 + Array [
1018 + "--schedule-render-1",
1019 + "--render-start-1",
1020 + "--render-stop",
1021 + "--commit-start-1",
1022 + "--react-version-<filtered-version>",
1023 + "--profiler-version-1",
1024 + "--react-internal-module-start-<filtered-file-system-path>",
1025 + "--react-internal-module-stop-<filtered-file-system-path>",
1026 + "--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",
1027 + "--layout-effects-start-1",
1028 + "--layout-effects-stop",
1029 + "--commit-stop",
1030 + ]
1031 + `);
1032 + });
1033 +
1034 + it('regression test DefaultLane', () => {
1035 + renderRootHelper(<div />);
1036 + expect(clearedMarks).toMatchInlineSnapshot(`
1037 + Array [
1038 + "--schedule-render-16",
1039 + ]
1040 + `);
1041 + });
1042 +
1043 + it('regression test InputDiscreteLane', async () => {
1044 + const targetRef = React.createRef(null);
1045 +
1046 + function App() {
1047 + const [count, setCount] = React.useState(0);
1048 + const handleClick = () => {
1049 + setCount(count + 1);
1050 + };
1051 + return <button ref={targetRef} onClick={handleClick} />;
1052 + }
1053 +
1054 + renderRootHelper(<App />);
1055 + expect(Scheduler).toFlushAndYield([]);
1056 +
1057 + clearedMarks.splice(0);
1058 +
1059 + targetRef.current.click();
1060 +
1061 + // Wait a frame, for React to process the "click" update.
1062 + await Promise.resolve();
1063 +
1064 + expect(clearedMarks).toMatchInlineSnapshot(`
1065 + Array [
1066 + "--schedule-state-update-1-App",
1067 + "--render-start-1",
1068 + "--component-render-start-App",
1069 + "--component-render-stop",
1070 + "--render-stop",
1071 + "--commit-start-1",
1072 + "--react-version-<filtered-version>",
1073 + "--profiler-version-1",
1074 + "--react-internal-module-start-<filtered-file-system-path>",
1075 + "--react-internal-module-stop-<filtered-file-system-path>",
1076 + "--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",
1077 + "--layout-effects-start-1",
1078 + "--layout-effects-stop",
1079 + "--commit-stop",
1080 + ]
1081 + `);
1082 + });
1083 +
1084 + it('regression test InputContinuousLane', async () => {
1085 + const targetRef = React.createRef(null);
1086 +
1087 + function App() {
1088 + const [count, setCount] = React.useState(0);
1089 + const handleMouseOver = () => setCount(count + 1);
1090 + return <div ref={targetRef} onMouseOver={handleMouseOver} />;
1091 + }
1092 +
1093 + renderRootHelper(<App />);
1094 + expect(Scheduler).toFlushAndYield([]);
1095 +
1096 + clearedMarks.splice(0);
1097 +
1098 + const event = document.createEvent('MouseEvents');
1099 + event.initEvent('mouseover', true, true);
1100 + dispatchAndSetCurrentEvent(targetRef.current, event);
1101 +
1102 + expect(Scheduler).toFlushAndYield([]);
1103 +
1104 + expect(clearedMarks).toMatchInlineSnapshot(`
1105 + Array [
1106 + "--schedule-state-update-4-App",
1107 + "--render-start-4",
1108 + "--component-render-start-App",
1109 + "--component-render-stop",
1110 + "--render-stop",
1111 + "--commit-start-4",
1112 + "--react-version-<filtered-version>",
1113 + "--profiler-version-1",
1114 + "--react-internal-module-start-<filtered-file-system-path>",
1115 + "--react-internal-module-stop-<filtered-file-system-path>",
1116 + "--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",
1117 + "--layout-effects-start-4",
1118 + "--layout-effects-stop",
1119 + "--commit-stop",
1120 + ]
1121 + `);
1122 + });
1123 + });
1124 +});
packages/react-devtools-shared/src/__tests__/preprocessData-test.js new
+1929
@@ -0,0 +1,1929 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +describe('Timeline profiler', () => {
13 + let React;
14 + let ReactDOM;
15 + let Scheduler;
16 + let utils;
17 +
18 + let clearedMarks;
19 + let featureDetectionMarkName = null;
20 + let marks;
21 + let setPerformanceMock;
22 +
23 + function createUserTimingPolyfill() {
24 + featureDetectionMarkName = null;
25 +
26 + clearedMarks = [];
27 + marks = [];
28 +
29 + // Remove file-system specific bits or version-specific bits of information from the module range marks.
30 + function filterMarkData(markName) {
31 + if (markName.startsWith('--react-internal-module-start')) {
32 + return `${markName.substr(0, 29)}-<filtered-file-system-path>`;
33 + } else if (markName.startsWith('--react-internal-module-stop')) {
34 + return `${markName.substr(0, 28)}-<filtered-file-system-path>`;
35 + } else if (markName.startsWith('--react-version')) {
36 + return `${markName.substr(0, 15)}-0.0.0`;
37 + } else {
38 + return markName;
39 + }
40 + }
41 +
42 + // This is not a true polyfill, but it gives us enough to capture marks.
43 + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
44 + return {
45 + clearMarks(markName) {
46 + markName = filterMarkData(markName);
47 +
48 + clearedMarks.push(markName);
49 + marks = marks.filter(mark => mark !== markName);
50 + },
51 + mark(markName, markOptions) {
52 + markName = filterMarkData(markName);
53 +
54 + if (featureDetectionMarkName === null) {
55 + featureDetectionMarkName = markName;
56 + }
57 +
58 + marks.push(markName);
59 +
60 + if (markOptions != null) {
61 + // This is triggers the feature detection.
62 + markOptions.startTime++;
63 + }
64 + },
65 + };
66 + }
67 +
68 + function clearPendingMarks() {
69 + clearedMarks.splice(0);
70 + }
71 +
72 + beforeEach(() => {
73 + utils = require('./utils');
74 + utils.beforeEachProfiling();
75 +
76 + React = require('react');
77 + ReactDOM = require('react-dom');
78 + Scheduler = require('scheduler');
79 +
80 + setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
81 + .setPerformanceMock_ONLY_FOR_TESTING;
82 + setPerformanceMock(createUserTimingPolyfill());
83 +
84 + global.IS_REACT_ACT_ENVIRONMENT = true;
85 + });
86 +
87 + afterEach(() => {
88 + // Verify all logged marks also get cleared.
89 + expect(marks).toHaveLength(0);
90 +
91 + setPerformanceMock(null);
92 + });
93 +
94 + describe('getLanesFromTransportDecimalBitmask', () => {
95 + let getLanesFromTransportDecimalBitmask;
96 +
97 + beforeEach(() => {
98 + getLanesFromTransportDecimalBitmask = require('react-devtools-timeline/src/import-worker/preprocessData')
99 + .getLanesFromTransportDecimalBitmask;
100 + });
101 +
102 + it('should return array of lane numbers from bitmask string', () => {
103 + expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
104 + expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
105 + expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
106 + expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
107 + 1,
108 + 4,
109 + 6,
110 + 7,
111 + 10,
112 + ]); // 2 + 16 + 64 + 128 + 1024
113 + expect(
114 + getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
115 + ).toEqual([30]);
116 + expect(
117 + getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
118 + ).toEqual(Array.from(Array(31).keys()));
119 + });
120 +
121 + it('should return empty array if laneBitmaskString is not a bitmask', () => {
122 + expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
123 + expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
124 + expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
125 + expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
126 + });
127 +
128 + it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
129 + const REACT_TOTAL_NUM_LANES = require('react-devtools-timeline/src/constants')
130 + .REACT_TOTAL_NUM_LANES;
131 +
132 + // Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
133 + expect(REACT_TOTAL_NUM_LANES).toBe(31);
134 +
135 + expect(
136 + getLanesFromTransportDecimalBitmask(
137 + '4294967297', // 2^32 + 1
138 + ),
139 + ).toEqual([0]);
140 + });
141 + });
142 +
143 + describe('preprocessData', () => {
144 + let preprocessData;
145 +
146 + beforeEach(() => {
147 + preprocessData = require('react-devtools-timeline/src/import-worker/preprocessData')
148 + .default;
149 + });
150 +
151 + // These should be dynamic to mimic a real profile,
152 + // but reprooducible between test runs.
153 + let pid = 0;
154 + let tid = 0;
155 + let startTime = 0;
156 +
157 + function createUserTimingEntry(data) {
158 + return {
159 + pid: ++pid,
160 + tid: ++tid,
161 + ts: ++startTime,
162 + ...data,
163 + };
164 + }
165 +
166 + function createProfilerVersionEntry() {
167 + const SCHEDULING_PROFILER_VERSION = require('react-devtools-timeline/src/constants')
168 + .SCHEDULING_PROFILER_VERSION;
169 + return createUserTimingEntry({
170 + cat: 'blink.user_timing',
171 + name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
172 + });
173 + }
174 +
175 + function createReactVersionEntry() {
176 + return createUserTimingEntry({
177 + cat: 'blink.user_timing',
178 + name: '--react-version-0.0.0',
179 + });
180 + }
181 +
182 + function createLaneLabelsEntry() {
183 + return createUserTimingEntry({
184 + cat: 'blink.user_timing',
185 + name:
186 + '--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',
187 + });
188 + }
189 +
190 + function createNativeEventEntry(type, duration) {
191 + return createUserTimingEntry({
192 + cat: 'devtools.timeline',
193 + name: 'EventDispatch',
194 + args: {data: {type}},
195 + dur: duration,
196 + tdur: duration,
197 + });
198 + }
199 +
200 + function creactCpuProfilerSample() {
201 + return createUserTimingEntry({
202 + args: {data: {startTime: ++startTime}},
203 + cat: 'disabled-by-default-v8.cpu_profiler',
204 + id: '0x1',
205 + name: 'Profile',
206 + ph: 'P',
207 + });
208 + }
209 +
210 + function createBoilerplateEntries() {
211 + return [
212 + createProfilerVersionEntry(),
213 + createReactVersionEntry(),
214 + createLaneLabelsEntry(),
215 + ];
216 + }
217 +
218 + function createUserTimingData(sampleMarks) {
219 + const cpuProfilerSample = creactCpuProfilerSample();
220 +
221 + const randomSample = createUserTimingEntry({
222 + dur: 100,
223 + tdur: 200,
224 + ph: 'X',
225 + cat: 'disabled-by-default-devtools.timeline',
226 + name: 'RunTask',
227 + args: {},
228 + });
229 +
230 + const userTimingData = [cpuProfilerSample, randomSample];
231 +
232 + sampleMarks.forEach(markName => {
233 + userTimingData.push({
234 + pid: ++pid,
235 + tid: ++tid,
236 + ts: ++startTime,
237 + args: {data: {}},
238 + cat: 'blink.user_timing',
239 + name: markName,
240 + ph: 'R',
241 + });
242 + });
243 +
244 + return userTimingData;
245 + }
246 +
247 + beforeEach(() => {
248 + tid = 0;
249 + pid = 0;
250 + startTime = 0;
251 + });
252 +
253 + it('should throw given an empty timeline', async () => {
254 + await expect(async () => preprocessData([])).rejects.toThrow();
255 + });
256 +
257 + it('should throw given a timeline with no Profile event', async () => {
258 + const randomSample = createUserTimingEntry({
259 + dur: 100,
260 + tdur: 200,
261 + ph: 'X',
262 + cat: 'disabled-by-default-devtools.timeline',
263 + name: 'RunTask',
264 + args: {},
265 + });
266 +
267 + await expect(async () =>
268 + preprocessData([randomSample]),
269 + ).rejects.toThrow();
270 + });
271 +
272 + it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
273 + const cpuProfilerSample = creactCpuProfilerSample();
274 +
275 + await expect(
276 + async () => await preprocessData([cpuProfilerSample]),
277 + ).rejects.toThrow(
278 + 'Please provide profiling data from an React application',
279 + );
280 + });
281 +
282 + it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
283 + const cpuProfilerSample = creactCpuProfilerSample();
284 + const scheduleRenderSample = createUserTimingEntry({
285 + cat: 'blink.user_timing',
286 + name: '--schedule-render-512-',
287 + });
288 + const samples = [cpuProfilerSample, scheduleRenderSample];
289 +
290 + await expect(async () => await preprocessData(samples)).rejects.toThrow(
291 + 'This version of profiling data is not supported',
292 + );
293 + });
294 +
295 + it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
296 + const cpuProfilerSample = creactCpuProfilerSample();
297 + const randomSample = createUserTimingEntry({
298 + dur: 100,
299 + tdur: 200,
300 + ph: 'X',
301 + cat: 'disabled-by-default-devtools.timeline',
302 + name: 'RunTask',
303 + args: {},
304 + });
305 +
306 + const data = await preprocessData([
307 + ...createBoilerplateEntries(),
308 + cpuProfilerSample,
309 + randomSample,
310 + ]);
311 + expect(data).toMatchInlineSnapshot(`
312 + Object {
313 + "batchUIDToMeasuresMap": Map {},
314 + "componentMeasures": Array [],
315 + "duration": 0.005,
316 + "flamechart": Array [],
317 + "internalModuleSourceToRanges": Map {},
318 + "laneToLabelMap": Map {
319 + 0 => "Sync",
320 + 1 => "InputContinuousHydration",
321 + 2 => "InputContinuous",
322 + 3 => "DefaultHydration",
323 + 4 => "Default",
324 + 5 => "TransitionHydration",
325 + 6 => "Transition",
326 + 7 => "Transition",
327 + 8 => "Transition",
328 + 9 => "Transition",
329 + 10 => "Transition",
330 + 11 => "Transition",
331 + 12 => "Transition",
332 + 13 => "Transition",
333 + 14 => "Transition",
334 + 15 => "Transition",
335 + 16 => "Transition",
336 + 17 => "Transition",
337 + 18 => "Transition",
338 + 19 => "Transition",
339 + 20 => "Transition",
340 + 21 => "Transition",
341 + 22 => "Retry",
342 + 23 => "Retry",
343 + 24 => "Retry",
344 + 25 => "Retry",
345 + 26 => "Retry",
346 + 27 => "SelectiveHydration",
347 + 28 => "IdleHydration",
348 + 29 => "Idle",
349 + 30 => "Offscreen",
350 + },
351 + "laneToReactMeasureMap": Map {
352 + 0 => Array [],
353 + 1 => Array [],
354 + 2 => Array [],
355 + 3 => Array [],
356 + 4 => Array [],
357 + 5 => Array [],
358 + 6 => Array [],
359 + 7 => Array [],
360 + 8 => Array [],
361 + 9 => Array [],
362 + 10 => Array [],
363 + 11 => Array [],
364 + 12 => Array [],
365 + 13 => Array [],
366 + 14 => Array [],
367 + 15 => Array [],
368 + 16 => Array [],
369 + 17 => Array [],
370 + 18 => Array [],
371 + 19 => Array [],
372 + 20 => Array [],
373 + 21 => Array [],
374 + 22 => Array [],
375 + 23 => Array [],
376 + 24 => Array [],
377 + 25 => Array [],
378 + 26 => Array [],
379 + 27 => Array [],
380 + 28 => Array [],
381 + 29 => Array [],
382 + 30 => Array [],
383 + },
384 + "nativeEvents": Array [],
385 + "networkMeasures": Array [],
386 + "otherUserTimingMarks": Array [],
387 + "reactVersion": "0.0.0",
388 + "schedulingEvents": Array [],
389 + "snapshotHeight": 0,
390 + "snapshots": Array [],
391 + "startTime": 1,
392 + "suspenseEvents": Array [],
393 + "thrownErrors": Array [],
394 + }
395 + `);
396 + });
397 +
398 + it('should process legacy data format (before lane labels were added)', async () => {
399 + const cpuProfilerSample = creactCpuProfilerSample();
400 +
401 + // Data below is hard-coded based on an older profile sample.
402 + // Should be fine since this is explicitly a legacy-format test.
403 + const data = await preprocessData([
404 + ...createBoilerplateEntries(),
405 + cpuProfilerSample,
406 + createUserTimingEntry({
407 + cat: 'blink.user_timing',
408 + name: '--schedule-render-512-',
409 + }),
410 + createUserTimingEntry({
411 + cat: 'blink.user_timing',
412 + name: '--render-start-512',
413 + }),
414 + createUserTimingEntry({
415 + cat: 'blink.user_timing',
416 + name: '--render-stop',
417 + }),
418 + createUserTimingEntry({
419 + cat: 'blink.user_timing',
420 + name: '--commit-start-512',
421 + }),
422 + createUserTimingEntry({
423 + cat: 'blink.user_timing',
424 + name: '--layout-effects-start-512',
425 + }),
426 + createUserTimingEntry({
427 + cat: 'blink.user_timing',
428 + name: '--layout-effects-stop',
429 + }),
430 + createUserTimingEntry({
431 + cat: 'blink.user_timing',
432 + name: '--commit-stop',
433 + }),
434 + ]);
435 + expect(data).toMatchInlineSnapshot(`
436 + Object {
437 + "batchUIDToMeasuresMap": Map {
438 + 0 => Array [
439 + Object {
440 + "batchUID": 0,
441 + "depth": 0,
442 + "duration": 0.004999999999999999,
443 + "lanes": Array [
444 + 9,
445 + ],
446 + "timestamp": 0.006,
447 + "type": "render-idle",
448 + },
449 + Object {
450 + "batchUID": 0,
451 + "depth": 0,
452 + "duration": 0.001,
453 + "lanes": Array [
454 + 9,
455 + ],
456 + "timestamp": 0.006,
457 + "type": "render",
458 + },
459 + Object {
460 + "batchUID": 0,
461 + "depth": 0,
462 + "duration": 0.002999999999999999,
463 + "lanes": Array [
464 + 9,
465 + ],
466 + "timestamp": 0.008,
467 + "type": "commit",
468 + },
469 + Object {
470 + "batchUID": 0,
471 + "depth": 1,
472 + "duration": 0.0010000000000000009,
473 + "lanes": Array [
474 + 9,
475 + ],
476 + "timestamp": 0.009,
477 + "type": "layout-effects",
478 + },
479 + ],
480 + },
481 + "componentMeasures": Array [],
482 + "duration": 0.011,
483 + "flamechart": Array [],
484 + "internalModuleSourceToRanges": Map {},
485 + "laneToLabelMap": Map {
486 + 0 => "Sync",
487 + 1 => "InputContinuousHydration",
488 + 2 => "InputContinuous",
489 + 3 => "DefaultHydration",
490 + 4 => "Default",
491 + 5 => "TransitionHydration",
492 + 6 => "Transition",
493 + 7 => "Transition",
494 + 8 => "Transition",
495 + 9 => "Transition",
496 + 10 => "Transition",
497 + 11 => "Transition",
498 + 12 => "Transition",
499 + 13 => "Transition",
500 + 14 => "Transition",
501 + 15 => "Transition",
502 + 16 => "Transition",
503 + 17 => "Transition",
504 + 18 => "Transition",
505 + 19 => "Transition",
506 + 20 => "Transition",
507 + 21 => "Transition",
508 + 22 => "Retry",
509 + 23 => "Retry",
510 + 24 => "Retry",
511 + 25 => "Retry",
512 + 26 => "Retry",
513 + 27 => "SelectiveHydration",
514 + 28 => "IdleHydration",
515 + 29 => "Idle",
516 + 30 => "Offscreen",
517 + },
518 + "laneToReactMeasureMap": Map {
519 + 0 => Array [],
520 + 1 => Array [],
521 + 2 => Array [],
522 + 3 => Array [],
523 + 4 => Array [],
524 + 5 => Array [],
525 + 6 => Array [],
526 + 7 => Array [],
527 + 8 => Array [],
528 + 9 => Array [
529 + Object {
530 + "batchUID": 0,
531 + "depth": 0,
532 + "duration": 0.004999999999999999,
533 + "lanes": Array [
534 + 9,
535 + ],
536 + "timestamp": 0.006,
537 + "type": "render-idle",
538 + },
539 + Object {
540 + "batchUID": 0,
541 + "depth": 0,
542 + "duration": 0.001,
543 + "lanes": Array [
544 + 9,
545 + ],
546 + "timestamp": 0.006,
547 + "type": "render",
548 + },
549 + Object {
550 + "batchUID": 0,
551 + "depth": 0,
552 + "duration": 0.002999999999999999,
553 + "lanes": Array [
554 + 9,
555 + ],
556 + "timestamp": 0.008,
557 + "type": "commit",
558 + },
559 + Object {
560 + "batchUID": 0,
561 + "depth": 1,
562 + "duration": 0.0010000000000000009,
563 + "lanes": Array [
564 + 9,
565 + ],
566 + "timestamp": 0.009,
567 + "type": "layout-effects",
568 + },
569 + ],
570 + 10 => Array [],
571 + 11 => Array [],
572 + 12 => Array [],
573 + 13 => Array [],
574 + 14 => Array [],
575 + 15 => Array [],
576 + 16 => Array [],
577 + 17 => Array [],
578 + 18 => Array [],
579 + 19 => Array [],
580 + 20 => Array [],
581 + 21 => Array [],
582 + 22 => Array [],
583 + 23 => Array [],
584 + 24 => Array [],
585 + 25 => Array [],
586 + 26 => Array [],
587 + 27 => Array [],
588 + 28 => Array [],
589 + 29 => Array [],
590 + 30 => Array [],
591 + },
592 + "nativeEvents": Array [],
593 + "networkMeasures": Array [],
594 + "otherUserTimingMarks": Array [],
595 + "reactVersion": "0.0.0",
596 + "schedulingEvents": Array [
597 + Object {
598 + "lanes": Array [
599 + 9,
600 + ],
601 + "timestamp": 0.005,
602 + "type": "schedule-render",
603 + "warning": null,
604 + },
605 + ],
606 + "snapshotHeight": 0,
607 + "snapshots": Array [],
608 + "startTime": 1,
609 + "suspenseEvents": Array [],
610 + "thrownErrors": Array [],
611 + }
612 + `);
613 + });
614 +
615 + it('should process a sample legacy render sequence', async () => {
616 + utils.legacyRender(<div />, document.createElement('div'));
617 +
618 + const data = await preprocessData([
619 + ...createBoilerplateEntries(),
620 + ...createUserTimingData(clearedMarks),
621 + ]);
622 + expect(data).toMatchInlineSnapshot(`
623 + Object {
624 + "batchUIDToMeasuresMap": Map {
625 + 0 => Array [
626 + Object {
627 + "batchUID": 0,
628 + "depth": 0,
629 + "duration": 0.01,
630 + "lanes": Array [
631 + 0,
632 + ],
633 + "timestamp": 0.004,
634 + "type": "render-idle",
635 + },
636 + Object {
637 + "batchUID": 0,
638 + "depth": 0,
639 + "duration": 0.001,
640 + "lanes": Array [
641 + 0,
642 + ],
643 + "timestamp": 0.004,
644 + "type": "render",
645 + },
646 + Object {
647 + "batchUID": 0,
648 + "depth": 0,
649 + "duration": 0.008,
650 + "lanes": Array [
651 + 0,
652 + ],
653 + "timestamp": 0.006,
654 + "type": "commit",
655 + },
656 + Object {
657 + "batchUID": 0,
658 + "depth": 1,
659 + "duration": 0.0009999999999999992,
660 + "lanes": Array [
661 + 0,
662 + ],
663 + "timestamp": 0.012,
664 + "type": "layout-effects",
665 + },
666 + ],
667 + },
668 + "componentMeasures": Array [],
669 + "duration": 0.014,
670 + "flamechart": Array [],
671 + "internalModuleSourceToRanges": Map {
672 + undefined => Array [
673 + Array [
674 + Object {
675 + "functionName": "<filtered-file-system-path>",
676 + },
677 + Object {
678 + "functionName": "dule-stop-<filtered-file-system-path>",
679 + },
680 + ],
681 + ],
682 + },
683 + "laneToLabelMap": Map {
684 + 0 => "Sync",
685 + 1 => "InputContinuousHydration",
686 + 2 => "InputContinuous",
687 + 3 => "DefaultHydration",
688 + 4 => "Default",
689 + 5 => "TransitionHydration",
690 + 6 => "Transition",
691 + 7 => "Transition",
692 + 8 => "Transition",
693 + 9 => "Transition",
694 + 10 => "Transition",
695 + 11 => "Transition",
696 + 12 => "Transition",
697 + 13 => "Transition",
698 + 14 => "Transition",
699 + 15 => "Transition",
700 + 16 => "Transition",
701 + 17 => "Transition",
702 + 18 => "Transition",
703 + 19 => "Transition",
704 + 20 => "Transition",
705 + 21 => "Transition",
706 + 22 => "Retry",
707 + 23 => "Retry",
708 + 24 => "Retry",
709 + 25 => "Retry",
710 + 26 => "Retry",
711 + 27 => "SelectiveHydration",
712 + 28 => "IdleHydration",
713 + 29 => "Idle",
714 + 30 => "Offscreen",
715 + },
716 + "laneToReactMeasureMap": Map {
717 + 0 => Array [
718 + Object {
719 + "batchUID": 0,
720 + "depth": 0,
721 + "duration": 0.01,
722 + "lanes": Array [
723 + 0,
724 + ],
725 + "timestamp": 0.004,
726 + "type": "render-idle",
727 + },
728 + Object {
729 + "batchUID": 0,
730 + "depth": 0,
731 + "duration": 0.001,
732 + "lanes": Array [
733 + 0,
734 + ],
735 + "timestamp": 0.004,
736 + "type": "render",
737 + },
738 + Object {
739 + "batchUID": 0,
740 + "depth": 0,
741 + "duration": 0.008,
742 + "lanes": Array [
743 + 0,
744 + ],
745 + "timestamp": 0.006,
746 + "type": "commit",
747 + },
748 + Object {
749 + "batchUID": 0,
750 + "depth": 1,
751 + "duration": 0.0009999999999999992,
752 + "lanes": Array [
753 + 0,
754 + ],
755 + "timestamp": 0.012,
756 + "type": "layout-effects",
757 + },
758 + ],
759 + 1 => Array [],
760 + 2 => Array [],
761 + 3 => Array [],
762 + 4 => Array [],
763 + 5 => Array [],
764 + 6 => Array [],
765 + 7 => Array [],
766 + 8 => Array [],
767 + 9 => Array [],
768 + 10 => Array [],
769 + 11 => Array [],
770 + 12 => Array [],
771 + 13 => Array [],
772 + 14 => Array [],
773 + 15 => Array [],
774 + 16 => Array [],
775 + 17 => Array [],
776 + 18 => Array [],
777 + 19 => Array [],
778 + 20 => Array [],
779 + 21 => Array [],
780 + 22 => Array [],
781 + 23 => Array [],
782 + 24 => Array [],
783 + 25 => Array [],
784 + 26 => Array [],
785 + 27 => Array [],
786 + 28 => Array [],
787 + 29 => Array [],
788 + 30 => Array [],
789 + },
790 + "nativeEvents": Array [],
791 + "networkMeasures": Array [],
792 + "otherUserTimingMarks": Array [],
793 + "reactVersion": "0.0.0",
794 + "schedulingEvents": Array [
795 + Object {
796 + "lanes": Array [
797 + 0,
798 + ],
799 + "timestamp": 0.003,
800 + "type": "schedule-render",
801 + "warning": null,
802 + },
803 + ],
804 + "snapshotHeight": 0,
805 + "snapshots": Array [],
806 + "startTime": 4,
807 + "suspenseEvents": Array [],
808 + "thrownErrors": Array [],
809 + }
810 + `);
811 + });
812 +
813 + it('should process a sample createRoot render sequence', async () => {
814 + function App() {
815 + const [didMount, setDidMount] = React.useState(false);
816 + React.useEffect(() => {
817 + if (!didMount) {
818 + setDidMount(true);
819 + }
820 + });
821 + return true;
822 + }
823 +
824 + const root = ReactDOM.createRoot(document.createElement('div'));
825 + utils.act(() => root.render(<App />));
826 +
827 + const data = await preprocessData([
828 + ...createBoilerplateEntries(),
829 + ...createUserTimingData(clearedMarks),
830 + ]);
831 + expect(data).toMatchInlineSnapshot(`
832 + Object {
833 + "batchUIDToMeasuresMap": Map {
834 + 0 => Array [
835 + Object {
836 + "batchUID": 0,
837 + "depth": 0,
838 + "duration": 0.012,
839 + "lanes": Array [
840 + 4,
841 + ],
842 + "timestamp": 0.004,
843 + "type": "render-idle",
844 + },
845 + Object {
846 + "batchUID": 0,
847 + "depth": 0,
848 + "duration": 0.003,
849 + "lanes": Array [
850 + 4,
851 + ],
852 + "timestamp": 0.004,
853 + "type": "render",
854 + },
855 + Object {
856 + "batchUID": 0,
857 + "depth": 0,
858 + "duration": 0.008,
859 + "lanes": Array [
860 + 4,
861 + ],
862 + "timestamp": 0.008,
863 + "type": "commit",
864 + },
865 + Object {
866 + "batchUID": 0,
867 + "depth": 1,
868 + "duration": 0.0009999999999999992,
869 + "lanes": Array [
870 + 4,
871 + ],
872 + "timestamp": 0.014,
873 + "type": "layout-effects",
874 + },
875 + Object {
876 + "batchUID": 0,
877 + "depth": 0,
878 + "duration": 0.004,
879 + "lanes": Array [
880 + 4,
881 + ],
882 + "timestamp": 0.017,
883 + "type": "passive-effects",
884 + },
885 + ],
886 + 1 => Array [
887 + Object {
888 + "batchUID": 1,
889 + "depth": 0,
890 + "duration": 0.012000000000000004,
891 + "lanes": Array [
892 + 4,
893 + ],
894 + "timestamp": 0.022,
895 + "type": "render-idle",
896 + },
897 + Object {
898 + "batchUID": 1,
899 + "depth": 0,
900 + "duration": 0.0030000000000000027,
901 + "lanes": Array [
902 + 4,
903 + ],
904 + "timestamp": 0.022,
905 + "type": "render",
906 + },
907 + Object {
908 + "batchUID": 1,
909 + "depth": 0,
910 + "duration": 0.008000000000000004,
911 + "lanes": Array [
912 + 4,
913 + ],
914 + "timestamp": 0.026,
915 + "type": "commit",
916 + },
917 + Object {
918 + "batchUID": 1,
919 + "depth": 1,
920 + "duration": 0.0010000000000000009,
921 + "lanes": Array [
922 + 4,
923 + ],
924 + "timestamp": 0.032,
925 + "type": "layout-effects",
926 + },
927 + Object {
928 + "batchUID": 1,
929 + "depth": 0,
930 + "duration": 0.0029999999999999957,
931 + "lanes": Array [
932 + 4,
933 + ],
934 + "timestamp": 0.035,
935 + "type": "passive-effects",
936 + },
937 + ],
938 + },
939 + "componentMeasures": Array [
940 + Object {
941 + "componentName": "App",
942 + "duration": 0.001,
943 + "timestamp": 0.005,
944 + "type": "render",
945 + "warning": null,
946 + },
947 + Object {
948 + "componentName": "App",
949 + "duration": 0.0020000000000000018,
950 + "timestamp": 0.018,
951 + "type": "passive-effect-mount",
952 + "warning": null,
953 + },
954 + Object {
955 + "componentName": "App",
956 + "duration": 0.0010000000000000009,
957 + "timestamp": 0.023,
958 + "type": "render",
959 + "warning": null,
960 + },
961 + Object {
962 + "componentName": "App",
963 + "duration": 0.0010000000000000009,
964 + "timestamp": 0.036,
965 + "type": "passive-effect-mount",
966 + "warning": null,
967 + },
968 + ],
969 + "duration": 0.038,
970 + "flamechart": Array [],
971 + "internalModuleSourceToRanges": Map {
972 + undefined => Array [
973 + Array [
974 + Object {
975 + "functionName": "<filtered-file-system-path>",
976 + },
977 + Object {
978 + "functionName": "dule-stop-<filtered-file-system-path>",
979 + },
980 + ],
981 + ],
982 + },
983 + "laneToLabelMap": Map {
984 + 0 => "Sync",
985 + 1 => "InputContinuousHydration",
986 + 2 => "InputContinuous",
987 + 3 => "DefaultHydration",
988 + 4 => "Default",
989 + 5 => "TransitionHydration",
990 + 6 => "Transition",
991 + 7 => "Transition",
992 + 8 => "Transition",
993 + 9 => "Transition",
994 + 10 => "Transition",
995 + 11 => "Transition",
996 + 12 => "Transition",
997 + 13 => "Transition",
998 + 14 => "Transition",
999 + 15 => "Transition",
1000 + 16 => "Transition",
1001 + 17 => "Transition",
1002 + 18 => "Transition",
1003 + 19 => "Transition",
1004 + 20 => "Transition",
1005 + 21 => "Transition",
1006 + 22 => "Retry",
1007 + 23 => "Retry",
1008 + 24 => "Retry",
1009 + 25 => "Retry",
1010 + 26 => "Retry",
1011 + 27 => "SelectiveHydration",
1012 + 28 => "IdleHydration",
1013 + 29 => "Idle",
1014 + 30 => "Offscreen",
1015 + },
1016 + "laneToReactMeasureMap": Map {
1017 + 0 => Array [],
1018 + 1 => Array [],
1019 + 2 => Array [],
1020 + 3 => Array [],
1021 + 4 => Array [
1022 + Object {
1023 + "batchUID": 0,
1024 + "depth": 0,
1025 + "duration": 0.012,
1026 + "lanes": Array [
1027 + 4,
1028 + ],
1029 + "timestamp": 0.004,
1030 + "type": "render-idle",
1031 + },
1032 + Object {
1033 + "batchUID": 0,
1034 + "depth": 0,
1035 + "duration": 0.003,
1036 + "lanes": Array [
1037 + 4,
1038 + ],
1039 + "timestamp": 0.004,
1040 + "type": "render",
1041 + },
1042 + Object {
1043 + "batchUID": 0,
1044 + "depth": 0,
1045 + "duration": 0.008,
1046 + "lanes": Array [
1047 + 4,
1048 + ],
1049 + "timestamp": 0.008,
1050 + "type": "commit",
1051 + },
1052 + Object {
1053 + "batchUID": 0,
1054 + "depth": 1,
1055 + "duration": 0.0009999999999999992,
1056 + "lanes": Array [
1057 + 4,
1058 + ],
1059 + "timestamp": 0.014,
1060 + "type": "layout-effects",
1061 + },
1062 + Object {
1063 + "batchUID": 0,
1064 + "depth": 0,
1065 + "duration": 0.004,
1066 + "lanes": Array [
1067 + 4,
1068 + ],
1069 + "timestamp": 0.017,
1070 + "type": "passive-effects",
1071 + },
1072 + Object {
1073 + "batchUID": 1,
1074 + "depth": 0,
1075 + "duration": 0.012000000000000004,
1076 + "lanes": Array [
1077 + 4,
1078 + ],
1079 + "timestamp": 0.022,
1080 + "type": "render-idle",
1081 + },
1082 + Object {
1083 + "batchUID": 1,
1084 + "depth": 0,
1085 + "duration": 0.0030000000000000027,
1086 + "lanes": Array [
1087 + 4,
1088 + ],
1089 + "timestamp": 0.022,
1090 + "type": "render",
1091 + },
1092 + Object {
1093 + "batchUID": 1,
1094 + "depth": 0,
1095 + "duration": 0.008000000000000004,
1096 + "lanes": Array [
1097 + 4,
1098 + ],
1099 + "timestamp": 0.026,
1100 + "type": "commit",
1101 + },
1102 + Object {
1103 + "batchUID": 1,
1104 + "depth": 1,
1105 + "duration": 0.0010000000000000009,
1106 + "lanes": Array [
1107 + 4,
1108 + ],
1109 + "timestamp": 0.032,
1110 + "type": "layout-effects",
1111 + },
1112 + Object {
1113 + "batchUID": 1,
1114 + "depth": 0,
1115 + "duration": 0.0029999999999999957,
1116 + "lanes": Array [
1117 + 4,
1118 + ],
1119 + "timestamp": 0.035,
1120 + "type": "passive-effects",
1121 + },
1122 + ],
1123 + 5 => Array [],
1124 + 6 => Array [],
1125 + 7 => Array [],
1126 + 8 => Array [],
1127 + 9 => Array [],
1128 + 10 => Array [],
1129 + 11 => Array [],
1130 + 12 => Array [],
1131 + 13 => Array [],
1132 + 14 => Array [],
1133 + 15 => Array [],
1134 + 16 => Array [],
1135 + 17 => Array [],
1136 + 18 => Array [],
1137 + 19 => Array [],
1138 + 20 => Array [],
1139 + 21 => Array [],
1140 + 22 => Array [],
1141 + 23 => Array [],
1142 + 24 => Array [],
1143 + 25 => Array [],
1144 + 26 => Array [],
1145 + 27 => Array [],
1146 + 28 => Array [],
1147 + 29 => Array [],
1148 + 30 => Array [],
1149 + },
1150 + "nativeEvents": Array [],
1151 + "networkMeasures": Array [],
1152 + "otherUserTimingMarks": Array [],
1153 + "reactVersion": "0.0.0",
1154 + "schedulingEvents": Array [
1155 + Object {
1156 + "lanes": Array [
1157 + 4,
1158 + ],
1159 + "timestamp": 0.003,
1160 + "type": "schedule-render",
1161 + "warning": null,
1162 + },
1163 + Object {
1164 + "componentName": "App",
1165 + "lanes": Array [
1166 + 4,
1167 + ],
1168 + "timestamp": 0.019,
1169 + "type": "schedule-state-update",
1170 + "warning": null,
1171 + },
1172 + ],
1173 + "snapshotHeight": 0,
1174 + "snapshots": Array [],
1175 + "startTime": 4,
1176 + "suspenseEvents": Array [],
1177 + "thrownErrors": Array [],
1178 + }
1179 + `);
1180 + });
1181 +
1182 + it('should error if events and measures are incomplete', async () => {
1183 + const container = document.createElement('div');
1184 + utils.legacyRender(<div />, container);
1185 +
1186 + const invalidMarks = clearedMarks.filter(
1187 + mark => !mark.includes('render-stop'),
1188 + );
1189 + const invalidUserTimingData = createUserTimingData(invalidMarks);
1190 +
1191 + const error = spyOn(console, 'error');
1192 + preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
1193 + expect(error).toHaveBeenCalled();
1194 + });
1195 +
1196 + it('should error if work is completed without being started', async () => {
1197 + const container = document.createElement('div');
1198 + utils.legacyRender(<div />, container);
1199 +
1200 + const invalidMarks = clearedMarks.filter(
1201 + mark => !mark.includes('render-start'),
1202 + );
1203 + const invalidUserTimingData = createUserTimingData(invalidMarks);
1204 +
1205 + const error = spyOn(console, 'error');
1206 + preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
1207 + expect(error).toHaveBeenCalled();
1208 + });
1209 +
1210 + it('should populate other user timing marks', async () => {
1211 + const userTimingData = createUserTimingData([]);
1212 + userTimingData.push(
1213 + createUserTimingEntry({
1214 + args: {},
1215 + cat: 'blink.user_timing',
1216 + id: '0xcdf75f7c',
1217 + name: 'VCWithoutImage: root',
1218 + ph: 'n',
1219 + scope: 'blink.user_timing',
1220 + }),
1221 + );
1222 + userTimingData.push(
1223 + createUserTimingEntry({
1224 + cat: 'blink.user_timing',
1225 + name: '--a-mark-that-looks-like-one-of-ours',
1226 + ph: 'R',
1227 + }),
1228 + );
1229 + userTimingData.push(
1230 + createUserTimingEntry({
1231 + cat: 'blink.user_timing',
1232 + name: 'Some other mark',
1233 + ph: 'R',
1234 + }),
1235 + );
1236 +
1237 + const data = await preprocessData([
1238 + ...createBoilerplateEntries(),
1239 + ...userTimingData,
1240 + ]);
1241 + expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
1242 + Array [
1243 + Object {
1244 + "name": "VCWithoutImage: root",
1245 + "timestamp": 0.003,
1246 + },
1247 + Object {
1248 + "name": "--a-mark-that-looks-like-one-of-ours",
1249 + "timestamp": 0.004,
1250 + },
1251 + Object {
1252 + "name": "Some other mark",
1253 + "timestamp": 0.005,
1254 + },
1255 + ]
1256 + `);
1257 + });
1258 +
1259 + it('should include a suspended resource "displayName" if one is set', async () => {
1260 + let promise = null;
1261 + let resolvedValue = null;
1262 + function readValue(value) {
1263 + if (resolvedValue !== null) {
1264 + return resolvedValue;
1265 + } else if (promise === null) {
1266 + promise = Promise.resolve(true).then(() => {
1267 + resolvedValue = value;
1268 + });
1269 + promise.displayName = 'Testing displayName';
1270 + }
1271 + throw promise;
1272 + }
1273 +
1274 + function Component() {
1275 + const value = readValue(123);
1276 + return value;
1277 + }
1278 +
1279 + const testMarks = [creactCpuProfilerSample()];
1280 +
1281 + const root = ReactDOM.createRoot(document.createElement('div'));
1282 + utils.act(() =>
1283 + root.render(
1284 + <React.Suspense fallback="Loading...">
1285 + <Component />
1286 + </React.Suspense>,
1287 + ),
1288 + );
1289 +
1290 + testMarks.push(...createUserTimingData(clearedMarks));
1291 +
1292 + let data;
1293 + await utils.actAsync(async () => {
1294 + data = await preprocessData(testMarks);
1295 + });
1296 + expect(data.suspenseEvents).toHaveLength(1);
1297 + expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1298 + });
1299 +
1300 + describe('warnings', () => {
1301 + describe('long event handlers', () => {
1302 + it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
1303 + function App() {
1304 + return null;
1305 + }
1306 +
1307 + const testMarks = [
1308 + creactCpuProfilerSample(),
1309 + ...createBoilerplateEntries(),
1310 + createNativeEventEntry('click', 5),
1311 + ];
1312 +
1313 + clearPendingMarks();
1314 +
1315 + utils.legacyRender(<App />, document.createElement('div'));
1316 +
1317 + testMarks.push(...createUserTimingData(clearedMarks));
1318 +
1319 + const data = await preprocessData(testMarks);
1320 + const event = data.nativeEvents.find(({type}) => type === 'click');
1321 + expect(event.warning).toBe(null);
1322 + });
1323 +
1324 + it('should not warn about long events if the cause was non-React JavaScript', async () => {
1325 + function App() {
1326 + return null;
1327 + }
1328 +
1329 + const testMarks = [
1330 + creactCpuProfilerSample(),
1331 + ...createBoilerplateEntries(),
1332 + createNativeEventEntry('click', 25000),
1333 + ];
1334 +
1335 + startTime += 2000;
1336 +
1337 + clearPendingMarks();
1338 +
1339 + utils.legacyRender(<App />, document.createElement('div'));
1340 +
1341 + testMarks.push(...createUserTimingData(clearedMarks));
1342 +
1343 + const data = await preprocessData(testMarks);
1344 + const event = data.nativeEvents.find(({type}) => type === 'click');
1345 + expect(event.warning).toBe(null);
1346 + });
1347 +
1348 + it('should warn when React scedules a long (sync) update inside of an event', async () => {
1349 + function App() {
1350 + return null;
1351 + }
1352 +
1353 + const testMarks = [
1354 + creactCpuProfilerSample(),
1355 + ...createBoilerplateEntries(),
1356 + createNativeEventEntry('click', 25000),
1357 + ];
1358 +
1359 + clearPendingMarks();
1360 +
1361 + utils.legacyRender(<App />, document.createElement('div'));
1362 +
1363 + clearedMarks.forEach(markName => {
1364 + if (markName === '--render-stop') {
1365 + // Fake a long running render
1366 + startTime += 20000;
1367 + }
1368 +
1369 + testMarks.push({
1370 + pid: ++pid,
1371 + tid: ++tid,
1372 + ts: ++startTime,
1373 + args: {data: {}},
1374 + cat: 'blink.user_timing',
1375 + name: markName,
1376 + ph: 'R',
1377 + });
1378 + });
1379 +
1380 + const data = await preprocessData(testMarks);
1381 + const event = data.nativeEvents.find(({type}) => type === 'click');
1382 + expect(event.warning).toMatchInlineSnapshot(
1383 + `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
1384 + );
1385 + });
1386 +
1387 + it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
1388 + function Yield({id, value}) {
1389 + Scheduler.unstable_yieldValue(`${id}:${value}`);
1390 + return null;
1391 + }
1392 +
1393 + const testMarks = [
1394 + creactCpuProfilerSample(),
1395 + ...createBoilerplateEntries(),
1396 + ];
1397 +
1398 + // Advance the clock by some arbitrary amount.
1399 + startTime += 50000;
1400 +
1401 + const root = ReactDOM.createRoot(document.createElement('div'));
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 + expect(Scheduler).toFlushAndYieldThrough(['A:1']);
1414 +
1415 + testMarks.push(...createUserTimingData(clearedMarks));
1416 + clearPendingMarks();
1417 +
1418 + // Advance the clock some more to make the pending React update seem long.
1419 + startTime += 20000;
1420 +
1421 + // Fake a long "click" event in the middle
1422 + // and schedule a sync update that will also flush the previous work.
1423 + testMarks.push(createNativeEventEntry('click', 25000));
1424 + ReactDOM.flushSync(() => {
1425 + root.render(
1426 + <>
1427 + <Yield id="A" value={2} />
1428 + <Yield id="B" value={2} />
1429 + </>,
1430 + );
1431 + });
1432 + });
1433 +
1434 + expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
1435 +
1436 + testMarks.push(...createUserTimingData(clearedMarks));
1437 +
1438 + const data = await preprocessData(testMarks);
1439 + const event = data.nativeEvents.find(({type}) => type === 'click');
1440 + expect(event.warning).toBe(null);
1441 + });
1442 + });
1443 +
1444 + describe('nested updates', () => {
1445 + it('should not warn about short nested (state) updates during layout effects', async () => {
1446 + function Component() {
1447 + const [didMount, setDidMount] = React.useState(false);
1448 + Scheduler.unstable_yieldValue(
1449 + `Component ${didMount ? 'update' : 'mount'}`,
1450 + );
1451 + React.useLayoutEffect(() => {
1452 + setDidMount(true);
1453 + }, []);
1454 + return didMount;
1455 + }
1456 +
1457 + const root = ReactDOM.createRoot(document.createElement('div'));
1458 + utils.act(() => {
1459 + root.render(<Component />);
1460 + });
1461 +
1462 + expect(Scheduler).toHaveYielded([
1463 + 'Component mount',
1464 + 'Component update',
1465 + ]);
1466 +
1467 + const data = await preprocessData([
1468 + ...createBoilerplateEntries(),
1469 + ...createUserTimingData(clearedMarks),
1470 + ]);
1471 +
1472 + const event = data.schedulingEvents.find(
1473 + ({type}) => type === 'schedule-state-update',
1474 + );
1475 + expect(event.warning).toBe(null);
1476 + });
1477 +
1478 + it('should not warn about short (forced) updates during layout effects', async () => {
1479 + class Component extends React.Component {
1480 + _didMount: boolean = false;
1481 + componentDidMount() {
1482 + this._didMount = true;
1483 + this.forceUpdate();
1484 + }
1485 + render() {
1486 + Scheduler.unstable_yieldValue(
1487 + `Component ${this._didMount ? 'update' : 'mount'}`,
1488 + );
1489 + return null;
1490 + }
1491 + }
1492 +
1493 + const root = ReactDOM.createRoot(document.createElement('div'));
1494 + utils.act(() => {
1495 + root.render(<Component />);
1496 + });
1497 +
1498 + expect(Scheduler).toHaveYielded([
1499 + 'Component mount',
1500 + 'Component update',
1501 + ]);
1502 +
1503 + const data = await preprocessData([
1504 + ...createBoilerplateEntries(),
1505 + ...createUserTimingData(clearedMarks),
1506 + ]);
1507 +
1508 + const event = data.schedulingEvents.find(
1509 + ({type}) => type === 'schedule-force-update',
1510 + );
1511 + expect(event.warning).toBe(null);
1512 + });
1513 +
1514 + it('should warn about long nested (state) updates during layout effects', async () => {
1515 + function Component() {
1516 + const [didMount, setDidMount] = React.useState(false);
1517 + Scheduler.unstable_yieldValue(
1518 + `Component ${didMount ? 'update' : 'mount'}`,
1519 + );
1520 + // Fake a long render
1521 + startTime += 20000;
1522 + React.useLayoutEffect(() => {
1523 + setDidMount(true);
1524 + }, []);
1525 + return didMount;
1526 + }
1527 +
1528 + const cpuProfilerSample = creactCpuProfilerSample();
1529 +
1530 + const root = ReactDOM.createRoot(document.createElement('div'));
1531 + utils.act(() => {
1532 + root.render(<Component />);
1533 + });
1534 +
1535 + expect(Scheduler).toHaveYielded([
1536 + 'Component mount',
1537 + 'Component update',
1538 + ]);
1539 +
1540 + const testMarks = [];
1541 + clearedMarks.forEach(markName => {
1542 + if (markName === '--component-render-start-Component') {
1543 + // Fake a long running render
1544 + startTime += 20000;
1545 + }
1546 +
1547 + testMarks.push({
1548 + pid: ++pid,
1549 + tid: ++tid,
1550 + ts: ++startTime,
1551 + args: {data: {}},
1552 + cat: 'blink.user_timing',
1553 + name: markName,
1554 + ph: 'R',
1555 + });
1556 + });
1557 +
1558 + const data = await preprocessData([
1559 + cpuProfilerSample,
1560 + ...createBoilerplateEntries(),
1561 + ...testMarks,
1562 + ]);
1563 +
1564 + const event = data.schedulingEvents.find(
1565 + ({type}) => type === 'schedule-state-update',
1566 + );
1567 + expect(event.warning).toMatchInlineSnapshot(
1568 + `"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)."`,
1569 + );
1570 + });
1571 +
1572 + it('should warn about long nested (forced) updates during layout effects', async () => {
1573 + class Component extends React.Component {
1574 + _didMount: boolean = false;
1575 + componentDidMount() {
1576 + this._didMount = true;
1577 + this.forceUpdate();
1578 + }
1579 + render() {
1580 + Scheduler.unstable_yieldValue(
1581 + `Component ${this._didMount ? 'update' : 'mount'}`,
1582 + );
1583 + return null;
1584 + }
1585 + }
1586 +
1587 + const cpuProfilerSample = creactCpuProfilerSample();
1588 +
1589 + const root = ReactDOM.createRoot(document.createElement('div'));
1590 + utils.act(() => {
1591 + root.render(<Component />);
1592 + });
1593 +
1594 + expect(Scheduler).toHaveYielded([
1595 + 'Component mount',
1596 + 'Component update',
1597 + ]);
1598 +
1599 + const testMarks = [];
1600 + clearedMarks.forEach(markName => {
1601 + if (markName === '--component-render-start-Component') {
1602 + // Fake a long running render
1603 + startTime += 20000;
1604 + }
1605 +
1606 + testMarks.push({
1607 + pid: ++pid,
1608 + tid: ++tid,
1609 + ts: ++startTime,
1610 + args: {data: {}},
1611 + cat: 'blink.user_timing',
1612 + name: markName,
1613 + ph: 'R',
1614 + });
1615 + });
1616 +
1617 + const data = await preprocessData([
1618 + cpuProfilerSample,
1619 + ...createBoilerplateEntries(),
1620 + ...testMarks,
1621 + ]);
1622 +
1623 + const event = data.schedulingEvents.find(
1624 + ({type}) => type === 'schedule-force-update',
1625 + );
1626 + expect(event.warning).toMatchInlineSnapshot(
1627 + `"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)."`,
1628 + );
1629 + });
1630 +
1631 + it('should not warn about transition updates scheduled during commit phase', async () => {
1632 + function Component() {
1633 + const [value, setValue] = React.useState(0);
1634 + // eslint-disable-next-line no-unused-vars
1635 + const [isPending, startTransition] = React.useTransition();
1636 +
1637 + Scheduler.unstable_yieldValue(
1638 + `Component rendered with value ${value}`,
1639 + );
1640 +
1641 + // Fake a long render
1642 + if (value !== 0) {
1643 + Scheduler.unstable_yieldValue('Long render');
1644 + startTime += 20000;
1645 + }
1646 +
1647 + React.useLayoutEffect(() => {
1648 + startTransition(() => {
1649 + setValue(1);
1650 + });
1651 + }, []);
1652 +
1653 + return value;
1654 + }
1655 +
1656 + const cpuProfilerSample = creactCpuProfilerSample();
1657 +
1658 + const root = ReactDOM.createRoot(document.createElement('div'));
1659 + utils.act(() => {
1660 + root.render(<Component />);
1661 + });
1662 +
1663 + expect(Scheduler).toHaveYielded([
1664 + 'Component rendered with value 0',
1665 + 'Component rendered with value 0',
1666 + 'Component rendered with value 1',
1667 + 'Long render',
1668 + ]);
1669 +
1670 + const testMarks = [];
1671 + clearedMarks.forEach(markName => {
1672 + if (markName === '--component-render-start-Component') {
1673 + // Fake a long running render
1674 + startTime += 20000;
1675 + }
1676 +
1677 + testMarks.push({
1678 + pid: ++pid,
1679 + tid: ++tid,
1680 + ts: ++startTime,
1681 + args: {data: {}},
1682 + cat: 'blink.user_timing',
1683 + name: markName,
1684 + ph: 'R',
1685 + });
1686 + });
1687 +
1688 + const data = await preprocessData([
1689 + cpuProfilerSample,
1690 + ...createBoilerplateEntries(),
1691 + ...testMarks,
1692 + ]);
1693 +
1694 + data.schedulingEvents.forEach(event => {
1695 + expect(event.warning).toBeNull();
1696 + });
1697 + });
1698 +
1699 + it('should not warn about deferred value updates scheduled during commit phase', async () => {
1700 + function Component() {
1701 + const [value, setValue] = React.useState(0);
1702 + const deferredValue = React.useDeferredValue(value);
1703 +
1704 + Scheduler.unstable_yieldValue(
1705 + `Component rendered with value ${value} and deferredValue ${deferredValue}`,
1706 + );
1707 +
1708 + // Fake a long render
1709 + if (deferredValue !== 0) {
1710 + Scheduler.unstable_yieldValue('Long render');
1711 + startTime += 20000;
1712 + }
1713 +
1714 + React.useLayoutEffect(() => {
1715 + setValue(1);
1716 + }, []);
1717 +
1718 + return value + deferredValue;
1719 + }
1720 +
1721 + const cpuProfilerSample = creactCpuProfilerSample();
1722 +
1723 + const root = ReactDOM.createRoot(document.createElement('div'));
1724 + utils.act(() => {
1725 + root.render(<Component />);
1726 + });
1727 +
1728 + expect(Scheduler).toHaveYielded([
1729 + 'Component rendered with value 0 and deferredValue 0',
1730 + 'Component rendered with value 1 and deferredValue 0',
1731 + 'Component rendered with value 1 and deferredValue 1',
1732 + 'Long render',
1733 + ]);
1734 +
1735 + const testMarks = [];
1736 + clearedMarks.forEach(markName => {
1737 + if (markName === '--component-render-start-Component') {
1738 + // Fake a long running render
1739 + startTime += 20000;
1740 + }
1741 +
1742 + testMarks.push({
1743 + pid: ++pid,
1744 + tid: ++tid,
1745 + ts: ++startTime,
1746 + args: {data: {}},
1747 + cat: 'blink.user_timing',
1748 + name: markName,
1749 + ph: 'R',
1750 + });
1751 + });
1752 +
1753 + const data = await preprocessData([
1754 + cpuProfilerSample,
1755 + ...createBoilerplateEntries(),
1756 + ...testMarks,
1757 + ]);
1758 +
1759 + data.schedulingEvents.forEach(event => {
1760 + expect(event.warning).toBeNull();
1761 + });
1762 + });
1763 + });
1764 +
1765 + describe('errors thrown while rendering', () => {
1766 + it('shoult parse Errors thrown during render', async () => {
1767 + spyOn(console, 'error');
1768 +
1769 + class ErrorBoundary extends React.Component {
1770 + state = {error: null};
1771 + componentDidCatch(error) {
1772 + this.setState({error});
1773 + }
1774 + render() {
1775 + if (this.state.error) {
1776 + return null;
1777 + }
1778 + return this.props.children;
1779 + }
1780 + }
1781 +
1782 + function ExampleThatThrows() {
1783 + throw Error('Expected error');
1784 + }
1785 +
1786 + const testMarks = [creactCpuProfilerSample()];
1787 +
1788 + // Mount and commit the app
1789 + const root = ReactDOM.createRoot(document.createElement('div'));
1790 + utils.act(() =>
1791 + root.render(
1792 + <ErrorBoundary>
1793 + <ExampleThatThrows />
1794 + </ErrorBoundary>,
1795 + ),
1796 + );
1797 +
1798 + testMarks.push(...createUserTimingData(clearedMarks));
1799 +
1800 + const data = await preprocessData(testMarks);
1801 + expect(data.thrownErrors).toHaveLength(2);
1802 + expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
1803 + '"Expected error"',
1804 + );
1805 + });
1806 + });
1807 +
1808 + describe('suspend during an update', () => {
1809 + // This also tests an edge case where the a component suspends while profiling
1810 + // before the first commit is logged (so the lane-to-labels map will not yet exist).
1811 + it('should warn about suspending during an udpate', async () => {
1812 + let promise = null;
1813 + let resolvedValue = null;
1814 + function readValue(value) {
1815 + if (resolvedValue !== null) {
1816 + return resolvedValue;
1817 + } else if (promise === null) {
1818 + promise = Promise.resolve(true).then(() => {
1819 + resolvedValue = value;
1820 + });
1821 + }
1822 + throw promise;
1823 + }
1824 +
1825 + function Component({shouldSuspend}) {
1826 + Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1827 + if (shouldSuspend) {
1828 + readValue(123);
1829 + }
1830 + return null;
1831 + }
1832 +
1833 + // Mount and commit the app
1834 + const root = ReactDOM.createRoot(document.createElement('div'));
1835 + utils.act(() =>
1836 + root.render(
1837 + <React.Suspense fallback="Loading...">
1838 + <Component shouldSuspend={false} />
1839 + </React.Suspense>,
1840 + ),
1841 + );
1842 +
1843 + clearPendingMarks();
1844 +
1845 + const testMarks = [creactCpuProfilerSample()];
1846 +
1847 + // Start profiling and suspend during a render.
1848 + utils.act(() =>
1849 + root.render(
1850 + <React.Suspense fallback="Loading...">
1851 + <Component shouldSuspend={true} />
1852 + </React.Suspense>,
1853 + ),
1854 + );
1855 +
1856 + testMarks.push(...createUserTimingData(clearedMarks));
1857 +
1858 + let data;
1859 + await utils.actAsync(async () => {
1860 + data = await preprocessData(testMarks);
1861 + });
1862 + expect(data.suspenseEvents).toHaveLength(1);
1863 + expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
1864 + `"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."`,
1865 + );
1866 + });
1867 +
1868 + it('should not warn about suspending during an transition', async () => {
1869 + let promise = null;
1870 + let resolvedValue = null;
1871 + function readValue(value) {
1872 + if (resolvedValue !== null) {
1873 + return resolvedValue;
1874 + } else if (promise === null) {
1875 + promise = Promise.resolve(true).then(() => {
1876 + resolvedValue = value;
1877 + });
1878 + }
1879 + throw promise;
1880 + }
1881 +
1882 + function Component({shouldSuspend}) {
1883 + Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1884 + if (shouldSuspend) {
1885 + readValue(123);
1886 + }
1887 + return null;
1888 + }
1889 +
1890 + // Mount and commit the app
1891 + const root = ReactDOM.createRoot(document.createElement('div'));
1892 + utils.act(() =>
1893 + root.render(
1894 + <React.Suspense fallback="Loading...">
1895 + <Component shouldSuspend={false} />
1896 + </React.Suspense>,
1897 + ),
1898 + );
1899 +
1900 + const testMarks = [creactCpuProfilerSample()];
1901 +
1902 + // Start profiling and suspend during a render.
1903 + await utils.actAsync(async () =>
1904 + React.startTransition(() =>
1905 + root.render(
1906 + <React.Suspense fallback="Loading...">
1907 + <Component shouldSuspend={true} />
1908 + </React.Suspense>,
1909 + ),
1910 + ),
1911 + );
1912 +
1913 + testMarks.push(...createUserTimingData(clearedMarks));
1914 +
1915 + let data;
1916 + await utils.actAsync(async () => {
1917 + data = await preprocessData(testMarks);
1918 + });
1919 + expect(data.suspenseEvents).toHaveLength(1);
1920 + expect(data.suspenseEvents[0].warning).toBe(null);
1921 + });
1922 + });
1923 + });
1924 +
1925 + // TODO: Add test for snapshot base64 parsing
1926 +
1927 + // TODO: Add test for flamechart parsing
1928 + });
1929 +});
packages/react-devtools-shared/src/__tests__/setupTests.js
+4
@@ -162,3 +162,7 @@ env.afterEach(() => {
162 // so that we don't disconnect the ReactCurrentDispatcher ref.
163 jest.resetModules();
164 });
165 +
166 +expect.extend({
167 + ...require('../../../../scripts/jest/matchers/schedulerTestMatchers'),
168 +});
packages/react-devtools-shared/src/__tests__/utils.js
+8
@@ -163,6 +163,10 @@ export function getRendererID(): number {
163 }
164
165 export function legacyRender(elements, container) {
166 + if (container == null) {
167 + container = document.createElement('div');
168 + }
169 +
170 const ReactDOM = require('react-dom');
171 withErrorsOrWarningsIgnored(
172 ['ReactDOM.render is no longer supported in React 18'],
@@ -170,6 +174,10 @@ export function legacyRender(elements, container) {
174 ReactDOM.render(elements, container);
175 },
176 );
177 +
178 + return () => {
179 + ReactDOM.unmountComponentAtNode(container);
180 + };
181 }
182
183 export function requireTestRenderer(): ReactTestRenderer {
packages/react-devtools-shared/src/backend/profilingHooks.js new
+364
@@ -0,0 +1,364 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {
11 + Lane,
12 + Lanes,
13 + DevToolsProfilingHooks,
14 +} from 'react-devtools-shared/src/backend/types';
15 +import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
16 +import type {Wakeable} from 'shared/ReactTypes';
17 +
18 +import isArray from 'shared/isArray';
19 +import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-timeline/src/constants';
20 +
21 +let performanceTarget: Performance | null = null;
22 +
23 +// If performance exists and supports the subset of the User Timing API that we require.
24 +let supportsUserTiming =
25 + typeof performance !== 'undefined' &&
26 + typeof performance.mark === 'function' &&
27 + typeof performance.clearMarks === 'function';
28 +
29 +let supportsUserTimingV3 = false;
30 +if (supportsUserTiming) {
31 + const CHECK_V3_MARK = '__v3';
32 + const markOptions = {};
33 + // $FlowFixMe: Ignore Flow complaining about needing a value
34 + Object.defineProperty(markOptions, 'startTime', {
35 + get: function() {
36 + supportsUserTimingV3 = true;
37 + return 0;
38 + },
39 + set: function() {},
40 + });
41 +
42 + try {
43 + // $FlowFixMe: Flow expects the User Timing level 2 API.
44 + performance.mark(CHECK_V3_MARK, markOptions);
45 + } catch (error) {
46 + // Ignore
47 + } finally {
48 + performance.clearMarks(CHECK_V3_MARK);
49 + }
50 +}
51 +
52 +if (supportsUserTimingV3) {
53 + performanceTarget = performance;
54 +}
55 +
56 +// Mocking the Performance Object (and User Timing APIs) for testing is fragile.
57 +// This API allows tests to directly override the User Timing APIs.
58 +export function setPerformanceMock_ONLY_FOR_TESTING(
59 + performanceMock: Performance | null,
60 +) {
61 + performanceTarget = performanceMock;
62 + supportsUserTiming = performanceMock !== null;
63 + supportsUserTimingV3 = performanceMock !== null;
64 +}
65 +
66 +function markAndClear(markName) {
67 + // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
68 + ((performanceTarget: any): Performance).mark(markName);
69 + ((performanceTarget: any): Performance).clearMarks(markName);
70 +}
71 +
72 +export function createProfilingHooks({
73 + getDisplayNameForFiber,
74 + getLaneLabelMap,
75 + reactVersion,
76 +}: {|
77 + getDisplayNameForFiber: (fiber: Fiber) => string | null,
78 + getLaneLabelMap?: () => Map<Lane, string>,
79 + reactVersion: string,
80 +|}): DevToolsProfilingHooks {
81 + function markMetadata() {
82 + markAndClear(`--react-version-${reactVersion}`);
83 + markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
84 +
85 + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
86 + if (
87 + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
88 + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
89 + 'function'
90 + ) {
91 + // Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
92 + const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
93 +
94 + // This check would not be required,
95 + // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
96 + if (isArray(ranges)) {
97 + for (let i = 0; i < ranges.length; i++) {
98 + const range = ranges[i];
99 + if (isArray(range) && range.length === 2) {
100 + const [startStackFrame, stopStackFrame] = ranges[i];
101 +
102 + markAndClear(`--react-internal-module-start-${startStackFrame}`);
103 + markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
104 + }
105 + }
106 + }
107 + }
108 +
109 + if (typeof getLaneLabelMap === 'function') {
110 + const map = getLaneLabelMap();
111 + const labels = Array.from(map.values()).join(',');
112 + markAndClear(`--react-lane-labels-${labels}`);
113 + }
114 + }
115 +
116 + function markCommitStarted(lanes: Lanes): void {
117 + if (supportsUserTimingV3) {
118 + markAndClear(`--commit-start-${lanes}`);
119 +
120 + // Certain types of metadata should be logged infrequently.
121 + // Normally we would log this during module init,
122 + // but there's no guarantee a user is profiling at that time.
123 + // Commits happen infrequently (less than renders or state updates)
124 + // so we log this extra information along with a commit.
125 + // It will likely be logged more than once but that's okay.
126 + //
127 + // TODO (timeline) Only log this once, when profiling starts.
128 + // For the first phase– refactoring– we'll match the previous behavior.
129 + markMetadata();
130 + }
131 + }
132 +
133 + function markCommitStopped(): void {
134 + if (supportsUserTimingV3) {
135 + markAndClear('--commit-stop');
136 + }
137 + }
138 +
139 + function markComponentRenderStarted(fiber: Fiber): void {
140 + if (supportsUserTimingV3) {
141 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
142 + // TODO (timeline) Record and cache component stack
143 + markAndClear(`--component-render-start-${componentName}`);
144 + }
145 + }
146 +
147 + function markComponentRenderStopped(): void {
148 + if (supportsUserTimingV3) {
149 + markAndClear('--component-render-stop');
150 + }
151 + }
152 +
153 + function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
154 + if (supportsUserTimingV3) {
155 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
156 + // TODO (timeline) Record and cache component stack
157 + markAndClear(`--component-passive-effect-mount-start-${componentName}`);
158 + }
159 + }
160 +
161 + function markComponentPassiveEffectMountStopped(): void {
162 + if (supportsUserTimingV3) {
163 + markAndClear('--component-passive-effect-mount-stop');
164 + }
165 + }
166 +
167 + function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
168 + if (supportsUserTimingV3) {
169 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
170 + // TODO (timeline) Record and cache component stack
171 + markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
172 + }
173 + }
174 +
175 + function markComponentPassiveEffectUnmountStopped(): void {
176 + if (supportsUserTimingV3) {
177 + markAndClear('--component-passive-effect-unmount-stop');
178 + }
179 + }
180 +
181 + function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
182 + if (supportsUserTimingV3) {
183 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
184 + // TODO (timeline) Record and cache component stack
185 + markAndClear(`--component-layout-effect-mount-start-${componentName}`);
186 + }
187 + }
188 +
189 + function markComponentLayoutEffectMountStopped(): void {
190 + if (supportsUserTimingV3) {
191 + markAndClear('--component-layout-effect-mount-stop');
192 + }
193 + }
194 +
195 + function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
196 + if (supportsUserTimingV3) {
197 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
198 + // TODO (timeline) Record and cache component stack
199 + markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
200 + }
201 + }
202 +
203 + function markComponentLayoutEffectUnmountStopped(): void {
204 + if (supportsUserTimingV3) {
205 + markAndClear('--component-layout-effect-unmount-stop');
206 + }
207 + }
208 +
209 + function markComponentErrored(
210 + fiber: Fiber,
211 + thrownValue: mixed,
212 + lanes: Lanes,
213 + ): void {
214 + if (supportsUserTimingV3) {
215 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
216 + const phase = fiber.alternate === null ? 'mount' : 'update';
217 +
218 + let message = '';
219 + if (
220 + thrownValue !== null &&
221 + typeof thrownValue === 'object' &&
222 + typeof thrownValue.message === 'string'
223 + ) {
224 + message = thrownValue.message;
225 + } else if (typeof thrownValue === 'string') {
226 + message = thrownValue;
227 + }
228 +
229 + // TODO (timeline) Record and cache component stack
230 + markAndClear(`--error-${componentName}-${phase}-${message}`);
231 + }
232 + }
233 +
234 + const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
235 +
236 + // $FlowFixMe: Flow cannot handle polymorphic WeakMaps
237 + const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
238 + let wakeableID: number = 0;
239 + function getWakeableID(wakeable: Wakeable): number {
240 + if (!wakeableIDs.has(wakeable)) {
241 + wakeableIDs.set(wakeable, wakeableID++);
242 + }
243 + return ((wakeableIDs.get(wakeable): any): number);
244 + }
245 +
246 + function markComponentSuspended(
247 + fiber: Fiber,
248 + wakeable: Wakeable,
249 + lanes: Lanes,
250 + ): void {
251 + if (supportsUserTimingV3) {
252 + const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
253 + const id = getWakeableID(wakeable);
254 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
255 + const phase = fiber.alternate === null ? 'mount' : 'update';
256 +
257 + // Following the non-standard fn.displayName convention,
258 + // frameworks like Relay may also annotate Promises with a displayName,
259 + // describing what operation/data the thrown Promise is related to.
260 + // When this is available we should pass it along to the Timeline.
261 + const displayName = (wakeable: any).displayName || '';
262 +
263 + // TODO (timeline) Record and cache component stack
264 + markAndClear(
265 + `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
266 + );
267 + wakeable.then(
268 + () => markAndClear(`--suspense-resolved-${id}-${componentName}`),
269 + () => markAndClear(`--suspense-rejected-${id}-${componentName}`),
270 + );
271 + }
272 + }
273 +
274 + function markLayoutEffectsStarted(lanes: Lanes): void {
275 + if (supportsUserTimingV3) {
276 + markAndClear(`--layout-effects-start-${lanes}`);
277 + }
278 + }
279 +
280 + function markLayoutEffectsStopped(): void {
281 + if (supportsUserTimingV3) {
282 + markAndClear('--layout-effects-stop');
283 + }
284 + }
285 +
286 + function markPassiveEffectsStarted(lanes: Lanes): void {
287 + if (supportsUserTimingV3) {
288 + markAndClear(`--passive-effects-start-${lanes}`);
289 + }
290 + }
291 +
292 + function markPassiveEffectsStopped(): void {
293 + if (supportsUserTimingV3) {
294 + markAndClear('--passive-effects-stop');
295 + }
296 + }
297 +
298 + function markRenderStarted(lanes: Lanes): void {
299 + if (supportsUserTimingV3) {
300 + markAndClear(`--render-start-${lanes}`);
301 + }
302 + }
303 +
304 + function markRenderYielded(): void {
305 + if (supportsUserTimingV3) {
306 + markAndClear('--render-yield');
307 + }
308 + }
309 +
310 + function markRenderStopped(): void {
311 + if (supportsUserTimingV3) {
312 + markAndClear('--render-stop');
313 + }
314 + }
315 +
316 + function markRenderScheduled(lane: Lane): void {
317 + if (supportsUserTimingV3) {
318 + markAndClear(`--schedule-render-${lane}`);
319 + }
320 + }
321 +
322 + function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
323 + if (supportsUserTimingV3) {
324 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
325 + // TODO (timeline) Record and cache component stack
326 + markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
327 + }
328 + }
329 +
330 + function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
331 + if (supportsUserTimingV3) {
332 + const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
333 + // TODO (timeline) Record and cache component stack
334 + markAndClear(`--schedule-state-update-${lane}-${componentName}`);
335 + }
336 + }
337 +
338 + return {
339 + markCommitStarted,
340 + markCommitStopped,
341 + markComponentRenderStarted,
342 + markComponentRenderStopped,
343 + markComponentPassiveEffectMountStarted,
344 + markComponentPassiveEffectMountStopped,
345 + markComponentPassiveEffectUnmountStarted,
346 + markComponentPassiveEffectUnmountStopped,
347 + markComponentLayoutEffectMountStarted,
348 + markComponentLayoutEffectMountStopped,
349 + markComponentLayoutEffectUnmountStarted,
350 + markComponentLayoutEffectUnmountStopped,
351 + markComponentErrored,
352 + markComponentSuspended,
353 + markLayoutEffectsStarted,
354 + markLayoutEffectsStopped,
355 + markPassiveEffectsStarted,
356 + markPassiveEffectsStopped,
357 + markRenderStarted,
358 + markRenderYielded,
359 + markRenderStopped,
360 + markRenderScheduled,
361 + markForceUpdateScheduled,
362 + markStateUpdateScheduled,
363 + };
364 +}
packages/react-devtools-shared/src/backend/renderer.js
+13
@@ -92,6 +92,7 @@ import is from 'shared/objectIs';
92 import isArray from 'shared/isArray';
93 import hasOwnProperty from 'shared/hasOwnProperty';
94 import {getStyleXData} from './StyleX/utils';
95 +import {createProfilingHooks} from './profilingHooks';
96
97 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
98 import type {
@@ -590,6 +591,8 @@ export function attach(
591 } = ReactPriorityLevels;
592
593 const {
594 + getLaneLabelMap,
595 + injectProfilingHooks,
596 overrideHookState,
597 overrideHookStateDeletePath,
598 overrideHookStateRenamePath,
@@ -624,6 +627,16 @@ export function attach(
627 };
628 }
629
630 + if (typeof injectProfilingHooks === 'function') {
631 + injectProfilingHooks(
632 + createProfilingHooks({
633 + getDisplayNameForFiber,
634 + getLaneLabelMap,
635 + reactVersion: version,
636 + }),
637 + );
638 + }
639 +
640 // Tracks Fibers with recently changed number of error/warning messages.
641 // These collections store the Fiber rather than the ID,
642 // in order to avoid generating an ID for Fibers that never get mounted
packages/react-devtools-shared/src/backend/types.js
+53 -1
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {ReactContext} from 'shared/ReactTypes';
10 +import type {ReactContext, Wakeable} from 'shared/ReactTypes';
11 import type {Source} from 'shared/ReactElementType';
12 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
13 import type {
@@ -87,6 +87,9 @@ export type ReactProviderType<T> = {
87 ...
88 };
89
90 +export type Lane = number;
91 +export type Lanes = number;
92 +
93 export type ReactRenderer = {
94 findFiberByHostInstance: (hostInstance: NativeType) => ?Fiber,
95 version: string,
@@ -147,6 +150,9 @@ export type ReactRenderer = {
150 setErrorHandler?: ?(shouldError: (fiber: Object) => ?boolean) => void,
151 // Intentionally opaque type to avoid coupling DevTools to different Fast Refresh versions.
152 scheduleRefresh?: Function,
153 + // 18.0+
154 + injectProfilingHooks?: (profilingHooks: DevToolsProfilingHooks) => void,
155 + getLaneLabelMap?: () => Map<Lane, string>,
156 ...
157 };
158
@@ -390,11 +396,56 @@ export type RendererInterface = {
396 ) => void,
397 unpatchConsoleForStrictMode: () => void,
398 updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
399 +
400 + // Timeline profiler interface
401 +
402 ...
403 };
404
405 export type Handler = (data: any) => void;
406
407 +// Renderers use these APIs to report profiling data to DevTools at runtime.
408 +// They get passed from the DevTools backend to the reconciler during injection.
409 +export type DevToolsProfilingHooks = {|
410 + // Scheduling methods:
411 + markRenderScheduled: (lane: Lane) => void,
412 + markStateUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
413 + markForceUpdateScheduled: (fiber: Fiber, lane: Lane) => void,
414 +
415 + // Work loop level methods:
416 + markRenderStarted: (lanes: Lanes) => void,
417 + markRenderYielded: () => void,
418 + markRenderStopped: () => void,
419 + markCommitStarted: (lanes: Lanes) => void,
420 + markCommitStopped: () => void,
421 + markLayoutEffectsStarted: (lanes: Lanes) => void,
422 + markLayoutEffectsStopped: () => void,
423 + markPassiveEffectsStarted: (lanes: Lanes) => void,
424 + markPassiveEffectsStopped: () => void,
425 +
426 + // Fiber level methods:
427 + markComponentRenderStarted: (fiber: Fiber) => void,
428 + markComponentRenderStopped: () => void,
429 + markComponentErrored: (
430 + fiber: Fiber,
431 + thrownValue: mixed,
432 + lanes: Lanes,
433 + ) => void,
434 + markComponentSuspended: (
435 + fiber: Fiber,
436 + wakeable: Wakeable,
437 + lanes: Lanes,
438 + ) => void,
439 + markComponentLayoutEffectMountStarted: (fiber: Fiber) => void,
440 + markComponentLayoutEffectMountStopped: () => void,
441 + markComponentLayoutEffectUnmountStarted: (fiber: Fiber) => void,
442 + markComponentLayoutEffectUnmountStopped: () => void,
443 + markComponentPassiveEffectMountStarted: (fiber: Fiber) => void,
444 + markComponentPassiveEffectMountStopped: () => void,
445 + markComponentPassiveEffectUnmountStarted: (fiber: Fiber) => void,
446 + markComponentPassiveEffectUnmountStopped: () => void,
447 +|};
448 +
449 export type DevToolsHook = {
450 listeners: {[key: string]: Array<Handler>, ...},
451 rendererInterfaces: Map<RendererID, RendererInterface>,
@@ -431,5 +482,6 @@ export type DevToolsHook = {
482
483 // Testing
484 dangerous_setTargetConsoleForTesting?: (fakeConsole: Object) => void,
485 +
486 ...
487 };
packages/react-devtools-shared/src/hook.js
+1 -2
@@ -9,14 +9,13 @@
9 */
10
11 import type {BrowserTheme} from 'react-devtools-shared/src/devtools/views/DevTools';
12 +import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
13
14 import {
15 patch as patchConsole,
16 registerRenderer as registerRendererWithConsole,
17 } from './backend/console';
18
18 -import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
19 -
19 declare var window: any;
20
21 export function installHook(target: any): DevToolsHook | null {
packages/react-devtools-timeline/src/import-worker/__tests__/preprocessData-test.internal.js deleted
-1915
@@ -1,1915 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -'use strict';
9 -
10 -import {getLaneLabels} from 'react-reconciler/src/SchedulingProfiler';
11 -import preprocessData, {
12 - getLanesFromTransportDecimalBitmask,
13 -} from '../preprocessData';
14 -import {
15 - REACT_TOTAL_NUM_LANES,
16 - SCHEDULING_PROFILER_VERSION,
17 -} from '../../constants';
18 -import REACT_VERSION from 'shared/ReactVersion';
19 -
20 -describe('getLanesFromTransportDecimalBitmask', () => {
21 - it('should return array of lane numbers from bitmask string', () => {
22 - expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
23 - expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
24 - expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
25 - expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
26 - 1,
27 - 4,
28 - 6,
29 - 7,
30 - 10,
31 - ]); // 2 + 16 + 64 + 128 + 1024
32 - expect(
33 - getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
34 - ).toEqual([30]);
35 - expect(
36 - getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
37 - ).toEqual(Array.from(Array(31).keys()));
38 - });
39 -
40 - it('should return empty array if laneBitmaskString is not a bitmask', () => {
41 - expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
42 - expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
43 - expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
44 - expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
45 - });
46 -
47 - it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
48 - // Sanity check; this test may need to be updated when the no. of fiber
49 - // lanes are changed.
50 - expect(REACT_TOTAL_NUM_LANES).toBe(31);
51 -
52 - expect(
53 - getLanesFromTransportDecimalBitmask(
54 - '4294967297', // 2^32 + 1
55 - ),
56 - ).toEqual([0]);
57 - });
58 -});
59 -
60 -describe('preprocessData', () => {
61 - let React;
62 - let ReactDOM;
63 - let Scheduler;
64 -
65 - let act;
66 - let clearedMarks;
67 - let featureDetectionMarkName = null;
68 - let marks;
69 -
70 - function polyfillJSDomUserTiming() {
71 - featureDetectionMarkName = null;
72 -
73 - clearedMarks = [];
74 - marks = [];
75 -
76 - // This is not a true polyfill, but it gives us enough to capture marks.
77 - // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
78 -
79 - // JSDom already implements global.performance and it can't be overridden.
80 - // However it only supports now() and timeOrigin() so we need to "upgrade it" in place.
81 -
82 - if (!global.performance) {
83 - global.performance = {};
84 - }
85 -
86 - global.performance.clearMarks = function clearMarks(markName) {
87 - clearedMarks.push(markName);
88 - marks = marks.filter(mark => mark !== markName);
89 - };
90 -
91 - global.performance.mark = function mark(markName, markOptions) {
92 - if (featureDetectionMarkName === null) {
93 - featureDetectionMarkName = markName;
94 - }
95 - marks.push(markName);
96 - if (markOptions != null) {
97 - // This is triggers the feature detection.
98 - markOptions.startTime++;
99 - }
100 - };
101 - }
102 -
103 - // These should be dynamic to mimic a real profile,
104 - // but reprooducible between test runs.
105 - let pid = 0;
106 - let tid = 0;
107 - let startTime = 0;
108 -
109 - function createUserTimingEntry(data) {
110 - return {
111 - pid: ++pid,
112 - tid: ++tid,
113 - ts: ++startTime,
114 - ...data,
115 - };
116 - }
117 -
118 - function createProfilerVersionEntry() {
119 - return createUserTimingEntry({
120 - cat: 'blink.user_timing',
121 - name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
122 - });
123 - }
124 -
125 - function createReactVersionEntry() {
126 - return createUserTimingEntry({
127 - cat: 'blink.user_timing',
128 - name: '--react-version-' + REACT_VERSION,
129 - });
130 - }
131 -
132 - function createLaneLabelsEntry() {
133 - return createUserTimingEntry({
134 - cat: 'blink.user_timing',
135 - name: '--react-lane-labels-' + getLaneLabels().join(','),
136 - });
137 - }
138 -
139 - function createNativeEventEntry(type, duration) {
140 - return createUserTimingEntry({
141 - cat: 'devtools.timeline',
142 - name: 'EventDispatch',
143 - args: {data: {type}},
144 - dur: duration,
145 - tdur: duration,
146 - });
147 - }
148 -
149 - function creactCpuProfilerSample() {
150 - return createUserTimingEntry({
151 - args: {data: {startTime: ++startTime}},
152 - cat: 'disabled-by-default-v8.cpu_profiler',
153 - id: '0x1',
154 - name: 'Profile',
155 - ph: 'P',
156 - });
157 - }
158 -
159 - function createBoilerplateEntries() {
160 - return [
161 - createProfilerVersionEntry(),
162 - createReactVersionEntry(),
163 - createLaneLabelsEntry(),
164 - ];
165 - }
166 -
167 - function createUserTimingData(sampleMarks) {
168 - const cpuProfilerSample = creactCpuProfilerSample();
169 -
170 - const randomSample = createUserTimingEntry({
171 - dur: 100,
172 - tdur: 200,
173 - ph: 'X',
174 - cat: 'disabled-by-default-devtools.timeline',
175 - name: 'RunTask',
176 - args: {},
177 - });
178 -
179 - const userTimingData = [cpuProfilerSample, randomSample];
180 -
181 - sampleMarks.forEach(markName => {
182 - userTimingData.push({
183 - pid: ++pid,
184 - tid: ++tid,
185 - ts: ++startTime,
186 - args: {data: {}},
187 - cat: 'blink.user_timing',
188 - name: markName,
189 - ph: 'R',
190 - });
191 - });
192 -
193 - return userTimingData;
194 - }
195 -
196 - beforeEach(() => {
197 - jest.resetModules();
198 -
199 - polyfillJSDomUserTiming();
200 -
201 - React = require('react');
202 - ReactDOM = require('react-dom');
203 - Scheduler = require('scheduler');
204 -
205 - const TestUtils = require('react-dom/test-utils');
206 - act = TestUtils.act;
207 -
208 - tid = 0;
209 - pid = 0;
210 - startTime = 0;
211 -
212 - global.IS_REACT_ACT_ENVIRONMENT = true;
213 - });
214 -
215 - afterEach(() => {
216 - // Verify all logged marks also get cleared.
217 - expect(marks).toHaveLength(0);
218 -
219 - delete global.performance;
220 - });
221 -
222 - it('should throw given an empty timeline', async () => {
223 - await expect(async () => preprocessData([])).rejects.toThrow();
224 - });
225 -
226 - it('should throw given a timeline with no Profile event', async () => {
227 - const randomSample = createUserTimingEntry({
228 - dur: 100,
229 - tdur: 200,
230 - ph: 'X',
231 - cat: 'disabled-by-default-devtools.timeline',
232 - name: 'RunTask',
233 - args: {},
234 - });
235 -
236 - await expect(async () => preprocessData([randomSample])).rejects.toThrow();
237 - });
238 -
239 - it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
240 - const cpuProfilerSample = creactCpuProfilerSample();
241 -
242 - await expect(
243 - async () => await preprocessData([cpuProfilerSample]),
244 - ).rejects.toThrow(
245 - 'Please provide profiling data from an React application',
246 - );
247 - });
248 -
249 - it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
250 - const cpuProfilerSample = creactCpuProfilerSample();
251 - const scheduleRenderSample = createUserTimingEntry({
252 - cat: 'blink.user_timing',
253 - name: '--schedule-render-512-',
254 - });
255 - const samples = [cpuProfilerSample, scheduleRenderSample];
256 -
257 - await expect(async () => await preprocessData(samples)).rejects.toThrow(
258 - 'This version of profiling data is not supported',
259 - );
260 - });
261 -
262 - it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
263 - const cpuProfilerSample = creactCpuProfilerSample();
264 - const randomSample = createUserTimingEntry({
265 - dur: 100,
266 - tdur: 200,
267 - ph: 'X',
268 - cat: 'disabled-by-default-devtools.timeline',
269 - name: 'RunTask',
270 - args: {},
271 - });
272 -
273 - if (gate(flags => flags.enableSchedulingProfiler)) {
274 - const data = await preprocessData([
275 - ...createBoilerplateEntries(),
276 - cpuProfilerSample,
277 - randomSample,
278 - ]);
279 - expect(data).toMatchInlineSnapshot(`
280 - Object {
281 - "batchUIDToMeasuresMap": Map {},
282 - "componentMeasures": Array [],
283 - "duration": 0.005,
284 - "flamechart": Array [],
285 - "internalModuleSourceToRanges": Map {},
286 - "laneToLabelMap": Map {
287 - 0 => "Sync",
288 - 1 => "InputContinuousHydration",
289 - 2 => "InputContinuous",
290 - 3 => "DefaultHydration",
291 - 4 => "Default",
292 - 5 => "TransitionHydration",
293 - 6 => "Transition",
294 - 7 => "Transition",
295 - 8 => "Transition",
296 - 9 => "Transition",
297 - 10 => "Transition",
298 - 11 => "Transition",
299 - 12 => "Transition",
300 - 13 => "Transition",
301 - 14 => "Transition",
302 - 15 => "Transition",
303 - 16 => "Transition",
304 - 17 => "Transition",
305 - 18 => "Transition",
306 - 19 => "Transition",
307 - 20 => "Transition",
308 - 21 => "Transition",
309 - 22 => "Retry",
310 - 23 => "Retry",
311 - 24 => "Retry",
312 - 25 => "Retry",
313 - 26 => "Retry",
314 - 27 => "SelectiveHydration",
315 - 28 => "IdleHydration",
316 - 29 => "Idle",
317 - 30 => "Offscreen",
318 - },
319 - "laneToReactMeasureMap": Map {
320 - 0 => Array [],
321 - 1 => Array [],
322 - 2 => Array [],
323 - 3 => Array [],
324 - 4 => Array [],
325 - 5 => Array [],
326 - 6 => Array [],
327 - 7 => Array [],
328 - 8 => Array [],
329 - 9 => Array [],
330 - 10 => Array [],
331 - 11 => Array [],
332 - 12 => Array [],
333 - 13 => Array [],
334 - 14 => Array [],
335 - 15 => Array [],
336 - 16 => Array [],
337 - 17 => Array [],
338 - 18 => Array [],
339 - 19 => Array [],
340 - 20 => Array [],
341 - 21 => Array [],
342 - 22 => Array [],
343 - 23 => Array [],
344 - 24 => Array [],
345 - 25 => Array [],
346 - 26 => Array [],
347 - 27 => Array [],
348 - 28 => Array [],
349 - 29 => Array [],
350 - 30 => Array [],
351 - },
352 - "nativeEvents": Array [],
353 - "networkMeasures": Array [],
354 - "otherUserTimingMarks": Array [],
355 - "reactVersion": "17.0.3",
356 - "schedulingEvents": Array [],
357 - "snapshotHeight": 0,
358 - "snapshots": Array [],
359 - "startTime": 1,
360 - "suspenseEvents": Array [],
361 - "thrownErrors": Array [],
362 - }
363 - `);
364 - }
365 - });
366 -
367 - it('should process legacy data format (before lane labels were added)', async () => {
368 - const cpuProfilerSample = creactCpuProfilerSample();
369 -
370 - if (gate(flags => flags.enableSchedulingProfiler)) {
371 - // Data below is hard-coded based on an older profile sample.
372 - // Should be fine since this is explicitly a legacy-format test.
373 - const data = await preprocessData([
374 - ...createBoilerplateEntries(),
375 - cpuProfilerSample,
376 - createUserTimingEntry({
377 - cat: 'blink.user_timing',
378 - name: '--schedule-render-512-',
379 - }),
380 - createUserTimingEntry({
381 - cat: 'blink.user_timing',
382 - name: '--render-start-512',
383 - }),
384 - createUserTimingEntry({
385 - cat: 'blink.user_timing',
386 - name: '--render-stop',
387 - }),
388 - createUserTimingEntry({
389 - cat: 'blink.user_timing',
390 - name: '--commit-start-512',
391 - }),
392 - createUserTimingEntry({
393 - cat: 'blink.user_timing',
394 - name: '--layout-effects-start-512',
395 - }),
396 - createUserTimingEntry({
397 - cat: 'blink.user_timing',
398 - name: '--layout-effects-stop',
399 - }),
400 - createUserTimingEntry({
401 - cat: 'blink.user_timing',
402 - name: '--commit-stop',
403 - }),
404 - ]);
405 - expect(data).toMatchInlineSnapshot(`
406 - Object {
407 - "batchUIDToMeasuresMap": Map {
408 - 0 => Array [
409 - Object {
410 - "batchUID": 0,
411 - "depth": 0,
412 - "duration": 0.004999999999999999,
413 - "lanes": Array [
414 - 9,
415 - ],
416 - "timestamp": 0.006,
417 - "type": "render-idle",
418 - },
419 - Object {
420 - "batchUID": 0,
421 - "depth": 0,
422 - "duration": 0.001,
423 - "lanes": Array [
424 - 9,
425 - ],
426 - "timestamp": 0.006,
427 - "type": "render",
428 - },
429 - Object {
430 - "batchUID": 0,
431 - "depth": 0,
432 - "duration": 0.002999999999999999,
433 - "lanes": Array [
434 - 9,
435 - ],
436 - "timestamp": 0.008,
437 - "type": "commit",
438 - },
439 - Object {
440 - "batchUID": 0,
441 - "depth": 1,
442 - "duration": 0.0010000000000000009,
443 - "lanes": Array [
444 - 9,
445 - ],
446 - "timestamp": 0.009,
447 - "type": "layout-effects",
448 - },
449 - ],
450 - },
451 - "componentMeasures": Array [],
452 - "duration": 0.011,
453 - "flamechart": Array [],
454 - "internalModuleSourceToRanges": Map {},
455 - "laneToLabelMap": Map {
456 - 0 => "Sync",
457 - 1 => "InputContinuousHydration",
458 - 2 => "InputContinuous",
459 - 3 => "DefaultHydration",
460 - 4 => "Default",
461 - 5 => "TransitionHydration",
462 - 6 => "Transition",
463 - 7 => "Transition",
464 - 8 => "Transition",
465 - 9 => "Transition",
466 - 10 => "Transition",
467 - 11 => "Transition",
468 - 12 => "Transition",
469 - 13 => "Transition",
470 - 14 => "Transition",
471 - 15 => "Transition",
472 - 16 => "Transition",
473 - 17 => "Transition",
474 - 18 => "Transition",
475 - 19 => "Transition",
476 - 20 => "Transition",
477 - 21 => "Transition",
478 - 22 => "Retry",
479 - 23 => "Retry",
480 - 24 => "Retry",
481 - 25 => "Retry",
482 - 26 => "Retry",
483 - 27 => "SelectiveHydration",
484 - 28 => "IdleHydration",
485 - 29 => "Idle",
486 - 30 => "Offscreen",
487 - },
488 - "laneToReactMeasureMap": Map {
489 - 0 => Array [],
490 - 1 => Array [],
491 - 2 => Array [],
492 - 3 => Array [],
493 - 4 => Array [],
494 - 5 => Array [],
495 - 6 => Array [],
496 - 7 => Array [],
497 - 8 => Array [],
498 - 9 => Array [
499 - Object {
500 - "batchUID": 0,
501 - "depth": 0,
502 - "duration": 0.004999999999999999,
503 - "lanes": Array [
504 - 9,
505 - ],
506 - "timestamp": 0.006,
507 - "type": "render-idle",
508 - },
509 - Object {
510 - "batchUID": 0,
511 - "depth": 0,
512 - "duration": 0.001,
513 - "lanes": Array [
514 - 9,
515 - ],
516 - "timestamp": 0.006,
517 - "type": "render",
518 - },
519 - Object {
520 - "batchUID": 0,
521 - "depth": 0,
522 - "duration": 0.002999999999999999,
523 - "lanes": Array [
524 - 9,
525 - ],
526 - "timestamp": 0.008,
527 - "type": "commit",
528 - },
529 - Object {
530 - "batchUID": 0,
531 - "depth": 1,
532 - "duration": 0.0010000000000000009,
533 - "lanes": Array [
534 - 9,
535 - ],
536 - "timestamp": 0.009,
537 - "type": "layout-effects",
538 - },
539 - ],
540 - 10 => Array [],
541 - 11 => Array [],
542 - 12 => Array [],
543 - 13 => Array [],
544 - 14 => Array [],
545 - 15 => Array [],
546 - 16 => Array [],
547 - 17 => Array [],
548 - 18 => Array [],
549 - 19 => Array [],
550 - 20 => Array [],
551 - 21 => Array [],
552 - 22 => Array [],
553 - 23 => Array [],
554 - 24 => Array [],
555 - 25 => Array [],
556 - 26 => Array [],
557 - 27 => Array [],
558 - 28 => Array [],
559 - 29 => Array [],
560 - 30 => Array [],
561 - },
562 - "nativeEvents": Array [],
563 - "networkMeasures": Array [],
564 - "otherUserTimingMarks": Array [],
565 - "reactVersion": "17.0.3",
566 - "schedulingEvents": Array [
567 - Object {
568 - "lanes": Array [
569 - 9,
570 - ],
571 - "timestamp": 0.005,
572 - "type": "schedule-render",
573 - "warning": null,
574 - },
575 - ],
576 - "snapshotHeight": 0,
577 - "snapshots": Array [],
578 - "startTime": 1,
579 - "suspenseEvents": Array [],
580 - "thrownErrors": Array [],
581 - }
582 - `);
583 - }
584 - });
585 -
586 - it('should process a sample legacy render sequence', async () => {
587 - ReactDOM.render(<div />, document.createElement('div'));
588 -
589 - if (gate(flags => flags.enableSchedulingProfiler)) {
590 - const data = await preprocessData([
591 - ...createBoilerplateEntries(),
592 - ...createUserTimingData(clearedMarks),
593 - ]);
594 - expect(data).toMatchInlineSnapshot(`
595 - Object {
596 - "batchUIDToMeasuresMap": Map {
597 - 0 => Array [
598 - Object {
599 - "batchUID": 0,
600 - "depth": 0,
601 - "duration": 0.008,
602 - "lanes": Array [
603 - 0,
604 - ],
605 - "timestamp": 0.005,
606 - "type": "render-idle",
607 - },
608 - Object {
609 - "batchUID": 0,
610 - "depth": 0,
611 - "duration": 0.001,
612 - "lanes": Array [
613 - 0,
614 - ],
615 - "timestamp": 0.005,
616 - "type": "render",
617 - },
618 - Object {
619 - "batchUID": 0,
620 - "depth": 0,
621 - "duration": 0.005999999999999999,
622 - "lanes": Array [
623 - 0,
624 - ],
625 - "timestamp": 0.007,
626 - "type": "commit",
627 - },
628 - Object {
629 - "batchUID": 0,
630 - "depth": 1,
631 - "duration": 0.0010000000000000009,
632 - "lanes": Array [
633 - 0,
634 - ],
635 - "timestamp": 0.011,
636 - "type": "layout-effects",
637 - },
638 - ],
639 - },
640 - "componentMeasures": Array [],
641 - "duration": 0.013,
642 - "flamechart": Array [],
643 - "internalModuleSourceToRanges": Map {},
644 - "laneToLabelMap": Map {
645 - 0 => "Sync",
646 - 1 => "InputContinuousHydration",
647 - 2 => "InputContinuous",
648 - 3 => "DefaultHydration",
649 - 4 => "Default",
650 - 5 => "TransitionHydration",
651 - 6 => "Transition",
652 - 7 => "Transition",
653 - 8 => "Transition",
654 - 9 => "Transition",
655 - 10 => "Transition",
656 - 11 => "Transition",
657 - 12 => "Transition",
658 - 13 => "Transition",
659 - 14 => "Transition",
660 - 15 => "Transition",
661 - 16 => "Transition",
662 - 17 => "Transition",
663 - 18 => "Transition",
664 - 19 => "Transition",
665 - 20 => "Transition",
666 - 21 => "Transition",
667 - 22 => "Retry",
668 - 23 => "Retry",
669 - 24 => "Retry",
670 - 25 => "Retry",
671 - 26 => "Retry",
672 - 27 => "SelectiveHydration",
673 - 28 => "IdleHydration",
674 - 29 => "Idle",
675 - 30 => "Offscreen",
676 - },
677 - "laneToReactMeasureMap": Map {
678 - 0 => Array [
679 - Object {
680 - "batchUID": 0,
681 - "depth": 0,
682 - "duration": 0.008,
683 - "lanes": Array [
684 - 0,
685 - ],
686 - "timestamp": 0.005,
687 - "type": "render-idle",
688 - },
689 - Object {
690 - "batchUID": 0,
691 - "depth": 0,
692 - "duration": 0.001,
693 - "lanes": Array [
694 - 0,
695 - ],
696 - "timestamp": 0.005,
697 - "type": "render",
698 - },
699 - Object {
700 - "batchUID": 0,
701 - "depth": 0,
702 - "duration": 0.005999999999999999,
703 - "lanes": Array [
704 - 0,
705 - ],
706 - "timestamp": 0.007,
707 - "type": "commit",
708 - },
709 - Object {
710 - "batchUID": 0,
711 - "depth": 1,
712 - "duration": 0.0010000000000000009,
713 - "lanes": Array [
714 - 0,
715 - ],
716 - "timestamp": 0.011,
717 - "type": "layout-effects",
718 - },
719 - ],
720 - 1 => Array [],
721 - 2 => Array [],
722 - 3 => Array [],
723 - 4 => Array [],
724 - 5 => Array [],
725 - 6 => Array [],
726 - 7 => Array [],
727 - 8 => Array [],
728 - 9 => Array [],
729 - 10 => Array [],
730 - 11 => Array [],
731 - 12 => Array [],
732 - 13 => Array [],
733 - 14 => Array [],
734 - 15 => Array [],
735 - 16 => Array [],
736 - 17 => Array [],
737 - 18 => Array [],
738 - 19 => Array [],
739 - 20 => Array [],
740 - 21 => Array [],
741 - 22 => Array [],
742 - 23 => Array [],
743 - 24 => Array [],
744 - 25 => Array [],
745 - 26 => Array [],
746 - 27 => Array [],
747 - 28 => Array [],
748 - 29 => Array [],
749 - 30 => Array [],
750 - },
751 - "nativeEvents": Array [],
752 - "networkMeasures": Array [],
753 - "otherUserTimingMarks": Array [
754 - Object {
755 - "name": "__v3",
756 - "timestamp": 0.003,
757 - },
758 - ],
759 - "reactVersion": "17.0.3",
760 - "schedulingEvents": Array [
761 - Object {
762 - "lanes": Array [
763 - 0,
764 - ],
765 - "timestamp": 0.004,
766 - "type": "schedule-render",
767 - "warning": null,
768 - },
769 - ],
770 - "snapshotHeight": 0,
771 - "snapshots": Array [],
772 - "startTime": 4,
773 - "suspenseEvents": Array [],
774 - "thrownErrors": Array [],
775 - }
776 - `);
777 - }
778 - });
779 -
780 - it('should process a sample createRoot render sequence', async () => {
781 - function App() {
782 - const [didMount, setDidMount] = React.useState(false);
783 - React.useEffect(() => {
784 - if (!didMount) {
785 - setDidMount(true);
786 - }
787 - });
788 - return true;
789 - }
790 -
791 - if (gate(flags => flags.enableSchedulingProfiler)) {
792 - const root = ReactDOM.createRoot(document.createElement('div'));
793 - act(() => root.render(<App />));
794 -
795 - const data = await preprocessData([
796 - ...createBoilerplateEntries(),
797 - ...createUserTimingData(clearedMarks),
798 - ]);
799 - expect(data).toMatchInlineSnapshot(`
800 - Object {
801 - "batchUIDToMeasuresMap": Map {
802 - 0 => Array [
803 - Object {
804 - "batchUID": 0,
805 - "depth": 0,
806 - "duration": 0.009999999999999998,
807 - "lanes": Array [
808 - 4,
809 - ],
810 - "timestamp": 0.005,
811 - "type": "render-idle",
812 - },
813 - Object {
814 - "batchUID": 0,
815 - "depth": 0,
816 - "duration": 0.003,
817 - "lanes": Array [
818 - 4,
819 - ],
820 - "timestamp": 0.005,
821 - "type": "render",
822 - },
823 - Object {
824 - "batchUID": 0,
825 - "depth": 0,
826 - "duration": 0.006,
827 - "lanes": Array [
828 - 4,
829 - ],
830 - "timestamp": 0.009,
831 - "type": "commit",
832 - },
833 - Object {
834 - "batchUID": 0,
835 - "depth": 1,
836 - "duration": 0.0010000000000000009,
837 - "lanes": Array [
838 - 4,
839 - ],
840 - "timestamp": 0.013,
841 - "type": "layout-effects",
842 - },
843 - Object {
844 - "batchUID": 0,
845 - "depth": 0,
846 - "duration": 0.004,
847 - "lanes": Array [
848 - 4,
849 - ],
850 - "timestamp": 0.016,
851 - "type": "passive-effects",
852 - },
853 - ],
854 - 1 => Array [
855 - Object {
856 - "batchUID": 1,
857 - "depth": 0,
858 - "duration": 0.009999999999999998,
859 - "lanes": Array [
860 - 4,
861 - ],
862 - "timestamp": 0.021,
863 - "type": "render-idle",
864 - },
865 - Object {
866 - "batchUID": 1,
867 - "depth": 0,
868 - "duration": 0.002999999999999999,
869 - "lanes": Array [
870 - 4,
871 - ],
872 - "timestamp": 0.021,
873 - "type": "render",
874 - },
875 - Object {
876 - "batchUID": 1,
877 - "depth": 0,
878 - "duration": 0.005999999999999998,
879 - "lanes": Array [
880 - 4,
881 - ],
882 - "timestamp": 0.025,
883 - "type": "commit",
884 - },
885 - Object {
886 - "batchUID": 1,
887 - "depth": 1,
888 - "duration": 0.0009999999999999974,
889 - "lanes": Array [
890 - 4,
891 - ],
892 - "timestamp": 0.029,
893 - "type": "layout-effects",
894 - },
895 - Object {
896 - "batchUID": 1,
897 - "depth": 0,
898 - "duration": 0.0030000000000000027,
899 - "lanes": Array [
900 - 4,
901 - ],
902 - "timestamp": 0.032,
903 - "type": "passive-effects",
904 - },
905 - ],
906 - },
907 - "componentMeasures": Array [
908 - Object {
909 - "componentName": "App",
910 - "duration": 0.001,
911 - "timestamp": 0.006,
912 - "type": "render",
913 - "warning": null,
914 - },
915 - Object {
916 - "componentName": "App",
917 - "duration": 0.0019999999999999983,
918 - "timestamp": 0.017,
919 - "type": "passive-effect-mount",
920 - "warning": null,
921 - },
922 - Object {
923 - "componentName": "App",
924 - "duration": 0.0010000000000000009,
925 - "timestamp": 0.022,
926 - "type": "render",
927 - "warning": null,
928 - },
929 - Object {
930 - "componentName": "App",
931 - "duration": 0.0010000000000000009,
932 - "timestamp": 0.033,
933 - "type": "passive-effect-mount",
934 - "warning": null,
935 - },
936 - ],
937 - "duration": 0.035,
938 - "flamechart": Array [],
939 - "internalModuleSourceToRanges": Map {},
940 - "laneToLabelMap": Map {
941 - 0 => "Sync",
942 - 1 => "InputContinuousHydration",
943 - 2 => "InputContinuous",
944 - 3 => "DefaultHydration",
945 - 4 => "Default",
946 - 5 => "TransitionHydration",
947 - 6 => "Transition",
948 - 7 => "Transition",
949 - 8 => "Transition",
950 - 9 => "Transition",
951 - 10 => "Transition",
952 - 11 => "Transition",
953 - 12 => "Transition",
954 - 13 => "Transition",
955 - 14 => "Transition",
956 - 15 => "Transition",
957 - 16 => "Transition",
958 - 17 => "Transition",
959 - 18 => "Transition",
960 - 19 => "Transition",
961 - 20 => "Transition",
962 - 21 => "Transition",
963 - 22 => "Retry",
964 - 23 => "Retry",
965 - 24 => "Retry",
966 - 25 => "Retry",
967 - 26 => "Retry",
968 - 27 => "SelectiveHydration",
969 - 28 => "IdleHydration",
970 - 29 => "Idle",
971 - 30 => "Offscreen",
972 - },
973 - "laneToReactMeasureMap": Map {
974 - 0 => Array [],
975 - 1 => Array [],
976 - 2 => Array [],
977 - 3 => Array [],
978 - 4 => Array [
979 - Object {
980 - "batchUID": 0,
981 - "depth": 0,
982 - "duration": 0.009999999999999998,
983 - "lanes": Array [
984 - 4,
985 - ],
986 - "timestamp": 0.005,
987 - "type": "render-idle",
988 - },
989 - Object {
990 - "batchUID": 0,
991 - "depth": 0,
992 - "duration": 0.003,
993 - "lanes": Array [
994 - 4,
995 - ],
996 - "timestamp": 0.005,
997 - "type": "render",
998 - },
999 - Object {
1000 - "batchUID": 0,
1001 - "depth": 0,
1002 - "duration": 0.006,
1003 - "lanes": Array [
1004 - 4,
1005 - ],
1006 - "timestamp": 0.009,
1007 - "type": "commit",
1008 - },
1009 - Object {
1010 - "batchUID": 0,
1011 - "depth": 1,
1012 - "duration": 0.0010000000000000009,
1013 - "lanes": Array [
1014 - 4,
1015 - ],
1016 - "timestamp": 0.013,
1017 - "type": "layout-effects",
1018 - },
1019 - Object {
1020 - "batchUID": 0,
1021 - "depth": 0,
1022 - "duration": 0.004,
1023 - "lanes": Array [
1024 - 4,
1025 - ],
1026 - "timestamp": 0.016,
1027 - "type": "passive-effects",
1028 - },
1029 - Object {
1030 - "batchUID": 1,
1031 - "depth": 0,
1032 - "duration": 0.009999999999999998,
1033 - "lanes": Array [
1034 - 4,
1035 - ],
1036 - "timestamp": 0.021,
1037 - "type": "render-idle",
1038 - },
1039 - Object {
1040 - "batchUID": 1,
1041 - "depth": 0,
1042 - "duration": 0.002999999999999999,
1043 - "lanes": Array [
1044 - 4,
1045 - ],
1046 - "timestamp": 0.021,
1047 - "type": "render",
1048 - },
1049 - Object {
1050 - "batchUID": 1,
1051 - "depth": 0,
1052 - "duration": 0.005999999999999998,
1053 - "lanes": Array [
1054 - 4,
1055 - ],
1056 - "timestamp": 0.025,
1057 - "type": "commit",
1058 - },
1059 - Object {
1060 - "batchUID": 1,
1061 - "depth": 1,
1062 - "duration": 0.0009999999999999974,
1063 - "lanes": Array [
1064 - 4,
1065 - ],
1066 - "timestamp": 0.029,
1067 - "type": "layout-effects",
1068 - },
1069 - Object {
1070 - "batchUID": 1,
1071 - "depth": 0,
1072 - "duration": 0.0030000000000000027,
1073 - "lanes": Array [
1074 - 4,
1075 - ],
1076 - "timestamp": 0.032,
1077 - "type": "passive-effects",
1078 - },
1079 - ],
1080 - 5 => Array [],
1081 - 6 => Array [],
1082 - 7 => Array [],
1083 - 8 => Array [],
1084 - 9 => Array [],
1085 - 10 => Array [],
1086 - 11 => Array [],
1087 - 12 => Array [],
1088 - 13 => Array [],
1089 - 14 => Array [],
1090 - 15 => Array [],
1091 - 16 => Array [],
1092 - 17 => Array [],
1093 - 18 => Array [],
1094 - 19 => Array [],
1095 - 20 => Array [],
1096 - 21 => Array [],
1097 - 22 => Array [],
1098 - 23 => Array [],
1099 - 24 => Array [],
1100 - 25 => Array [],
1101 - 26 => Array [],
1102 - 27 => Array [],
1103 - 28 => Array [],
1104 - 29 => Array [],
1105 - 30 => Array [],
1106 - },
1107 - "nativeEvents": Array [],
1108 - "networkMeasures": Array [],
1109 - "otherUserTimingMarks": Array [
1110 - Object {
1111 - "name": "__v3",
1112 - "timestamp": 0.003,
1113 - },
1114 - ],
1115 - "reactVersion": "17.0.3",
1116 - "schedulingEvents": Array [
1117 - Object {
1118 - "lanes": Array [
1119 - 4,
1120 - ],
1121 - "timestamp": 0.004,
1122 - "type": "schedule-render",
1123 - "warning": null,
1124 - },
1125 - Object {
1126 - "componentName": "App",
1127 - "lanes": Array [
1128 - 4,
1129 - ],
1130 - "timestamp": 0.018,
1131 - "type": "schedule-state-update",
1132 - "warning": null,
1133 - },
1134 - ],
1135 - "snapshotHeight": 0,
1136 - "snapshots": Array [],
1137 - "startTime": 4,
1138 - "suspenseEvents": Array [],
1139 - "thrownErrors": Array [],
1140 - }
1141 - `);
1142 - }
1143 - });
1144 -
1145 - // @gate enableSchedulingProfiler
1146 - it('should error if events and measures are incomplete', async () => {
1147 - const container = document.createElement('div');
1148 - ReactDOM.render(<div />, container);
1149 -
1150 - const invalidMarks = clearedMarks.filter(
1151 - mark => !mark.includes('render-stop'),
1152 - );
1153 - const invalidUserTimingData = createUserTimingData(invalidMarks);
1154 -
1155 - const error = spyOnDevAndProd(console, 'error');
1156 - preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
1157 - expect(error).toHaveBeenCalled();
1158 - });
1159 -
1160 - // @gate enableSchedulingProfiler
1161 - it('should error if work is completed without being started', async () => {
1162 - const container = document.createElement('div');
1163 - ReactDOM.render(<div />, container);
1164 -
1165 - const invalidMarks = clearedMarks.filter(
1166 - mark => !mark.includes('render-start'),
1167 - );
1168 - const invalidUserTimingData = createUserTimingData(invalidMarks);
1169 -
1170 - const error = spyOnDevAndProd(console, 'error');
1171 - preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
1172 - expect(error).toHaveBeenCalled();
1173 - });
1174 -
1175 - it('should populate other user timing marks', async () => {
1176 - const userTimingData = createUserTimingData([]);
1177 - userTimingData.push(
1178 - createUserTimingEntry({
1179 - args: {},
1180 - cat: 'blink.user_timing',
1181 - id: '0xcdf75f7c',
1182 - name: 'VCWithoutImage: root',
1183 - ph: 'n',
1184 - scope: 'blink.user_timing',
1185 - }),
1186 - );
1187 - userTimingData.push(
1188 - createUserTimingEntry({
1189 - cat: 'blink.user_timing',
1190 - name: '--a-mark-that-looks-like-one-of-ours',
1191 - ph: 'R',
1192 - }),
1193 - );
1194 - userTimingData.push(
1195 - createUserTimingEntry({
1196 - cat: 'blink.user_timing',
1197 - name: 'Some other mark',
1198 - ph: 'R',
1199 - }),
1200 - );
1201 -
1202 - const data = await preprocessData([
1203 - ...createBoilerplateEntries(),
1204 - ...userTimingData,
1205 - ]);
1206 - expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
1207 - Array [
1208 - Object {
1209 - "name": "VCWithoutImage: root",
1210 - "timestamp": 0.003,
1211 - },
1212 - Object {
1213 - "name": "--a-mark-that-looks-like-one-of-ours",
1214 - "timestamp": 0.004,
1215 - },
1216 - Object {
1217 - "name": "Some other mark",
1218 - "timestamp": 0.005,
1219 - },
1220 - ]
1221 - `);
1222 - });
1223 -
1224 - it('should include a suspended resource "displayName" if one is set', async () => {
1225 - let promise = null;
1226 - let resolvedValue = null;
1227 - function readValue(value) {
1228 - if (resolvedValue !== null) {
1229 - return resolvedValue;
1230 - } else if (promise === null) {
1231 - promise = Promise.resolve(true).then(() => {
1232 - resolvedValue = value;
1233 - });
1234 - promise.displayName = 'Testing displayName';
1235 - }
1236 - throw promise;
1237 - }
1238 -
1239 - function Component() {
1240 - const value = readValue(123);
1241 - return value;
1242 - }
1243 -
1244 - if (gate(flags => flags.enableSchedulingProfiler)) {
1245 - const testMarks = [creactCpuProfilerSample()];
1246 -
1247 - const root = ReactDOM.createRoot(document.createElement('div'));
1248 - act(() =>
1249 - root.render(
1250 - <React.Suspense fallback="Loading...">
1251 - <Component />
1252 - </React.Suspense>,
1253 - ),
1254 - );
1255 -
1256 - testMarks.push(...createUserTimingData(clearedMarks));
1257 -
1258 - const data = await act(() => preprocessData(testMarks));
1259 - expect(data.suspenseEvents).toHaveLength(1);
1260 - expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1261 - }
1262 - });
1263 -
1264 - describe('warnings', () => {
1265 - describe('long event handlers', () => {
1266 - it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
1267 - function App() {
1268 - return null;
1269 - }
1270 -
1271 - if (gate(flags => flags.enableSchedulingProfiler)) {
1272 - const testMarks = [
1273 - creactCpuProfilerSample(),
1274 - ...createBoilerplateEntries(),
1275 - createNativeEventEntry('click', 5),
1276 - ];
1277 -
1278 - clearedMarks.splice(0);
1279 -
1280 - ReactDOM.render(<App />, document.createElement('div'));
1281 -
1282 - testMarks.push(...createUserTimingData(clearedMarks));
1283 -
1284 - const data = await preprocessData(testMarks);
1285 - const event = data.nativeEvents.find(({type}) => type === 'click');
1286 - expect(event.warning).toBe(null);
1287 - }
1288 - });
1289 -
1290 - it('should not warn about long events if the cause was non-React JavaScript', async () => {
1291 - function App() {
1292 - return null;
1293 - }
1294 -
1295 - if (gate(flags => flags.enableSchedulingProfiler)) {
1296 - const testMarks = [
1297 - creactCpuProfilerSample(),
1298 - ...createBoilerplateEntries(),
1299 - createNativeEventEntry('click', 25000),
1300 - ];
1301 -
1302 - startTime += 2000;
1303 -
1304 - clearedMarks.splice(0);
1305 -
1306 - ReactDOM.render(<App />, document.createElement('div'));
1307 -
1308 - testMarks.push(...createUserTimingData(clearedMarks));
1309 -
1310 - const data = await preprocessData(testMarks);
1311 - const event = data.nativeEvents.find(({type}) => type === 'click');
1312 - expect(event.warning).toBe(null);
1313 - }
1314 - });
1315 -
1316 - it('should warn when React scedules a long (sync) update inside of an event', async () => {
1317 - function App() {
1318 - return null;
1319 - }
1320 -
1321 - if (gate(flags => flags.enableSchedulingProfiler)) {
1322 - const testMarks = [
1323 - creactCpuProfilerSample(),
1324 - ...createBoilerplateEntries(),
1325 - createNativeEventEntry('click', 25000),
1326 - ];
1327 -
1328 - clearedMarks.splice(0);
1329 -
1330 - ReactDOM.render(<App />, document.createElement('div'));
1331 -
1332 - clearedMarks.forEach(markName => {
1333 - if (markName === '--render-stop') {
1334 - // Fake a long running render
1335 - startTime += 20000;
1336 - }
1337 -
1338 - testMarks.push({
1339 - pid: ++pid,
1340 - tid: ++tid,
1341 - ts: ++startTime,
1342 - args: {data: {}},
1343 - cat: 'blink.user_timing',
1344 - name: markName,
1345 - ph: 'R',
1346 - });
1347 - });
1348 -
1349 - const data = await preprocessData(testMarks);
1350 - const event = data.nativeEvents.find(({type}) => type === 'click');
1351 - expect(event.warning).toMatchInlineSnapshot(
1352 - `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
1353 - );
1354 - }
1355 - });
1356 -
1357 - it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
1358 - function Yield({id, value}) {
1359 - Scheduler.unstable_yieldValue(`${id}:${value}`);
1360 - return null;
1361 - }
1362 -
1363 - if (gate(flags => flags.enableSchedulingProfiler)) {
1364 - const testMarks = [
1365 - creactCpuProfilerSample(),
1366 - ...createBoilerplateEntries(),
1367 - ];
1368 -
1369 - // Advance the clock by some arbitrary amount.
1370 - startTime += 50000;
1371 -
1372 - const root = ReactDOM.createRoot(document.createElement('div'));
1373 -
1374 - // Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
1375 - global.IS_REACT_ACT_ENVIRONMENT = false;
1376 - React.startTransition(() => {
1377 - // Start rendering an async update (but don't finish).
1378 - root.render(
1379 - <>
1380 - <Yield id="A" value={1} />
1381 - <Yield id="B" value={1} />
1382 - </>,
1383 - );
1384 - expect(Scheduler).toFlushAndYieldThrough(['A:1']);
1385 -
1386 - testMarks.push(...createUserTimingData(clearedMarks));
1387 - clearedMarks.splice(0);
1388 -
1389 - // Advance the clock some more to make the pending React update seem long.
1390 - startTime += 20000;
1391 -
1392 - // Fake a long "click" event in the middle
1393 - // and schedule a sync update that will also flush the previous work.
1394 - testMarks.push(createNativeEventEntry('click', 25000));
1395 - ReactDOM.flushSync(() => {
1396 - root.render(
1397 - <>
1398 - <Yield id="A" value={2} />
1399 - <Yield id="B" value={2} />
1400 - </>,
1401 - );
1402 - });
1403 - });
1404 -
1405 - expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
1406 -
1407 - testMarks.push(...createUserTimingData(clearedMarks));
1408 -
1409 - const data = await preprocessData(testMarks);
1410 - const event = data.nativeEvents.find(({type}) => type === 'click');
1411 - expect(event.warning).toBe(null);
1412 - }
1413 - });
1414 - });
1415 -
1416 - describe('nested updates', () => {
1417 - it('should not warn about short nested (state) updates during layout effects', async () => {
1418 - function Component() {
1419 - const [didMount, setDidMount] = React.useState(false);
1420 - Scheduler.unstable_yieldValue(
1421 - `Component ${didMount ? 'update' : 'mount'}`,
1422 - );
1423 - React.useLayoutEffect(() => {
1424 - setDidMount(true);
1425 - }, []);
1426 - return didMount;
1427 - }
1428 -
1429 - if (gate(flags => flags.enableSchedulingProfiler)) {
1430 - const root = ReactDOM.createRoot(document.createElement('div'));
1431 - act(() => {
1432 - root.render(<Component />);
1433 - });
1434 -
1435 - expect(Scheduler).toHaveYielded([
1436 - 'Component mount',
1437 - 'Component update',
1438 - ]);
1439 -
1440 - const data = await preprocessData([
1441 - ...createBoilerplateEntries(),
1442 - ...createUserTimingData(clearedMarks),
1443 - ]);
1444 -
1445 - const event = data.schedulingEvents.find(
1446 - ({type}) => type === 'schedule-state-update',
1447 - );
1448 - expect(event.warning).toBe(null);
1449 - }
1450 - });
1451 -
1452 - it('should not warn about short (forced) updates during layout effects', async () => {
1453 - class Component extends React.Component {
1454 - _didMount: boolean = false;
1455 - componentDidMount() {
1456 - this._didMount = true;
1457 - this.forceUpdate();
1458 - }
1459 - render() {
1460 - Scheduler.unstable_yieldValue(
1461 - `Component ${this._didMount ? 'update' : 'mount'}`,
1462 - );
1463 - return null;
1464 - }
1465 - }
1466 -
1467 - if (gate(flags => flags.enableSchedulingProfiler)) {
1468 - const root = ReactDOM.createRoot(document.createElement('div'));
1469 - act(() => {
1470 - root.render(<Component />);
1471 - });
1472 -
1473 - expect(Scheduler).toHaveYielded([
1474 - 'Component mount',
1475 - 'Component update',
1476 - ]);
1477 -
1478 - const data = await preprocessData([
1479 - ...createBoilerplateEntries(),
1480 - ...createUserTimingData(clearedMarks),
1481 - ]);
1482 -
1483 - const event = data.schedulingEvents.find(
1484 - ({type}) => type === 'schedule-force-update',
1485 - );
1486 - expect(event.warning).toBe(null);
1487 - }
1488 - });
1489 -
1490 - it('should warn about long nested (state) updates during layout effects', async () => {
1491 - function Component() {
1492 - const [didMount, setDidMount] = React.useState(false);
1493 - Scheduler.unstable_yieldValue(
1494 - `Component ${didMount ? 'update' : 'mount'}`,
1495 - );
1496 - // Fake a long render
1497 - startTime += 20000;
1498 - React.useLayoutEffect(() => {
1499 - setDidMount(true);
1500 - }, []);
1501 - return didMount;
1502 - }
1503 -
1504 - if (gate(flags => flags.enableSchedulingProfiler)) {
1505 - const cpuProfilerSample = creactCpuProfilerSample();
1506 -
1507 - const root = ReactDOM.createRoot(document.createElement('div'));
1508 - act(() => {
1509 - root.render(<Component />);
1510 - });
1511 -
1512 - expect(Scheduler).toHaveYielded([
1513 - 'Component mount',
1514 - 'Component update',
1515 - ]);
1516 -
1517 - const testMarks = [];
1518 - clearedMarks.forEach(markName => {
1519 - if (markName === '--component-render-start-Component') {
1520 - // Fake a long running render
1521 - startTime += 20000;
1522 - }
1523 -
1524 - testMarks.push({
1525 - pid: ++pid,
1526 - tid: ++tid,
1527 - ts: ++startTime,
1528 - args: {data: {}},
1529 - cat: 'blink.user_timing',
1530 - name: markName,
1531 - ph: 'R',
1532 - });
1533 - });
1534 -
1535 - const data = await preprocessData([
1536 - cpuProfilerSample,
1537 - ...createBoilerplateEntries(),
1538 - ...testMarks,
1539 - ]);
1540 -
1541 - const event = data.schedulingEvents.find(
1542 - ({type}) => type === 'schedule-state-update',
1543 - );
1544 - expect(event.warning).toMatchInlineSnapshot(
1545 - `"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)."`,
1546 - );
1547 - }
1548 - });
1549 -
1550 - it('should warn about long nested (forced) updates during layout effects', async () => {
1551 - class Component extends React.Component {
1552 - _didMount: boolean = false;
1553 - componentDidMount() {
1554 - this._didMount = true;
1555 - this.forceUpdate();
1556 - }
1557 - render() {
1558 - Scheduler.unstable_yieldValue(
1559 - `Component ${this._didMount ? 'update' : 'mount'}`,
1560 - );
1561 - return null;
1562 - }
1563 - }
1564 -
1565 - if (gate(flags => flags.enableSchedulingProfiler)) {
1566 - const cpuProfilerSample = creactCpuProfilerSample();
1567 -
1568 - const root = ReactDOM.createRoot(document.createElement('div'));
1569 - act(() => {
1570 - root.render(<Component />);
1571 - });
1572 -
1573 - expect(Scheduler).toHaveYielded([
1574 - 'Component mount',
1575 - 'Component update',
1576 - ]);
1577 -
1578 - const testMarks = [];
1579 - clearedMarks.forEach(markName => {
1580 - if (markName === '--component-render-start-Component') {
1581 - // Fake a long running render
1582 - startTime += 20000;
1583 - }
1584 -
1585 - testMarks.push({
1586 - pid: ++pid,
1587 - tid: ++tid,
1588 - ts: ++startTime,
1589 - args: {data: {}},
1590 - cat: 'blink.user_timing',
1591 - name: markName,
1592 - ph: 'R',
1593 - });
1594 - });
1595 -
1596 - const data = await preprocessData([
1597 - cpuProfilerSample,
1598 - ...createBoilerplateEntries(),
1599 - ...testMarks,
1600 - ]);
1601 -
1602 - const event = data.schedulingEvents.find(
1603 - ({type}) => type === 'schedule-force-update',
1604 - );
1605 - expect(event.warning).toMatchInlineSnapshot(
1606 - `"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)."`,
1607 - );
1608 - }
1609 - });
1610 -
1611 - it('should not warn about transition updates scheduled during commit phase', async () => {
1612 - function Component() {
1613 - const [value, setValue] = React.useState(0);
1614 - // eslint-disable-next-line no-unused-vars
1615 - const [isPending, startTransition] = React.useTransition();
1616 -
1617 - Scheduler.unstable_yieldValue(
1618 - `Component rendered with value ${value}`,
1619 - );
1620 -
1621 - // Fake a long render
1622 - if (value !== 0) {
1623 - Scheduler.unstable_yieldValue('Long render');
1624 - startTime += 20000;
1625 - }
1626 -
1627 - React.useLayoutEffect(() => {
1628 - startTransition(() => {
1629 - setValue(1);
1630 - });
1631 - }, []);
1632 -
1633 - return value;
1634 - }
1635 -
1636 - if (gate(flags => flags.enableSchedulingProfiler)) {
1637 - const cpuProfilerSample = creactCpuProfilerSample();
1638 -
1639 - const root = ReactDOM.createRoot(document.createElement('div'));
1640 - act(() => {
1641 - root.render(<Component />);
1642 - });
1643 -
1644 - expect(Scheduler).toHaveYielded([
1645 - 'Component rendered with value 0',
1646 - 'Component rendered with value 0',
1647 - 'Component rendered with value 1',
1648 - 'Long render',
1649 - ]);
1650 -
1651 - const testMarks = [];
1652 - clearedMarks.forEach(markName => {
1653 - if (markName === '--component-render-start-Component') {
1654 - // Fake a long running render
1655 - startTime += 20000;
1656 - }
1657 -
1658 - testMarks.push({
1659 - pid: ++pid,
1660 - tid: ++tid,
1661 - ts: ++startTime,
1662 - args: {data: {}},
1663 - cat: 'blink.user_timing',
1664 - name: markName,
1665 - ph: 'R',
1666 - });
1667 - });
1668 -
1669 - const data = await preprocessData([
1670 - cpuProfilerSample,
1671 - ...createBoilerplateEntries(),
1672 - ...testMarks,
1673 - ]);
1674 -
1675 - data.schedulingEvents.forEach(event => {
1676 - expect(event.warning).toBeNull();
1677 - });
1678 - }
1679 - });
1680 -
1681 - it('should not warn about deferred value updates scheduled during commit phase', async () => {
1682 - function Component() {
1683 - const [value, setValue] = React.useState(0);
1684 - const deferredValue = React.useDeferredValue(value);
1685 -
1686 - Scheduler.unstable_yieldValue(
1687 - `Component rendered with value ${value} and deferredValue ${deferredValue}`,
1688 - );
1689 -
1690 - // Fake a long render
1691 - if (deferredValue !== 0) {
1692 - Scheduler.unstable_yieldValue('Long render');
1693 - startTime += 20000;
1694 - }
1695 -
1696 - React.useLayoutEffect(() => {
1697 - setValue(1);
1698 - }, []);
1699 -
1700 - return value + deferredValue;
1701 - }
1702 -
1703 - if (gate(flags => flags.enableSchedulingProfiler)) {
1704 - const cpuProfilerSample = creactCpuProfilerSample();
1705 -
1706 - const root = ReactDOM.createRoot(document.createElement('div'));
1707 - act(() => {
1708 - root.render(<Component />);
1709 - });
1710 -
1711 - expect(Scheduler).toHaveYielded([
1712 - 'Component rendered with value 0 and deferredValue 0',
1713 - 'Component rendered with value 1 and deferredValue 0',
1714 - 'Component rendered with value 1 and deferredValue 1',
1715 - 'Long render',
1716 - ]);
1717 -
1718 - const testMarks = [];
1719 - clearedMarks.forEach(markName => {
1720 - if (markName === '--component-render-start-Component') {
1721 - // Fake a long running render
1722 - startTime += 20000;
1723 - }
1724 -
1725 - testMarks.push({
1726 - pid: ++pid,
1727 - tid: ++tid,
1728 - ts: ++startTime,
1729 - args: {data: {}},
1730 - cat: 'blink.user_timing',
1731 - name: markName,
1732 - ph: 'R',
1733 - });
1734 - });
1735 -
1736 - const data = await preprocessData([
1737 - cpuProfilerSample,
1738 - ...createBoilerplateEntries(),
1739 - ...testMarks,
1740 - ]);
1741 -
1742 - data.schedulingEvents.forEach(event => {
1743 - expect(event.warning).toBeNull();
1744 - });
1745 - }
1746 - });
1747 - });
1748 -
1749 - describe('errors thrown while rendering', () => {
1750 - it('shoult parse Errors thrown during render', async () => {
1751 - spyOnDev(console, 'error');
1752 - spyOnProd(console, 'error');
1753 -
1754 - class ErrorBoundary extends React.Component {
1755 - state = {error: null};
1756 - componentDidCatch(error) {
1757 - this.setState({error});
1758 - }
1759 - render() {
1760 - if (this.state.error) {
1761 - return null;
1762 - }
1763 - return this.props.children;
1764 - }
1765 - }
1766 -
1767 - function ExampleThatThrows() {
1768 - throw Error('Expected error');
1769 - }
1770 -
1771 - if (gate(flags => flags.enableSchedulingProfiler)) {
1772 - const testMarks = [creactCpuProfilerSample()];
1773 -
1774 - // Mount and commit the app
1775 - const root = ReactDOM.createRoot(document.createElement('div'));
1776 - act(() =>
1777 - root.render(
1778 - <ErrorBoundary>
1779 - <ExampleThatThrows />
1780 - </ErrorBoundary>,
1781 - ),
1782 - );
1783 -
1784 - testMarks.push(...createUserTimingData(clearedMarks));
1785 -
1786 - const data = await preprocessData(testMarks);
1787 - expect(data.thrownErrors).toHaveLength(2);
1788 - expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
1789 - '"Expected error"',
1790 - );
1791 - }
1792 - });
1793 - });
1794 -
1795 - describe('suspend during an update', () => {
1796 - // This also tests an edge case where the a component suspends while profiling
1797 - // before the first commit is logged (so the lane-to-labels map will not yet exist).
1798 - it('should warn about suspending during an udpate', async () => {
1799 - let promise = null;
1800 - let resolvedValue = null;
1801 - function readValue(value) {
1802 - if (resolvedValue !== null) {
1803 - return resolvedValue;
1804 - } else if (promise === null) {
1805 - promise = Promise.resolve(true).then(() => {
1806 - resolvedValue = value;
1807 - });
1808 - }
1809 - throw promise;
1810 - }
1811 -
1812 - function Component({shouldSuspend}) {
1813 - Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1814 - if (shouldSuspend) {
1815 - readValue(123);
1816 - }
1817 - return null;
1818 - }
1819 -
1820 - if (gate(flags => flags.enableSchedulingProfiler)) {
1821 - // Mount and commit the app
1822 - const root = ReactDOM.createRoot(document.createElement('div'));
1823 - act(() =>
1824 - root.render(
1825 - <React.Suspense fallback="Loading...">
1826 - <Component shouldSuspend={false} />
1827 - </React.Suspense>,
1828 - ),
1829 - );
1830 -
1831 - clearedMarks.splice(0);
1832 -
1833 - const testMarks = [creactCpuProfilerSample()];
1834 -
1835 - // Start profiling and suspend during a render.
1836 - act(() =>
1837 - root.render(
1838 - <React.Suspense fallback="Loading...">
1839 - <Component shouldSuspend={true} />
1840 - </React.Suspense>,
1841 - ),
1842 - );
1843 -
1844 - testMarks.push(...createUserTimingData(clearedMarks));
1845 -
1846 - const data = await act(() => preprocessData(testMarks));
1847 - expect(data.suspenseEvents).toHaveLength(1);
1848 - expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
1849 - `"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."`,
1850 - );
1851 - }
1852 - });
1853 -
1854 - it('should not warn about suspending during an transition', async () => {
1855 - let promise = null;
1856 - let resolvedValue = null;
1857 - function readValue(value) {
1858 - if (resolvedValue !== null) {
1859 - return resolvedValue;
1860 - } else if (promise === null) {
1861 - promise = Promise.resolve(true).then(() => {
1862 - resolvedValue = value;
1863 - });
1864 - }
1865 - throw promise;
1866 - }
1867 -
1868 - function Component({shouldSuspend}) {
1869 - Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1870 - if (shouldSuspend) {
1871 - readValue(123);
1872 - }
1873 - return null;
1874 - }
1875 -
1876 - if (gate(flags => flags.enableSchedulingProfiler)) {
1877 - // Mount and commit the app
1878 - const root = ReactDOM.createRoot(document.createElement('div'));
1879 - act(() =>
1880 - root.render(
1881 - <React.Suspense fallback="Loading...">
1882 - <Component shouldSuspend={false} />
1883 - </React.Suspense>,
1884 - ),
1885 - );
1886 -
1887 - clearedMarks.splice(0);
1888 -
1889 - const testMarks = [creactCpuProfilerSample()];
1890 -
1891 - // Start profiling and suspend during a render.
1892 - await act(async () =>
1893 - React.startTransition(() =>
1894 - root.render(
1895 - <React.Suspense fallback="Loading...">
1896 - <Component shouldSuspend={true} />
1897 - </React.Suspense>,
1898 - ),
1899 - ),
1900 - );
1901 -
1902 - testMarks.push(...createUserTimingData(clearedMarks));
1903 -
1904 - const data = await act(() => preprocessData(testMarks));
1905 - expect(data.suspenseEvents).toHaveLength(1);
1906 - expect(data.suspenseEvents[0].warning).toBe(null);
1907 - }
1908 - });
1909 - });
1910 - });
1911 -
1912 - // TODO: Add test for snapshot base64 parsing
1913 -
1914 - // TODO: Add test for flamechart parsing
1915 -});
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+2 -2
@@ -35,7 +35,8 @@ import checkPropTypes from 'shared/checkPropTypes';
35 import {
36 markComponentRenderStarted,
37 markComponentRenderStopped,
38 -} from './SchedulingProfiler';
38 + setIsStrictModeForDevtools,
39 +} from './ReactFiberDevToolsHook.new';
40 import {
41 IndeterminateComponent,
42 FunctionComponent,
@@ -240,7 +241,6 @@ import {createCapturedValue} from './ReactCapturedValue';
241 import {createClassErrorUpdate} from './ReactFiberThrow.new';
242 import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.new';
243 import is from 'shared/objectIs';
243 -import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
244 import {
245 getForksAtLevel,
246 isForkedChild,
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+2 -2
@@ -35,7 +35,8 @@ import checkPropTypes from 'shared/checkPropTypes';
35 import {
36 markComponentRenderStarted,
37 markComponentRenderStopped,
38 -} from './SchedulingProfiler';
38 + setIsStrictModeForDevtools,
39 +} from './ReactFiberDevToolsHook.old';
40 import {
41 IndeterminateComponent,
42 FunctionComponent,
@@ -240,7 +241,6 @@ import {createCapturedValue} from './ReactCapturedValue';
241 import {createClassErrorUpdate} from './ReactFiberThrow.old';
242 import {completeSuspendedOffscreenHostContainer} from './ReactFiberCompleteWork.old';
243 import is from 'shared/objectIs';
243 -import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
244 import {
245 getForksAtLevel,
246 isForkedChild,
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+2 -3
@@ -37,7 +37,6 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
37 import getComponentNameFromType from 'shared/getComponentNameFromType';
38 import isArray from 'shared/isArray';
39 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
40 -import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
40
41 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
42 import {
@@ -74,11 +73,11 @@ import {
73 scheduleUpdateOnFiber,
74 } from './ReactFiberWorkLoop.new';
75 import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
77 -
76 import {
77 markForceUpdateScheduled,
78 markStateUpdateScheduled,
81 -} from './SchedulingProfiler';
79 + setIsStrictModeForDevtools,
80 +} from './ReactFiberDevToolsHook.new';
81
82 const fakeInternalInstance = {};
83
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+2 -3
@@ -37,7 +37,6 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
37 import getComponentNameFromType from 'shared/getComponentNameFromType';
38 import isArray from 'shared/isArray';
39 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
40 -import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
40
41 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
42 import {
@@ -74,11 +73,11 @@ import {
73 scheduleUpdateOnFiber,
74 } from './ReactFiberWorkLoop.old';
75 import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
77 -
76 import {
77 markForceUpdateScheduled,
78 markStateUpdateScheduled,
81 -} from './SchedulingProfiler';
79 + setIsStrictModeForDevtools,
80 +} from './ReactFiberDevToolsHook.old';
81
82 const fakeInternalInstance = {};
83
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+3 -3
@@ -86,8 +86,6 @@ import {
86 resetCurrentFiber as resetCurrentDebugFiberInDEV,
87 setCurrentFiber as setCurrentDebugFiberInDEV,
88 } from './ReactCurrentFiber';
89 -import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
90 -import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
89 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
90 import {
91 isCurrentUpdateNested,
@@ -147,6 +145,7 @@ import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new';
145 import {doesFiberContain} from './ReactFiberTreeReflection';
146 import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
147 import {
148 + isDevToolsPresent,
149 markComponentPassiveEffectMountStarted,
150 markComponentPassiveEffectMountStopped,
151 markComponentPassiveEffectUnmountStarted,
@@ -155,7 +154,8 @@ import {
154 markComponentLayoutEffectMountStopped,
155 markComponentLayoutEffectUnmountStarted,
156 markComponentLayoutEffectUnmountStopped,
158 -} from './SchedulingProfiler';
157 + onCommitUnmount,
158 +} from './ReactFiberDevToolsHook.new';
159 import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
160
161 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+3 -3
@@ -86,8 +86,6 @@ import {
86 resetCurrentFiber as resetCurrentDebugFiberInDEV,
87 setCurrentFiber as setCurrentDebugFiberInDEV,
88 } from './ReactCurrentFiber';
89 -import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
90 -import {onCommitUnmount} from './ReactFiberDevToolsHook.old';
89 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
90 import {
91 isCurrentUpdateNested,
@@ -147,6 +145,7 @@ import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.old';
145 import {doesFiberContain} from './ReactFiberTreeReflection';
146 import {invokeGuardedCallback, clearCaughtError} from 'shared/ReactErrorUtils';
147 import {
148 + isDevToolsPresent,
149 markComponentPassiveEffectMountStarted,
150 markComponentPassiveEffectMountStopped,
151 markComponentPassiveEffectUnmountStarted,
@@ -155,7 +154,8 @@ import {
154 markComponentLayoutEffectMountStopped,
155 markComponentLayoutEffectUnmountStarted,
156 markComponentLayoutEffectUnmountStopped,
158 -} from './SchedulingProfiler';
157 + onCommitUnmount,
158 +} from './ReactFiberDevToolsHook.old';
159 import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
160
161 let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
packages/react-reconciler/src/ReactFiberDevToolsHook.new.js
+317 -7
@@ -7,16 +7,22 @@
7 * @flow
8 */
9
10 -import {
11 - consoleManagedByDevToolsDuringStrictMode,
12 - enableProfilerTimer,
13 -} from 'shared/ReactFeatureFlags';
14 -
10 +import type {Lane, Lanes} from './ReactFiberLane.new';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
16 -import type {ReactNodeList} from 'shared/ReactTypes';
12 +import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
13 import type {EventPriority} from './ReactEventPriorities.new';
14 +import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
15
16 +import {
17 + getLabelForLane,
18 + TotalLanes,
19 +} from 'react-reconciler/src/ReactFiberLane.new';
20 import {DidCapture} from './ReactFiberFlags';
21 +import {
22 + consoleManagedByDevToolsDuringStrictMode,
23 + enableProfilerTimer,
24 + enableSchedulingProfiler,
25 +} from 'shared/ReactFeatureFlags';
26 import {
27 DiscreteEventPriority,
28 ContinuousEventPriority,
@@ -38,6 +44,7 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
44
45 let rendererID = null;
46 let injectedHook = null;
47 +let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
48 let hasLoggedError = false;
49
50 export const isDevToolsPresent =
@@ -67,7 +74,11 @@ export function injectInternals(internals: Object): boolean {
74 return true;
75 }
76 try {
70 - rendererID = hook.inject(internals);
77 + rendererID = hook.inject({
78 + ...internals,
79 + getLaneLabelMap,
80 + injectProfilingHooks,
81 + });
82 // We have successfully injected, so now it is safe to set up hooks.
83 injectedHook = hook;
84 } catch (err) {
@@ -212,3 +223,302 @@ export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
223 }
224 }
225 }
226 +
227 +// Profiler API hooks
228 +
229 +function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
230 + injectedProfilingHooks = profilingHooks;
231 +}
232 +
233 +function getLaneLabelMap(): Map<Lane, string> {
234 + const map: Map<Lane, string> = new Map();
235 +
236 + let lane = 1;
237 + for (let index = 0; index < TotalLanes; index++) {
238 + const label = ((getLabelForLane(lane): any): string);
239 + map.set(lane, label);
240 + lane *= 2;
241 + }
242 +
243 + return map;
244 +}
245 +
246 +export function markCommitStarted(lanes: Lanes): void {
247 + if (enableSchedulingProfiler) {
248 + if (
249 + injectedProfilingHooks !== null &&
250 + typeof injectedProfilingHooks.markCommitStarted === 'function'
251 + ) {
252 + injectedProfilingHooks.markCommitStarted(lanes);
253 + }
254 + }
255 +}
256 +
257 +export function markCommitStopped(): void {
258 + if (enableSchedulingProfiler) {
259 + if (
260 + injectedProfilingHooks !== null &&
261 + typeof injectedProfilingHooks.markCommitStopped === 'function'
262 + ) {
263 + injectedProfilingHooks.markCommitStopped();
264 + }
265 + }
266 +}
267 +
268 +export function markComponentRenderStarted(fiber: Fiber): void {
269 + if (enableSchedulingProfiler) {
270 + if (
271 + injectedProfilingHooks !== null &&
272 + typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
273 + ) {
274 + injectedProfilingHooks.markComponentRenderStarted(fiber);
275 + }
276 + }
277 +}
278 +
279 +export function markComponentRenderStopped(): void {
280 + if (enableSchedulingProfiler) {
281 + if (
282 + injectedProfilingHooks !== null &&
283 + typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
284 + ) {
285 + injectedProfilingHooks.markComponentRenderStopped();
286 + }
287 + }
288 +}
289 +
290 +export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
291 + if (enableSchedulingProfiler) {
292 + if (
293 + injectedProfilingHooks !== null &&
294 + typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
295 + 'function'
296 + ) {
297 + injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
298 + }
299 + }
300 +}
301 +
302 +export function markComponentPassiveEffectMountStopped(): void {
303 + if (enableSchedulingProfiler) {
304 + if (
305 + injectedProfilingHooks !== null &&
306 + typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
307 + 'function'
308 + ) {
309 + injectedProfilingHooks.markComponentPassiveEffectMountStopped();
310 + }
311 + }
312 +}
313 +
314 +export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
315 + if (enableSchedulingProfiler) {
316 + if (
317 + injectedProfilingHooks !== null &&
318 + typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
319 + 'function'
320 + ) {
321 + injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
322 + }
323 + }
324 +}
325 +
326 +export function markComponentPassiveEffectUnmountStopped(): void {
327 + if (enableSchedulingProfiler) {
328 + if (
329 + injectedProfilingHooks !== null &&
330 + typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
331 + 'function'
332 + ) {
333 + injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
334 + }
335 + }
336 +}
337 +
338 +export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
339 + if (enableSchedulingProfiler) {
340 + if (
341 + injectedProfilingHooks !== null &&
342 + typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
343 + 'function'
344 + ) {
345 + injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
346 + }
347 + }
348 +}
349 +
350 +export function markComponentLayoutEffectMountStopped(): void {
351 + if (enableSchedulingProfiler) {
352 + if (
353 + injectedProfilingHooks !== null &&
354 + typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
355 + 'function'
356 + ) {
357 + injectedProfilingHooks.markComponentLayoutEffectMountStopped();
358 + }
359 + }
360 +}
361 +
362 +export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
363 + if (enableSchedulingProfiler) {
364 + if (
365 + injectedProfilingHooks !== null &&
366 + typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
367 + 'function'
368 + ) {
369 + injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
370 + }
371 + }
372 +}
373 +
374 +export function markComponentLayoutEffectUnmountStopped(): void {
375 + if (enableSchedulingProfiler) {
376 + if (
377 + injectedProfilingHooks !== null &&
378 + typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
379 + 'function'
380 + ) {
381 + injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
382 + }
383 + }
384 +}
385 +
386 +export function markComponentErrored(
387 + fiber: Fiber,
388 + thrownValue: mixed,
389 + lanes: Lanes,
390 +): void {
391 + if (enableSchedulingProfiler) {
392 + if (
393 + injectedProfilingHooks !== null &&
394 + typeof injectedProfilingHooks.markComponentErrored === 'function'
395 + ) {
396 + injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
397 + }
398 + }
399 +}
400 +
401 +export function markComponentSuspended(
402 + fiber: Fiber,
403 + wakeable: Wakeable,
404 + lanes: Lanes,
405 +): void {
406 + if (enableSchedulingProfiler) {
407 + if (
408 + injectedProfilingHooks !== null &&
409 + typeof injectedProfilingHooks.markComponentSuspended === 'function'
410 + ) {
411 + injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
412 + }
413 + }
414 +}
415 +
416 +export function markLayoutEffectsStarted(lanes: Lanes): void {
417 + if (enableSchedulingProfiler) {
418 + if (
419 + injectedProfilingHooks !== null &&
420 + typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
421 + ) {
422 + injectedProfilingHooks.markLayoutEffectsStarted(lanes);
423 + }
424 + }
425 +}
426 +
427 +export function markLayoutEffectsStopped(): void {
428 + if (enableSchedulingProfiler) {
429 + if (
430 + injectedProfilingHooks !== null &&
431 + typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
432 + ) {
433 + injectedProfilingHooks.markLayoutEffectsStopped();
434 + }
435 + }
436 +}
437 +
438 +export function markPassiveEffectsStarted(lanes: Lanes): void {
439 + if (enableSchedulingProfiler) {
440 + if (
441 + injectedProfilingHooks !== null &&
442 + typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
443 + ) {
444 + injectedProfilingHooks.markPassiveEffectsStarted(lanes);
445 + }
446 + }
447 +}
448 +
449 +export function markPassiveEffectsStopped(): void {
450 + if (enableSchedulingProfiler) {
451 + if (
452 + injectedProfilingHooks !== null &&
453 + typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
454 + ) {
455 + injectedProfilingHooks.markPassiveEffectsStopped();
456 + }
457 + }
458 +}
459 +
460 +export function markRenderStarted(lanes: Lanes): void {
461 + if (enableSchedulingProfiler) {
462 + if (
463 + injectedProfilingHooks !== null &&
464 + typeof injectedProfilingHooks.markRenderStarted === 'function'
465 + ) {
466 + injectedProfilingHooks.markRenderStarted(lanes);
467 + }
468 + }
469 +}
470 +
471 +export function markRenderYielded(): void {
472 + if (enableSchedulingProfiler) {
473 + if (
474 + injectedProfilingHooks !== null &&
475 + typeof injectedProfilingHooks.markRenderYielded === 'function'
476 + ) {
477 + injectedProfilingHooks.markRenderYielded();
478 + }
479 + }
480 +}
481 +
482 +export function markRenderStopped(): void {
483 + if (enableSchedulingProfiler) {
484 + if (
485 + injectedProfilingHooks !== null &&
486 + typeof injectedProfilingHooks.markRenderStopped === 'function'
487 + ) {
488 + injectedProfilingHooks.markRenderStopped();
489 + }
490 + }
491 +}
492 +
493 +export function markRenderScheduled(lane: Lane): void {
494 + if (enableSchedulingProfiler) {
495 + if (
496 + injectedProfilingHooks !== null &&
497 + typeof injectedProfilingHooks.markRenderScheduled === 'function'
498 + ) {
499 + injectedProfilingHooks.markRenderScheduled(lane);
500 + }
501 + }
502 +}
503 +
504 +export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
505 + if (enableSchedulingProfiler) {
506 + if (
507 + injectedProfilingHooks !== null &&
508 + typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
509 + ) {
510 + injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
511 + }
512 + }
513 +}
514 +
515 +export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
516 + if (enableSchedulingProfiler) {
517 + if (
518 + injectedProfilingHooks !== null &&
519 + typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
520 + ) {
521 + injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
522 + }
523 + }
524 +}
packages/react-reconciler/src/ReactFiberDevToolsHook.old.js
+317 -7
@@ -7,16 +7,22 @@
7 * @flow
8 */
9
10 -import {
11 - consoleManagedByDevToolsDuringStrictMode,
12 - enableProfilerTimer,
13 -} from 'shared/ReactFeatureFlags';
14 -
10 +import type {Lane, Lanes} from './ReactFiberLane.old';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
16 -import type {ReactNodeList} from 'shared/ReactTypes';
12 +import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
13 import type {EventPriority} from './ReactEventPriorities.old';
14 +import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
15
16 +import {
17 + getLabelForLane,
18 + TotalLanes,
19 +} from 'react-reconciler/src/ReactFiberLane.old';
20 import {DidCapture} from './ReactFiberFlags';
21 +import {
22 + consoleManagedByDevToolsDuringStrictMode,
23 + enableProfilerTimer,
24 + enableSchedulingProfiler,
25 +} from 'shared/ReactFeatureFlags';
26 import {
27 DiscreteEventPriority,
28 ContinuousEventPriority,
@@ -38,6 +44,7 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
44
45 let rendererID = null;
46 let injectedHook = null;
47 +let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
48 let hasLoggedError = false;
49
50 export const isDevToolsPresent =
@@ -67,7 +74,11 @@ export function injectInternals(internals: Object): boolean {
74 return true;
75 }
76 try {
70 - rendererID = hook.inject(internals);
77 + rendererID = hook.inject({
78 + ...internals,
79 + getLaneLabelMap,
80 + injectProfilingHooks,
81 + });
82 // We have successfully injected, so now it is safe to set up hooks.
83 injectedHook = hook;
84 } catch (err) {
@@ -212,3 +223,302 @@ export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
223 }
224 }
225 }
226 +
227 +// Profiler API hooks
228 +
229 +function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
230 + injectedProfilingHooks = profilingHooks;
231 +}
232 +
233 +function getLaneLabelMap(): Map<Lane, string> {
234 + const map: Map<Lane, string> = new Map();
235 +
236 + let lane = 1;
237 + for (let index = 0; index < TotalLanes; index++) {
238 + const label = ((getLabelForLane(lane): any): string);
239 + map.set(lane, label);
240 + lane *= 2;
241 + }
242 +
243 + return map;
244 +}
245 +
246 +export function markCommitStarted(lanes: Lanes): void {
247 + if (enableSchedulingProfiler) {
248 + if (
249 + injectedProfilingHooks !== null &&
250 + typeof injectedProfilingHooks.markCommitStarted === 'function'
251 + ) {
252 + injectedProfilingHooks.markCommitStarted(lanes);
253 + }
254 + }
255 +}
256 +
257 +export function markCommitStopped(): void {
258 + if (enableSchedulingProfiler) {
259 + if (
260 + injectedProfilingHooks !== null &&
261 + typeof injectedProfilingHooks.markCommitStopped === 'function'
262 + ) {
263 + injectedProfilingHooks.markCommitStopped();
264 + }
265 + }
266 +}
267 +
268 +export function markComponentRenderStarted(fiber: Fiber): void {
269 + if (enableSchedulingProfiler) {
270 + if (
271 + injectedProfilingHooks !== null &&
272 + typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
273 + ) {
274 + injectedProfilingHooks.markComponentRenderStarted(fiber);
275 + }
276 + }
277 +}
278 +
279 +export function markComponentRenderStopped(): void {
280 + if (enableSchedulingProfiler) {
281 + if (
282 + injectedProfilingHooks !== null &&
283 + typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
284 + ) {
285 + injectedProfilingHooks.markComponentRenderStopped();
286 + }
287 + }
288 +}
289 +
290 +export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
291 + if (enableSchedulingProfiler) {
292 + if (
293 + injectedProfilingHooks !== null &&
294 + typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
295 + 'function'
296 + ) {
297 + injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
298 + }
299 + }
300 +}
301 +
302 +export function markComponentPassiveEffectMountStopped(): void {
303 + if (enableSchedulingProfiler) {
304 + if (
305 + injectedProfilingHooks !== null &&
306 + typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
307 + 'function'
308 + ) {
309 + injectedProfilingHooks.markComponentPassiveEffectMountStopped();
310 + }
311 + }
312 +}
313 +
314 +export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
315 + if (enableSchedulingProfiler) {
316 + if (
317 + injectedProfilingHooks !== null &&
318 + typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
319 + 'function'
320 + ) {
321 + injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
322 + }
323 + }
324 +}
325 +
326 +export function markComponentPassiveEffectUnmountStopped(): void {
327 + if (enableSchedulingProfiler) {
328 + if (
329 + injectedProfilingHooks !== null &&
330 + typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
331 + 'function'
332 + ) {
333 + injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
334 + }
335 + }
336 +}
337 +
338 +export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
339 + if (enableSchedulingProfiler) {
340 + if (
341 + injectedProfilingHooks !== null &&
342 + typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
343 + 'function'
344 + ) {
345 + injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
346 + }
347 + }
348 +}
349 +
350 +export function markComponentLayoutEffectMountStopped(): void {
351 + if (enableSchedulingProfiler) {
352 + if (
353 + injectedProfilingHooks !== null &&
354 + typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
355 + 'function'
356 + ) {
357 + injectedProfilingHooks.markComponentLayoutEffectMountStopped();
358 + }
359 + }
360 +}
361 +
362 +export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
363 + if (enableSchedulingProfiler) {
364 + if (
365 + injectedProfilingHooks !== null &&
366 + typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
367 + 'function'
368 + ) {
369 + injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
370 + }
371 + }
372 +}
373 +
374 +export function markComponentLayoutEffectUnmountStopped(): void {
375 + if (enableSchedulingProfiler) {
376 + if (
377 + injectedProfilingHooks !== null &&
378 + typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
379 + 'function'
380 + ) {
381 + injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
382 + }
383 + }
384 +}
385 +
386 +export function markComponentErrored(
387 + fiber: Fiber,
388 + thrownValue: mixed,
389 + lanes: Lanes,
390 +): void {
391 + if (enableSchedulingProfiler) {
392 + if (
393 + injectedProfilingHooks !== null &&
394 + typeof injectedProfilingHooks.markComponentErrored === 'function'
395 + ) {
396 + injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
397 + }
398 + }
399 +}
400 +
401 +export function markComponentSuspended(
402 + fiber: Fiber,
403 + wakeable: Wakeable,
404 + lanes: Lanes,
405 +): void {
406 + if (enableSchedulingProfiler) {
407 + if (
408 + injectedProfilingHooks !== null &&
409 + typeof injectedProfilingHooks.markComponentSuspended === 'function'
410 + ) {
411 + injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
412 + }
413 + }
414 +}
415 +
416 +export function markLayoutEffectsStarted(lanes: Lanes): void {
417 + if (enableSchedulingProfiler) {
418 + if (
419 + injectedProfilingHooks !== null &&
420 + typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
421 + ) {
422 + injectedProfilingHooks.markLayoutEffectsStarted(lanes);
423 + }
424 + }
425 +}
426 +
427 +export function markLayoutEffectsStopped(): void {
428 + if (enableSchedulingProfiler) {
429 + if (
430 + injectedProfilingHooks !== null &&
431 + typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
432 + ) {
433 + injectedProfilingHooks.markLayoutEffectsStopped();
434 + }
435 + }
436 +}
437 +
438 +export function markPassiveEffectsStarted(lanes: Lanes): void {
439 + if (enableSchedulingProfiler) {
440 + if (
441 + injectedProfilingHooks !== null &&
442 + typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
443 + ) {
444 + injectedProfilingHooks.markPassiveEffectsStarted(lanes);
445 + }
446 + }
447 +}
448 +
449 +export function markPassiveEffectsStopped(): void {
450 + if (enableSchedulingProfiler) {
451 + if (
452 + injectedProfilingHooks !== null &&
453 + typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
454 + ) {
455 + injectedProfilingHooks.markPassiveEffectsStopped();
456 + }
457 + }
458 +}
459 +
460 +export function markRenderStarted(lanes: Lanes): void {
461 + if (enableSchedulingProfiler) {
462 + if (
463 + injectedProfilingHooks !== null &&
464 + typeof injectedProfilingHooks.markRenderStarted === 'function'
465 + ) {
466 + injectedProfilingHooks.markRenderStarted(lanes);
467 + }
468 + }
469 +}
470 +
471 +export function markRenderYielded(): void {
472 + if (enableSchedulingProfiler) {
473 + if (
474 + injectedProfilingHooks !== null &&
475 + typeof injectedProfilingHooks.markRenderYielded === 'function'
476 + ) {
477 + injectedProfilingHooks.markRenderYielded();
478 + }
479 + }
480 +}
481 +
482 +export function markRenderStopped(): void {
483 + if (enableSchedulingProfiler) {
484 + if (
485 + injectedProfilingHooks !== null &&
486 + typeof injectedProfilingHooks.markRenderStopped === 'function'
487 + ) {
488 + injectedProfilingHooks.markRenderStopped();
489 + }
490 + }
491 +}
492 +
493 +export function markRenderScheduled(lane: Lane): void {
494 + if (enableSchedulingProfiler) {
495 + if (
496 + injectedProfilingHooks !== null &&
497 + typeof injectedProfilingHooks.markRenderScheduled === 'function'
498 + ) {
499 + injectedProfilingHooks.markRenderScheduled(lane);
500 + }
501 + }
502 +}
503 +
504 +export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
505 + if (enableSchedulingProfiler) {
506 + if (
507 + injectedProfilingHooks !== null &&
508 + typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
509 + ) {
510 + injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
511 + }
512 + }
513 +}
514 +
515 +export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
516 + if (enableSchedulingProfiler) {
517 + if (
518 + injectedProfilingHooks !== null &&
519 + typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
520 + ) {
521 + injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
522 + }
523 + }
524 +}
packages/react-reconciler/src/ReactFiberHooks.new.js
+1 -1
@@ -101,7 +101,7 @@ import {
101 warnAboutMultipleRenderersDEV,
102 } from './ReactMutableSource.new';
103 import {logStateUpdateScheduled} from './DebugTracing';
104 -import {markStateUpdateScheduled} from './SchedulingProfiler';
104 +import {markStateUpdateScheduled} from './ReactFiberDevToolsHook.new';
105 import {createCache, CacheContext} from './ReactFiberCacheComponent.new';
106 import {
107 createUpdate as createLegacyQueueUpdate,
packages/react-reconciler/src/ReactFiberHooks.old.js
+1 -1
@@ -101,7 +101,7 @@ import {
101 warnAboutMultipleRenderersDEV,
102 } from './ReactMutableSource.old';
103 import {logStateUpdateScheduled} from './DebugTracing';
104 -import {markStateUpdateScheduled} from './SchedulingProfiler';
104 +import {markStateUpdateScheduled} from './ReactFiberDevToolsHook.old';
105 import {createCache, CacheContext} from './ReactFiberCacheComponent.old';
106 import {
107 createUpdate as createLegacyQueueUpdate,
packages/react-reconciler/src/ReactFiberReconciler.new.js
+5 -2
@@ -44,7 +44,11 @@ import {
44 isContextProvider as isLegacyContextProvider,
45 } from './ReactFiberContext.new';
46 import {createFiberRoot} from './ReactFiberRoot.new';
47 -import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.new';
47 +import {
48 + injectInternals,
49 + markRenderScheduled,
50 + onScheduleRoot,
51 +} from './ReactFiberDevToolsHook.new';
52 import {
53 requestEventTime,
54 requestUpdateLane,
@@ -87,7 +91,6 @@ import {
91 setRefreshHandler,
92 findHostInstancesForRefresh,
93 } from './ReactFiberHotReloading.new';
90 -import {markRenderScheduled} from './SchedulingProfiler';
94 import ReactVersion from 'shared/ReactVersion';
95 export {registerMutableSourceForHydration} from './ReactMutableSource.new';
96 export {createPortal} from './ReactPortal';
packages/react-reconciler/src/ReactFiberReconciler.old.js
+5 -2
@@ -44,7 +44,11 @@ import {
44 isContextProvider as isLegacyContextProvider,
45 } from './ReactFiberContext.old';
46 import {createFiberRoot} from './ReactFiberRoot.old';
47 -import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.old';
47 +import {
48 + injectInternals,
49 + markRenderScheduled,
50 + onScheduleRoot,
51 +} from './ReactFiberDevToolsHook.old';
52 import {
53 requestEventTime,
54 requestUpdateLane,
@@ -87,7 +91,6 @@ import {
91 setRefreshHandler,
92 findHostInstancesForRefresh,
93 } from './ReactFiberHotReloading.old';
90 -import {markRenderScheduled} from './SchedulingProfiler';
94 import ReactVersion from 'shared/ReactVersion';
95 export {registerMutableSourceForHydration} from './ReactMutableSource.old';
96 export {createPortal} from './ReactPortal';
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+13 -15
@@ -65,20 +65,6 @@ import {
65 logRenderStarted,
66 logRenderStopped,
67 } from './DebugTracing';
68 -import {
69 - markCommitStarted,
70 - markCommitStopped,
71 - markComponentRenderStopped,
72 - markComponentSuspended,
73 - markComponentErrored,
74 - markLayoutEffectsStarted,
75 - markLayoutEffectsStopped,
76 - markPassiveEffectsStarted,
77 - markPassiveEffectsStopped,
78 - markRenderStarted,
79 - markRenderYielded,
80 - markRenderStopped,
81 -} from './SchedulingProfiler';
68
69 import {
70 resetAfterCommit,
@@ -224,9 +210,21 @@ import {
210 clearCaughtError,
211 } from 'shared/ReactErrorUtils';
212 import {
213 + isDevToolsPresent,
214 + markCommitStarted,
215 + markCommitStopped,
216 + markComponentRenderStopped,
217 + markComponentSuspended,
218 + markComponentErrored,
219 + markLayoutEffectsStarted,
220 + markLayoutEffectsStopped,
221 + markPassiveEffectsStarted,
222 + markPassiveEffectsStopped,
223 + markRenderStarted,
224 + markRenderYielded,
225 + markRenderStopped,
226 onCommitRoot as onCommitRootDevTools,
227 onPostCommitRoot as onPostCommitRootDevTools,
229 - isDevToolsPresent,
228 } from './ReactFiberDevToolsHook.new';
229 import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
230 import {releaseCache} from './ReactFiberCacheComponent.new';
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+13 -15
@@ -65,20 +65,6 @@ import {
65 logRenderStarted,
66 logRenderStopped,
67 } from './DebugTracing';
68 -import {
69 - markCommitStarted,
70 - markCommitStopped,
71 - markComponentRenderStopped,
72 - markComponentSuspended,
73 - markComponentErrored,
74 - markLayoutEffectsStarted,
75 - markLayoutEffectsStopped,
76 - markPassiveEffectsStarted,
77 - markPassiveEffectsStopped,
78 - markRenderStarted,
79 - markRenderYielded,
80 - markRenderStopped,
81 -} from './SchedulingProfiler';
68
69 import {
70 resetAfterCommit,
@@ -224,9 +210,21 @@ import {
210 clearCaughtError,
211 } from 'shared/ReactErrorUtils';
212 import {
213 + isDevToolsPresent,
214 + markCommitStarted,
215 + markCommitStopped,
216 + markComponentRenderStopped,
217 + markComponentSuspended,
218 + markComponentErrored,
219 + markLayoutEffectsStarted,
220 + markLayoutEffectsStopped,
221 + markPassiveEffectsStarted,
222 + markPassiveEffectsStopped,
223 + markRenderStarted,
224 + markRenderYielded,
225 + markRenderStopped,
226 onCommitRoot as onCommitRootDevTools,
227 onPostCommitRoot as onPostCommitRootDevTools,
229 - isDevToolsPresent,
228 } from './ReactFiberDevToolsHook.old';
229 import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
230 import {releaseCache} from './ReactFiberCacheComponent.old';
packages/react-reconciler/src/SchedulingProfiler.js deleted
-395
@@ -1,395 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {Lane, Lanes} from './ReactFiberLane.old';
11 -import type {Fiber} from './ReactInternalTypes';
12 -import type {Wakeable} from 'shared/ReactTypes';
13 -
14 -import {
15 - enableNewReconciler,
16 - enableSchedulingProfiler,
17 -} from 'shared/ReactFeatureFlags';
18 -import ReactVersion from 'shared/ReactVersion';
19 -import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
20 -import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-timeline/src/constants';
21 -import isArray from 'shared/isArray';
22 -
23 -import {
24 - getLabelForLane as getLabelForLane_old,
25 - TotalLanes as TotalLanes_old,
26 -} from 'react-reconciler/src/ReactFiberLane.old';
27 -import {
28 - getLabelForLane as getLabelForLane_new,
29 - TotalLanes as TotalLanes_new,
30 -} from 'react-reconciler/src/ReactFiberLane.new';
31 -
32 -const getLabelForLane = enableNewReconciler
33 - ? getLabelForLane_new
34 - : getLabelForLane_old;
35 -
36 -const TotalLanes = enableNewReconciler ? TotalLanes_new : TotalLanes_old;
37 -
38 -/**
39 - * If performance exists and supports the subset of the User Timing API that we
40 - * require.
41 - */
42 -const supportsUserTiming =
43 - typeof performance !== 'undefined' &&
44 - typeof performance.mark === 'function' &&
45 - typeof performance.clearMarks === 'function';
46 -
47 -let supportsUserTimingV3 = false;
48 -if (enableSchedulingProfiler) {
49 - if (supportsUserTiming) {
50 - const CHECK_V3_MARK = '__v3';
51 - const markOptions = {};
52 - // $FlowFixMe: Ignore Flow complaining about needing a value
53 - Object.defineProperty(markOptions, 'startTime', {
54 - get: function() {
55 - supportsUserTimingV3 = true;
56 - return 0;
57 - },
58 - set: function() {},
59 - });
60 -
61 - try {
62 - // $FlowFixMe: Flow expects the User Timing level 2 API.
63 - performance.mark(CHECK_V3_MARK, markOptions);
64 - } catch (error) {
65 - // Ignore
66 - } finally {
67 - performance.clearMarks(CHECK_V3_MARK);
68 - }
69 - }
70 -}
71 -
72 -const laneLabels: Array<string> = [];
73 -
74 -export function getLaneLabels(): Array<string> {
75 - if (laneLabels.length === 0) {
76 - let lane = 1;
77 - for (let index = 0; index < TotalLanes; index++) {
78 - laneLabels.push(((getLabelForLane(lane): any): string));
79 -
80 - lane *= 2;
81 - }
82 - }
83 - return laneLabels;
84 -}
85 -
86 -function markLaneToLabelMetadata() {
87 - getLaneLabels();
88 -
89 - markAndClear(`--react-lane-labels-${laneLabels.join(',')}`);
90 -}
91 -
92 -function markAndClear(name) {
93 - performance.mark(name);
94 - performance.clearMarks(name);
95 -}
96 -
97 -function markVersionMetadata() {
98 - markAndClear(`--react-version-${ReactVersion}`);
99 - markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
100 -}
101 -
102 -function markInternalModuleRanges() {
103 - /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
104 - if (
105 - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
106 - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges === 'function'
107 - ) {
108 - const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
109 - // This check would not be required,
110 - // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
111 - if (isArray(ranges)) {
112 - for (let i = 0; i < ranges.length; i++) {
113 - const range = ranges[i];
114 - if (isArray(range) && range.length === 2) {
115 - const [startStackFrame, stopStackFrame] = ranges[i];
116 -
117 - markAndClear(`--react-internal-module-start-${startStackFrame}`);
118 - markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
119 - }
120 - }
121 - }
122 - }
123 -}
124 -
125 -export function markCommitStarted(lanes: Lanes): void {
126 - if (enableSchedulingProfiler) {
127 - if (supportsUserTimingV3) {
128 - markAndClear(`--commit-start-${lanes}`);
129 -
130 - // Certain types of metadata should be logged infrequently.
131 - // Normally we would log this during module init,
132 - // but there's no guarantee a user is profiling at that time.
133 - // Commits happen infrequently (less than renders or state updates)
134 - // so we log this extra information along with a commit.
135 - // It will likely be logged more than once but that's okay.
136 - //
137 - // TODO Once DevTools supports starting/stopping the profiler,
138 - // we can log this data only once (when started) and remove the per-commit logging.
139 - markVersionMetadata();
140 - markLaneToLabelMetadata();
141 - markInternalModuleRanges();
142 - }
143 - }
144 -}
145 -
146 -export function markCommitStopped(): void {
147 - if (enableSchedulingProfiler) {
148 - if (supportsUserTimingV3) {
149 - markAndClear('--commit-stop');
150 - }
151 - }
152 -}
153 -
154 -export function markComponentRenderStarted(fiber: Fiber): void {
155 - if (enableSchedulingProfiler) {
156 - if (supportsUserTimingV3) {
157 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
158 - // TODO (timeline) Add component stack id
159 - markAndClear(`--component-render-start-${componentName}`);
160 - }
161 - }
162 -}
163 -
164 -export function markComponentRenderStopped(): void {
165 - if (enableSchedulingProfiler) {
166 - if (supportsUserTimingV3) {
167 - markAndClear('--component-render-stop');
168 - }
169 - }
170 -}
171 -
172 -export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
173 - if (enableSchedulingProfiler) {
174 - if (supportsUserTimingV3) {
175 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
176 - // TODO (timeline) Add component stack id
177 - markAndClear(`--component-passive-effect-mount-start-${componentName}`);
178 - }
179 - }
180 -}
181 -
182 -export function markComponentPassiveEffectMountStopped(): void {
183 - if (enableSchedulingProfiler) {
184 - if (supportsUserTimingV3) {
185 - markAndClear('--component-passive-effect-mount-stop');
186 - }
187 - }
188 -}
189 -
190 -export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
191 - if (enableSchedulingProfiler) {
192 - if (supportsUserTimingV3) {
193 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
194 - // TODO (timeline) Add component stack id
195 - markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
196 - }
197 - }
198 -}
199 -
200 -export function markComponentPassiveEffectUnmountStopped(): void {
201 - if (enableSchedulingProfiler) {
202 - if (supportsUserTimingV3) {
203 - markAndClear('--component-passive-effect-unmount-stop');
204 - }
205 - }
206 -}
207 -
208 -export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
209 - if (enableSchedulingProfiler) {
210 - if (supportsUserTimingV3) {
211 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
212 - // TODO (timeline) Add component stack id
213 - markAndClear(`--component-layout-effect-mount-start-${componentName}`);
214 - }
215 - }
216 -}
217 -
218 -export function markComponentLayoutEffectMountStopped(): void {
219 - if (enableSchedulingProfiler) {
220 - if (supportsUserTimingV3) {
221 - markAndClear('--component-layout-effect-mount-stop');
222 - }
223 - }
224 -}
225 -
226 -export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
227 - if (enableSchedulingProfiler) {
228 - if (supportsUserTimingV3) {
229 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
230 - // TODO (timeline) Add component stack id
231 - markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
232 - }
233 - }
234 -}
235 -
236 -export function markComponentLayoutEffectUnmountStopped(): void {
237 - if (enableSchedulingProfiler) {
238 - if (supportsUserTimingV3) {
239 - markAndClear('--component-layout-effect-unmount-stop');
240 - }
241 - }
242 -}
243 -
244 -export function markComponentErrored(
245 - fiber: Fiber,
246 - thrownValue: mixed,
247 - lanes: Lanes,
248 -): void {
249 - if (enableSchedulingProfiler) {
250 - if (supportsUserTimingV3) {
251 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
252 - const phase = fiber.alternate === null ? 'mount' : 'update';
253 -
254 - let message = '';
255 - if (
256 - thrownValue !== null &&
257 - typeof thrownValue === 'object' &&
258 - typeof thrownValue.message === 'string'
259 - ) {
260 - message = thrownValue.message;
261 - } else if (typeof thrownValue === 'string') {
262 - message = thrownValue;
263 - }
264 -
265 - // TODO (timeline) Add component stack id
266 - markAndClear(`--error-${componentName}-${phase}-${message}`);
267 - }
268 - }
269 -}
270 -
271 -const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
272 -
273 -// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
274 -const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
275 -let wakeableID: number = 0;
276 -function getWakeableID(wakeable: Wakeable): number {
277 - if (!wakeableIDs.has(wakeable)) {
278 - wakeableIDs.set(wakeable, wakeableID++);
279 - }
280 - return ((wakeableIDs.get(wakeable): any): number);
281 -}
282 -
283 -export function markComponentSuspended(
284 - fiber: Fiber,
285 - wakeable: Wakeable,
286 - lanes: Lanes,
287 -): void {
288 - if (enableSchedulingProfiler) {
289 - if (supportsUserTimingV3) {
290 - const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
291 - const id = getWakeableID(wakeable);
292 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
293 - const phase = fiber.alternate === null ? 'mount' : 'update';
294 -
295 - // Following the non-standard fn.displayName convention,
296 - // frameworks like Relay may also annotate Promises with a displayName,
297 - // describing what operation/data the thrown Promise is related to.
298 - // When this is available we should pass it along to the Timeline.
299 - const displayName = (wakeable: any).displayName || '';
300 -
301 - // TODO (timeline) Add component stack id
302 - markAndClear(
303 - `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
304 - );
305 - wakeable.then(
306 - () => markAndClear(`--suspense-resolved-${id}-${componentName}`),
307 - () => markAndClear(`--suspense-rejected-${id}-${componentName}`),
308 - );
309 - }
310 - }
311 -}
312 -
313 -export function markLayoutEffectsStarted(lanes: Lanes): void {
314 - if (enableSchedulingProfiler) {
315 - if (supportsUserTimingV3) {
316 - markAndClear(`--layout-effects-start-${lanes}`);
317 - }
318 - }
319 -}
320 -
321 -export function markLayoutEffectsStopped(): void {
322 - if (enableSchedulingProfiler) {
323 - if (supportsUserTimingV3) {
324 - markAndClear('--layout-effects-stop');
325 - }
326 - }
327 -}
328 -
329 -export function markPassiveEffectsStarted(lanes: Lanes): void {
330 - if (enableSchedulingProfiler) {
331 - if (supportsUserTimingV3) {
332 - markAndClear(`--passive-effects-start-${lanes}`);
333 - }
334 - }
335 -}
336 -
337 -export function markPassiveEffectsStopped(): void {
338 - if (enableSchedulingProfiler) {
339 - if (supportsUserTimingV3) {
340 - markAndClear('--passive-effects-stop');
341 - }
342 - }
343 -}
344 -
345 -export function markRenderStarted(lanes: Lanes): void {
346 - if (enableSchedulingProfiler) {
347 - if (supportsUserTimingV3) {
348 - markAndClear(`--render-start-${lanes}`);
349 - }
350 - }
351 -}
352 -
353 -export function markRenderYielded(): void {
354 - if (enableSchedulingProfiler) {
355 - if (supportsUserTimingV3) {
356 - markAndClear('--render-yield');
357 - }
358 - }
359 -}
360 -
361 -export function markRenderStopped(): void {
362 - if (enableSchedulingProfiler) {
363 - if (supportsUserTimingV3) {
364 - markAndClear('--render-stop');
365 - }
366 - }
367 -}
368 -
369 -export function markRenderScheduled(lane: Lane): void {
370 - if (enableSchedulingProfiler) {
371 - if (supportsUserTimingV3) {
372 - markAndClear(`--schedule-render-${lane}`);
373 - }
374 - }
375 -}
376 -
377 -export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
378 - if (enableSchedulingProfiler) {
379 - if (supportsUserTimingV3) {
380 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
381 - // TODO (timeline) Add component stack id
382 - markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
383 - }
384 - }
385 -}
386 -
387 -export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
388 - if (enableSchedulingProfiler) {
389 - if (supportsUserTimingV3) {
390 - const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
391 - // TODO (timeline) Add component stack id
392 - markAndClear(`--schedule-state-update-${lane}-${componentName}`);
393 - }
394 - }
395 -}
packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
+26
@@ -51,6 +51,32 @@ describe('updaters', () => {
51 onCommitUnmount: jest.fn(() => {}),
52 onPostCommitRoot: jest.fn(() => {}),
53 onScheduleRoot: jest.fn(() => {}),
54 +
55 + // Profiling APIs
56 + markCommitStarted: jest.fn(() => {}),
57 + markCommitStopped: jest.fn(() => {}),
58 + markComponentRenderStarted: jest.fn(() => {}),
59 + markComponentRenderStopped: jest.fn(() => {}),
60 + markComponentPassiveEffectMountStarted: jest.fn(() => {}),
61 + markComponentPassiveEffectMountStopped: jest.fn(() => {}),
62 + markComponentPassiveEffectUnmountStarted: jest.fn(() => {}),
63 + markComponentPassiveEffectUnmountStopped: jest.fn(() => {}),
64 + markComponentLayoutEffectMountStarted: jest.fn(() => {}),
65 + markComponentLayoutEffectMountStopped: jest.fn(() => {}),
66 + markComponentLayoutEffectUnmountStarted: jest.fn(() => {}),
67 + markComponentLayoutEffectUnmountStopped: jest.fn(() => {}),
68 + markComponentErrored: jest.fn(() => {}),
69 + markComponentSuspended: jest.fn(() => {}),
70 + markLayoutEffectsStarted: jest.fn(() => {}),
71 + markLayoutEffectsStopped: jest.fn(() => {}),
72 + markPassiveEffectsStarted: jest.fn(() => {}),
73 + markPassiveEffectsStopped: jest.fn(() => {}),
74 + markRenderStarted: jest.fn(() => {}),
75 + markRenderYielded: jest.fn(() => {}),
76 + markRenderStopped: jest.fn(() => {}),
77 + markRenderScheduled: jest.fn(() => {}),
78 + markForceUpdateScheduled: jest.fn(() => {}),
79 + markStateUpdateScheduled: jest.fn(() => {}),
80 };
81
82 jest.mock(
packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js deleted
-998
@@ -1,998 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - * @jest-environment node
9 - */
10 -
11 -'use strict';
12 -
13 -describe('SchedulingProfiler', () => {
14 - let React;
15 - let ReactTestRenderer;
16 - let ReactNoop;
17 - let Scheduler;
18 - let act;
19 -
20 - let clearedMarks;
21 - let featureDetectionMarkName = null;
22 - let marks;
23 -
24 - function createUserTimingPolyfill() {
25 - featureDetectionMarkName = null;
26 -
27 - clearedMarks = [];
28 - marks = [];
29 -
30 - // This is not a true polyfill, but it gives us enough to capture marks.
31 - // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
32 - return {
33 - clearMarks(markName) {
34 - clearedMarks.push(markName);
35 - marks = marks.filter(mark => mark !== markName);
36 - },
37 - mark(markName, markOptions) {
38 - if (featureDetectionMarkName === null) {
39 - featureDetectionMarkName = markName;
40 - }
41 - marks.push(markName);
42 - if (markOptions != null) {
43 - // This is triggers the feature detection.
44 - markOptions.startTime++;
45 - }
46 - },
47 - };
48 - }
49 -
50 - function clearPendingMarks() {
51 - clearedMarks.splice(0);
52 - }
53 -
54 - function getMarks() {
55 - return clearedMarks[0] === featureDetectionMarkName
56 - ? clearedMarks.slice(1)
57 - : clearedMarks;
58 - }
59 -
60 - beforeEach(() => {
61 - jest.resetModules();
62 -
63 - global.performance = createUserTimingPolyfill();
64 -
65 - React = require('react');
66 -
67 - // ReactNoop must be imported after ReactTestRenderer!
68 - ReactTestRenderer = require('react-test-renderer');
69 - ReactNoop = require('react-noop-renderer');
70 -
71 - Scheduler = require('scheduler');
72 - act = require('jest-react').act;
73 - });
74 -
75 - afterEach(() => {
76 - // Verify all logged marks also get cleared.
77 - expect(marks).toHaveLength(0);
78 -
79 - delete global.performance;
80 - });
81 -
82 - // @gate !enableSchedulingProfiler
83 - it('should not mark if enableSchedulingProfiler is false', () => {
84 - ReactTestRenderer.create(<div />);
85 - expect(getMarks()).toEqual([]);
86 - });
87 -
88 - it('should mark sync render without suspends or state updates', () => {
89 - ReactTestRenderer.create(<div />);
90 -
91 - if (gate(flags => flags.enableSchedulingProfiler)) {
92 - expect(getMarks()).toMatchInlineSnapshot(`
93 - Array [
94 - "--schedule-render-1",
95 - "--render-start-1",
96 - "--render-stop",
97 - "--commit-start-1",
98 - "--react-version-17.0.3",
99 - "--profiler-version-1",
100 - "--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",
101 - "--layout-effects-start-1",
102 - "--layout-effects-stop",
103 - "--commit-stop",
104 - ]
105 - `);
106 - }
107 - });
108 -
109 - it('should mark concurrent render without suspends or state updates', () => {
110 - ReactTestRenderer.create(<div />, {unstable_isConcurrent: true});
111 -
112 - if (gate(flags => flags.enableSchedulingProfiler)) {
113 - expect(getMarks()).toMatchInlineSnapshot(`
114 - Array [
115 - "--schedule-render-16",
116 - ]
117 - `);
118 - }
119 -
120 - clearPendingMarks();
121 -
122 - expect(Scheduler).toFlushUntilNextPaint([]);
123 -
124 - if (gate(flags => flags.enableSchedulingProfiler)) {
125 - expect(getMarks()).toMatchInlineSnapshot(`
126 - Array [
127 - "--render-start-16",
128 - "--render-stop",
129 - "--commit-start-16",
130 - "--react-version-17.0.3",
131 - "--profiler-version-1",
132 - "--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",
133 - "--layout-effects-start-16",
134 - "--layout-effects-stop",
135 - "--commit-stop",
136 - ]
137 - `);
138 - }
139 - });
140 -
141 - it('should mark render yields', async () => {
142 - function Bar() {
143 - Scheduler.unstable_yieldValue('Bar');
144 - return null;
145 - }
146 -
147 - function Foo() {
148 - Scheduler.unstable_yieldValue('Foo');
149 - return <Bar />;
150 - }
151 -
152 - if (gate(flags => flags.enableSyncDefaultUpdates)) {
153 - React.startTransition(() => {
154 - ReactNoop.render(<Foo />);
155 - });
156 -
157 - // Do one step of work.
158 - expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
159 -
160 - if (gate(flags => flags.enableSchedulingProfiler)) {
161 - expect(getMarks()).toMatchInlineSnapshot(`
162 - Array [
163 - "--schedule-render-64",
164 - "--render-start-64",
165 - "--component-render-start-Foo",
166 - "--component-render-stop",
167 - "--render-yield",
168 - ]
169 - `);
170 - }
171 - } else {
172 - ReactNoop.render(<Foo />);
173 -
174 - // Do one step of work.
175 - expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
176 -
177 - if (gate(flags => flags.enableSchedulingProfiler)) {
178 - expect(getMarks()).toMatchInlineSnapshot(`
179 - Array []
180 - `);
181 - }
182 - }
183 - });
184 -
185 - it('should mark sync render with suspense that resolves', async () => {
186 - const fakeSuspensePromise = Promise.resolve(true);
187 - function Example() {
188 - throw fakeSuspensePromise;
189 - }
190 -
191 - ReactTestRenderer.create(
192 - <React.Suspense fallback={null}>
193 - <Example />
194 - </React.Suspense>,
195 - );
196 -
197 - if (gate(flags => flags.enableSchedulingProfiler)) {
198 - expect(getMarks()).toMatchInlineSnapshot(`
199 - Array [
200 - "--schedule-render-1",
201 - "--render-start-1",
202 - "--component-render-start-Example",
203 - "--component-render-stop",
204 - "--suspense-suspend-0-Example-mount-1-",
205 - "--render-stop",
206 - "--commit-start-1",
207 - "--react-version-17.0.3",
208 - "--profiler-version-1",
209 - "--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",
210 - "--layout-effects-start-1",
211 - "--layout-effects-stop",
212 - "--commit-stop",
213 - ]
214 - `);
215 - }
216 -
217 - clearPendingMarks();
218 -
219 - await fakeSuspensePromise;
220 - if (gate(flags => flags.enableSchedulingProfiler)) {
221 - expect(getMarks()).toMatchInlineSnapshot(`
222 - Array [
223 - "--suspense-resolved-0-Example",
224 - ]
225 - `);
226 - }
227 - });
228 -
229 - it('should mark sync render with suspense that rejects', async () => {
230 - const fakeSuspensePromise = Promise.reject(new Error('error'));
231 - function Example() {
232 - throw fakeSuspensePromise;
233 - }
234 -
235 - ReactTestRenderer.create(
236 - <React.Suspense fallback={null}>
237 - <Example />
238 - </React.Suspense>,
239 - );
240 -
241 - if (gate(flags => flags.enableSchedulingProfiler)) {
242 - expect(getMarks()).toMatchInlineSnapshot(`
243 - Array [
244 - "--schedule-render-1",
245 - "--render-start-1",
246 - "--component-render-start-Example",
247 - "--component-render-stop",
248 - "--suspense-suspend-0-Example-mount-1-",
249 - "--render-stop",
250 - "--commit-start-1",
251 - "--react-version-17.0.3",
252 - "--profiler-version-1",
253 - "--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",
254 - "--layout-effects-start-1",
255 - "--layout-effects-stop",
256 - "--commit-stop",
257 - ]
258 - `);
259 - }
260 -
261 - clearPendingMarks();
262 -
263 - await expect(fakeSuspensePromise).rejects.toThrow();
264 - if (gate(flags => flags.enableSchedulingProfiler)) {
265 - expect(getMarks()).toMatchInlineSnapshot(`
266 - Array [
267 - "--suspense-rejected-0-Example",
268 - ]
269 - `);
270 - }
271 - });
272 -
273 - it('should mark concurrent render with suspense that resolves', async () => {
274 - const fakeSuspensePromise = Promise.resolve(true);
275 - function Example() {
276 - throw fakeSuspensePromise;
277 - }
278 -
279 - ReactTestRenderer.create(
280 - <React.Suspense fallback={null}>
281 - <Example />
282 - </React.Suspense>,
283 - {unstable_isConcurrent: true},
284 - );
285 -
286 - if (gate(flags => flags.enableSchedulingProfiler)) {
287 - expect(getMarks()).toMatchInlineSnapshot(`
288 - Array [
289 - "--schedule-render-16",
290 - ]
291 - `);
292 - }
293 -
294 - clearPendingMarks();
295 -
296 - expect(Scheduler).toFlushUntilNextPaint([]);
297 -
298 - if (gate(flags => flags.enableSchedulingProfiler)) {
299 - expect(getMarks()).toMatchInlineSnapshot(`
300 - Array [
301 - "--render-start-16",
302 - "--component-render-start-Example",
303 - "--component-render-stop",
304 - "--suspense-suspend-0-Example-mount-16-",
305 - "--render-stop",
306 - "--commit-start-16",
307 - "--react-version-17.0.3",
308 - "--profiler-version-1",
309 - "--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",
310 - "--layout-effects-start-16",
311 - "--layout-effects-stop",
312 - "--commit-stop",
313 - ]
314 - `);
315 - }
316 -
317 - clearPendingMarks();
318 -
319 - await fakeSuspensePromise;
320 - if (gate(flags => flags.enableSchedulingProfiler)) {
321 - expect(getMarks()).toMatchInlineSnapshot(`
322 - Array [
323 - "--suspense-resolved-0-Example",
324 - ]
325 - `);
326 - }
327 - });
328 -
329 - it('should mark concurrent render with suspense that rejects', async () => {
330 - const fakeSuspensePromise = Promise.reject(new Error('error'));
331 - function Example() {
332 - throw fakeSuspensePromise;
333 - }
334 -
335 - ReactTestRenderer.create(
336 - <React.Suspense fallback={null}>
337 - <Example />
338 - </React.Suspense>,
339 - {unstable_isConcurrent: true},
340 - );
341 -
342 - if (gate(flags => flags.enableSchedulingProfiler)) {
343 - expect(getMarks()).toMatchInlineSnapshot(`
344 - Array [
345 - "--schedule-render-16",
346 - ]
347 - `);
348 - }
349 -
350 - clearPendingMarks();
351 -
352 - expect(Scheduler).toFlushUntilNextPaint([]);
353 -
354 - if (gate(flags => flags.enableSchedulingProfiler)) {
355 - expect(getMarks()).toMatchInlineSnapshot(`
356 - Array [
357 - "--render-start-16",
358 - "--component-render-start-Example",
359 - "--component-render-stop",
360 - "--suspense-suspend-0-Example-mount-16-",
361 - "--render-stop",
362 - "--commit-start-16",
363 - "--react-version-17.0.3",
364 - "--profiler-version-1",
365 - "--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",
366 - "--layout-effects-start-16",
367 - "--layout-effects-stop",
368 - "--commit-stop",
369 - ]
370 - `);
371 - }
372 -
373 - clearPendingMarks();
374 -
375 - await expect(fakeSuspensePromise).rejects.toThrow();
376 - if (gate(flags => flags.enableSchedulingProfiler)) {
377 - expect(getMarks()).toMatchInlineSnapshot(`
378 - Array [
379 - "--suspense-rejected-0-Example",
380 - ]
381 - `);
382 - }
383 - });
384 -
385 - it('should mark cascading class component state updates', () => {
386 - class Example extends React.Component {
387 - state = {didMount: false};
388 - componentDidMount() {
389 - this.setState({didMount: true});
390 - }
391 - render() {
392 - return null;
393 - }
394 - }
395 -
396 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
397 -
398 - if (gate(flags => flags.enableSchedulingProfiler)) {
399 - expect(getMarks()).toMatchInlineSnapshot(`
400 - Array [
401 - "--schedule-render-16",
402 - ]
403 - `);
404 - }
405 -
406 - clearPendingMarks();
407 -
408 - expect(Scheduler).toFlushUntilNextPaint([]);
409 -
410 - if (gate(flags => flags.enableSchedulingProfiler)) {
411 - expect(getMarks()).toMatchInlineSnapshot(`
412 - Array [
413 - "--render-start-16",
414 - "--component-render-start-Example",
415 - "--component-render-stop",
416 - "--render-stop",
417 - "--commit-start-16",
418 - "--react-version-17.0.3",
419 - "--profiler-version-1",
420 - "--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",
421 - "--layout-effects-start-16",
422 - "--schedule-state-update-1-Example",
423 - "--layout-effects-stop",
424 - "--render-start-1",
425 - "--component-render-start-Example",
426 - "--component-render-stop",
427 - "--render-stop",
428 - "--commit-start-1",
429 - "--react-version-17.0.3",
430 - "--profiler-version-1",
431 - "--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",
432 - "--commit-stop",
433 - "--commit-stop",
434 - ]
435 - `);
436 - }
437 - });
438 -
439 - it('should mark cascading class component force updates', () => {
440 - class Example extends React.Component {
441 - componentDidMount() {
442 - this.forceUpdate();
443 - }
444 - render() {
445 - return null;
446 - }
447 - }
448 -
449 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
450 -
451 - if (gate(flags => flags.enableSchedulingProfiler)) {
452 - expect(getMarks()).toMatchInlineSnapshot(`
453 - Array [
454 - "--schedule-render-16",
455 - ]
456 - `);
457 - }
458 -
459 - clearPendingMarks();
460 -
461 - expect(Scheduler).toFlushUntilNextPaint([]);
462 -
463 - if (gate(flags => flags.enableSchedulingProfiler)) {
464 - expect(getMarks()).toMatchInlineSnapshot(`
465 - Array [
466 - "--render-start-16",
467 - "--component-render-start-Example",
468 - "--component-render-stop",
469 - "--render-stop",
470 - "--commit-start-16",
471 - "--react-version-17.0.3",
472 - "--profiler-version-1",
473 - "--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",
474 - "--layout-effects-start-16",
475 - "--schedule-forced-update-1-Example",
476 - "--layout-effects-stop",
477 - "--render-start-1",
478 - "--component-render-start-Example",
479 - "--component-render-stop",
480 - "--render-stop",
481 - "--commit-start-1",
482 - "--react-version-17.0.3",
483 - "--profiler-version-1",
484 - "--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",
485 - "--commit-stop",
486 - "--commit-stop",
487 - ]
488 - `);
489 - }
490 - });
491 -
492 - it('should mark render phase state updates for class component', () => {
493 - class Example extends React.Component {
494 - state = {didRender: false};
495 - render() {
496 - if (this.state.didRender === false) {
497 - this.setState({didRender: true});
498 - }
499 - return null;
500 - }
501 - }
502 -
503 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
504 -
505 - if (gate(flags => flags.enableSchedulingProfiler)) {
506 - expect(getMarks()).toMatchInlineSnapshot(`
507 - Array [
508 - "--schedule-render-16",
509 - ]
510 - `);
511 - }
512 -
513 - clearPendingMarks();
514 -
515 - expect(() => {
516 - expect(Scheduler).toFlushUntilNextPaint([]);
517 - }).toErrorDev('Cannot update during an existing state transition');
518 -
519 - if (gate(flags => flags.enableSchedulingProfiler)) {
520 - expect(getMarks()).toMatchInlineSnapshot(`
521 - Array [
522 - "--render-start-16",
523 - "--component-render-start-Example",
524 - "--schedule-state-update-16-Example",
525 - "--component-render-stop",
526 - "--render-stop",
527 - "--commit-start-16",
528 - "--react-version-17.0.3",
529 - "--profiler-version-1",
530 - "--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",
531 - "--layout-effects-start-16",
532 - "--layout-effects-stop",
533 - "--commit-stop",
534 - ]
535 - `);
536 - }
537 - });
538 -
539 - it('should mark render phase force updates for class component', () => {
540 - class Example extends React.Component {
541 - state = {didRender: false};
542 - render() {
543 - if (this.state.didRender === false) {
544 - this.forceUpdate(() => this.setState({didRender: true}));
545 - }
546 - return null;
547 - }
548 - }
549 -
550 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
551 -
552 - if (gate(flags => flags.enableSchedulingProfiler)) {
553 - expect(getMarks()).toMatchInlineSnapshot(`
554 - Array [
555 - "--schedule-render-16",
556 - ]
557 - `);
558 - }
559 -
560 - clearPendingMarks();
561 -
562 - expect(() => {
563 - expect(Scheduler).toFlushUntilNextPaint([]);
564 - }).toErrorDev('Cannot update during an existing state transition');
565 -
566 - if (gate(flags => flags.enableSchedulingProfiler)) {
567 - expect(getMarks()).toMatchInlineSnapshot(`
568 - Array [
569 - "--render-start-16",
570 - "--component-render-start-Example",
571 - "--schedule-forced-update-16-Example",
572 - "--component-render-stop",
573 - "--render-stop",
574 - "--commit-start-16",
575 - "--react-version-17.0.3",
576 - "--profiler-version-1",
577 - "--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",
578 - "--layout-effects-start-16",
579 - "--layout-effects-stop",
580 - "--commit-stop",
581 - ]
582 - `);
583 - }
584 - });
585 -
586 - it('should mark cascading layout updates', () => {
587 - function Example() {
588 - const [didMount, setDidMount] = React.useState(false);
589 - React.useLayoutEffect(() => {
590 - setDidMount(true);
591 - }, []);
592 - return didMount;
593 - }
594 -
595 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
596 -
597 - if (gate(flags => flags.enableSchedulingProfiler)) {
598 - expect(getMarks()).toMatchInlineSnapshot(`
599 - Array [
600 - "--schedule-render-16",
601 - ]
602 - `);
603 - }
604 -
605 - clearPendingMarks();
606 -
607 - expect(Scheduler).toFlushUntilNextPaint([]);
608 -
609 - if (gate(flags => flags.enableSchedulingProfiler)) {
610 - expect(getMarks()).toMatchInlineSnapshot(`
611 - Array [
612 - "--render-start-16",
613 - "--component-render-start-Example",
614 - "--component-render-stop",
615 - "--render-stop",
616 - "--commit-start-16",
617 - "--react-version-17.0.3",
618 - "--profiler-version-1",
619 - "--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",
620 - "--layout-effects-start-16",
621 - "--component-layout-effect-mount-start-Example",
622 - "--schedule-state-update-1-Example",
623 - "--component-layout-effect-mount-stop",
624 - "--layout-effects-stop",
625 - "--render-start-1",
626 - "--component-render-start-Example",
627 - "--component-render-stop",
628 - "--render-stop",
629 - "--commit-start-1",
630 - "--react-version-17.0.3",
631 - "--profiler-version-1",
632 - "--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",
633 - "--commit-stop",
634 - "--commit-stop",
635 - ]
636 - `);
637 - }
638 - });
639 -
640 - // This test is coupled to lane implementation details, so I'm disabling it in
641 - // the new fork until it stabilizes so we don't have to repeatedly update it.
642 - it('should mark cascading passive updates', () => {
643 - function Example() {
644 - const [didMount, setDidMount] = React.useState(false);
645 - React.useEffect(() => {
646 - setDidMount(true);
647 - }, []);
648 - return didMount;
649 - }
650 -
651 - act(() => {
652 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
653 - });
654 -
655 - if (gate(flags => flags.enableSchedulingProfiler)) {
656 - expect(getMarks()).toMatchInlineSnapshot(`
657 - Array [
658 - "--schedule-render-16",
659 - "--render-start-16",
660 - "--component-render-start-Example",
661 - "--component-render-stop",
662 - "--render-stop",
663 - "--commit-start-16",
664 - "--react-version-17.0.3",
665 - "--profiler-version-1",
666 - "--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",
667 - "--layout-effects-start-16",
668 - "--layout-effects-stop",
669 - "--commit-stop",
670 - "--passive-effects-start-16",
671 - "--component-passive-effect-mount-start-Example",
672 - "--schedule-state-update-16-Example",
673 - "--component-passive-effect-mount-stop",
674 - "--passive-effects-stop",
675 - "--render-start-16",
676 - "--component-render-start-Example",
677 - "--component-render-stop",
678 - "--render-stop",
679 - "--commit-start-16",
680 - "--react-version-17.0.3",
681 - "--profiler-version-1",
682 - "--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",
683 - "--commit-stop",
684 - ]
685 - `);
686 - }
687 - });
688 -
689 - it('should mark render phase updates', () => {
690 - function Example() {
691 - const [didRender, setDidRender] = React.useState(false);
692 - if (!didRender) {
693 - setDidRender(true);
694 - }
695 - return didRender;
696 - }
697 -
698 - act(() => {
699 - ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
700 - });
701 -
702 - if (gate(flags => flags.enableSchedulingProfiler)) {
703 - expect(getMarks()).toMatchInlineSnapshot(`
704 - Array [
705 - "--schedule-render-16",
706 - "--render-start-16",
707 - "--component-render-start-Example",
708 - "--schedule-state-update-16-Example",
709 - "--component-render-stop",
710 - "--render-stop",
711 - "--commit-start-16",
712 - "--react-version-17.0.3",
713 - "--profiler-version-1",
714 - "--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",
715 - "--layout-effects-start-16",
716 - "--layout-effects-stop",
717 - "--commit-stop",
718 - ]
719 - `);
720 - }
721 - });
722 -
723 - it('should mark sync render that throws', async () => {
724 - class ErrorBoundary extends React.Component {
725 - state = {error: null};
726 - componentDidCatch(error) {
727 - this.setState({error});
728 - }
729 - render() {
730 - if (this.state.error) {
731 - return null;
732 - }
733 - return this.props.children;
734 - }
735 - }
736 -
737 - function ExampleThatThrows() {
738 - throw Error('Expected error');
739 - }
740 -
741 - ReactTestRenderer.create(
742 - <ErrorBoundary>
743 - <ExampleThatThrows />
744 - </ErrorBoundary>,
745 - );
746 -
747 - if (gate(flags => flags.enableSchedulingProfiler)) {
748 - expect(getMarks()).toMatchInlineSnapshot(`
749 - Array [
750 - "--schedule-render-1",
751 - "--render-start-1",
752 - "--component-render-start-ErrorBoundary",
753 - "--component-render-stop",
754 - "--component-render-start-ExampleThatThrows",
755 - "--component-render-start-ExampleThatThrows",
756 - "--component-render-stop",
757 - "--error-ExampleThatThrows-mount-Expected error",
758 - "--render-stop",
759 - "--commit-start-1",
760 - "--react-version-17.0.3",
761 - "--profiler-version-1",
762 - "--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",
763 - "--layout-effects-start-1",
764 - "--schedule-state-update-1-ErrorBoundary",
765 - "--layout-effects-stop",
766 - "--commit-stop",
767 - "--render-start-1",
768 - "--component-render-start-ErrorBoundary",
769 - "--component-render-stop",
770 - "--render-stop",
771 - "--commit-start-1",
772 - "--react-version-17.0.3",
773 - "--profiler-version-1",
774 - "--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",
775 - "--commit-stop",
776 - ]
777 - `);
778 - }
779 - });
780 -
781 - it('should mark concurrent render that throws', async () => {
782 - spyOnProd(console, 'error');
783 -
784 - class ErrorBoundary extends React.Component {
785 - state = {error: null};
786 - componentDidCatch(error) {
787 - this.setState({error});
788 - }
789 - render() {
790 - if (this.state.error) {
791 - return null;
792 - }
793 - return this.props.children;
794 - }
795 - }
796 -
797 - function ExampleThatThrows() {
798 - // eslint-disable-next-line no-throw-literal
799 - throw 'Expected error';
800 - }
801 -
802 - ReactTestRenderer.create(
803 - <ErrorBoundary>
804 - <ExampleThatThrows />
805 - </ErrorBoundary>,
806 - {unstable_isConcurrent: true},
807 - );
808 -
809 - if (gate(flags => flags.enableSchedulingProfiler)) {
810 - expect(getMarks()).toMatchInlineSnapshot(`
811 - Array [
812 - "--schedule-render-16",
813 - ]
814 - `);
815 - }
816 -
817 - clearPendingMarks();
818 -
819 - expect(Scheduler).toFlushUntilNextPaint([]);
820 -
821 - if (gate(flags => flags.enableSchedulingProfiler)) {
822 - expect(getMarks()).toMatchInlineSnapshot(`
823 - Array [
824 - "--render-start-16",
825 - "--component-render-start-ErrorBoundary",
826 - "--component-render-stop",
827 - "--component-render-start-ExampleThatThrows",
828 - "--component-render-start-ExampleThatThrows",
829 - "--component-render-stop",
830 - "--error-ExampleThatThrows-mount-Expected error",
831 - "--render-stop",
832 - "--render-start-16",
833 - "--component-render-start-ErrorBoundary",
834 - "--component-render-stop",
835 - "--component-render-start-ExampleThatThrows",
836 - "--component-render-start-ExampleThatThrows",
837 - "--component-render-stop",
838 - "--error-ExampleThatThrows-mount-Expected error",
839 - "--render-stop",
840 - "--commit-start-16",
841 - "--react-version-17.0.3",
842 - "--profiler-version-1",
843 - "--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",
844 - "--layout-effects-start-16",
845 - "--schedule-state-update-1-ErrorBoundary",
846 - "--layout-effects-stop",
847 - "--render-start-1",
848 - "--component-render-start-ErrorBoundary",
849 - "--component-render-stop",
850 - "--render-stop",
851 - "--commit-start-1",
852 - "--react-version-17.0.3",
853 - "--profiler-version-1",
854 - "--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",
855 - "--commit-stop",
856 - "--commit-stop",
857 - ]
858 - `);
859 - }
860 - });
861 -
862 - it('should mark passive and layout effects', async () => {
863 - function ComponentWithEffects() {
864 - React.useLayoutEffect(() => {
865 - Scheduler.unstable_yieldValue('layout 1 mount');
866 - return () => {
867 - Scheduler.unstable_yieldValue('layout 1 unmount');
868 - };
869 - }, []);
870 -
871 - React.useEffect(() => {
872 - Scheduler.unstable_yieldValue('passive 1 mount');
873 - return () => {
874 - Scheduler.unstable_yieldValue('passive 1 unmount');
875 - };
876 - }, []);
877 -
878 - React.useLayoutEffect(() => {
879 - Scheduler.unstable_yieldValue('layout 2 mount');
880 - return () => {
881 - Scheduler.unstable_yieldValue('layout 2 unmount');
882 - };
883 - }, []);
884 -
885 - React.useEffect(() => {
886 - Scheduler.unstable_yieldValue('passive 2 mount');
887 - return () => {
888 - Scheduler.unstable_yieldValue('passive 2 unmount');
889 - };
890 - }, []);
891 -
892 - React.useEffect(() => {
893 - Scheduler.unstable_yieldValue('passive 3 mount');
894 - return () => {
895 - Scheduler.unstable_yieldValue('passive 3 unmount');
896 - };
897 - }, []);
898 -
899 - return null;
900 - }
901 -
902 - const renderer = ReactTestRenderer.create(<ComponentWithEffects />, {
903 - unstable_isConcurrent: true,
904 - });
905 -
906 - expect(Scheduler).toFlushUntilNextPaint([
907 - 'layout 1 mount',
908 - 'layout 2 mount',
909 - ]);
910 -
911 - if (gate(flags => flags.enableSchedulingProfiler)) {
912 - expect(getMarks()).toMatchInlineSnapshot(`
913 - Array [
914 - "--schedule-render-16",
915 - "--render-start-16",
916 - "--component-render-start-ComponentWithEffects",
917 - "--component-render-stop",
918 - "--render-stop",
919 - "--commit-start-16",
920 - "--react-version-17.0.3",
921 - "--profiler-version-1",
922 - "--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",
923 - "--layout-effects-start-16",
924 - "--component-layout-effect-mount-start-ComponentWithEffects",
925 - "--component-layout-effect-mount-stop",
926 - "--component-layout-effect-mount-start-ComponentWithEffects",
927 - "--component-layout-effect-mount-stop",
928 - "--layout-effects-stop",
929 - "--commit-stop",
930 - ]
931 - `);
932 - }
933 -
934 - clearPendingMarks();
935 -
936 - expect(Scheduler).toFlushAndYield([
937 - 'passive 1 mount',
938 - 'passive 2 mount',
939 - 'passive 3 mount',
940 - ]);
941 -
942 - if (gate(flags => flags.enableSchedulingProfiler)) {
943 - expect(getMarks()).toMatchInlineSnapshot(`
944 - Array [
945 - "--passive-effects-start-16",
946 - "--component-passive-effect-mount-start-ComponentWithEffects",
947 - "--component-passive-effect-mount-stop",
948 - "--component-passive-effect-mount-start-ComponentWithEffects",
949 - "--component-passive-effect-mount-stop",
950 - "--component-passive-effect-mount-start-ComponentWithEffects",
951 - "--component-passive-effect-mount-stop",
952 - "--passive-effects-stop",
953 - ]
954 - `);
955 - }
956 -
957 - clearPendingMarks();
958 -
959 - renderer.unmount();
960 -
961 - expect(Scheduler).toFlushAndYield([
962 - 'layout 1 unmount',
963 - 'layout 2 unmount',
964 - 'passive 1 unmount',
965 - 'passive 2 unmount',
966 - 'passive 3 unmount',
967 - ]);
968 -
969 - if (gate(flags => flags.enableSchedulingProfiler)) {
970 - expect(getMarks()).toMatchInlineSnapshot(`
971 - Array [
972 - "--schedule-render-16",
973 - "--render-start-16",
974 - "--render-stop",
975 - "--commit-start-16",
976 - "--react-version-17.0.3",
977 - "--profiler-version-1",
978 - "--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",
979 - "--component-layout-effect-unmount-start-ComponentWithEffects",
980 - "--component-layout-effect-unmount-stop",
981 - "--component-layout-effect-unmount-start-ComponentWithEffects",
982 - "--component-layout-effect-unmount-stop",
983 - "--layout-effects-start-16",
984 - "--layout-effects-stop",
985 - "--commit-stop",
986 - "--passive-effects-start-16",
987 - "--component-passive-effect-unmount-start-ComponentWithEffects",
988 - "--component-passive-effect-unmount-stop",
989 - "--component-passive-effect-unmount-start-ComponentWithEffects",
990 - "--component-passive-effect-unmount-stop",
991 - "--component-passive-effect-unmount-start-ComponentWithEffects",
992 - "--component-passive-effect-unmount-stop",
993 - "--passive-effects-stop",
994 - ]
995 - `);
996 - }
997 - });
998 -});
packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js deleted
-216
@@ -1,216 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -'use strict';
11 -
12 -// This test schedules work for as many lanes as we can (easily) using public APIs.
13 -// It will hopefully serve as a reminder to update getLabelsForLanes() when we update Lanes.
14 -// It's okay to delete any individual test in this file if the public API changes.
15 -describe('SchedulingProfiler labels', () => {
16 - let React;
17 - let ReactDOM;
18 -
19 - let act;
20 - let clearedMarks;
21 - let featureDetectionMarkName = null;
22 - let marks;
23 -
24 - global.IS_REACT_ACT_ENVIRONMENT = true;
25 -
26 - function polyfillJSDomUserTiming() {
27 - featureDetectionMarkName = null;
28 -
29 - clearedMarks = [];
30 - marks = [];
31 -
32 - // This is not a true polyfill, but it gives us enough to capture marks.
33 - // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
34 -
35 - // JSDom already implements global.performance and it can't be overridden.
36 - // However it only supports now() and timeOrigin() so we need to "upgrade it" in place.
37 -
38 - if (!global.performance) {
39 - global.performance = {};
40 - }
41 -
42 - global.performance.clearMarks = function clearMarks(markName) {
43 - clearedMarks.push(markName);
44 - marks = marks.filter(mark => mark !== markName);
45 - };
46 -
47 - global.performance.mark = function mark(markName, markOptions) {
48 - if (featureDetectionMarkName === null) {
49 - featureDetectionMarkName = markName;
50 - }
51 - marks.push(markName);
52 - if (markOptions != null) {
53 - // This is triggers the feature detection.
54 - markOptions.startTime++;
55 - }
56 - };
57 - }
58 -
59 - function dispatchAndSetCurrentEvent(element, event) {
60 - try {
61 - window.event = event;
62 - element.dispatchEvent(event);
63 - } finally {
64 - window.event = undefined;
65 - }
66 - }
67 -
68 - beforeEach(() => {
69 - jest.resetModules();
70 -
71 - polyfillJSDomUserTiming();
72 -
73 - React = require('react');
74 - ReactDOM = require('react-dom');
75 -
76 - const TestUtils = require('react-dom/test-utils');
77 - act = TestUtils.act;
78 - });
79 -
80 - afterEach(() => {
81 - // Verify all logged marks also get cleared.
82 - expect(marks).toHaveLength(0);
83 -
84 - delete global.performance;
85 - });
86 -
87 - it('regression test SyncLane', () => {
88 - ReactDOM.render(<div />, document.createElement('div'));
89 -
90 - if (gate(flags => flags.enableSchedulingProfiler)) {
91 - expect(clearedMarks).toMatchInlineSnapshot(`
92 - Array [
93 - "__v3",
94 - "--schedule-render-1",
95 - "--render-start-1",
96 - "--render-stop",
97 - "--commit-start-1",
98 - "--react-version-17.0.3",
99 - "--profiler-version-1",
100 - "--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",
101 - "--layout-effects-start-1",
102 - "--layout-effects-stop",
103 - "--commit-stop",
104 - ]
105 - `);
106 - }
107 - });
108 -
109 - it('regression test DefaultLane', () => {
110 - if (gate(flags => flags.enableSchedulingProfiler)) {
111 - act(() => {
112 - const container = document.createElement('div');
113 - const root = ReactDOM.createRoot(container);
114 -
115 - root.render(<div />);
116 - expect(clearedMarks).toMatchInlineSnapshot(`
117 - Array [
118 - "__v3",
119 - "--schedule-render-16",
120 - ]
121 - `);
122 - });
123 - }
124 - });
125 -
126 - it('regression test InputDiscreteLane', () => {
127 - const container = document.createElement('div');
128 - const root = ReactDOM.createRoot(container);
129 - const targetRef = React.createRef(null);
130 -
131 - function App() {
132 - const [count, setCount] = React.useState(0);
133 - const handleClick = () => {
134 - setCount(count + 1);
135 - };
136 - return <button ref={targetRef} onClick={handleClick} />;
137 - }
138 -
139 - if (
140 - gate(
141 - flags => flags.enableSchedulingProfiler && !flags.enableLegacyFBSupport,
142 - )
143 - ) {
144 - act(() => {
145 - root.render(<App />);
146 - });
147 -
148 - clearedMarks.splice(0);
149 -
150 - act(() => {
151 - targetRef.current.click();
152 - });
153 -
154 - expect(clearedMarks).toMatchInlineSnapshot(`
155 - Array [
156 - "--schedule-state-update-1-App",
157 - "--render-start-1",
158 - "--component-render-start-App",
159 - "--component-render-stop",
160 - "--render-stop",
161 - "--commit-start-1",
162 - "--react-version-17.0.3",
163 - "--profiler-version-1",
164 - "--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",
165 - "--layout-effects-start-1",
166 - "--layout-effects-stop",
167 - "--commit-stop",
168 - ]
169 - `);
170 - }
171 - });
172 -
173 - it('regression test InputContinuousLane', () => {
174 - const targetRef = React.createRef(null);
175 -
176 - function App() {
177 - const [count, setCount] = React.useState(0);
178 - const handleMouseOver = () => setCount(count + 1);
179 - return <div ref={targetRef} onMouseOver={handleMouseOver} />;
180 - }
181 -
182 - if (gate(flags => flags.enableSchedulingProfiler)) {
183 - const container = document.createElement('div');
184 - const root = ReactDOM.createRoot(container);
185 -
186 - act(() => {
187 - root.render(<App />);
188 - });
189 -
190 - clearedMarks.splice(0);
191 -
192 - act(() => {
193 - const event = document.createEvent('MouseEvents');
194 - event.initEvent('mouseover', true, true);
195 - dispatchAndSetCurrentEvent(targetRef.current, event);
196 - });
197 -
198 - expect(clearedMarks).toMatchInlineSnapshot(`
199 - Array [
200 - "--schedule-state-update-4-App",
201 - "--render-start-4",
202 - "--component-render-start-App",
203 - "--component-render-stop",
204 - "--render-stop",
205 - "--commit-start-4",
206 - "--react-version-17.0.3",
207 - "--profiler-version-1",
208 - "--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",
209 - "--layout-effects-start-4",
210 - "--layout-effects-stop",
211 - "--commit-stop",
212 - ]
213 - `);
214 - }
215 - });
216 -});
scripts/jest/matchers/profilerMatchers.js