@samitouri / QOS-React / commits / fa816be7f0

DevTools: Timeline profiler refactor

Refactor DevTools to record Timeline data (in memory) while profiling. Updated the Profiler UI to import/export Timeline data along with legacy profiler data. Relates to issue #22529

Brian Vaughn committed Jan 28, 2022 at 13:09 UTC fa816be7f0b28df051d80acfc85156197a9365de
49 files changed +10716 -4497
packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+2243 -950
@@ -15,72 +15,10 @@ describe('Timeline profiler', () => {
15 let Scheduler;
16 let renderHelper;
17 let renderRootHelper;
18 + let store;
19 let unmountFns;
20 let utils;
21
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 -
22 beforeEach(() => {
23 utils = require('./utils');
24 utils.beforeEachProfiling();
@@ -104,1021 +42,2376 @@ describe('Timeline profiler', () => {
42 ReactDOM = require('react-dom');
43 Scheduler = require('scheduler');
44
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);
45 + store = global.store;
46 });
47
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 - });
48 + describe('User Timing API', () => {
49 + let clearedMarks;
50 + let featureDetectionMarkName = null;
51 + let marks;
52 + let setPerformanceMock;
53 +
54 + function createUserTimingPolyfill() {
55 + featureDetectionMarkName = null;
56 +
57 + clearedMarks = [];
58 + marks = [];
59 +
60 + // Remove file-system specific bits or version-specific bits of information from the module range marks.
61 + function filterMarkData(markName) {
62 + if (markName.startsWith('--react-internal-module-start')) {
63 + return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
64 + } else if (markName.startsWith('--react-internal-module-stop')) {
65 + return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
66 + } else if (markName.startsWith('--react-version')) {
67 + return '--react-version-<filtered-version>';
68 + } else {
69 + return markName;
70 + }
71 + }
72
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 - });
73 + // This is not a true polyfill, but it gives us enough to capture marks.
74 + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
75 + return {
76 + clearMarks(markName) {
77 + markName = filterMarkData(markName);
78 +
79 + clearedMarks.push(markName);
80 + marks = marks.filter(mark => mark !== markName);
81 + },
82 + mark(markName, markOptions) {
83 + markName = filterMarkData(markName);
84 +
85 + if (featureDetectionMarkName === null) {
86 + featureDetectionMarkName = markName;
87 + }
88 +
89 + marks.push(markName);
90 +
91 + if (markOptions != null) {
92 + // This is triggers the feature detection.
93 + markOptions.startTime++;
94 + }
95 + },
96 + };
97 + }
98
172 - it('should mark render yields', async () => {
173 - function Bar() {
174 - Scheduler.unstable_yieldValue('Bar');
175 - return null;
99 + function clearPendingMarks() {
100 + clearedMarks.splice(0);
101 }
102
178 - function Foo() {
179 - Scheduler.unstable_yieldValue('Foo');
180 - return <Bar />;
103 + function dispatchAndSetCurrentEvent(element, event) {
104 + try {
105 + window.event = event;
106 + element.dispatchEvent(event);
107 + } finally {
108 + window.event = undefined;
109 + }
110 }
111
183 - React.startTransition(() => {
184 - renderRootHelper(<Foo />);
112 + beforeEach(() => {
113 + setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
114 + .setPerformanceMock_ONLY_FOR_TESTING;
115 + setPerformanceMock(createUserTimingPolyfill());
116 });
117
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 - });
118 + afterEach(() => {
119 + // Verify all logged marks also get cleared.
120 + expect(marks).toHaveLength(0);
121
201 - it('should mark sync render with suspense that resolves', async () => {
202 - const fakeSuspensePromise = Promise.resolve(true);
203 - function Example() {
204 - throw fakeSuspensePromise;
205 - }
122 + unmountFns.forEach(unmountFn => unmountFn());
123 +
124 + setPerformanceMock(null);
125 + });
126 +
127 + it('should mark sync render without suspends or state updates', () => {
128 + renderHelper(<div />);
129
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(`
130 + expect(clearedMarks).toMatchInlineSnapshot(`
131 Array [
238 - "--suspense-resolved-0-Example",
132 + "--schedule-render-1",
133 + "--render-start-1",
134 + "--render-stop",
135 + "--commit-start-1",
136 + "--react-version-<filtered-version>",
137 + "--profiler-version-1",
138 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
139 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
140 + "--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",
141 + "--layout-effects-start-1",
142 + "--layout-effects-stop",
143 + "--commit-stop",
144 ]
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 - }
145 + `);
146 + });
147
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 - });
148 + it('should mark concurrent render without suspends or state updates', () => {
149 + renderRootHelper(<div />);
150
281 - it('should mark concurrent render with suspense that resolves', async () => {
282 - const fakeSuspensePromise = Promise.resolve(true);
283 - function Example() {
284 - throw fakeSuspensePromise;
285 - }
151 + expect(clearedMarks).toMatchInlineSnapshot(`
152 + Array [
153 + "--schedule-render-16",
154 + ]
155 + `);
156
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 - });
157 + clearPendingMarks();
158
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 - }
159 + expect(Scheduler).toFlushUntilNextPaint([]);
160
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(`
161 + expect(clearedMarks).toMatchInlineSnapshot(`
162 Array [
378 - "--suspense-rejected-0-Example",
163 + "--render-start-16",
164 + "--render-stop",
165 + "--commit-start-16",
166 + "--react-version-<filtered-version>",
167 + "--profiler-version-1",
168 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
169 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
170 + "--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",
171 + "--layout-effects-start-16",
172 + "--layout-effects-stop",
173 + "--commit-stop",
174 ]
380 - `);
381 - });
175 + `);
176 + });
177
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() {
178 + it('should mark render yields', async () => {
179 + function Bar() {
180 + Scheduler.unstable_yieldValue('Bar');
181 return null;
182 }
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 - });
183
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;
184 + function Foo() {
185 + Scheduler.unstable_yieldValue('Foo');
186 + return <Bar />;
187 }
445 - }
188
447 - renderRootHelper(<Example />);
189 + React.startTransition(() => {
190 + renderRootHelper(<Foo />);
191 + });
192
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 - });
193 + // Do one step of work.
194 + expect(Scheduler).toFlushAndYieldThrough(['Foo']);
195
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;
196 + expect(clearedMarks).toMatchInlineSnapshot(`
197 + Array [
198 + "--schedule-render-64",
199 + "--render-start-64",
200 + "--component-render-start-Foo",
201 + "--component-render-stop",
202 + "--render-yield",
203 + ]
204 + `);
205 + });
206 +
207 + it('should mark sync render with suspense that resolves', async () => {
208 + const fakeSuspensePromise = Promise.resolve(true);
209 + function Example() {
210 + throw fakeSuspensePromise;
211 }
499 - }
212
501 - renderRootHelper(<Example />);
213 + renderHelper(
214 + <React.Suspense fallback={null}>
215 + <Example />
216 + </React.Suspense>,
217 + );
218
503 - expect(clearedMarks).toMatchInlineSnapshot(`
219 + expect(clearedMarks).toMatchInlineSnapshot(`
220 Array [
505 - "--schedule-render-16",
221 + "--schedule-render-1",
222 + "--render-start-1",
223 + "--component-render-start-Example",
224 + "--component-render-stop",
225 + "--suspense-suspend-0-Example-mount-1-",
226 + "--render-stop",
227 + "--commit-start-1",
228 + "--react-version-<filtered-version>",
229 + "--profiler-version-1",
230 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
231 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
232 + "--react-lane-labels-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",
233 + "--layout-effects-start-1",
234 + "--layout-effects-stop",
235 + "--commit-stop",
236 ]
507 - `);
237 + `);
238
509 - clearPendingMarks();
239 + clearPendingMarks();
240
511 - let errorMessage;
512 - spyOn(console, 'error').and.callFake(message => {
513 - errorMessage = message;
241 + await fakeSuspensePromise;
242 + expect(clearedMarks).toMatchInlineSnapshot(`
243 + Array [
244 + "--suspense-resolved-0-Example",
245 + ]
246 + `);
247 });
248
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;
249 + it('should mark sync render with suspense that rejects', async () => {
250 + const fakeSuspensePromise = Promise.reject(new Error('error'));
251 + function Example() {
252 + throw fakeSuspensePromise;
253 }
552 - }
254
554 - renderRootHelper(<Example />);
255 + renderHelper(
256 + <React.Suspense fallback={null}>
257 + <Example />
258 + </React.Suspense>,
259 + );
260
556 - expect(clearedMarks).toMatchInlineSnapshot(`
261 + expect(clearedMarks).toMatchInlineSnapshot(`
262 Array [
558 - "--schedule-render-16",
263 + "--schedule-render-1",
264 + "--render-start-1",
265 + "--component-render-start-Example",
266 + "--component-render-stop",
267 + "--suspense-suspend-0-Example-mount-1-",
268 + "--render-stop",
269 + "--commit-start-1",
270 + "--react-version-<filtered-version>",
271 + "--profiler-version-1",
272 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
273 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
274 + "--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",
275 + "--layout-effects-start-1",
276 + "--layout-effects-stop",
277 + "--commit-stop",
278 ]
560 - `);
279 + `);
280
562 - clearPendingMarks();
281 + clearPendingMarks();
282
564 - let errorMessage;
565 - spyOn(console, 'error').and.callFake(message => {
566 - errorMessage = message;
283 + await expect(fakeSuspensePromise).rejects.toThrow();
284 + expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
285 });
286
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 - });
287 + it('should mark concurrent render with suspense that resolves', async () => {
288 + const fakeSuspensePromise = Promise.resolve(true);
289 + function Example() {
290 + throw fakeSuspensePromise;
291 + }
292
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 - }
293 + renderRootHelper(
294 + <React.Suspense fallback={null}>
295 + <Example />
296 + </React.Suspense>,
297 + );
298 +
299 + expect(clearedMarks).toMatchInlineSnapshot(`
300 + Array [
301 + "--schedule-render-16",
302 + ]
303 + `);
304 +
305 + clearPendingMarks();
306
605 - renderRootHelper(<Example />);
307 + expect(Scheduler).toFlushUntilNextPaint([]);
308
607 - expect(clearedMarks).toMatchInlineSnapshot(`
309 + expect(clearedMarks).toMatchInlineSnapshot(`
310 Array [
609 - "--schedule-render-16",
311 + "--render-start-16",
312 + "--component-render-start-Example",
313 + "--component-render-stop",
314 + "--suspense-suspend-0-Example-mount-16-",
315 + "--render-stop",
316 + "--commit-start-16",
317 + "--react-version-<filtered-version>",
318 + "--profiler-version-1",
319 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
320 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
321 + "--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",
322 + "--layout-effects-start-16",
323 + "--layout-effects-stop",
324 + "--commit-stop",
325 ]
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 - });
326 + `);
327
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 - }
328 + clearPendingMarks();
329
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 - });
330 + await fakeSuspensePromise;
331 + expect(clearedMarks).toMatchInlineSnapshot(`
332 + Array [
333 + "--suspense-resolved-0-Example",
334 + ]
335 + `);
336 + });
337
701 - it('should mark render phase updates', () => {
702 - function Example() {
703 - const [didRender, setDidRender] = React.useState(false);
704 - if (!didRender) {
705 - setDidRender(true);
338 + it('should mark concurrent render with suspense that rejects', async () => {
339 + const fakeSuspensePromise = Promise.reject(new Error('error'));
340 + function Example() {
341 + throw fakeSuspensePromise;
342 }
707 - return didRender;
708 - }
343
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 - });
344 + renderRootHelper(
345 + <React.Suspense fallback={null}>
346 + <Example />
347 + </React.Suspense>,
348 + );
349
735 - it('should mark sync render that throws', async () => {
736 - spyOn(console, 'error');
350 + expect(clearedMarks).toMatchInlineSnapshot(`
351 + Array [
352 + "--schedule-render-16",
353 + ]
354 + `);
355
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 - }
356 + clearPendingMarks();
357
751 - function ExampleThatThrows() {
752 - throw Error('Expected error');
753 - }
358 + expect(Scheduler).toFlushUntilNextPaint([]);
359
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 - });
360 + expect(clearedMarks).toMatchInlineSnapshot(`
361 + Array [
362 + "--render-start-16",
363 + "--component-render-start-Example",
364 + "--component-render-stop",
365 + "--suspense-suspend-0-Example-mount-16-",
366 + "--render-stop",
367 + "--commit-start-16",
368 + "--react-version-<filtered-version>",
369 + "--profiler-version-1",
370 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
371 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
372 + "--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",
373 + "--layout-effects-start-16",
374 + "--layout-effects-stop",
375 + "--commit-stop",
376 + ]
377 + `);
378
797 - it('should mark concurrent render that throws', async () => {
798 - spyOn(console, 'error');
379 + clearPendingMarks();
380
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) {
381 + await expect(fakeSuspensePromise).rejects.toThrow();
382 + expect(clearedMarks).toMatchInlineSnapshot(`
383 + Array [
384 + "--suspense-rejected-0-Example",
385 + ]
386 + `);
387 + });
388 +
389 + it('should mark cascading class component state updates', () => {
390 + class Example extends React.Component {
391 + state = {didMount: false};
392 + componentDidMount() {
393 + this.setState({didMount: true});
394 + }
395 + render() {
396 return null;
397 }
809 - return this.props.children;
398 }
811 - }
399
813 - function ExampleThatThrows() {
814 - // eslint-disable-next-line no-throw-literal
815 - throw 'Expected error';
816 - }
400 + renderRootHelper(<Example />);
401 +
402 + expect(clearedMarks).toMatchInlineSnapshot(`
403 + Array [
404 + "--schedule-render-16",
405 + ]
406 + `);
407 +
408 + clearPendingMarks();
409
818 - renderRootHelper(
819 - <ErrorBoundary>
820 - <ExampleThatThrows />
821 - </ErrorBoundary>,
822 - );
410 + expect(Scheduler).toFlushUntilNextPaint([]);
411
824 - expect(clearedMarks).toMatchInlineSnapshot(`
412 + expect(clearedMarks).toMatchInlineSnapshot(`
413 Array [
826 - "--schedule-render-16",
414 + "--render-start-16",
415 + "--component-render-start-Example",
416 + "--component-render-stop",
417 + "--render-stop",
418 + "--commit-start-16",
419 + "--react-version-<filtered-version>",
420 + "--profiler-version-1",
421 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
422 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
423 + "--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",
424 + "--layout-effects-start-16",
425 + "--schedule-state-update-1-Example",
426 + "--layout-effects-stop",
427 + "--render-start-1",
428 + "--component-render-start-Example",
429 + "--component-render-stop",
430 + "--render-stop",
431 + "--commit-start-1",
432 + "--react-version-<filtered-version>",
433 + "--profiler-version-1",
434 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
435 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
436 + "--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",
437 + "--commit-stop",
438 + "--commit-stop",
439 ]
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 - });
440 + `);
441 + });
442
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 - }
443 + it('should mark cascading class component force updates', () => {
444 + class Example extends React.Component {
445 + componentDidMount() {
446 + this.forceUpdate();
447 + }
448 + render() {
449 + return null;
450 + }
451 + }
452
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 - });
453 + renderRootHelper(<Example />);
454
1012 - describe('lane labels', () => {
1013 - it('regression test SyncLane', () => {
1014 - renderHelper(<div />);
455 + expect(clearedMarks).toMatchInlineSnapshot(`
456 + Array [
457 + "--schedule-render-16",
458 + ]
459 + `);
460 +
461 + clearPendingMarks();
462 +
463 + expect(Scheduler).toFlushUntilNextPaint([]);
464
465 expect(clearedMarks).toMatchInlineSnapshot(`
466 Array [
1018 - "--schedule-render-1",
467 + "--render-start-16",
468 + "--component-render-start-Example",
469 + "--component-render-stop",
470 + "--render-stop",
471 + "--commit-start-16",
472 + "--react-version-<filtered-version>",
473 + "--profiler-version-1",
474 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
475 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
476 + "--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",
477 + "--layout-effects-start-16",
478 + "--schedule-forced-update-1-Example",
479 + "--layout-effects-stop",
480 "--render-start-1",
481 + "--component-render-start-Example",
482 + "--component-render-stop",
483 "--render-stop",
484 "--commit-start-1",
485 "--react-version-<filtered-version>",
486 "--profiler-version-1",
1024 - "--react-internal-module-start-<filtered-file-system-path>",
1025 - "--react-internal-module-stop-<filtered-file-system-path>",
487 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
488 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
489 "--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",
490 + "--commit-stop",
491 "--commit-stop",
492 ]
493 `);
494 });
495
1034 - it('regression test DefaultLane', () => {
1035 - renderRootHelper(<div />);
496 + it('should mark render phase state updates for class component', () => {
497 + class Example extends React.Component {
498 + state = {didRender: false};
499 + render() {
500 + if (this.state.didRender === false) {
501 + this.setState({didRender: true});
502 + }
503 + return null;
504 + }
505 + }
506 +
507 + renderRootHelper(<Example />);
508 +
509 expect(clearedMarks).toMatchInlineSnapshot(`
1037 - Array [
1038 - "--schedule-render-16",
1039 - ]
510 + Array [
511 + "--schedule-render-16",
512 + ]
513 + `);
514 +
515 + clearPendingMarks();
516 +
517 + let errorMessage;
518 + spyOn(console, 'error').and.callFake(message => {
519 + errorMessage = message;
520 + });
521 +
522 + expect(Scheduler).toFlushUntilNextPaint([]);
523 +
524 + expect(console.error).toHaveBeenCalledTimes(1);
525 + expect(errorMessage).toContain(
526 + 'Cannot update during an existing state transition',
527 + );
528 +
529 + expect(clearedMarks).toMatchInlineSnapshot(`
530 + Array [
531 + "--render-start-16",
532 + "--component-render-start-Example",
533 + "--schedule-state-update-16-Example",
534 + "--component-render-stop",
535 + "--render-stop",
536 + "--commit-start-16",
537 + "--react-version-<filtered-version>",
538 + "--profiler-version-1",
539 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
540 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
541 + "--react-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",
542 + "--layout-effects-start-16",
543 + "--layout-effects-stop",
544 + "--commit-stop",
545 + ]
546 `);
547 });
548
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} />;
549 + it('should mark render phase force updates for class component', () => {
550 + let forced = false;
551 + class Example extends React.Component {
552 + render() {
553 + if (!forced) {
554 + forced = true;
555 + this.forceUpdate();
556 + }
557 + return null;
558 + }
559 }
560
1054 - renderRootHelper(<App />);
1055 - expect(Scheduler).toFlushAndYield([]);
561 + renderRootHelper(<Example />);
562
1057 - clearedMarks.splice(0);
563 + expect(clearedMarks).toMatchInlineSnapshot(`
564 + Array [
565 + "--schedule-render-16",
566 + ]
567 + `);
568 +
569 + clearPendingMarks();
570 +
571 + let errorMessage;
572 + spyOn(console, 'error').and.callFake(message => {
573 + errorMessage = message;
574 + });
575
1059 - targetRef.current.click();
576 + expect(Scheduler).toFlushUntilNextPaint([]);
577
1061 - // Wait a frame, for React to process the "click" update.
1062 - await Promise.resolve();
578 + expect(console.error).toHaveBeenCalledTimes(1);
579 + expect(errorMessage).toContain(
580 + 'Cannot update during an existing state transition',
581 + );
582
583 expect(clearedMarks).toMatchInlineSnapshot(`
584 Array [
1066 - "--schedule-state-update-1-App",
1067 - "--render-start-1",
1068 - "--component-render-start-App",
585 + "--render-start-16",
586 + "--component-render-start-Example",
587 + "--schedule-forced-update-16-Example",
588 "--component-render-stop",
589 "--render-stop",
1071 - "--commit-start-1",
590 + "--commit-start-16",
591 "--react-version-<filtered-version>",
592 "--profiler-version-1",
1074 - "--react-internal-module-start-<filtered-file-system-path>",
1075 - "--react-internal-module-stop-<filtered-file-system-path>",
593 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
594 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
595 "--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",
596 + "--layout-effects-start-16",
597 "--layout-effects-stop",
598 "--commit-stop",
599 ]
600 `);
601 });
602
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} />;
603 + it('should mark cascading layout updates', () => {
604 + function Example() {
605 + const [didMount, setDidMount] = React.useState(false);
606 + React.useLayoutEffect(() => {
607 + setDidMount(true);
608 + }, []);
609 + return didMount;
610 }
611
1093 - renderRootHelper(<App />);
1094 - expect(Scheduler).toFlushAndYield([]);
612 + renderRootHelper(<Example />);
613
1096 - clearedMarks.splice(0);
614 + expect(clearedMarks).toMatchInlineSnapshot(`
615 + Array [
616 + "--schedule-render-16",
617 + ]
618 + `);
619
1098 - const event = document.createEvent('MouseEvents');
1099 - event.initEvent('mouseover', true, true);
1100 - dispatchAndSetCurrentEvent(targetRef.current, event);
620 + clearPendingMarks();
621
1102 - expect(Scheduler).toFlushAndYield([]);
622 + expect(Scheduler).toFlushUntilNextPaint([]);
623
624 expect(clearedMarks).toMatchInlineSnapshot(`
625 Array [
1106 - "--schedule-state-update-4-App",
1107 - "--render-start-4",
1108 - "--component-render-start-App",
626 + "--render-start-16",
627 + "--component-render-start-Example",
628 "--component-render-stop",
629 "--render-stop",
1111 - "--commit-start-4",
630 + "--commit-start-16",
631 "--react-version-<filtered-version>",
632 "--profiler-version-1",
1114 - "--react-internal-module-start-<filtered-file-system-path>",
1115 - "--react-internal-module-stop-<filtered-file-system-path>",
633 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
634 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
635 "--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",
636 + "--layout-effects-start-16",
637 + "--component-layout-effect-mount-start-Example",
638 + "--schedule-state-update-1-Example",
639 + "--component-layout-effect-mount-stop",
640 "--layout-effects-stop",
641 + "--render-start-1",
642 + "--component-render-start-Example",
643 + "--component-render-stop",
644 + "--render-stop",
645 + "--commit-start-1",
646 + "--react-version-<filtered-version>",
647 + "--profiler-version-1",
648 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
649 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
650 + "--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",
651 + "--commit-stop",
652 "--commit-stop",
653 ]
654 `);
655 });
656 +
657 + it('should mark cascading passive updates', () => {
658 + function Example() {
659 + const [didMount, setDidMount] = React.useState(false);
660 + React.useEffect(() => {
661 + setDidMount(true);
662 + }, []);
663 + return didMount;
664 + }
665 +
666 + renderRootHelper(<Example />);
667 +
668 + expect(Scheduler).toFlushAndYield([]);
669 +
670 + expect(clearedMarks).toMatchInlineSnapshot(`
671 + Array [
672 + "--schedule-render-16",
673 + "--render-start-16",
674 + "--component-render-start-Example",
675 + "--component-render-stop",
676 + "--render-stop",
677 + "--commit-start-16",
678 + "--react-version-<filtered-version>",
679 + "--profiler-version-1",
680 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
681 + "--react-internal-module-stop- at filtered (<anonymous>:1: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 + "--layout-effects-start-16",
684 + "--layout-effects-stop",
685 + "--commit-stop",
686 + "--passive-effects-start-16",
687 + "--component-passive-effect-mount-start-Example",
688 + "--schedule-state-update-16-Example",
689 + "--component-passive-effect-mount-stop",
690 + "--passive-effects-stop",
691 + "--render-start-16",
692 + "--component-render-start-Example",
693 + "--component-render-stop",
694 + "--render-stop",
695 + "--commit-start-16",
696 + "--react-version-<filtered-version>",
697 + "--profiler-version-1",
698 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
699 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
700 + "--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",
701 + "--commit-stop",
702 + ]
703 + `);
704 + });
705 +
706 + it('should mark render phase updates', () => {
707 + function Example() {
708 + const [didRender, setDidRender] = React.useState(false);
709 + if (!didRender) {
710 + setDidRender(true);
711 + }
712 + return didRender;
713 + }
714 +
715 + renderRootHelper(<Example />);
716 +
717 + expect(Scheduler).toFlushAndYield([]);
718 +
719 + expect(clearedMarks).toMatchInlineSnapshot(`
720 + Array [
721 + "--schedule-render-16",
722 + "--render-start-16",
723 + "--component-render-start-Example",
724 + "--schedule-state-update-16-Example",
725 + "--component-render-stop",
726 + "--render-stop",
727 + "--commit-start-16",
728 + "--react-version-<filtered-version>",
729 + "--profiler-version-1",
730 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
731 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
732 + "--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",
733 + "--layout-effects-start-16",
734 + "--layout-effects-stop",
735 + "--commit-stop",
736 + ]
737 + `);
738 + });
739 +
740 + it('should mark sync render that throws', async () => {
741 + spyOn(console, 'error');
742 +
743 + class ErrorBoundary extends React.Component {
744 + state = {error: null};
745 + componentDidCatch(error) {
746 + this.setState({error});
747 + }
748 + render() {
749 + if (this.state.error) {
750 + return null;
751 + }
752 + return this.props.children;
753 + }
754 + }
755 +
756 + function ExampleThatThrows() {
757 + throw Error('Expected error');
758 + }
759 +
760 + renderHelper(
761 + <ErrorBoundary>
762 + <ExampleThatThrows />
763 + </ErrorBoundary>,
764 + );
765 +
766 + expect(clearedMarks).toMatchInlineSnapshot(`
767 + Array [
768 + "--schedule-render-1",
769 + "--render-start-1",
770 + "--component-render-start-ErrorBoundary",
771 + "--component-render-stop",
772 + "--component-render-start-ExampleThatThrows",
773 + "--component-render-start-ExampleThatThrows",
774 + "--component-render-stop",
775 + "--error-ExampleThatThrows-mount-Expected error",
776 + "--render-stop",
777 + "--commit-start-1",
778 + "--react-version-<filtered-version>",
779 + "--profiler-version-1",
780 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
781 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
782 + "--react-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",
783 + "--layout-effects-start-1",
784 + "--schedule-state-update-1-ErrorBoundary",
785 + "--layout-effects-stop",
786 + "--commit-stop",
787 + "--render-start-1",
788 + "--component-render-start-ErrorBoundary",
789 + "--component-render-stop",
790 + "--render-stop",
791 + "--commit-start-1",
792 + "--react-version-<filtered-version>",
793 + "--profiler-version-1",
794 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
795 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
796 + "--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",
797 + "--commit-stop",
798 + ]
799 + `);
800 + });
801 +
802 + it('should mark concurrent render that throws', async () => {
803 + spyOn(console, 'error');
804 +
805 + class ErrorBoundary extends React.Component {
806 + state = {error: null};
807 + componentDidCatch(error) {
808 + this.setState({error});
809 + }
810 + render() {
811 + if (this.state.error) {
812 + return null;
813 + }
814 + return this.props.children;
815 + }
816 + }
817 +
818 + function ExampleThatThrows() {
819 + // eslint-disable-next-line no-throw-literal
820 + throw 'Expected error';
821 + }
822 +
823 + renderRootHelper(
824 + <ErrorBoundary>
825 + <ExampleThatThrows />
826 + </ErrorBoundary>,
827 + );
828 +
829 + expect(clearedMarks).toMatchInlineSnapshot(`
830 + Array [
831 + "--schedule-render-16",
832 + ]
833 + `);
834 +
835 + clearPendingMarks();
836 +
837 + expect(Scheduler).toFlushUntilNextPaint([]);
838 +
839 + expect(clearedMarks).toMatchInlineSnapshot(`
840 + Array [
841 + "--render-start-16",
842 + "--component-render-start-ErrorBoundary",
843 + "--component-render-stop",
844 + "--component-render-start-ExampleThatThrows",
845 + "--component-render-start-ExampleThatThrows",
846 + "--component-render-stop",
847 + "--error-ExampleThatThrows-mount-Expected error",
848 + "--render-stop",
849 + "--render-start-16",
850 + "--component-render-start-ErrorBoundary",
851 + "--component-render-stop",
852 + "--component-render-start-ExampleThatThrows",
853 + "--component-render-start-ExampleThatThrows",
854 + "--component-render-stop",
855 + "--error-ExampleThatThrows-mount-Expected error",
856 + "--render-stop",
857 + "--commit-start-16",
858 + "--react-version-<filtered-version>",
859 + "--profiler-version-1",
860 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
861 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
862 + "--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",
863 + "--layout-effects-start-16",
864 + "--schedule-state-update-1-ErrorBoundary",
865 + "--layout-effects-stop",
866 + "--render-start-1",
867 + "--component-render-start-ErrorBoundary",
868 + "--component-render-stop",
869 + "--render-stop",
870 + "--commit-start-1",
871 + "--react-version-<filtered-version>",
872 + "--profiler-version-1",
873 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
874 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
875 + "--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",
876 + "--commit-stop",
877 + "--commit-stop",
878 + ]
879 + `);
880 + });
881 +
882 + it('should mark passive and layout effects', async () => {
883 + function ComponentWithEffects() {
884 + React.useLayoutEffect(() => {
885 + Scheduler.unstable_yieldValue('layout 1 mount');
886 + return () => {
887 + Scheduler.unstable_yieldValue('layout 1 unmount');
888 + };
889 + }, []);
890 +
891 + React.useEffect(() => {
892 + Scheduler.unstable_yieldValue('passive 1 mount');
893 + return () => {
894 + Scheduler.unstable_yieldValue('passive 1 unmount');
895 + };
896 + }, []);
897 +
898 + React.useLayoutEffect(() => {
899 + Scheduler.unstable_yieldValue('layout 2 mount');
900 + return () => {
901 + Scheduler.unstable_yieldValue('layout 2 unmount');
902 + };
903 + }, []);
904 +
905 + React.useEffect(() => {
906 + Scheduler.unstable_yieldValue('passive 2 mount');
907 + return () => {
908 + Scheduler.unstable_yieldValue('passive 2 unmount');
909 + };
910 + }, []);
911 +
912 + React.useEffect(() => {
913 + Scheduler.unstable_yieldValue('passive 3 mount');
914 + return () => {
915 + Scheduler.unstable_yieldValue('passive 3 unmount');
916 + };
917 + }, []);
918 +
919 + return null;
920 + }
921 +
922 + const unmount = renderRootHelper(<ComponentWithEffects />);
923 +
924 + expect(Scheduler).toFlushUntilNextPaint([
925 + 'layout 1 mount',
926 + 'layout 2 mount',
927 + ]);
928 +
929 + expect(clearedMarks).toMatchInlineSnapshot(`
930 + Array [
931 + "--schedule-render-16",
932 + "--render-start-16",
933 + "--component-render-start-ComponentWithEffects",
934 + "--component-render-stop",
935 + "--render-stop",
936 + "--commit-start-16",
937 + "--react-version-<filtered-version>",
938 + "--profiler-version-1",
939 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
940 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
941 + "--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",
942 + "--layout-effects-start-16",
943 + "--component-layout-effect-mount-start-ComponentWithEffects",
944 + "--component-layout-effect-mount-stop",
945 + "--component-layout-effect-mount-start-ComponentWithEffects",
946 + "--component-layout-effect-mount-stop",
947 + "--layout-effects-stop",
948 + "--commit-stop",
949 + ]
950 + `);
951 +
952 + clearPendingMarks();
953 +
954 + expect(Scheduler).toFlushAndYield([
955 + 'passive 1 mount',
956 + 'passive 2 mount',
957 + 'passive 3 mount',
958 + ]);
959 +
960 + expect(clearedMarks).toMatchInlineSnapshot(`
961 + Array [
962 + "--passive-effects-start-16",
963 + "--component-passive-effect-mount-start-ComponentWithEffects",
964 + "--component-passive-effect-mount-stop",
965 + "--component-passive-effect-mount-start-ComponentWithEffects",
966 + "--component-passive-effect-mount-stop",
967 + "--component-passive-effect-mount-start-ComponentWithEffects",
968 + "--component-passive-effect-mount-stop",
969 + "--passive-effects-stop",
970 + ]
971 + `);
972 +
973 + clearPendingMarks();
974 +
975 + expect(Scheduler).toFlushAndYield([]);
976 +
977 + unmount();
978 +
979 + expect(Scheduler).toHaveYielded([
980 + 'layout 1 unmount',
981 + 'layout 2 unmount',
982 + 'passive 1 unmount',
983 + 'passive 2 unmount',
984 + 'passive 3 unmount',
985 + ]);
986 +
987 + expect(clearedMarks).toMatchInlineSnapshot(`
988 + Array [
989 + "--schedule-render-1",
990 + "--render-start-1",
991 + "--render-stop",
992 + "--commit-start-1",
993 + "--react-version-<filtered-version>",
994 + "--profiler-version-1",
995 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
996 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
997 + "--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",
998 + "--component-layout-effect-unmount-start-ComponentWithEffects",
999 + "--component-layout-effect-unmount-stop",
1000 + "--component-layout-effect-unmount-start-ComponentWithEffects",
1001 + "--component-layout-effect-unmount-stop",
1002 + "--layout-effects-start-1",
1003 + "--layout-effects-stop",
1004 + "--passive-effects-start-1",
1005 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1006 + "--component-passive-effect-unmount-stop",
1007 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1008 + "--component-passive-effect-unmount-stop",
1009 + "--component-passive-effect-unmount-start-ComponentWithEffects",
1010 + "--component-passive-effect-unmount-stop",
1011 + "--passive-effects-stop",
1012 + "--commit-stop",
1013 + ]
1014 + `);
1015 + });
1016 +
1017 + describe('lane labels', () => {
1018 + it('regression test SyncLane', () => {
1019 + renderHelper(<div />);
1020 +
1021 + expect(clearedMarks).toMatchInlineSnapshot(`
1022 + Array [
1023 + "--schedule-render-1",
1024 + "--render-start-1",
1025 + "--render-stop",
1026 + "--commit-start-1",
1027 + "--react-version-<filtered-version>",
1028 + "--profiler-version-1",
1029 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1030 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1031 + "--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",
1032 + "--layout-effects-start-1",
1033 + "--layout-effects-stop",
1034 + "--commit-stop",
1035 + ]
1036 + `);
1037 + });
1038 +
1039 + it('regression test DefaultLane', () => {
1040 + renderRootHelper(<div />);
1041 + expect(clearedMarks).toMatchInlineSnapshot(`
1042 + Array [
1043 + "--schedule-render-16",
1044 + ]
1045 + `);
1046 + });
1047 +
1048 + it('regression test InputDiscreteLane', async () => {
1049 + const targetRef = React.createRef(null);
1050 +
1051 + function App() {
1052 + const [count, setCount] = React.useState(0);
1053 + const handleClick = () => {
1054 + setCount(count + 1);
1055 + };
1056 + return <button ref={targetRef} onClick={handleClick} />;
1057 + }
1058 +
1059 + renderRootHelper(<App />);
1060 + expect(Scheduler).toFlushAndYield([]);
1061 +
1062 + clearedMarks.splice(0);
1063 +
1064 + targetRef.current.click();
1065 +
1066 + // Wait a frame, for React to process the "click" update.
1067 + await Promise.resolve();
1068 +
1069 + expect(clearedMarks).toMatchInlineSnapshot(`
1070 + Array [
1071 + "--schedule-state-update-1-App",
1072 + "--render-start-1",
1073 + "--component-render-start-App",
1074 + "--component-render-stop",
1075 + "--render-stop",
1076 + "--commit-start-1",
1077 + "--react-version-<filtered-version>",
1078 + "--profiler-version-1",
1079 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1080 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1081 + "--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",
1082 + "--layout-effects-start-1",
1083 + "--layout-effects-stop",
1084 + "--commit-stop",
1085 + ]
1086 + `);
1087 + });
1088 +
1089 + it('regression test InputContinuousLane', async () => {
1090 + const targetRef = React.createRef(null);
1091 +
1092 + function App() {
1093 + const [count, setCount] = React.useState(0);
1094 + const handleMouseOver = () => setCount(count + 1);
1095 + return <div ref={targetRef} onMouseOver={handleMouseOver} />;
1096 + }
1097 +
1098 + renderRootHelper(<App />);
1099 + expect(Scheduler).toFlushAndYield([]);
1100 +
1101 + clearedMarks.splice(0);
1102 +
1103 + const event = document.createEvent('MouseEvents');
1104 + event.initEvent('mouseover', true, true);
1105 + dispatchAndSetCurrentEvent(targetRef.current, event);
1106 +
1107 + expect(Scheduler).toFlushAndYield([]);
1108 +
1109 + expect(clearedMarks).toMatchInlineSnapshot(`
1110 + Array [
1111 + "--schedule-state-update-4-App",
1112 + "--render-start-4",
1113 + "--component-render-start-App",
1114 + "--component-render-stop",
1115 + "--render-stop",
1116 + "--commit-start-4",
1117 + "--react-version-<filtered-version>",
1118 + "--profiler-version-1",
1119 + "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1120 + "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1121 + "--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",
1122 + "--layout-effects-start-4",
1123 + "--layout-effects-stop",
1124 + "--commit-stop",
1125 + ]
1126 + `);
1127 + });
1128 + });
1129 + });
1130 +
1131 + describe('DevTools hook (in memory)', () => {
1132 + let getBatchOfWork;
1133 + let stopProfilingAndGetTimelineData;
1134 +
1135 + beforeEach(() => {
1136 + getBatchOfWork = index => {
1137 + const timelineData = stopProfilingAndGetTimelineData();
1138 + if (timelineData) {
1139 + if (timelineData.batchUIDToMeasuresMap.size > index) {
1140 + return Array.from(timelineData.batchUIDToMeasuresMap.values())[
1141 + index
1142 + ];
1143 + }
1144 + }
1145 +
1146 + return null;
1147 + };
1148 +
1149 + stopProfilingAndGetTimelineData = () => {
1150 + utils.act(() => store.profilerStore.stopProfiling());
1151 +
1152 + const timelineData = store.profilerStore.profilingData?.timelineData;
1153 +
1154 + if (timelineData) {
1155 + expect(timelineData).toHaveLength(1);
1156 +
1157 + return timelineData[0];
1158 + } else {
1159 + return null;
1160 + }
1161 + };
1162 + });
1163 +
1164 + afterEach(() => {
1165 + unmountFns.forEach(unmountFn => unmountFn());
1166 + });
1167 +
1168 + describe('when profiling', () => {
1169 + beforeEach(() => {
1170 + utils.act(() => store.profilerStore.startProfiling());
1171 + });
1172 +
1173 + it('should mark sync render without suspends or state updates', () => {
1174 + renderHelper(<div />);
1175 +
1176 + const timelineData = stopProfilingAndGetTimelineData();
1177 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1178 + Array [
1179 + Object {
1180 + "lanes": "0b0000000000000000000000000000001",
1181 + "timestamp": 10,
1182 + "type": "schedule-render",
1183 + "warning": null,
1184 + },
1185 + ]
1186 + `);
1187 + });
1188 +
1189 + it('should mark concurrent render without suspends or state updates', () => {
1190 + utils.act(() => renderRootHelper(<div />));
1191 +
1192 + const timelineData = stopProfilingAndGetTimelineData();
1193 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1194 + Array [
1195 + Object {
1196 + "lanes": "0b0000000000000000000000000010000",
1197 + "timestamp": 10,
1198 + "type": "schedule-render",
1199 + "warning": null,
1200 + },
1201 + ]
1202 + `);
1203 + });
1204 +
1205 + it('should mark concurrent render without suspends or state updates', () => {
1206 + let updaterFn;
1207 +
1208 + function Example() {
1209 + const setHigh = React.useState(0)[1];
1210 + const setLow = React.useState(0)[1];
1211 + const startTransition = React.useTransition()[1];
1212 +
1213 + updaterFn = () => {
1214 + startTransition(() => {
1215 + setLow(prevLow => prevLow + 1);
1216 + });
1217 + setHigh(prevHigh => prevHigh + 1);
1218 + };
1219 +
1220 + Scheduler.unstable_advanceTime(10);
1221 +
1222 + return null;
1223 + }
1224 +
1225 + utils.act(() => renderRootHelper(<Example />));
1226 + utils.act(() => store.profilerStore.stopProfiling());
1227 + utils.act(() => store.profilerStore.startProfiling());
1228 + utils.act(updaterFn);
1229 +
1230 + const timelineData = stopProfilingAndGetTimelineData();
1231 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1232 + Array [
1233 + Object {
1234 + "componentName": "Example",
1235 + "lanes": "0b0000000000000000000000000000100",
1236 + "timestamp": 10,
1237 + "type": "schedule-state-update",
1238 + "warning": null,
1239 + },
1240 + Object {
1241 + "componentName": "Example",
1242 + "lanes": "0b0000000000000000000000001000000",
1243 + "timestamp": 10,
1244 + "type": "schedule-state-update",
1245 + "warning": null,
1246 + },
1247 + Object {
1248 + "componentName": "Example",
1249 + "lanes": "0b0000000000000000000000001000000",
1250 + "timestamp": 10,
1251 + "type": "schedule-state-update",
1252 + "warning": null,
1253 + },
1254 + Object {
1255 + "componentName": "Example",
1256 + "lanes": "0b0000000000000000000000000010000",
1257 + "timestamp": 10,
1258 + "type": "schedule-state-update",
1259 + "warning": null,
1260 + },
1261 + ]
1262 + `);
1263 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1264 + Array [
1265 + Object {
1266 + "componentName": "Example",
1267 + "duration": 0,
1268 + "timestamp": 10,
1269 + "type": "render",
1270 + "warning": null,
1271 + },
1272 + Object {
1273 + "componentName": "Example",
1274 + "duration": 10,
1275 + "timestamp": 10,
1276 + "type": "render",
1277 + "warning": null,
1278 + },
1279 + ]
1280 + `);
1281 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1282 + });
1283 +
1284 + it('should mark render yields', async () => {
1285 + function Bar() {
1286 + Scheduler.unstable_yieldValue('Bar');
1287 + return null;
1288 + }
1289 +
1290 + function Foo() {
1291 + Scheduler.unstable_yieldValue('Foo');
1292 + return <Bar />;
1293 + }
1294 +
1295 + React.startTransition(() => {
1296 + renderRootHelper(<Foo />);
1297 + });
1298 +
1299 + // Do one step of work.
1300 + expect(Scheduler).toFlushAndYieldThrough(['Foo']);
1301 +
1302 + // Finish flushing so React commits;
1303 + // Unless we do this, the ProfilerStore won't collect Profiling data.
1304 + expect(Scheduler).toFlushAndYield(['Bar']);
1305 +
1306 + // Since we yielded, the batch should report two separate "render" chunks.
1307 + const batch = getBatchOfWork(0);
1308 + expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
1309 + });
1310 +
1311 + it('should mark sync render with suspense that resolves', async () => {
1312 + let resolveFn;
1313 + let resolved = false;
1314 + const suspensePromise = new Promise(resolve => {
1315 + resolveFn = () => {
1316 + resolved = true;
1317 + resolve();
1318 + };
1319 + });
1320 +
1321 + function Example() {
1322 + Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
1323 + if (!resolved) {
1324 + throw suspensePromise;
1325 + }
1326 + return null;
1327 + }
1328 +
1329 + renderHelper(
1330 + <React.Suspense fallback={null}>
1331 + <Example />
1332 + </React.Suspense>,
1333 + );
1334 +
1335 + expect(Scheduler).toHaveYielded(['suspended']);
1336 +
1337 + Scheduler.unstable_advanceTime(10);
1338 + resolveFn();
1339 + await suspensePromise;
1340 +
1341 + expect(Scheduler).toFlushAndYield(['resolved']);
1342 +
1343 + const timelineData = stopProfilingAndGetTimelineData();
1344 +
1345 + // Verify the Suspense event and duration was recorded.
1346 + expect(timelineData.suspenseEvents).toHaveLength(1);
1347 + const suspenseEvent = timelineData.suspenseEvents[0];
1348 + expect(suspenseEvent).toMatchInlineSnapshot(`
1349 + Object {
1350 + "componentName": "Example",
1351 + "depth": 0,
1352 + "duration": 10,
1353 + "id": "0",
1354 + "phase": "mount",
1355 + "promiseName": "",
1356 + "resolution": "resolved",
1357 + "timestamp": 10,
1358 + "type": "suspense",
1359 + "warning": null,
1360 + }
1361 + `);
1362 +
1363 + // There should be two batches of renders: Suspeneded and resolved.
1364 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1365 + expect(timelineData.componentMeasures).toHaveLength(2);
1366 + });
1367 +
1368 + it('should mark sync render with suspense that rejects', async () => {
1369 + let rejectFn;
1370 + let rejected = false;
1371 + const suspensePromise = new Promise((resolve, reject) => {
1372 + rejectFn = () => {
1373 + rejected = true;
1374 + reject(new Error('error'));
1375 + };
1376 + });
1377 +
1378 + function Example() {
1379 + Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
1380 + if (!rejected) {
1381 + throw suspensePromise;
1382 + }
1383 + return null;
1384 + }
1385 +
1386 + renderHelper(
1387 + <React.Suspense fallback={null}>
1388 + <Example />
1389 + </React.Suspense>,
1390 + );
1391 +
1392 + expect(Scheduler).toHaveYielded(['suspended']);
1393 +
1394 + Scheduler.unstable_advanceTime(10);
1395 + rejectFn();
1396 + await expect(suspensePromise).rejects.toThrow();
1397 +
1398 + expect(Scheduler).toHaveYielded(['rejected']);
1399 +
1400 + const timelineData = stopProfilingAndGetTimelineData();
1401 +
1402 + // Verify the Suspense event and duration was recorded.
1403 + expect(timelineData.suspenseEvents).toHaveLength(1);
1404 + const suspenseEvent = timelineData.suspenseEvents[0];
1405 + expect(suspenseEvent).toMatchInlineSnapshot(`
1406 + Object {
1407 + "componentName": "Example",
1408 + "depth": 0,
1409 + "duration": 10,
1410 + "id": "0",
1411 + "phase": "mount",
1412 + "promiseName": "",
1413 + "resolution": "rejected",
1414 + "timestamp": 10,
1415 + "type": "suspense",
1416 + "warning": null,
1417 + }
1418 + `);
1419 +
1420 + // There should be two batches of renders: Suspeneded and resolved.
1421 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1422 + expect(timelineData.componentMeasures).toHaveLength(2);
1423 + });
1424 +
1425 + it('should mark concurrent render with suspense that resolves', async () => {
1426 + let resolveFn;
1427 + let resolved = false;
1428 + const suspensePromise = new Promise(resolve => {
1429 + resolveFn = () => {
1430 + resolved = true;
1431 + resolve();
1432 + };
1433 + });
1434 +
1435 + function Example() {
1436 + Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
1437 + if (!resolved) {
1438 + throw suspensePromise;
1439 + }
1440 + return null;
1441 + }
1442 +
1443 + renderRootHelper(
1444 + <React.Suspense fallback={null}>
1445 + <Example />
1446 + </React.Suspense>,
1447 + );
1448 +
1449 + expect(Scheduler).toFlushAndYield(['suspended']);
1450 +
1451 + Scheduler.unstable_advanceTime(10);
1452 + resolveFn();
1453 + await suspensePromise;
1454 +
1455 + expect(Scheduler).toFlushAndYield(['resolved']);
1456 +
1457 + const timelineData = stopProfilingAndGetTimelineData();
1458 +
1459 + // Verify the Suspense event and duration was recorded.
1460 + expect(timelineData.suspenseEvents).toHaveLength(1);
1461 + const suspenseEvent = timelineData.suspenseEvents[0];
1462 + expect(suspenseEvent).toMatchInlineSnapshot(`
1463 + Object {
1464 + "componentName": "Example",
1465 + "depth": 0,
1466 + "duration": 10,
1467 + "id": "0",
1468 + "phase": "mount",
1469 + "promiseName": "",
1470 + "resolution": "resolved",
1471 + "timestamp": 10,
1472 + "type": "suspense",
1473 + "warning": null,
1474 + }
1475 + `);
1476 +
1477 + // There should be two batches of renders: Suspeneded and resolved.
1478 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1479 + expect(timelineData.componentMeasures).toHaveLength(2);
1480 + });
1481 +
1482 + it('should mark concurrent render with suspense that rejects', async () => {
1483 + let rejectFn;
1484 + let rejected = false;
1485 + const suspensePromise = new Promise((resolve, reject) => {
1486 + rejectFn = () => {
1487 + rejected = true;
1488 + reject(new Error('error'));
1489 + };
1490 + });
1491 +
1492 + function Example() {
1493 + Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
1494 + if (!rejected) {
1495 + throw suspensePromise;
1496 + }
1497 + return null;
1498 + }
1499 +
1500 + renderRootHelper(
1501 + <React.Suspense fallback={null}>
1502 + <Example />
1503 + </React.Suspense>,
1504 + );
1505 +
1506 + expect(Scheduler).toFlushAndYield(['suspended']);
1507 +
1508 + Scheduler.unstable_advanceTime(10);
1509 + rejectFn();
1510 + await expect(suspensePromise).rejects.toThrow();
1511 +
1512 + expect(Scheduler).toFlushAndYield(['rejected']);
1513 +
1514 + const timelineData = stopProfilingAndGetTimelineData();
1515 +
1516 + // Verify the Suspense event and duration was recorded.
1517 + expect(timelineData.suspenseEvents).toHaveLength(1);
1518 + const suspenseEvent = timelineData.suspenseEvents[0];
1519 + expect(suspenseEvent).toMatchInlineSnapshot(`
1520 + Object {
1521 + "componentName": "Example",
1522 + "depth": 0,
1523 + "duration": 10,
1524 + "id": "0",
1525 + "phase": "mount",
1526 + "promiseName": "",
1527 + "resolution": "rejected",
1528 + "timestamp": 10,
1529 + "type": "suspense",
1530 + "warning": null,
1531 + }
1532 + `);
1533 +
1534 + // There should be two batches of renders: Suspeneded and resolved.
1535 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1536 + expect(timelineData.componentMeasures).toHaveLength(2);
1537 + });
1538 +
1539 + it('should mark cascading class component state updates', () => {
1540 + class Example extends React.Component {
1541 + state = {didMount: false};
1542 + componentDidMount() {
1543 + this.setState({didMount: true});
1544 + }
1545 + render() {
1546 + Scheduler.unstable_advanceTime(10);
1547 + Scheduler.unstable_yieldValue(
1548 + this.state.didMount ? 'update' : 'mount',
1549 + );
1550 + return null;
1551 + }
1552 + }
1553 +
1554 + renderRootHelper(<Example />);
1555 +
1556 + expect(Scheduler).toFlushUntilNextPaint(['mount', 'update']);
1557 +
1558 + const timelineData = stopProfilingAndGetTimelineData();
1559 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1560 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1561 + Array [
1562 + Object {
1563 + "componentName": "Example",
1564 + "duration": 10,
1565 + "timestamp": 10,
1566 + "type": "render",
1567 + "warning": null,
1568 + },
1569 + Object {
1570 + "componentName": "Example",
1571 + "duration": 10,
1572 + "timestamp": 20,
1573 + "type": "render",
1574 + "warning": null,
1575 + },
1576 + ]
1577 + `);
1578 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1579 + Array [
1580 + Object {
1581 + "lanes": "0b0000000000000000000000000010000",
1582 + "timestamp": 10,
1583 + "type": "schedule-render",
1584 + "warning": null,
1585 + },
1586 + Object {
1587 + "componentName": "Example",
1588 + "lanes": "0b0000000000000000000000000000001",
1589 + "timestamp": 20,
1590 + "type": "schedule-state-update",
1591 + "warning": null,
1592 + },
1593 + ]
1594 + `);
1595 + });
1596 +
1597 + it('should mark cascading class component force updates', () => {
1598 + let forced = false;
1599 + class Example extends React.Component {
1600 + componentDidMount() {
1601 + forced = true;
1602 + this.forceUpdate();
1603 + }
1604 + render() {
1605 + Scheduler.unstable_advanceTime(10);
1606 + Scheduler.unstable_yieldValue(forced ? 'force update' : 'mount');
1607 + return null;
1608 + }
1609 + }
1610 +
1611 + renderRootHelper(<Example />);
1612 +
1613 + expect(Scheduler).toFlushUntilNextPaint(['mount', 'force update']);
1614 +
1615 + const timelineData = stopProfilingAndGetTimelineData();
1616 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1617 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1618 + Array [
1619 + Object {
1620 + "componentName": "Example",
1621 + "duration": 10,
1622 + "timestamp": 10,
1623 + "type": "render",
1624 + "warning": null,
1625 + },
1626 + Object {
1627 + "componentName": "Example",
1628 + "duration": 10,
1629 + "timestamp": 20,
1630 + "type": "render",
1631 + "warning": null,
1632 + },
1633 + ]
1634 + `);
1635 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1636 + Array [
1637 + Object {
1638 + "lanes": "0b0000000000000000000000000010000",
1639 + "timestamp": 10,
1640 + "type": "schedule-render",
1641 + "warning": null,
1642 + },
1643 + Object {
1644 + "componentName": "Example",
1645 + "lanes": "0b0000000000000000000000000000001",
1646 + "timestamp": 20,
1647 + "type": "schedule-force-update",
1648 + "warning": null,
1649 + },
1650 + ]
1651 + `);
1652 + });
1653 +
1654 + it('should mark render phase state updates for class component', () => {
1655 + class Example extends React.Component {
1656 + state = {didRender: false};
1657 + render() {
1658 + if (this.state.didRender === false) {
1659 + this.setState({didRender: true});
1660 + }
1661 + Scheduler.unstable_advanceTime(10);
1662 + Scheduler.unstable_yieldValue(
1663 + this.state.didRender ? 'second render' : 'first render',
1664 + );
1665 + return null;
1666 + }
1667 + }
1668 +
1669 + renderRootHelper(<Example />);
1670 +
1671 + let errorMessage;
1672 + spyOn(console, 'error').and.callFake(message => {
1673 + errorMessage = message;
1674 + });
1675 +
1676 + expect(Scheduler).toFlushAndYield(['first render', 'second render']);
1677 +
1678 + expect(console.error).toHaveBeenCalledTimes(1);
1679 + expect(errorMessage).toContain(
1680 + 'Cannot update during an existing state transition',
1681 + );
1682 +
1683 + const timelineData = stopProfilingAndGetTimelineData();
1684 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1685 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1686 + Array [
1687 + Object {
1688 + "componentName": "Example",
1689 + "duration": 10,
1690 + "timestamp": 10,
1691 + "type": "render",
1692 + "warning": null,
1693 + },
1694 + Object {
1695 + "componentName": "Example",
1696 + "duration": 10,
1697 + "timestamp": 20,
1698 + "type": "render",
1699 + "warning": null,
1700 + },
1701 + ]
1702 + `);
1703 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1704 + Array [
1705 + Object {
1706 + "lanes": "0b0000000000000000000000000010000",
1707 + "timestamp": 10,
1708 + "type": "schedule-render",
1709 + "warning": null,
1710 + },
1711 + Object {
1712 + "componentName": "Example",
1713 + "lanes": "0b0000000000000000000000000010000",
1714 + "timestamp": 10,
1715 + "type": "schedule-state-update",
1716 + "warning": null,
1717 + },
1718 + ]
1719 + `);
1720 + });
1721 +
1722 + it('should mark render phase force updates for class component', () => {
1723 + let forced = false;
1724 + class Example extends React.Component {
1725 + render() {
1726 + Scheduler.unstable_advanceTime(10);
1727 + Scheduler.unstable_yieldValue(forced ? 'force update' : 'render');
1728 + if (!forced) {
1729 + forced = true;
1730 + this.forceUpdate();
1731 + }
1732 + return null;
1733 + }
1734 + }
1735 +
1736 + renderRootHelper(<Example />);
1737 +
1738 + let errorMessage;
1739 + spyOn(console, 'error').and.callFake(message => {
1740 + errorMessage = message;
1741 + });
1742 +
1743 + expect(Scheduler).toFlushAndYield(['render', 'force update']);
1744 +
1745 + expect(console.error).toHaveBeenCalledTimes(1);
1746 + expect(errorMessage).toContain(
1747 + 'Cannot update during an existing state transition',
1748 + );
1749 +
1750 + const timelineData = stopProfilingAndGetTimelineData();
1751 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1752 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1753 + Array [
1754 + Object {
1755 + "componentName": "Example",
1756 + "duration": 10,
1757 + "timestamp": 10,
1758 + "type": "render",
1759 + "warning": null,
1760 + },
1761 + Object {
1762 + "componentName": "Example",
1763 + "duration": 10,
1764 + "timestamp": 20,
1765 + "type": "render",
1766 + "warning": null,
1767 + },
1768 + ]
1769 + `);
1770 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1771 + Array [
1772 + Object {
1773 + "lanes": "0b0000000000000000000000000010000",
1774 + "timestamp": 10,
1775 + "type": "schedule-render",
1776 + "warning": null,
1777 + },
1778 + Object {
1779 + "componentName": "Example",
1780 + "lanes": "0b0000000000000000000000000010000",
1781 + "timestamp": 20,
1782 + "type": "schedule-force-update",
1783 + "warning": null,
1784 + },
1785 + ]
1786 + `);
1787 + });
1788 +
1789 + it('should mark cascading layout updates', () => {
1790 + function Example() {
1791 + const [didMount, setDidMount] = React.useState(false);
1792 + React.useLayoutEffect(() => {
1793 + Scheduler.unstable_advanceTime(1);
1794 + setDidMount(true);
1795 + }, []);
1796 + Scheduler.unstable_advanceTime(10);
1797 + Scheduler.unstable_yieldValue(didMount ? 'update' : 'mount');
1798 + return didMount;
1799 + }
1800 +
1801 + renderRootHelper(<Example />);
1802 +
1803 + expect(Scheduler).toFlushAndYield(['mount', 'update']);
1804 +
1805 + const timelineData = stopProfilingAndGetTimelineData();
1806 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1807 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1808 + Array [
1809 + Object {
1810 + "componentName": "Example",
1811 + "duration": 10,
1812 + "timestamp": 10,
1813 + "type": "render",
1814 + "warning": null,
1815 + },
1816 + Object {
1817 + "componentName": "Example",
1818 + "duration": 1,
1819 + "timestamp": 20,
1820 + "type": "layout-effect-mount",
1821 + "warning": null,
1822 + },
1823 + Object {
1824 + "componentName": "Example",
1825 + "duration": 10,
1826 + "timestamp": 21,
1827 + "type": "render",
1828 + "warning": null,
1829 + },
1830 + ]
1831 + `);
1832 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1833 + Array [
1834 + Object {
1835 + "lanes": "0b0000000000000000000000000010000",
1836 + "timestamp": 10,
1837 + "type": "schedule-render",
1838 + "warning": null,
1839 + },
1840 + Object {
1841 + "componentName": "Example",
1842 + "lanes": "0b0000000000000000000000000000001",
1843 + "timestamp": 21,
1844 + "type": "schedule-state-update",
1845 + "warning": null,
1846 + },
1847 + ]
1848 + `);
1849 + });
1850 +
1851 + it('should mark cascading passive updates', () => {
1852 + function Example() {
1853 + const [didMount, setDidMount] = React.useState(false);
1854 + React.useEffect(() => {
1855 + Scheduler.unstable_advanceTime(1);
1856 + setDidMount(true);
1857 + }, []);
1858 + Scheduler.unstable_advanceTime(10);
1859 + Scheduler.unstable_yieldValue(didMount ? 'update' : 'mount');
1860 + return didMount;
1861 + }
1862 +
1863 + renderRootHelper(<Example />);
1864 + expect(Scheduler).toFlushAndYield(['mount', 'update']);
1865 +
1866 + const timelineData = stopProfilingAndGetTimelineData();
1867 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1868 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1869 + Array [
1870 + Object {
1871 + "componentName": "Example",
1872 + "duration": 10,
1873 + "timestamp": 10,
1874 + "type": "render",
1875 + "warning": null,
1876 + },
1877 + Object {
1878 + "componentName": "Example",
1879 + "duration": 1,
1880 + "timestamp": 20,
1881 + "type": "passive-effect-mount",
1882 + "warning": null,
1883 + },
1884 + Object {
1885 + "componentName": "Example",
1886 + "duration": 10,
1887 + "timestamp": 21,
1888 + "type": "render",
1889 + "warning": null,
1890 + },
1891 + ]
1892 + `);
1893 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1894 + Array [
1895 + Object {
1896 + "lanes": "0b0000000000000000000000000010000",
1897 + "timestamp": 10,
1898 + "type": "schedule-render",
1899 + "warning": null,
1900 + },
1901 + Object {
1902 + "componentName": "Example",
1903 + "lanes": "0b0000000000000000000000000010000",
1904 + "timestamp": 21,
1905 + "type": "schedule-state-update",
1906 + "warning": null,
1907 + },
1908 + ]
1909 + `);
1910 + });
1911 +
1912 + it('should mark render phase updates', () => {
1913 + function Example() {
1914 + const [didRender, setDidRender] = React.useState(false);
1915 + Scheduler.unstable_advanceTime(10);
1916 + if (!didRender) {
1917 + setDidRender(true);
1918 + }
1919 + Scheduler.unstable_yieldValue(didRender ? 'update' : 'mount');
1920 + return didRender;
1921 + }
1922 +
1923 + renderRootHelper(<Example />);
1924 + expect(Scheduler).toFlushAndYield(['mount', 'update']);
1925 +
1926 + const timelineData = stopProfilingAndGetTimelineData();
1927 + // Render phase updates should be retried as part of the same batch.
1928 + expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
1929 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1930 + Array [
1931 + Object {
1932 + "componentName": "Example",
1933 + "duration": 20,
1934 + "timestamp": 10,
1935 + "type": "render",
1936 + "warning": null,
1937 + },
1938 + ]
1939 + `);
1940 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1941 + Array [
1942 + Object {
1943 + "lanes": "0b0000000000000000000000000010000",
1944 + "timestamp": 10,
1945 + "type": "schedule-render",
1946 + "warning": null,
1947 + },
1948 + Object {
1949 + "componentName": "Example",
1950 + "lanes": "0b0000000000000000000000000010000",
1951 + "timestamp": 20,
1952 + "type": "schedule-state-update",
1953 + "warning": null,
1954 + },
1955 + ]
1956 + `);
1957 + });
1958 +
1959 + it('should mark sync render that throws', async () => {
1960 + spyOn(console, 'error');
1961 +
1962 + class ErrorBoundary extends React.Component {
1963 + state = {error: null};
1964 + componentDidCatch(error) {
1965 + this.setState({error});
1966 + }
1967 + render() {
1968 + Scheduler.unstable_advanceTime(10);
1969 + if (this.state.error) {
1970 + Scheduler.unstable_yieldValue('ErrorBoundary fallback');
1971 + return null;
1972 + }
1973 + Scheduler.unstable_yieldValue('ErrorBoundary render');
1974 + return this.props.children;
1975 + }
1976 + }
1977 +
1978 + function ExampleThatThrows() {
1979 + Scheduler.unstable_yieldValue('ExampleThatThrows');
1980 + throw Error('Expected error');
1981 + }
1982 +
1983 + renderHelper(
1984 + <ErrorBoundary>
1985 + <ExampleThatThrows />
1986 + </ErrorBoundary>,
1987 + );
1988 +
1989 + expect(Scheduler).toHaveYielded([
1990 + 'ErrorBoundary render',
1991 + 'ExampleThatThrows',
1992 + 'ExampleThatThrows',
1993 + 'ErrorBoundary fallback',
1994 + ]);
1995 +
1996 + const timelineData = stopProfilingAndGetTimelineData();
1997 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1998 + Array [
1999 + Object {
2000 + "componentName": "ErrorBoundary",
2001 + "duration": 10,
2002 + "timestamp": 10,
2003 + "type": "render",
2004 + "warning": null,
2005 + },
2006 + Object {
2007 + "componentName": "ExampleThatThrows",
2008 + "duration": 0,
2009 + "timestamp": 20,
2010 + "type": "render",
2011 + "warning": null,
2012 + },
2013 + Object {
2014 + "componentName": "ErrorBoundary",
2015 + "duration": 10,
2016 + "timestamp": 20,
2017 + "type": "render",
2018 + "warning": null,
2019 + },
2020 + ]
2021 + `);
2022 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2023 + Array [
2024 + Object {
2025 + "lanes": "0b0000000000000000000000000000001",
2026 + "timestamp": 10,
2027 + "type": "schedule-render",
2028 + "warning": null,
2029 + },
2030 + Object {
2031 + "componentName": "ErrorBoundary",
2032 + "lanes": "0b0000000000000000000000000000001",
2033 + "timestamp": 20,
2034 + "type": "schedule-state-update",
2035 + "warning": null,
2036 + },
2037 + ]
2038 + `);
2039 + expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
2040 + Array [
2041 + Object {
2042 + "componentName": "ExampleThatThrows",
2043 + "message": "Expected error",
2044 + "phase": "mount",
2045 + "timestamp": 20,
2046 + "type": "thrown-error",
2047 + },
2048 + ]
2049 + `);
2050 + });
2051 +
2052 + it('should mark concurrent render that throws', async () => {
2053 + spyOn(console, 'error');
2054 +
2055 + class ErrorBoundary extends React.Component {
2056 + state = {error: null};
2057 + componentDidCatch(error) {
2058 + this.setState({error});
2059 + }
2060 + render() {
2061 + Scheduler.unstable_advanceTime(10);
2062 + if (this.state.error) {
2063 + Scheduler.unstable_yieldValue('ErrorBoundary fallback');
2064 + return null;
2065 + }
2066 + Scheduler.unstable_yieldValue('ErrorBoundary render');
2067 + return this.props.children;
2068 + }
2069 + }
2070 +
2071 + function ExampleThatThrows() {
2072 + Scheduler.unstable_yieldValue('ExampleThatThrows');
2073 + // eslint-disable-next-line no-throw-literal
2074 + throw 'Expected error';
2075 + }
2076 +
2077 + renderRootHelper(
2078 + <ErrorBoundary>
2079 + <ExampleThatThrows />
2080 + </ErrorBoundary>,
2081 + );
2082 +
2083 + expect(Scheduler).toFlushAndYield([
2084 + 'ErrorBoundary render',
2085 + 'ExampleThatThrows',
2086 + 'ExampleThatThrows',
2087 + 'ErrorBoundary render',
2088 + 'ExampleThatThrows',
2089 + 'ExampleThatThrows',
2090 + 'ErrorBoundary fallback',
2091 + ]);
2092 +
2093 + const timelineData = stopProfilingAndGetTimelineData();
2094 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2095 + Array [
2096 + Object {
2097 + "componentName": "ErrorBoundary",
2098 + "duration": 10,
2099 + "timestamp": 10,
2100 + "type": "render",
2101 + "warning": null,
2102 + },
2103 + Object {
2104 + "componentName": "ExampleThatThrows",
2105 + "duration": 0,
2106 + "timestamp": 20,
2107 + "type": "render",
2108 + "warning": null,
2109 + },
2110 + Object {
2111 + "componentName": "ErrorBoundary",
2112 + "duration": 10,
2113 + "timestamp": 20,
2114 + "type": "render",
2115 + "warning": null,
2116 + },
2117 + Object {
2118 + "componentName": "ExampleThatThrows",
2119 + "duration": 0,
2120 + "timestamp": 30,
2121 + "type": "render",
2122 + "warning": null,
2123 + },
2124 + Object {
2125 + "componentName": "ErrorBoundary",
2126 + "duration": 10,
2127 + "timestamp": 30,
2128 + "type": "render",
2129 + "warning": null,
2130 + },
2131 + ]
2132 + `);
2133 + expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2134 + Array [
2135 + Object {
2136 + "lanes": "0b0000000000000000000000000010000",
2137 + "timestamp": 10,
2138 + "type": "schedule-render",
2139 + "warning": null,
2140 + },
2141 + Object {
2142 + "componentName": "ErrorBoundary",
2143 + "lanes": "0b0000000000000000000000000000001",
2144 + "timestamp": 30,
2145 + "type": "schedule-state-update",
2146 + "warning": null,
2147 + },
2148 + ]
2149 + `);
2150 + expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
2151 + Array [
2152 + Object {
2153 + "componentName": "ExampleThatThrows",
2154 + "message": "Expected error",
2155 + "phase": "mount",
2156 + "timestamp": 20,
2157 + "type": "thrown-error",
2158 + },
2159 + Object {
2160 + "componentName": "ExampleThatThrows",
2161 + "message": "Expected error",
2162 + "phase": "mount",
2163 + "timestamp": 30,
2164 + "type": "thrown-error",
2165 + },
2166 + ]
2167 + `);
2168 + });
2169 +
2170 + it('should mark passive and layout effects', async () => {
2171 + function ComponentWithEffects() {
2172 + React.useLayoutEffect(() => {
2173 + Scheduler.unstable_yieldValue('layout 1 mount');
2174 + return () => {
2175 + Scheduler.unstable_yieldValue('layout 1 unmount');
2176 + };
2177 + }, []);
2178 +
2179 + React.useEffect(() => {
2180 + Scheduler.unstable_yieldValue('passive 1 mount');
2181 + return () => {
2182 + Scheduler.unstable_yieldValue('passive 1 unmount');
2183 + };
2184 + }, []);
2185 +
2186 + React.useLayoutEffect(() => {
2187 + Scheduler.unstable_yieldValue('layout 2 mount');
2188 + return () => {
2189 + Scheduler.unstable_yieldValue('layout 2 unmount');
2190 + };
2191 + }, []);
2192 +
2193 + React.useEffect(() => {
2194 + Scheduler.unstable_yieldValue('passive 2 mount');
2195 + return () => {
2196 + Scheduler.unstable_yieldValue('passive 2 unmount');
2197 + };
2198 + }, []);
2199 +
2200 + React.useEffect(() => {
2201 + Scheduler.unstable_yieldValue('passive 3 mount');
2202 + return () => {
2203 + Scheduler.unstable_yieldValue('passive 3 unmount');
2204 + };
2205 + }, []);
2206 +
2207 + return null;
2208 + }
2209 +
2210 + const unmount = renderRootHelper(<ComponentWithEffects />);
2211 +
2212 + expect(Scheduler).toFlushUntilNextPaint([
2213 + 'layout 1 mount',
2214 + 'layout 2 mount',
2215 + ]);
2216 +
2217 + expect(Scheduler).toFlushAndYield([
2218 + 'passive 1 mount',
2219 + 'passive 2 mount',
2220 + 'passive 3 mount',
2221 + ]);
2222 +
2223 + expect(Scheduler).toFlushAndYield([]);
2224 +
2225 + unmount();
2226 +
2227 + expect(Scheduler).toHaveYielded([
2228 + 'layout 1 unmount',
2229 + 'layout 2 unmount',
2230 + 'passive 1 unmount',
2231 + 'passive 2 unmount',
2232 + 'passive 3 unmount',
2233 + ]);
2234 +
2235 + const timelineData = stopProfilingAndGetTimelineData();
2236 + expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2237 + Array [
2238 + Object {
2239 + "componentName": "ComponentWithEffects",
2240 + "duration": 0,
2241 + "timestamp": 10,
2242 + "type": "render",
2243 + "warning": null,
2244 + },
2245 + Object {
2246 + "componentName": "ComponentWithEffects",
2247 + "duration": 0,
2248 + "timestamp": 10,
2249 + "type": "layout-effect-mount",
2250 + "warning": null,
2251 + },
2252 + Object {
2253 + "componentName": "ComponentWithEffects",
2254 + "duration": 0,
2255 + "timestamp": 10,
2256 + "type": "layout-effect-mount",
2257 + "warning": null,
2258 + },
2259 + Object {
2260 + "componentName": "ComponentWithEffects",
2261 + "duration": 0,
2262 + "timestamp": 10,
2263 + "type": "passive-effect-mount",
2264 + "warning": null,
2265 + },
2266 + Object {
2267 + "componentName": "ComponentWithEffects",
2268 + "duration": 0,
2269 + "timestamp": 10,
2270 + "type": "passive-effect-mount",
2271 + "warning": null,
2272 + },
2273 + Object {
2274 + "componentName": "ComponentWithEffects",
2275 + "duration": 0,
2276 + "timestamp": 10,
2277 + "type": "passive-effect-mount",
2278 + "warning": null,
2279 + },
2280 + Object {
2281 + "componentName": "ComponentWithEffects",
2282 + "duration": 0,
2283 + "timestamp": 10,
2284 + "type": "layout-effect-unmount",
2285 + "warning": null,
2286 + },
2287 + Object {
2288 + "componentName": "ComponentWithEffects",
2289 + "duration": 0,
2290 + "timestamp": 10,
2291 + "type": "layout-effect-unmount",
2292 + "warning": null,
2293 + },
2294 + Object {
2295 + "componentName": "ComponentWithEffects",
2296 + "duration": 0,
2297 + "timestamp": 10,
2298 + "type": "passive-effect-unmount",
2299 + "warning": null,
2300 + },
2301 + Object {
2302 + "componentName": "ComponentWithEffects",
2303 + "duration": 0,
2304 + "timestamp": 10,
2305 + "type": "passive-effect-unmount",
2306 + "warning": null,
2307 + },
2308 + Object {
2309 + "componentName": "ComponentWithEffects",
2310 + "duration": 0,
2311 + "timestamp": 10,
2312 + "type": "passive-effect-unmount",
2313 + "warning": null,
2314 + },
2315 + ]
2316 + `);
2317 + expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
2318 + Map {
2319 + 1 => Array [
2320 + Object {
2321 + "batchUID": 1,
2322 + "depth": 0,
2323 + "duration": 0,
2324 + "lanes": "0b0000000000000000000000000010000",
2325 + "timestamp": 10,
2326 + "type": "render-idle",
2327 + },
2328 + Object {
2329 + "batchUID": 1,
2330 + "depth": 0,
2331 + "duration": 0,
2332 + "lanes": "0b0000000000000000000000000010000",
2333 + "timestamp": 10,
2334 + "type": "render",
2335 + },
2336 + Object {
2337 + "batchUID": 1,
2338 + "depth": 0,
2339 + "duration": 0,
2340 + "lanes": "0b0000000000000000000000000010000",
2341 + "timestamp": 10,
2342 + "type": "commit",
2343 + },
2344 + Object {
2345 + "batchUID": 1,
2346 + "depth": 1,
2347 + "duration": 0,
2348 + "lanes": "0b0000000000000000000000000010000",
2349 + "timestamp": 10,
2350 + "type": "layout-effects",
2351 + },
2352 + Object {
2353 + "batchUID": 1,
2354 + "depth": 0,
2355 + "duration": 0,
2356 + "lanes": "0b0000000000000000000000000010000",
2357 + "timestamp": 10,
2358 + "type": "passive-effects",
2359 + },
2360 + ],
2361 + 2 => Array [
2362 + Object {
2363 + "batchUID": 2,
2364 + "depth": 0,
2365 + "duration": 0,
2366 + "lanes": "0b0000000000000000000000000000001",
2367 + "timestamp": 10,
2368 + "type": "render-idle",
2369 + },
2370 + Object {
2371 + "batchUID": 2,
2372 + "depth": 0,
2373 + "duration": 0,
2374 + "lanes": "0b0000000000000000000000000000001",
2375 + "timestamp": 10,
2376 + "type": "render",
2377 + },
2378 + Object {
2379 + "batchUID": 2,
2380 + "depth": 0,
2381 + "duration": 0,
2382 + "lanes": "0b0000000000000000000000000000001",
2383 + "timestamp": 10,
2384 + "type": "commit",
2385 + },
2386 + Object {
2387 + "batchUID": 2,
2388 + "depth": 1,
2389 + "duration": 0,
2390 + "lanes": "0b0000000000000000000000000000001",
2391 + "timestamp": 10,
2392 + "type": "layout-effects",
2393 + },
2394 + Object {
2395 + "batchUID": 2,
2396 + "depth": 1,
2397 + "duration": 0,
2398 + "lanes": "0b0000000000000000000000000000001",
2399 + "timestamp": 10,
2400 + "type": "passive-effects",
2401 + },
2402 + ],
2403 + }
2404 + `);
2405 + });
2406 + });
2407 +
2408 + describe('when not profiling', () => {
2409 + it('should not log any marks', () => {
2410 + renderHelper(<div />);
2411 +
2412 + const timelineData = stopProfilingAndGetTimelineData();
2413 + expect(timelineData).toBeNull();
2414 + });
2415 + });
2416 });
2417 });
packages/react-devtools-shared/src/__tests__/__serializers__/profilingSerializer.js new
+25
@@ -0,0 +1,25 @@
1 +import hasOwnProperty from 'shared/hasOwnProperty';
2 +
3 +const FILTERED_VERSION_STRING = '<filtered-version>';
4 +
5 +// test() is part of Jest's serializer API
6 +export function test(maybeProfile) {
7 + if (
8 + maybeProfile != null &&
9 + typeof maybeProfile === 'object' &&
10 + hasOwnProperty.call(maybeProfile, 'reactVersion') &&
11 + maybeProfile.reactVersion !== FILTERED_VERSION_STRING
12 + ) {
13 + return true;
14 + }
15 +
16 + return false;
17 +}
18 +
19 +// print() is part of Jest's serializer API
20 +export function print(profile, serialize, indent) {
21 + return serialize({
22 + ...profile,
23 + reactVersion: FILTERED_VERSION_STRING,
24 + });
25 +}
packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js new
+29
@@ -0,0 +1,29 @@
1 +import hasOwnProperty from 'shared/hasOwnProperty';
2 +import isArray from 'shared/isArray';
3 +
4 +function formatLanes(laneArray) {
5 + const lanes = laneArray.reduce((current, reduced) => current + reduced, 0);
6 + return '0b' + lanes.toString(2).padStart(31, '0');
7 +}
8 +
9 +// test() is part of Jest's serializer API
10 +export function test(maybeTimelineData) {
11 + if (
12 + maybeTimelineData != null &&
13 + typeof maybeTimelineData === 'object' &&
14 + hasOwnProperty.call(maybeTimelineData, 'lanes') &&
15 + isArray(maybeTimelineData.lanes)
16 + ) {
17 + return true;
18 + }
19 +
20 + return false;
21 +}
22 +
23 +// print() is part of Jest's serializer API
24 +export function print(timelineData, serialize, indent) {
25 + return serialize({
26 + ...timelineData,
27 + lanes: formatLanes(timelineData.lanes),
28 + });
29 +}
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+4815 -1312
@@ -720,7 +720,7 @@ Object {
720 1,
721 11,
722 0,
723 - 1,
723 + 3,
724 1,
725 1,
726 4,
@@ -836,907 +836,2536 @@ Object {
836 "snapshots": Array [],
837 },
838 ],
839 - "version": 5,
840 -}
841 -`;
842 -
843 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 2 1`] = `
844 -Array [
845 - 0,
846 - 1,
847 - 2,
848 -]
849 -`;
850 -
851 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
852 -Array [
853 - 0,
854 - 1,
855 - 2,
856 -]
857 -`;
858 -
859 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
860 -Array [
861 - 0,
862 -]
863 -`;
864 -
865 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
866 -Array [
867 - 1,
868 - 2,
869 -]
870 -`;
871 -
872 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 7 1`] = `
873 -Array [
874 - 2,
875 -]
876 -`;
877 -
878 -exports[`ProfilingCache should collect data for each rendered fiber: imported data 1`] = `
879 -Object {
880 - "dataForRoots": Array [
839 + "timelineData": Array [
840 Object {
882 - "commitData": Array [
883 - Object {
884 - "changeDescriptions": Array [
885 - Array [
886 - 2,
887 - Object {
888 - "context": null,
889 - "didHooksChange": false,
890 - "isFirstMount": true,
891 - "props": null,
892 - "state": null,
893 - },
894 - ],
895 - Array [
896 - 4,
897 - Object {
898 - "context": null,
899 - "didHooksChange": false,
900 - "isFirstMount": true,
901 - "props": null,
902 - "state": null,
903 - },
904 - ],
905 - Array [
906 - 5,
907 - Object {
908 - "context": null,
909 - "didHooksChange": false,
910 - "isFirstMount": true,
911 - "props": null,
912 - "state": null,
913 - },
914 - ],
915 - ],
916 - "duration": 11,
917 - "effectDuration": null,
918 - "fiberActualDurations": Array [
919 - Array [
920 - 1,
921 - 11,
922 - ],
923 - Array [
924 - 2,
925 - 11,
926 - ],
927 - Array [
928 - 4,
929 - 0,
930 - ],
931 - Array [
932 - 5,
933 - 1,
934 - ],
935 - ],
936 - "fiberSelfDurations": Array [
937 - Array [
938 - 1,
939 - 0,
940 - ],
941 - Array [
942 - 2,
943 - 10,
944 - ],
945 - Array [
946 - 4,
947 - 0,
948 - ],
949 - Array [
950 - 5,
951 - 1,
952 - ],
953 - ],
954 - "passiveEffectDuration": null,
955 - "priorityLevel": "Immediate",
956 - "timestamp": 11,
957 - "updaters": Array [
841 + "batchUIDToMeasuresKeyValueArray": Array [
842 + Array [
843 + 1,
844 + Array [
845 Object {
959 - "displayName": "render()",
960 - "hocDisplayNames": null,
961 - "id": 1,
962 - "key": null,
963 - "type": 11,
846 + "batchUID": 1,
847 + "depth": 0,
848 + "duration": 12,
849 + "lanes": "0b0000000000000000000000000000001",
850 + "timestamp": 10,
851 + "type": "render-idle",
852 + },
853 + Object {
854 + "batchUID": 1,
855 + "depth": 0,
856 + "duration": 12,
857 + "lanes": "0b0000000000000000000000000000001",
858 + "timestamp": 10,
859 + "type": "render",
860 + },
861 + Object {
862 + "batchUID": 1,
863 + "depth": 0,
864 + "duration": 0,
865 + "lanes": "0b0000000000000000000000000000001",
866 + "timestamp": 22,
867 + "type": "commit",
868 + },
869 + Object {
870 + "batchUID": 1,
871 + "depth": 1,
872 + "duration": 0,
873 + "lanes": "0b0000000000000000000000000000001",
874 + "timestamp": 22,
875 + "type": "layout-effects",
876 },
877 ],
966 - },
967 - Object {
968 - "changeDescriptions": Array [
969 - Array [
970 - 4,
971 - Object {
972 - "context": null,
973 - "didHooksChange": false,
974 - "hooks": null,
975 - "isFirstMount": false,
976 - "props": Array [],
977 - "state": null,
978 - },
979 - ],
980 - Array [
981 - 6,
982 - Object {
983 - "context": null,
984 - "didHooksChange": false,
985 - "isFirstMount": true,
986 - "props": null,
987 - "state": null,
988 - },
989 - ],
990 - Array [
991 - 2,
992 - Object {
993 - "context": null,
994 - "didHooksChange": false,
995 - "hooks": null,
996 - "isFirstMount": false,
997 - "props": Array [
998 - "count",
999 - ],
1000 - "state": null,
1001 - },
1002 - ],
1003 - ],
1004 - "duration": 11,
1005 - "effectDuration": null,
1006 - "fiberActualDurations": Array [
1007 - Array [
1008 - 4,
1009 - 0,
1010 - ],
1011 - Array [
1012 - 6,
1013 - 1,
1014 - ],
1015 - Array [
1016 - 2,
1017 - 11,
1018 - ],
1019 - Array [
1020 - 1,
1021 - 11,
1022 - ],
878 + ],
879 + Array [
880 + 2,
881 + Array [
882 + Object {
883 + "batchUID": 2,
884 + "depth": 0,
885 + "duration": 13,
886 + "lanes": "0b0000000000000000000000000000001",
887 + "timestamp": 22,
888 + "type": "render-idle",
889 + },
890 + Object {
891 + "batchUID": 2,
892 + "depth": 0,
893 + "duration": 13,
894 + "lanes": "0b0000000000000000000000000000001",
895 + "timestamp": 22,
896 + "type": "render",
897 + },
898 + Object {
899 + "batchUID": 2,
900 + "depth": 0,
901 + "duration": 0,
902 + "lanes": "0b0000000000000000000000000000001",
903 + "timestamp": 35,
904 + "type": "commit",
905 + },
906 + Object {
907 + "batchUID": 2,
908 + "depth": 1,
909 + "duration": 0,
910 + "lanes": "0b0000000000000000000000000000001",
911 + "timestamp": 35,
912 + "type": "layout-effects",
913 + },
914 ],
1024 - "fiberSelfDurations": Array [
1025 - Array [
1026 - 4,
1027 - 0,
1028 - ],
1029 - Array [
1030 - 6,
1031 - 1,
1032 - ],
1033 - Array [
1034 - 2,
1035 - 10,
1036 - ],
1037 - Array [
1038 - 1,
1039 - 0,
1040 - ],
915 + ],
916 + Array [
917 + 3,
918 + Array [
919 + Object {
920 + "batchUID": 3,
921 + "depth": 0,
922 + "duration": 10,
923 + "lanes": "0b0000000000000000000000000000001",
924 + "timestamp": 35,
925 + "type": "render-idle",
926 + },
927 + Object {
928 + "batchUID": 3,
929 + "depth": 0,
930 + "duration": 10,
931 + "lanes": "0b0000000000000000000000000000001",
932 + "timestamp": 35,
933 + "type": "render",
934 + },
935 + Object {
936 + "batchUID": 3,
937 + "depth": 0,
938 + "duration": 0,
939 + "lanes": "0b0000000000000000000000000000001",
940 + "timestamp": 45,
941 + "type": "commit",
942 + },
943 + Object {
944 + "batchUID": 3,
945 + "depth": 1,
946 + "duration": 0,
947 + "lanes": "0b0000000000000000000000000000001",
948 + "timestamp": 45,
949 + "type": "layout-effects",
950 + },
951 + Object {
952 + "batchUID": 3,
953 + "depth": 0,
954 + "duration": 0,
955 + "lanes": "0b0000000000000000000000000000001",
956 + "timestamp": 45,
957 + "type": "passive-effects",
958 + },
959 ],
1042 - "passiveEffectDuration": null,
1043 - "priorityLevel": "Immediate",
1044 - "timestamp": 22,
1045 - "updaters": Array [
960 + ],
961 + Array [
962 + 4,
963 + Array [
964 Object {
1047 - "displayName": "render()",
1048 - "hocDisplayNames": null,
1049 - "id": 1,
1050 - "key": null,
1051 - "type": 11,
965 + "batchUID": 4,
966 + "depth": 0,
967 + "duration": 10,
968 + "lanes": "0b0000000000000000000000000000001",
969 + "timestamp": 45,
970 + "type": "render-idle",
971 + },
972 + Object {
973 + "batchUID": 4,
974 + "depth": 0,
975 + "duration": 10,
976 + "lanes": "0b0000000000000000000000000000001",
977 + "timestamp": 45,
978 + "type": "render",
979 + },
980 + Object {
981 + "batchUID": 4,
982 + "depth": 0,
983 + "duration": 0,
984 + "lanes": "0b0000000000000000000000000000001",
985 + "timestamp": 55,
986 + "type": "commit",
987 + },
988 + Object {
989 + "batchUID": 4,
990 + "depth": 1,
991 + "duration": 0,
992 + "lanes": "0b0000000000000000000000000000001",
993 + "timestamp": 55,
994 + "type": "layout-effects",
995 + },
996 + Object {
997 + "batchUID": 4,
998 + "depth": 0,
999 + "duration": 0,
1000 + "lanes": "0b0000000000000000000000000000001",
1001 + "timestamp": 55,
1002 + "type": "passive-effects",
1003 },
1004 ],
1005 + ],
1006 + ],
1007 + "componentMeasures": Array [
1008 + Object {
1009 + "componentName": "Parent",
1010 + "duration": 10,
1011 + "timestamp": 10,
1012 + "type": "render",
1013 + "warning": null,
1014 },
1015 Object {
1056 - "changeDescriptions": Array [
1057 - Array [
1058 - 4,
1059 - Object {
1060 - "context": null,
1061 - "didHooksChange": false,
1062 - "hooks": null,
1063 - "isFirstMount": false,
1064 - "props": Array [],
1065 - "state": null,
1066 - },
1067 - ],
1068 - Array [
1069 - 6,
1070 - Object {
1071 - "context": null,
1072 - "didHooksChange": false,
1073 - "hooks": null,
1074 - "isFirstMount": false,
1075 - "props": Array [],
1076 - "state": null,
1077 - },
1078 - ],
1079 - Array [
1080 - 7,
1081 - Object {
1082 - "context": null,
1083 - "didHooksChange": false,
1084 - "isFirstMount": true,
1085 - "props": null,
1086 - "state": null,
1087 - },
1088 - ],
1089 - Array [
1090 - 2,
1091 - Object {
1092 - "context": null,
1093 - "didHooksChange": false,
1094 - "hooks": null,
1095 - "isFirstMount": false,
1096 - "props": Array [
1097 - "count",
1098 - ],
1099 - "state": null,
1100 - },
1101 - ],
1102 - ],
1103 - "duration": 13,
1104 - "effectDuration": null,
1105 - "fiberActualDurations": Array [
1106 - Array [
1107 - 4,
1108 - 0,
1109 - ],
1110 - Array [
1111 - 6,
1112 - 1,
1113 - ],
1114 - Array [
1115 - 7,
1116 - 2,
1117 - ],
1118 - Array [
1119 - 2,
1120 - 13,
1121 - ],
1122 - Array [
1123 - 1,
1124 - 13,
1125 - ],
1126 - ],
1127 - "fiberSelfDurations": Array [
1128 - Array [
1129 - 4,
1130 - 0,
1131 - ],
1132 - Array [
1133 - 6,
1134 - 1,
1135 - ],
1136 - Array [
1137 - 7,
1138 - 2,
1139 - ],
1140 - Array [
1141 - 2,
1142 - 10,
1143 - ],
1144 - Array [
1145 - 1,
1146 - 0,
1147 - ],
1148 - ],
1149 - "passiveEffectDuration": null,
1150 - "priorityLevel": "Immediate",
1016 + "componentName": "Child",
1017 + "duration": 0,
1018 + "timestamp": 20,
1019 + "type": "render",
1020 + "warning": null,
1021 + },
1022 + Object {
1023 + "componentName": "Child",
1024 + "duration": 1,
1025 + "timestamp": 20,
1026 + "type": "render",
1027 + "warning": null,
1028 + },
1029 + Object {
1030 + "componentName": "Child",
1031 + "duration": 1,
1032 + "timestamp": 21,
1033 + "type": "render",
1034 + "warning": null,
1035 + },
1036 + Object {
1037 + "componentName": "Parent",
1038 + "duration": 10,
1039 + "timestamp": 22,
1040 + "type": "render",
1041 + "warning": null,
1042 + },
1043 + Object {
1044 + "componentName": "Child",
1045 + "duration": 0,
1046 + "timestamp": 32,
1047 + "type": "render",
1048 + "warning": null,
1049 + },
1050 + Object {
1051 + "componentName": "Child",
1052 + "duration": 1,
1053 + "timestamp": 32,
1054 + "type": "render",
1055 + "warning": null,
1056 + },
1057 + Object {
1058 + "componentName": "Child",
1059 + "duration": 2,
1060 + "timestamp": 33,
1061 + "type": "render",
1062 + "warning": null,
1063 + },
1064 + Object {
1065 + "componentName": "Parent",
1066 + "duration": 10,
1067 "timestamp": 35,
1152 - "updaters": Array [
1153 - Object {
1154 - "displayName": "render()",
1155 - "hocDisplayNames": null,
1156 - "id": 1,
1157 - "key": null,
1158 - "type": 11,
1159 - },
1160 - ],
1068 + "type": "render",
1069 + "warning": null,
1070 + },
1071 + Object {
1072 + "componentName": "Child",
1073 + "duration": 0,
1074 + "timestamp": 45,
1075 + "type": "render",
1076 + "warning": null,
1077 + },
1078 + Object {
1079 + "componentName": "Parent",
1080 + "duration": 10,
1081 + "timestamp": 45,
1082 + "type": "render",
1083 + "warning": null,
1084 },
1085 ],
1163 - "displayName": "Parent",
1164 - "initialTreeBaseDurations": Array [],
1165 - "operations": Array [
1086 + "duration": 65,
1087 + "flamechart": Array [],
1088 + "internalModuleSourceToRanges": Array [],
1089 + "laneToLabelKeyValueArray": Array [
1090 Array [
1091 1,
1168 - 1,
1169 - 15,
1170 - 6,
1171 - 80,
1172 - 97,
1173 - 114,
1174 - 101,
1175 - 110,
1176 - 116,
1177 - 5,
1178 - 67,
1179 - 104,
1180 - 105,
1181 - 108,
1182 - 100,
1183 - 1,
1184 - 48,
1185 - 1,
1186 - 1,
1187 - 11,
1188 - 0,
1189 - 1,
1190 - 1,
1191 - 1,
1192 - 4,
1193 - 1,
1194 - 11000,
1195 - 1,
1196 - 2,
1197 - 5,
1198 - 1,
1199 - 0,
1200 - 1,
1201 - 0,
1202 - 4,
1203 - 2,
1204 - 11000,
1205 - 1,
1206 - 4,
1207 - 5,
1208 - 2,
1209 - 2,
1210 - 2,
1211 - 3,
1212 - 4,
1213 - 4,
1214 - 0,
1215 - 1,
1216 - 5,
1217 - 8,
1218 - 2,
1219 - 2,
1220 - 2,
1221 - 0,
1222 - 4,
1223 - 5,
1224 - 1000,
1092 + "Sync",
1093 ],
1094 Array [
1227 - 1,
1228 - 1,
1229 - 8,
1230 - 5,
1231 - 67,
1232 - 104,
1233 - 105,
1234 - 108,
1235 - 100,
1236 - 1,
1237 - 49,
1238 - 1,
1239 - 6,
1240 - 5,
1241 - 2,
1242 - 2,
1243 - 1,
1244 - 2,
1245 - 4,
1246 - 6,
1247 - 1000,
1248 - 4,
1095 2,
1250 - 12000,
1251 - 3,
1252 - 2,
1253 - 3,
1254 - 4,
1255 - 6,
1256 - 5,
1096 + "InputContinuousHydration",
1097 + ],
1098 + Array [
1099 4,
1258 - 1,
1259 - 12000,
1100 + "InputContinuous",
1101 ],
1102 Array [
1262 - 1,
1263 - 1,
1103 8,
1265 - 5,
1266 - 67,
1267 - 104,
1268 - 105,
1269 - 108,
1270 - 100,
1271 - 1,
1272 - 50,
1273 - 1,
1274 - 7,
1275 - 5,
1276 - 2,
1277 - 2,
1278 - 1,
1279 - 2,
1280 - 4,
1281 - 7,
1282 - 2000,
1283 - 4,
1284 - 2,
1285 - 14000,
1286 - 3,
1287 - 2,
1288 - 4,
1289 - 4,
1290 - 6,
1291 - 7,
1292 - 5,
1293 - 4,
1294 - 1,
1295 - 14000,
1104 + "DefaultHydration",
1105 + ],
1106 + Array [
1107 + 16,
1108 + "Default",
1109 + ],
1110 + Array [
1111 + 32,
1112 + "TransitionHydration",
1113 + ],
1114 + Array [
1115 + 64,
1116 + "Transition",
1117 + ],
1118 + Array [
1119 + 128,
1120 + "Transition",
1121 + ],
1122 + Array [
1123 + 256,
1124 + "Transition",
1125 + ],
1126 + Array [
1127 + 512,
1128 + "Transition",
1129 + ],
1130 + Array [
1131 + 1024,
1132 + "Transition",
1133 + ],
1134 + Array [
1135 + 2048,
1136 + "Transition",
1137 + ],
1138 + Array [
1139 + 4096,
1140 + "Transition",
1141 + ],
1142 + Array [
1143 + 8192,
1144 + "Transition",
1145 + ],
1146 + Array [
1147 + 16384,
1148 + "Transition",
1149 + ],
1150 + Array [
1151 + 32768,
1152 + "Transition",
1153 + ],
1154 + Array [
1155 + 65536,
1156 + "Transition",
1157 + ],
1158 + Array [
1159 + 131072,
1160 + "Transition",
1161 + ],
1162 + Array [
1163 + 262144,
1164 + "Transition",
1165 + ],
1166 + Array [
1167 + 524288,
1168 + "Transition",
1169 + ],
1170 + Array [
1171 + 1048576,
1172 + "Transition",
1173 + ],
1174 + Array [
1175 + 2097152,
1176 + "Transition",
1177 + ],
1178 + Array [
1179 + 4194304,
1180 + "Retry",
1181 + ],
1182 + Array [
1183 + 8388608,
1184 + "Retry",
1185 + ],
1186 + Array [
1187 + 16777216,
1188 + "Retry",
1189 + ],
1190 + Array [
1191 + 33554432,
1192 + "Retry",
1193 + ],
1194 + Array [
1195 + 67108864,
1196 + "Retry",
1197 + ],
1198 + Array [
1199 + 134217728,
1200 + "SelectiveHydration",
1201 + ],
1202 + Array [
1203 + 268435456,
1204 + "IdleHydration",
1205 + ],
1206 + Array [
1207 + 536870912,
1208 + "Idle",
1209 + ],
1210 + Array [
1211 + 1073741824,
1212 + "Offscreen",
1213 ],
1214 ],
1298 - "rootID": 1,
1215 + "laneToReactMeasureKeyValueArray": Array [
1216 + Array [
1217 + 1,
1218 + Array [
1219 + Object {
1220 + "batchUID": 1,
1221 + "depth": 0,
1222 + "duration": 12,
1223 + "lanes": "0b0000000000000000000000000000001",
1224 + "timestamp": 10,
1225 + "type": "render-idle",
1226 + },
1227 + Object {
1228 + "batchUID": 1,
1229 + "depth": 0,
1230 + "duration": 12,
1231 + "lanes": "0b0000000000000000000000000000001",
1232 + "timestamp": 10,
1233 + "type": "render",
1234 + },
1235 + Object {
1236 + "batchUID": 1,
1237 + "depth": 0,
1238 + "duration": 0,
1239 + "lanes": "0b0000000000000000000000000000001",
1240 + "timestamp": 22,
1241 + "type": "commit",
1242 + },
1243 + Object {
1244 + "batchUID": 1,
1245 + "depth": 1,
1246 + "duration": 0,
1247 + "lanes": "0b0000000000000000000000000000001",
1248 + "timestamp": 22,
1249 + "type": "layout-effects",
1250 + },
1251 + Object {
1252 + "batchUID": 2,
1253 + "depth": 0,
1254 + "duration": 13,
1255 + "lanes": "0b0000000000000000000000000000001",
1256 + "timestamp": 22,
1257 + "type": "render-idle",
1258 + },
1259 + Object {
1260 + "batchUID": 2,
1261 + "depth": 0,
1262 + "duration": 13,
1263 + "lanes": "0b0000000000000000000000000000001",
1264 + "timestamp": 22,
1265 + "type": "render",
1266 + },
1267 + Object {
1268 + "batchUID": 2,
1269 + "depth": 0,
1270 + "duration": 0,
1271 + "lanes": "0b0000000000000000000000000000001",
1272 + "timestamp": 35,
1273 + "type": "commit",
1274 + },
1275 + Object {
1276 + "batchUID": 2,
1277 + "depth": 1,
1278 + "duration": 0,
1279 + "lanes": "0b0000000000000000000000000000001",
1280 + "timestamp": 35,
1281 + "type": "layout-effects",
1282 + },
1283 + Object {
1284 + "batchUID": 3,
1285 + "depth": 0,
1286 + "duration": 10,
1287 + "lanes": "0b0000000000000000000000000000001",
1288 + "timestamp": 35,
1289 + "type": "render-idle",
1290 + },
1291 + Object {
1292 + "batchUID": 3,
1293 + "depth": 0,
1294 + "duration": 10,
1295 + "lanes": "0b0000000000000000000000000000001",
1296 + "timestamp": 35,
1297 + "type": "render",
1298 + },
1299 + Object {
1300 + "batchUID": 3,
1301 + "depth": 0,
1302 + "duration": 0,
1303 + "lanes": "0b0000000000000000000000000000001",
1304 + "timestamp": 45,
1305 + "type": "commit",
1306 + },
1307 + Object {
1308 + "batchUID": 3,
1309 + "depth": 1,
1310 + "duration": 0,
1311 + "lanes": "0b0000000000000000000000000000001",
1312 + "timestamp": 45,
1313 + "type": "layout-effects",
1314 + },
1315 + Object {
1316 + "batchUID": 3,
1317 + "depth": 0,
1318 + "duration": 0,
1319 + "lanes": "0b0000000000000000000000000000001",
1320 + "timestamp": 45,
1321 + "type": "passive-effects",
1322 + },
1323 + Object {
1324 + "batchUID": 4,
1325 + "depth": 0,
1326 + "duration": 10,
1327 + "lanes": "0b0000000000000000000000000000001",
1328 + "timestamp": 45,
1329 + "type": "render-idle",
1330 + },
1331 + Object {
1332 + "batchUID": 4,
1333 + "depth": 0,
1334 + "duration": 10,
1335 + "lanes": "0b0000000000000000000000000000001",
1336 + "timestamp": 45,
1337 + "type": "render",
1338 + },
1339 + Object {
1340 + "batchUID": 4,
1341 + "depth": 0,
1342 + "duration": 0,
1343 + "lanes": "0b0000000000000000000000000000001",
1344 + "timestamp": 55,
1345 + "type": "commit",
1346 + },
1347 + Object {
1348 + "batchUID": 4,
1349 + "depth": 1,
1350 + "duration": 0,
1351 + "lanes": "0b0000000000000000000000000000001",
1352 + "timestamp": 55,
1353 + "type": "layout-effects",
1354 + },
1355 + Object {
1356 + "batchUID": 4,
1357 + "depth": 0,
1358 + "duration": 0,
1359 + "lanes": "0b0000000000000000000000000000001",
1360 + "timestamp": 55,
1361 + "type": "passive-effects",
1362 + },
1363 + ],
1364 + ],
1365 + Array [
1366 + 2,
1367 + Array [],
1368 + ],
1369 + Array [
1370 + 4,
1371 + Array [],
1372 + ],
1373 + Array [
1374 + 8,
1375 + Array [],
1376 + ],
1377 + Array [
1378 + 16,
1379 + Array [],
1380 + ],
1381 + Array [
1382 + 32,
1383 + Array [],
1384 + ],
1385 + Array [
1386 + 64,
1387 + Array [],
1388 + ],
1389 + Array [
1390 + 128,
1391 + Array [],
1392 + ],
1393 + Array [
1394 + 256,
1395 + Array [],
1396 + ],
1397 + Array [
1398 + 512,
1399 + Array [],
1400 + ],
1401 + Array [
1402 + 1024,
1403 + Array [],
1404 + ],
1405 + Array [
1406 + 2048,
1407 + Array [],
1408 + ],
1409 + Array [
1410 + 4096,
1411 + Array [],
1412 + ],
1413 + Array [
1414 + 8192,
1415 + Array [],
1416 + ],
1417 + Array [
1418 + 16384,
1419 + Array [],
1420 + ],
1421 + Array [
1422 + 32768,
1423 + Array [],
1424 + ],
1425 + Array [
1426 + 65536,
1427 + Array [],
1428 + ],
1429 + Array [
1430 + 131072,
1431 + Array [],
1432 + ],
1433 + Array [
1434 + 262144,
1435 + Array [],
1436 + ],
1437 + Array [
1438 + 524288,
1439 + Array [],
1440 + ],
1441 + Array [
1442 + 1048576,
1443 + Array [],
1444 + ],
1445 + Array [
1446 + 2097152,
1447 + Array [],
1448 + ],
1449 + Array [
1450 + 4194304,
1451 + Array [],
1452 + ],
1453 + Array [
1454 + 8388608,
1455 + Array [],
1456 + ],
1457 + Array [
1458 + 16777216,
1459 + Array [],
1460 + ],
1461 + Array [
1462 + 33554432,
1463 + Array [],
1464 + ],
1465 + Array [
1466 + 67108864,
1467 + Array [],
1468 + ],
1469 + Array [
1470 + 134217728,
1471 + Array [],
1472 + ],
1473 + Array [
1474 + 268435456,
1475 + Array [],
1476 + ],
1477 + Array [
1478 + 536870912,
1479 + Array [],
1480 + ],
1481 + Array [
1482 + 1073741824,
1483 + Array [],
1484 + ],
1485 + ],
1486 + "nativeEvents": Array [],
1487 + "networkMeasures": Array [],
1488 + "otherUserTimingMarks": Array [],
1489 + "reactVersion": "<filtered-version>",
1490 + "schedulingEvents": Array [
1491 + Object {
1492 + "lanes": "0b0000000000000000000000000000001",
1493 + "timestamp": 10,
1494 + "type": "schedule-render",
1495 + "warning": null,
1496 + },
1497 + Object {
1498 + "lanes": "0b0000000000000000000000000000001",
1499 + "timestamp": 22,
1500 + "type": "schedule-render",
1501 + "warning": null,
1502 + },
1503 + Object {
1504 + "lanes": "0b0000000000000000000000000000001",
1505 + "timestamp": 35,
1506 + "type": "schedule-render",
1507 + "warning": null,
1508 + },
1509 + Object {
1510 + "lanes": "0b0000000000000000000000000000001",
1511 + "timestamp": 45,
1512 + "type": "schedule-render",
1513 + "warning": null,
1514 + },
1515 + ],
1516 + "snapshotHeight": 0,
1517 "snapshots": Array [],
1518 + "startTime": -10,
1519 + "suspenseEvents": Array [],
1520 + "thrownErrors": Array [],
1521 },
1522 ],
1523 "version": 5,
1524 }
1525 `;
1526
1306 -exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): Data for root Parent 1`] = `
1527 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 2 1`] = `
1528 +Array [
1529 + 0,
1530 + 1,
1531 + 2,
1532 +]
1533 +`;
1534 +
1535 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
1536 +Array [
1537 + 0,
1538 + 1,
1539 + 2,
1540 +]
1541 +`;
1542 +
1543 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
1544 +Array [
1545 + 0,
1546 +]
1547 +`;
1548 +
1549 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
1550 +Array [
1551 + 1,
1552 + 2,
1553 +]
1554 +`;
1555 +
1556 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 7 1`] = `
1557 +Array [
1558 + 2,
1559 +]
1560 +`;
1561 +
1562 +exports[`ProfilingCache should collect data for each rendered fiber: imported data 1`] = `
1563 Object {
1308 - "commitData": Array [
1564 + "dataForRoots": Array [
1565 Object {
1310 - "changeDescriptions": Map {
1311 - 4 => Object {
1312 - "context": null,
1313 - "didHooksChange": false,
1314 - "hooks": null,
1315 - "isFirstMount": false,
1316 - "props": Array [],
1317 - "state": null,
1318 - },
1319 - 5 => Object {
1320 - "context": null,
1321 - "didHooksChange": false,
1322 - "hooks": null,
1323 - "isFirstMount": false,
1324 - "props": Array [],
1325 - "state": null,
1326 - },
1327 - 12 => Object {
1328 - "context": null,
1329 - "didHooksChange": false,
1330 - "isFirstMount": true,
1331 - "props": null,
1332 - "state": null,
1333 - },
1334 - 2 => Object {
1335 - "context": null,
1336 - "didHooksChange": false,
1337 - "hooks": null,
1338 - "isFirstMount": false,
1339 - "props": Array [
1340 - "count",
1341 - ],
1342 - "state": null,
1343 - },
1344 - },
1345 - "duration": 13,
1346 - "effectDuration": null,
1347 - "fiberActualDurations": Map {
1348 - 4 => 0,
1349 - 5 => 1,
1350 - 12 => 2,
1351 - 2 => 13,
1352 - 1 => 13,
1353 - },
1354 - "fiberSelfDurations": Map {
1355 - 4 => 0,
1356 - 5 => 1,
1357 - 12 => 2,
1358 - 2 => 10,
1359 - 1 => 0,
1360 - },
1361 - "passiveEffectDuration": null,
1362 - "priorityLevel": "Immediate",
1363 - "timestamp": 13,
1364 - "updaters": Array [
1566 + "commitData": Array [
1567 Object {
1366 - "displayName": "render()",
1367 - "hocDisplayNames": null,
1368 - "id": 1,
1369 - "key": null,
1370 - "type": 11,
1371 - },
1372 - ],
1373 - },
1374 - Object {
1375 - "changeDescriptions": Map {
1376 - 4 => Object {
1377 - "context": null,
1378 - "didHooksChange": false,
1379 - "hooks": null,
1380 - "isFirstMount": false,
1381 - "props": Array [],
1382 - "state": null,
1383 - },
1384 - 2 => Object {
1385 - "context": null,
1386 - "didHooksChange": false,
1387 - "hooks": null,
1388 - "isFirstMount": false,
1389 - "props": Array [
1390 - "count",
1568 + "changeDescriptions": Array [
1569 + Array [
1570 + 2,
1571 + Object {
1572 + "context": null,
1573 + "didHooksChange": false,
1574 + "isFirstMount": true,
1575 + "props": null,
1576 + "state": null,
1577 + },
1578 + ],
1579 + Array [
1580 + 4,
1581 + Object {
1582 + "context": null,
1583 + "didHooksChange": false,
1584 + "isFirstMount": true,
1585 + "props": null,
1586 + "state": null,
1587 + },
1588 + ],
1589 + Array [
1590 + 5,
1591 + Object {
1592 + "context": null,
1593 + "didHooksChange": false,
1594 + "isFirstMount": true,
1595 + "props": null,
1596 + "state": null,
1597 + },
1598 + ],
1599 + ],
1600 + "duration": 11,
1601 + "effectDuration": null,
1602 + "fiberActualDurations": Array [
1603 + Array [
1604 + 1,
1605 + 11,
1606 + ],
1607 + Array [
1608 + 2,
1609 + 11,
1610 + ],
1611 + Array [
1612 + 4,
1613 + 0,
1614 + ],
1615 + Array [
1616 + 5,
1617 + 1,
1618 + ],
1619 + ],
1620 + "fiberSelfDurations": Array [
1621 + Array [
1622 + 1,
1623 + 0,
1624 + ],
1625 + Array [
1626 + 2,
1627 + 10,
1628 + ],
1629 + Array [
1630 + 4,
1631 + 0,
1632 + ],
1633 + Array [
1634 + 5,
1635 + 1,
1636 + ],
1637 + ],
1638 + "passiveEffectDuration": null,
1639 + "priorityLevel": "Immediate",
1640 + "timestamp": 11,
1641 + "updaters": Array [
1642 + Object {
1643 + "displayName": "render()",
1644 + "hocDisplayNames": null,
1645 + "id": 1,
1646 + "key": null,
1647 + "type": 11,
1648 + },
1649 ],
1392 - "state": null,
1650 },
1394 - },
1395 - "duration": 10,
1396 - "effectDuration": 0,
1397 - "fiberActualDurations": Map {
1398 - 4 => 0,
1399 - 2 => 10,
1400 - 1 => 10,
1401 - },
1402 - "fiberSelfDurations": Map {
1403 - 4 => 0,
1404 - 2 => 10,
1405 - 1 => 0,
1406 - },
1407 - "passiveEffectDuration": 0,
1408 - "priorityLevel": "Immediate",
1409 - "timestamp": 34,
1410 - "updaters": Array [
1651 Object {
1412 - "displayName": "render()",
1413 - "hocDisplayNames": null,
1414 - "id": 1,
1415 - "key": null,
1416 - "type": 11,
1417 - },
1418 - ],
1419 - },
1420 - Object {
1421 - "changeDescriptions": Map {
1422 - 2 => Object {
1423 - "context": null,
1424 - "didHooksChange": false,
1425 - "hooks": null,
1426 - "isFirstMount": false,
1427 - "props": Array [
1428 - "count",
1652 + "changeDescriptions": Array [
1653 + Array [
1654 + 4,
1655 + Object {
1656 + "context": null,
1657 + "didHooksChange": false,
1658 + "hooks": null,
1659 + "isFirstMount": false,
1660 + "props": Array [],
1661 + "state": null,
1662 + },
1663 + ],
1664 + Array [
1665 + 6,
1666 + Object {
1667 + "context": null,
1668 + "didHooksChange": false,
1669 + "isFirstMount": true,
1670 + "props": null,
1671 + "state": null,
1672 + },
1673 + ],
1674 + Array [
1675 + 2,
1676 + Object {
1677 + "context": null,
1678 + "didHooksChange": false,
1679 + "hooks": null,
1680 + "isFirstMount": false,
1681 + "props": Array [
1682 + "count",
1683 + ],
1684 + "state": null,
1685 + },
1686 + ],
1687 + ],
1688 + "duration": 11,
1689 + "effectDuration": null,
1690 + "fiberActualDurations": Array [
1691 + Array [
1692 + 4,
1693 + 0,
1694 + ],
1695 + Array [
1696 + 6,
1697 + 1,
1698 + ],
1699 + Array [
1700 + 2,
1701 + 11,
1702 + ],
1703 + Array [
1704 + 1,
1705 + 11,
1706 + ],
1707 + ],
1708 + "fiberSelfDurations": Array [
1709 + Array [
1710 + 4,
1711 + 0,
1712 + ],
1713 + Array [
1714 + 6,
1715 + 1,
1716 + ],
1717 + Array [
1718 + 2,
1719 + 10,
1720 + ],
1721 + Array [
1722 + 1,
1723 + 0,
1724 + ],
1725 + ],
1726 + "passiveEffectDuration": null,
1727 + "priorityLevel": "Immediate",
1728 + "timestamp": 22,
1729 + "updaters": Array [
1730 + Object {
1731 + "displayName": "render()",
1732 + "hocDisplayNames": null,
1733 + "id": 1,
1734 + "key": null,
1735 + "type": 11,
1736 + },
1737 ],
1430 - "state": null,
1738 },
1432 - },
1433 - "duration": 10,
1434 - "effectDuration": 0,
1435 - "fiberActualDurations": Map {
1436 - 2 => 10,
1437 - 1 => 10,
1438 - },
1439 - "fiberSelfDurations": Map {
1440 - 2 => 10,
1441 - 1 => 0,
1442 - },
1443 - "passiveEffectDuration": 0,
1444 - "priorityLevel": "Immediate",
1445 - "timestamp": 44,
1446 - "updaters": Array [
1739 Object {
1448 - "displayName": "render()",
1449 - "hocDisplayNames": null,
1450 - "id": 1,
1451 - "key": null,
1452 - "type": 11,
1740 + "changeDescriptions": Array [
1741 + Array [
1742 + 4,
1743 + Object {
1744 + "context": null,
1745 + "didHooksChange": false,
1746 + "hooks": null,
1747 + "isFirstMount": false,
1748 + "props": Array [],
1749 + "state": null,
1750 + },
1751 + ],
1752 + Array [
1753 + 6,
1754 + Object {
1755 + "context": null,
1756 + "didHooksChange": false,
1757 + "hooks": null,
1758 + "isFirstMount": false,
1759 + "props": Array [],
1760 + "state": null,
1761 + },
1762 + ],
1763 + Array [
1764 + 7,
1765 + Object {
1766 + "context": null,
1767 + "didHooksChange": false,
1768 + "isFirstMount": true,
1769 + "props": null,
1770 + "state": null,
1771 + },
1772 + ],
1773 + Array [
1774 + 2,
1775 + Object {
1776 + "context": null,
1777 + "didHooksChange": false,
1778 + "hooks": null,
1779 + "isFirstMount": false,
1780 + "props": Array [
1781 + "count",
1782 + ],
1783 + "state": null,
1784 + },
1785 + ],
1786 + ],
1787 + "duration": 13,
1788 + "effectDuration": null,
1789 + "fiberActualDurations": Array [
1790 + Array [
1791 + 4,
1792 + 0,
1793 + ],
1794 + Array [
1795 + 6,
1796 + 1,
1797 + ],
1798 + Array [
1799 + 7,
1800 + 2,
1801 + ],
1802 + Array [
1803 + 2,
1804 + 13,
1805 + ],
1806 + Array [
1807 + 1,
1808 + 13,
1809 + ],
1810 + ],
1811 + "fiberSelfDurations": Array [
1812 + Array [
1813 + 4,
1814 + 0,
1815 + ],
1816 + Array [
1817 + 6,
1818 + 1,
1819 + ],
1820 + Array [
1821 + 7,
1822 + 2,
1823 + ],
1824 + Array [
1825 + 2,
1826 + 10,
1827 + ],
1828 + Array [
1829 + 1,
1830 + 0,
1831 + ],
1832 + ],
1833 + "passiveEffectDuration": null,
1834 + "priorityLevel": "Immediate",
1835 + "timestamp": 35,
1836 + "updaters": Array [
1837 + Object {
1838 + "displayName": "render()",
1839 + "hocDisplayNames": null,
1840 + "id": 1,
1841 + "key": null,
1842 + "type": 11,
1843 + },
1844 + ],
1845 },
1846 ],
1847 + "displayName": "Parent",
1848 + "initialTreeBaseDurations": Array [],
1849 + "operations": Array [
1850 + Array [
1851 + 1,
1852 + 1,
1853 + 15,
1854 + 6,
1855 + 80,
1856 + 97,
1857 + 114,
1858 + 101,
1859 + 110,
1860 + 116,
1861 + 5,
1862 + 67,
1863 + 104,
1864 + 105,
1865 + 108,
1866 + 100,
1867 + 1,
1868 + 48,
1869 + 1,
1870 + 1,
1871 + 11,
1872 + 0,
1873 + 3,
1874 + 1,
1875 + 1,
1876 + 4,
1877 + 1,
1878 + 11000,
1879 + 1,
1880 + 2,
1881 + 5,
1882 + 1,
1883 + 0,
1884 + 1,
1885 + 0,
1886 + 4,
1887 + 2,
1888 + 11000,
1889 + 1,
1890 + 4,
1891 + 5,
1892 + 2,
1893 + 2,
1894 + 2,
1895 + 3,
1896 + 4,
1897 + 4,
1898 + 0,
1899 + 1,
1900 + 5,
1901 + 8,
1902 + 2,
1903 + 2,
1904 + 2,
1905 + 0,
1906 + 4,
1907 + 5,
1908 + 1000,
1909 + ],
1910 + Array [
1911 + 1,
1912 + 1,
1913 + 8,
1914 + 5,
1915 + 67,
1916 + 104,
1917 + 105,
1918 + 108,
1919 + 100,
1920 + 1,
1921 + 49,
1922 + 1,
1923 + 6,
1924 + 5,
1925 + 2,
1926 + 2,
1927 + 1,
1928 + 2,
1929 + 4,
1930 + 6,
1931 + 1000,
1932 + 4,
1933 + 2,
1934 + 12000,
1935 + 3,
1936 + 2,
1937 + 3,
1938 + 4,
1939 + 6,
1940 + 5,
1941 + 4,
1942 + 1,
1943 + 12000,
1944 + ],
1945 + Array [
1946 + 1,
1947 + 1,
1948 + 8,
1949 + 5,
1950 + 67,
1951 + 104,
1952 + 105,
1953 + 108,
1954 + 100,
1955 + 1,
1956 + 50,
1957 + 1,
1958 + 7,
1959 + 5,
1960 + 2,
1961 + 2,
1962 + 1,
1963 + 2,
1964 + 4,
1965 + 7,
1966 + 2000,
1967 + 4,
1968 + 2,
1969 + 14000,
1970 + 3,
1971 + 2,
1972 + 4,
1973 + 4,
1974 + 6,
1975 + 7,
1976 + 5,
1977 + 4,
1978 + 1,
1979 + 14000,
1980 + ],
1981 + ],
1982 + "rootID": 1,
1983 + "snapshots": Array [],
1984 },
1985 ],
1457 - "displayName": "Parent",
1458 - "initialTreeBaseDurations": Map {
1459 - 1 => 12,
1460 - 2 => 12,
1461 - 4 => 0,
1462 - 5 => 1,
1463 - 6 => 1,
1464 - },
1465 - "operations": Array [
1466 - Array [
1467 - 1,
1468 - 1,
1469 - 8,
1470 - 5,
1471 - 67,
1472 - 104,
1473 - 105,
1474 - 108,
1475 - 100,
1476 - 1,
1477 - 50,
1478 - 1,
1479 - 12,
1480 - 5,
1481 - 2,
1482 - 2,
1483 - 1,
1484 - 2,
1485 - 4,
1486 - 12,
1487 - 2000,
1488 - 4,
1489 - 2,
1490 - 14000,
1491 - 3,
1492 - 2,
1493 - 4,
1494 - 4,
1495 - 5,
1496 - 12,
1497 - 6,
1498 - 4,
1499 - 1,
1500 - 14000,
1501 - ],
1502 - Array [
1503 - 1,
1504 - 1,
1505 - 0,
1506 - 2,
1507 - 2,
1508 - 12,
1509 - 5,
1510 - 4,
1511 - 2,
1512 - 11000,
1513 - 3,
1514 - 2,
1515 - 2,
1516 - 4,
1517 - 6,
1518 - 4,
1519 - 1,
1520 - 11000,
1521 - ],
1522 - Array [
1523 - 1,
1524 - 1,
1525 - 0,
1526 - 2,
1527 - 1,
1528 - 4,
1529 - ],
1530 - ],
1531 - "rootID": 1,
1532 - "snapshots": Map {
1533 - 1 => Object {
1534 - "children": Array [
1535 - 2,
1536 - ],
1537 - "displayName": null,
1538 - "hocDisplayNames": null,
1539 - "id": 1,
1540 - "key": null,
1541 - "type": 11,
1542 - },
1543 - 2 => Object {
1544 - "children": Array [
1545 - 4,
1546 - 5,
1547 - 6,
1548 - ],
1549 - "displayName": "Parent",
1550 - "hocDisplayNames": null,
1551 - "id": 2,
1552 - "key": null,
1553 - "type": 5,
1554 - },
1555 - 4 => Object {
1556 - "children": Array [],
1557 - "displayName": "Child",
1558 - "hocDisplayNames": null,
1559 - "id": 4,
1560 - "key": "0",
1561 - "type": 5,
1562 - },
1563 - 5 => Object {
1564 - "children": Array [],
1565 - "displayName": "Child",
1566 - "hocDisplayNames": null,
1567 - "id": 5,
1568 - "key": "1",
1569 - "type": 5,
1570 - },
1571 - 6 => Object {
1572 - "children": Array [],
1573 - "displayName": "Child",
1574 - "hocDisplayNames": Array [
1575 - "Memo",
1576 - ],
1577 - "id": 6,
1578 - "key": null,
1579 - "type": 8,
1580 - },
1581 - },
1582 -}
1583 -`;
1584 -
1585 -exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): Data for root Parent 2`] = `
1586 -Object {
1587 - "commitData": Array [
1986 + "timelineData": Array [
1987 Object {
1589 - "changeDescriptions": Map {
1590 - 14 => Object {
1591 - "context": null,
1592 - "didHooksChange": false,
1593 - "isFirstMount": true,
1594 - "props": null,
1595 - "state": null,
1988 + "batchUIDToMeasuresKeyValueArray": Array [
1989 + Array [
1990 + 1,
1991 + Array [
1992 + Object {
1993 + "batchUID": 1,
1994 + "depth": 0,
1995 + "duration": 11,
1996 + "lanes": "0b0000000000000000000000000000001",
1997 + "timestamp": 10,
1998 + "type": "render-idle",
1999 + },
2000 + Object {
2001 + "batchUID": 1,
2002 + "depth": 0,
2003 + "duration": 11,
2004 + "lanes": "0b0000000000000000000000000000001",
2005 + "timestamp": 10,
2006 + "type": "render",
2007 + },
2008 + Object {
2009 + "batchUID": 1,
2010 + "depth": 0,
2011 + "duration": 0,
2012 + "lanes": "0b0000000000000000000000000000001",
2013 + "timestamp": 21,
2014 + "type": "commit",
2015 + },
2016 + Object {
2017 + "batchUID": 1,
2018 + "depth": 1,
2019 + "duration": 0,
2020 + "lanes": "0b0000000000000000000000000000001",
2021 + "timestamp": 21,
2022 + "type": "layout-effects",
2023 + },
2024 + ],
2025 + ],
2026 + Array [
2027 + 2,
2028 + Array [
2029 + Object {
2030 + "batchUID": 2,
2031 + "depth": 0,
2032 + "duration": 11,
2033 + "lanes": "0b0000000000000000000000000000001",
2034 + "timestamp": 21,
2035 + "type": "render-idle",
2036 + },
2037 + Object {
2038 + "batchUID": 2,
2039 + "depth": 0,
2040 + "duration": 11,
2041 + "lanes": "0b0000000000000000000000000000001",
2042 + "timestamp": 21,
2043 + "type": "render",
2044 + },
2045 + Object {
2046 + "batchUID": 2,
2047 + "depth": 0,
2048 + "duration": 0,
2049 + "lanes": "0b0000000000000000000000000000001",
2050 + "timestamp": 32,
2051 + "type": "commit",
2052 + },
2053 + Object {
2054 + "batchUID": 2,
2055 + "depth": 1,
2056 + "duration": 0,
2057 + "lanes": "0b0000000000000000000000000000001",
2058 + "timestamp": 32,
2059 + "type": "layout-effects",
2060 + },
2061 + ],
2062 + ],
2063 + Array [
2064 + 3,
2065 + Array [
2066 + Object {
2067 + "batchUID": 3,
2068 + "depth": 0,
2069 + "duration": 13,
2070 + "lanes": "0b0000000000000000000000000000001",
2071 + "timestamp": 32,
2072 + "type": "render-idle",
2073 + },
2074 + Object {
2075 + "batchUID": 3,
2076 + "depth": 0,
2077 + "duration": 13,
2078 + "lanes": "0b0000000000000000000000000000001",
2079 + "timestamp": 32,
2080 + "type": "render",
2081 + },
2082 + Object {
2083 + "batchUID": 3,
2084 + "depth": 0,
2085 + "duration": 0,
2086 + "lanes": "0b0000000000000000000000000000001",
2087 + "timestamp": 45,
2088 + "type": "commit",
2089 + },
2090 + Object {
2091 + "batchUID": 3,
2092 + "depth": 1,
2093 + "duration": 0,
2094 + "lanes": "0b0000000000000000000000000000001",
2095 + "timestamp": 45,
2096 + "type": "layout-effects",
2097 + },
2098 + ],
2099 + ],
2100 + ],
2101 + "componentMeasures": Array [
2102 + Object {
2103 + "componentName": "Parent",
2104 + "duration": 10,
2105 + "timestamp": 10,
2106 + "type": "render",
2107 + "warning": null,
2108 },
1597 - 16 => Object {
1598 - "context": null,
1599 - "didHooksChange": false,
1600 - "isFirstMount": true,
1601 - "props": null,
1602 - "state": null,
2109 + Object {
2110 + "componentName": "Child",
2111 + "duration": 0,
2112 + "timestamp": 20,
2113 + "type": "render",
2114 + "warning": null,
2115 },
1604 - 17 => Object {
1605 - "context": null,
1606 - "didHooksChange": false,
1607 - "isFirstMount": true,
1608 - "props": null,
1609 - "state": null,
2116 + Object {
2117 + "componentName": "Child",
2118 + "duration": 1,
2119 + "timestamp": 20,
2120 + "type": "render",
2121 + "warning": null,
2122 },
1611 - },
1612 - "duration": 11,
1613 - "effectDuration": null,
1614 - "fiberActualDurations": Map {
1615 - 13 => 11,
1616 - 14 => 11,
1617 - 16 => 0,
1618 - 17 => 1,
1619 - },
1620 - "fiberSelfDurations": Map {
1621 - 13 => 0,
1622 - 14 => 10,
1623 - 16 => 0,
1624 - 17 => 1,
1625 - },
1626 - "passiveEffectDuration": null,
1627 - "priorityLevel": "Immediate",
1628 - "timestamp": 24,
1629 - "updaters": Array [
2123 Object {
1631 - "displayName": "render()",
1632 - "hocDisplayNames": null,
1633 - "id": 13,
1634 - "key": null,
1635 - "type": 11,
2124 + "componentName": "Parent",
2125 + "duration": 10,
2126 + "timestamp": 21,
2127 + "type": "render",
2128 + "warning": null,
2129 + },
2130 + Object {
2131 + "componentName": "Child",
2132 + "duration": 0,
2133 + "timestamp": 31,
2134 + "type": "render",
2135 + "warning": null,
2136 + },
2137 + Object {
2138 + "componentName": "Child",
2139 + "duration": 1,
2140 + "timestamp": 31,
2141 + "type": "render",
2142 + "warning": null,
2143 + },
2144 + Object {
2145 + "componentName": "Parent",
2146 + "duration": 10,
2147 + "timestamp": 32,
2148 + "type": "render",
2149 + "warning": null,
2150 + },
2151 + Object {
2152 + "componentName": "Child",
2153 + "duration": 0,
2154 + "timestamp": 42,
2155 + "type": "render",
2156 + "warning": null,
2157 + },
2158 + Object {
2159 + "componentName": "Child",
2160 + "duration": 1,
2161 + "timestamp": 42,
2162 + "type": "render",
2163 + "warning": null,
2164 + },
2165 + Object {
2166 + "componentName": "Child",
2167 + "duration": 2,
2168 + "timestamp": 43,
2169 + "type": "render",
2170 + "warning": null,
2171 },
2172 ],
1638 - },
1639 - ],
1640 - "displayName": "Parent",
1641 - "initialTreeBaseDurations": Map {},
1642 - "operations": Array [
1643 - Array [
1644 - 1,
1645 - 13,
1646 - 15,
1647 - 6,
1648 - 80,
1649 - 97,
1650 - 114,
1651 - 101,
1652 - 110,
1653 - 116,
1654 - 5,
1655 - 67,
1656 - 104,
1657 - 105,
1658 - 108,
1659 - 100,
1660 - 1,
1661 - 48,
1662 - 1,
1663 - 13,
1664 - 11,
1665 - 0,
1666 - 1,
1667 - 1,
1668 - 1,
1669 - 4,
1670 - 13,
1671 - 11000,
1672 - 1,
1673 - 14,
1674 - 5,
1675 - 13,
1676 - 0,
1677 - 1,
1678 - 0,
1679 - 4,
1680 - 14,
1681 - 11000,
1682 - 1,
1683 - 16,
1684 - 5,
1685 - 14,
1686 - 14,
1687 - 2,
1688 - 3,
1689 - 4,
1690 - 16,
1691 - 0,
1692 - 1,
1693 - 17,
1694 - 8,
1695 - 14,
1696 - 14,
1697 - 2,
1698 - 0,
1699 - 4,
1700 - 17,
1701 - 1000,
1702 - ],
2173 + "duration": 55,
2174 + "flamechart": Array [],
2175 + "internalModuleSourceToRanges": Array [],
2176 + "laneToLabelKeyValueArray": Array [
2177 + Array [
2178 + 1,
2179 + "Sync",
2180 + ],
2181 + Array [
2182 + 2,
2183 + "InputContinuousHydration",
2184 + ],
2185 + Array [
2186 + 4,
2187 + "InputContinuous",
2188 + ],
2189 + Array [
2190 + 8,
2191 + "DefaultHydration",
2192 + ],
2193 + Array [
2194 + 16,
2195 + "Default",
2196 + ],
2197 + Array [
2198 + 32,
2199 + "TransitionHydration",
2200 + ],
2201 + Array [
2202 + 64,
2203 + "Transition",
2204 + ],
2205 + Array [
2206 + 128,
2207 + "Transition",
2208 + ],
2209 + Array [
2210 + 256,
2211 + "Transition",
2212 + ],
2213 + Array [
2214 + 512,
2215 + "Transition",
2216 + ],
2217 + Array [
2218 + 1024,
2219 + "Transition",
2220 + ],
2221 + Array [
2222 + 2048,
2223 + "Transition",
2224 + ],
2225 + Array [
2226 + 4096,
2227 + "Transition",
2228 + ],
2229 + Array [
2230 + 8192,
2231 + "Transition",
2232 + ],
2233 + Array [
2234 + 16384,
2235 + "Transition",
2236 + ],
2237 + Array [
2238 + 32768,
2239 + "Transition",
2240 + ],
2241 + Array [
2242 + 65536,
2243 + "Transition",
2244 + ],
2245 + Array [
2246 + 131072,
2247 + "Transition",
2248 + ],
2249 + Array [
2250 + 262144,
2251 + "Transition",
2252 + ],
2253 + Array [
2254 + 524288,
2255 + "Transition",
2256 + ],
2257 + Array [
2258 + 1048576,
2259 + "Transition",
2260 + ],
2261 + Array [
2262 + 2097152,
2263 + "Transition",
2264 + ],
2265 + Array [
2266 + 4194304,
2267 + "Retry",
2268 + ],
2269 + Array [
2270 + 8388608,
2271 + "Retry",
2272 + ],
2273 + Array [
2274 + 16777216,
2275 + "Retry",
2276 + ],
2277 + Array [
2278 + 33554432,
2279 + "Retry",
2280 + ],
2281 + Array [
2282 + 67108864,
2283 + "Retry",
2284 + ],
2285 + Array [
2286 + 134217728,
2287 + "SelectiveHydration",
2288 + ],
2289 + Array [
2290 + 268435456,
2291 + "IdleHydration",
2292 + ],
2293 + Array [
2294 + 536870912,
2295 + "Idle",
2296 + ],
2297 + Array [
2298 + 1073741824,
2299 + "Offscreen",
2300 + ],
2301 + ],
2302 + "laneToReactMeasureKeyValueArray": Array [
2303 + Array [
2304 + 1,
2305 + Array [
2306 + Object {
2307 + "batchUID": 1,
2308 + "depth": 0,
2309 + "duration": 11,
2310 + "lanes": "0b0000000000000000000000000000001",
2311 + "timestamp": 10,
2312 + "type": "render-idle",
2313 + },
2314 + Object {
2315 + "batchUID": 1,
2316 + "depth": 0,
2317 + "duration": 11,
2318 + "lanes": "0b0000000000000000000000000000001",
2319 + "timestamp": 10,
2320 + "type": "render",
2321 + },
2322 + Object {
2323 + "batchUID": 1,
2324 + "depth": 0,
2325 + "duration": 0,
2326 + "lanes": "0b0000000000000000000000000000001",
2327 + "timestamp": 21,
2328 + "type": "commit",
2329 + },
2330 + Object {
2331 + "batchUID": 1,
2332 + "depth": 1,
2333 + "duration": 0,
2334 + "lanes": "0b0000000000000000000000000000001",
2335 + "timestamp": 21,
2336 + "type": "layout-effects",
2337 + },
2338 + Object {
2339 + "batchUID": 2,
2340 + "depth": 0,
2341 + "duration": 11,
2342 + "lanes": "0b0000000000000000000000000000001",
2343 + "timestamp": 21,
2344 + "type": "render-idle",
2345 + },
2346 + Object {
2347 + "batchUID": 2,
2348 + "depth": 0,
2349 + "duration": 11,
2350 + "lanes": "0b0000000000000000000000000000001",
2351 + "timestamp": 21,
2352 + "type": "render",
2353 + },
2354 + Object {
2355 + "batchUID": 2,
2356 + "depth": 0,
2357 + "duration": 0,
2358 + "lanes": "0b0000000000000000000000000000001",
2359 + "timestamp": 32,
2360 + "type": "commit",
2361 + },
2362 + Object {
2363 + "batchUID": 2,
2364 + "depth": 1,
2365 + "duration": 0,
2366 + "lanes": "0b0000000000000000000000000000001",
2367 + "timestamp": 32,
2368 + "type": "layout-effects",
2369 + },
2370 + Object {
2371 + "batchUID": 3,
2372 + "depth": 0,
2373 + "duration": 13,
2374 + "lanes": "0b0000000000000000000000000000001",
2375 + "timestamp": 32,
2376 + "type": "render-idle",
2377 + },
2378 + Object {
2379 + "batchUID": 3,
2380 + "depth": 0,
2381 + "duration": 13,
2382 + "lanes": "0b0000000000000000000000000000001",
2383 + "timestamp": 32,
2384 + "type": "render",
2385 + },
2386 + Object {
2387 + "batchUID": 3,
2388 + "depth": 0,
2389 + "duration": 0,
2390 + "lanes": "0b0000000000000000000000000000001",
2391 + "timestamp": 45,
2392 + "type": "commit",
2393 + },
2394 + Object {
2395 + "batchUID": 3,
2396 + "depth": 1,
2397 + "duration": 0,
2398 + "lanes": "0b0000000000000000000000000000001",
2399 + "timestamp": 45,
2400 + "type": "layout-effects",
2401 + },
2402 + ],
2403 + ],
2404 + Array [
2405 + 2,
2406 + Array [],
2407 + ],
2408 + Array [
2409 + 4,
2410 + Array [],
2411 + ],
2412 + Array [
2413 + 8,
2414 + Array [],
2415 + ],
2416 + Array [
2417 + 16,
2418 + Array [],
2419 + ],
2420 + Array [
2421 + 32,
2422 + Array [],
2423 + ],
2424 + Array [
2425 + 64,
2426 + Array [],
2427 + ],
2428 + Array [
2429 + 128,
2430 + Array [],
2431 + ],
2432 + Array [
2433 + 256,
2434 + Array [],
2435 + ],
2436 + Array [
2437 + 512,
2438 + Array [],
2439 + ],
2440 + Array [
2441 + 1024,
2442 + Array [],
2443 + ],
2444 + Array [
2445 + 2048,
2446 + Array [],
2447 + ],
2448 + Array [
2449 + 4096,
2450 + Array [],
2451 + ],
2452 + Array [
2453 + 8192,
2454 + Array [],
2455 + ],
2456 + Array [
2457 + 16384,
2458 + Array [],
2459 + ],
2460 + Array [
2461 + 32768,
2462 + Array [],
2463 + ],
2464 + Array [
2465 + 65536,
2466 + Array [],
2467 + ],
2468 + Array [
2469 + 131072,
2470 + Array [],
2471 + ],
2472 + Array [
2473 + 262144,
2474 + Array [],
2475 + ],
2476 + Array [
2477 + 524288,
2478 + Array [],
2479 + ],
2480 + Array [
2481 + 1048576,
2482 + Array [],
2483 + ],
2484 + Array [
2485 + 2097152,
2486 + Array [],
2487 + ],
2488 + Array [
2489 + 4194304,
2490 + Array [],
2491 + ],
2492 + Array [
2493 + 8388608,
2494 + Array [],
2495 + ],
2496 + Array [
2497 + 16777216,
2498 + Array [],
2499 + ],
2500 + Array [
2501 + 33554432,
2502 + Array [],
2503 + ],
2504 + Array [
2505 + 67108864,
2506 + Array [],
2507 + ],
2508 + Array [
2509 + 134217728,
2510 + Array [],
2511 + ],
2512 + Array [
2513 + 268435456,
2514 + Array [],
2515 + ],
2516 + Array [
2517 + 536870912,
2518 + Array [],
2519 + ],
2520 + Array [
2521 + 1073741824,
2522 + Array [],
2523 + ],
2524 + ],
2525 + "nativeEvents": Array [],
2526 + "networkMeasures": Array [],
2527 + "otherUserTimingMarks": Array [],
2528 + "reactVersion": "<filtered-version>",
2529 + "schedulingEvents": Array [
2530 + Object {
2531 + "lanes": "0b0000000000000000000000000000001",
2532 + "timestamp": 10,
2533 + "type": "schedule-render",
2534 + "warning": null,
2535 + },
2536 + Object {
2537 + "lanes": "0b0000000000000000000000000000001",
2538 + "timestamp": 21,
2539 + "type": "schedule-render",
2540 + "warning": null,
2541 + },
2542 + Object {
2543 + "lanes": "0b0000000000000000000000000000001",
2544 + "timestamp": 32,
2545 + "type": "schedule-render",
2546 + "warning": null,
2547 + },
2548 + ],
2549 + "snapshotHeight": 0,
2550 + "snapshots": Array [],
2551 + "startTime": -10,
2552 + "suspenseEvents": Array [],
2553 + "thrownErrors": Array [],
2554 + },
2555 ],
1704 - "rootID": 13,
1705 - "snapshots": Map {},
2556 + "version": 5,
2557 }
2558 `;
2559
1709 -exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): imported data 1`] = `
2560 +exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): Data for root Parent 1`] = `
2561 Object {
1711 - "dataForRoots": Array [
2562 + "commitData": Array [
2563 + Object {
2564 + "changeDescriptions": Map {
2565 + 4 => Object {
2566 + "context": null,
2567 + "didHooksChange": false,
2568 + "hooks": null,
2569 + "isFirstMount": false,
2570 + "props": Array [],
2571 + "state": null,
2572 + },
2573 + 5 => Object {
2574 + "context": null,
2575 + "didHooksChange": false,
2576 + "hooks": null,
2577 + "isFirstMount": false,
2578 + "props": Array [],
2579 + "state": null,
2580 + },
2581 + 12 => Object {
2582 + "context": null,
2583 + "didHooksChange": false,
2584 + "isFirstMount": true,
2585 + "props": null,
2586 + "state": null,
2587 + },
2588 + 2 => Object {
2589 + "context": null,
2590 + "didHooksChange": false,
2591 + "hooks": null,
2592 + "isFirstMount": false,
2593 + "props": Array [
2594 + "count",
2595 + ],
2596 + "state": null,
2597 + },
2598 + },
2599 + "duration": 13,
2600 + "effectDuration": null,
2601 + "fiberActualDurations": Map {
2602 + 4 => 0,
2603 + 5 => 1,
2604 + 12 => 2,
2605 + 2 => 13,
2606 + 1 => 13,
2607 + },
2608 + "fiberSelfDurations": Map {
2609 + 4 => 0,
2610 + 5 => 1,
2611 + 12 => 2,
2612 + 2 => 10,
2613 + 1 => 0,
2614 + },
2615 + "passiveEffectDuration": null,
2616 + "priorityLevel": "Immediate",
2617 + "timestamp": 13,
2618 + "updaters": Array [
2619 + Object {
2620 + "displayName": "render()",
2621 + "hocDisplayNames": null,
2622 + "id": 1,
2623 + "key": null,
2624 + "type": 11,
2625 + },
2626 + ],
2627 + },
2628 + Object {
2629 + "changeDescriptions": Map {
2630 + 4 => Object {
2631 + "context": null,
2632 + "didHooksChange": false,
2633 + "hooks": null,
2634 + "isFirstMount": false,
2635 + "props": Array [],
2636 + "state": null,
2637 + },
2638 + 2 => Object {
2639 + "context": null,
2640 + "didHooksChange": false,
2641 + "hooks": null,
2642 + "isFirstMount": false,
2643 + "props": Array [
2644 + "count",
2645 + ],
2646 + "state": null,
2647 + },
2648 + },
2649 + "duration": 10,
2650 + "effectDuration": 0,
2651 + "fiberActualDurations": Map {
2652 + 4 => 0,
2653 + 2 => 10,
2654 + 1 => 10,
2655 + },
2656 + "fiberSelfDurations": Map {
2657 + 4 => 0,
2658 + 2 => 10,
2659 + 1 => 0,
2660 + },
2661 + "passiveEffectDuration": 0,
2662 + "priorityLevel": "Immediate",
2663 + "timestamp": 34,
2664 + "updaters": Array [
2665 + Object {
2666 + "displayName": "render()",
2667 + "hocDisplayNames": null,
2668 + "id": 1,
2669 + "key": null,
2670 + "type": 11,
2671 + },
2672 + ],
2673 + },
2674 + Object {
2675 + "changeDescriptions": Map {
2676 + 2 => Object {
2677 + "context": null,
2678 + "didHooksChange": false,
2679 + "hooks": null,
2680 + "isFirstMount": false,
2681 + "props": Array [
2682 + "count",
2683 + ],
2684 + "state": null,
2685 + },
2686 + },
2687 + "duration": 10,
2688 + "effectDuration": 0,
2689 + "fiberActualDurations": Map {
2690 + 2 => 10,
2691 + 1 => 10,
2692 + },
2693 + "fiberSelfDurations": Map {
2694 + 2 => 10,
2695 + 1 => 0,
2696 + },
2697 + "passiveEffectDuration": 0,
2698 + "priorityLevel": "Immediate",
2699 + "timestamp": 44,
2700 + "updaters": Array [
2701 + Object {
2702 + "displayName": "render()",
2703 + "hocDisplayNames": null,
2704 + "id": 1,
2705 + "key": null,
2706 + "type": 11,
2707 + },
2708 + ],
2709 + },
2710 + ],
2711 + "displayName": "Parent",
2712 + "initialTreeBaseDurations": Map {
2713 + 1 => 12,
2714 + 2 => 12,
2715 + 4 => 0,
2716 + 5 => 1,
2717 + 6 => 1,
2718 + },
2719 + "operations": Array [
2720 + Array [
2721 + 1,
2722 + 1,
2723 + 8,
2724 + 5,
2725 + 67,
2726 + 104,
2727 + 105,
2728 + 108,
2729 + 100,
2730 + 1,
2731 + 50,
2732 + 1,
2733 + 12,
2734 + 5,
2735 + 2,
2736 + 2,
2737 + 1,
2738 + 2,
2739 + 4,
2740 + 12,
2741 + 2000,
2742 + 4,
2743 + 2,
2744 + 14000,
2745 + 3,
2746 + 2,
2747 + 4,
2748 + 4,
2749 + 5,
2750 + 12,
2751 + 6,
2752 + 4,
2753 + 1,
2754 + 14000,
2755 + ],
2756 + Array [
2757 + 1,
2758 + 1,
2759 + 0,
2760 + 2,
2761 + 2,
2762 + 12,
2763 + 5,
2764 + 4,
2765 + 2,
2766 + 11000,
2767 + 3,
2768 + 2,
2769 + 2,
2770 + 4,
2771 + 6,
2772 + 4,
2773 + 1,
2774 + 11000,
2775 + ],
2776 + Array [
2777 + 1,
2778 + 1,
2779 + 0,
2780 + 2,
2781 + 1,
2782 + 4,
2783 + ],
2784 + ],
2785 + "rootID": 1,
2786 + "snapshots": Map {
2787 + 1 => Object {
2788 + "children": Array [
2789 + 2,
2790 + ],
2791 + "displayName": null,
2792 + "hocDisplayNames": null,
2793 + "id": 1,
2794 + "key": null,
2795 + "type": 11,
2796 + },
2797 + 2 => Object {
2798 + "children": Array [
2799 + 4,
2800 + 5,
2801 + 6,
2802 + ],
2803 + "displayName": "Parent",
2804 + "hocDisplayNames": null,
2805 + "id": 2,
2806 + "key": null,
2807 + "type": 5,
2808 + },
2809 + 4 => Object {
2810 + "children": Array [],
2811 + "displayName": "Child",
2812 + "hocDisplayNames": null,
2813 + "id": 4,
2814 + "key": "0",
2815 + "type": 5,
2816 + },
2817 + 5 => Object {
2818 + "children": Array [],
2819 + "displayName": "Child",
2820 + "hocDisplayNames": null,
2821 + "id": 5,
2822 + "key": "1",
2823 + "type": 5,
2824 + },
2825 + 6 => Object {
2826 + "children": Array [],
2827 + "displayName": "Child",
2828 + "hocDisplayNames": Array [
2829 + "Memo",
2830 + ],
2831 + "id": 6,
2832 + "key": null,
2833 + "type": 8,
2834 + },
2835 + },
2836 +}
2837 +`;
2838 +
2839 +exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): Data for root Parent 2`] = `
2840 +Object {
2841 + "commitData": Array [
2842 + Object {
2843 + "changeDescriptions": Map {
2844 + 14 => Object {
2845 + "context": null,
2846 + "didHooksChange": false,
2847 + "isFirstMount": true,
2848 + "props": null,
2849 + "state": null,
2850 + },
2851 + 16 => Object {
2852 + "context": null,
2853 + "didHooksChange": false,
2854 + "isFirstMount": true,
2855 + "props": null,
2856 + "state": null,
2857 + },
2858 + 17 => Object {
2859 + "context": null,
2860 + "didHooksChange": false,
2861 + "isFirstMount": true,
2862 + "props": null,
2863 + "state": null,
2864 + },
2865 + },
2866 + "duration": 11,
2867 + "effectDuration": null,
2868 + "fiberActualDurations": Map {
2869 + 13 => 11,
2870 + 14 => 11,
2871 + 16 => 0,
2872 + 17 => 1,
2873 + },
2874 + "fiberSelfDurations": Map {
2875 + 13 => 0,
2876 + 14 => 10,
2877 + 16 => 0,
2878 + 17 => 1,
2879 + },
2880 + "passiveEffectDuration": null,
2881 + "priorityLevel": "Immediate",
2882 + "timestamp": 24,
2883 + "updaters": Array [
2884 + Object {
2885 + "displayName": "render()",
2886 + "hocDisplayNames": null,
2887 + "id": 13,
2888 + "key": null,
2889 + "type": 11,
2890 + },
2891 + ],
2892 + },
2893 + ],
2894 + "displayName": "Parent",
2895 + "initialTreeBaseDurations": Map {},
2896 + "operations": Array [
2897 + Array [
2898 + 1,
2899 + 13,
2900 + 15,
2901 + 6,
2902 + 80,
2903 + 97,
2904 + 114,
2905 + 101,
2906 + 110,
2907 + 116,
2908 + 5,
2909 + 67,
2910 + 104,
2911 + 105,
2912 + 108,
2913 + 100,
2914 + 1,
2915 + 48,
2916 + 1,
2917 + 13,
2918 + 11,
2919 + 0,
2920 + 3,
2921 + 1,
2922 + 1,
2923 + 4,
2924 + 13,
2925 + 11000,
2926 + 1,
2927 + 14,
2928 + 5,
2929 + 13,
2930 + 0,
2931 + 1,
2932 + 0,
2933 + 4,
2934 + 14,
2935 + 11000,
2936 + 1,
2937 + 16,
2938 + 5,
2939 + 14,
2940 + 14,
2941 + 2,
2942 + 3,
2943 + 4,
2944 + 16,
2945 + 0,
2946 + 1,
2947 + 17,
2948 + 8,
2949 + 14,
2950 + 14,
2951 + 2,
2952 + 0,
2953 + 4,
2954 + 17,
2955 + 1000,
2956 + ],
2957 + ],
2958 + "rootID": 13,
2959 + "snapshots": Map {},
2960 +}
2961 +`;
2962 +
2963 +exports[`ProfilingCache should collect data for each root (including ones added or mounted after profiling started): imported data 1`] = `
2964 +Object {
2965 + "dataForRoots": Array [
2966 + Object {
2967 + "commitData": Array [
2968 + Object {
2969 + "changeDescriptions": Array [
2970 + Array [
2971 + 4,
2972 + Object {
2973 + "context": null,
2974 + "didHooksChange": false,
2975 + "hooks": null,
2976 + "isFirstMount": false,
2977 + "props": Array [],
2978 + "state": null,
2979 + },
2980 + ],
2981 + Array [
2982 + 5,
2983 + Object {
2984 + "context": null,
2985 + "didHooksChange": false,
2986 + "hooks": null,
2987 + "isFirstMount": false,
2988 + "props": Array [],
2989 + "state": null,
2990 + },
2991 + ],
2992 + Array [
2993 + 12,
2994 + Object {
2995 + "context": null,
2996 + "didHooksChange": false,
2997 + "isFirstMount": true,
2998 + "props": null,
2999 + "state": null,
3000 + },
3001 + ],
3002 + Array [
3003 + 2,
3004 + Object {
3005 + "context": null,
3006 + "didHooksChange": false,
3007 + "hooks": null,
3008 + "isFirstMount": false,
3009 + "props": Array [
3010 + "count",
3011 + ],
3012 + "state": null,
3013 + },
3014 + ],
3015 + ],
3016 + "duration": 13,
3017 + "effectDuration": null,
3018 + "fiberActualDurations": Array [
3019 + Array [
3020 + 4,
3021 + 0,
3022 + ],
3023 + Array [
3024 + 5,
3025 + 1,
3026 + ],
3027 + Array [
3028 + 12,
3029 + 2,
3030 + ],
3031 + Array [
3032 + 2,
3033 + 13,
3034 + ],
3035 + Array [
3036 + 1,
3037 + 13,
3038 + ],
3039 + ],
3040 + "fiberSelfDurations": Array [
3041 + Array [
3042 + 4,
3043 + 0,
3044 + ],
3045 + Array [
3046 + 5,
3047 + 1,
3048 + ],
3049 + Array [
3050 + 12,
3051 + 2,
3052 + ],
3053 + Array [
3054 + 2,
3055 + 10,
3056 + ],
3057 + Array [
3058 + 1,
3059 + 0,
3060 + ],
3061 + ],
3062 + "passiveEffectDuration": null,
3063 + "priorityLevel": "Immediate",
3064 + "timestamp": 13,
3065 + "updaters": Array [
3066 + Object {
3067 + "displayName": "render()",
3068 + "hocDisplayNames": null,
3069 + "id": 1,
3070 + "key": null,
3071 + "type": 11,
3072 + },
3073 + ],
3074 + },
3075 + Object {
3076 + "changeDescriptions": Array [
3077 + Array [
3078 + 4,
3079 + Object {
3080 + "context": null,
3081 + "didHooksChange": false,
3082 + "hooks": null,
3083 + "isFirstMount": false,
3084 + "props": Array [],
3085 + "state": null,
3086 + },
3087 + ],
3088 + Array [
3089 + 2,
3090 + Object {
3091 + "context": null,
3092 + "didHooksChange": false,
3093 + "hooks": null,
3094 + "isFirstMount": false,
3095 + "props": Array [
3096 + "count",
3097 + ],
3098 + "state": null,
3099 + },
3100 + ],
3101 + ],
3102 + "duration": 10,
3103 + "effectDuration": 0,
3104 + "fiberActualDurations": Array [
3105 + Array [
3106 + 4,
3107 + 0,
3108 + ],
3109 + Array [
3110 + 2,
3111 + 10,
3112 + ],
3113 + Array [
3114 + 1,
3115 + 10,
3116 + ],
3117 + ],
3118 + "fiberSelfDurations": Array [
3119 + Array [
3120 + 4,
3121 + 0,
3122 + ],
3123 + Array [
3124 + 2,
3125 + 10,
3126 + ],
3127 + Array [
3128 + 1,
3129 + 0,
3130 + ],
3131 + ],
3132 + "passiveEffectDuration": 0,
3133 + "priorityLevel": "Immediate",
3134 + "timestamp": 34,
3135 + "updaters": Array [
3136 + Object {
3137 + "displayName": "render()",
3138 + "hocDisplayNames": null,
3139 + "id": 1,
3140 + "key": null,
3141 + "type": 11,
3142 + },
3143 + ],
3144 + },
3145 + Object {
3146 + "changeDescriptions": Array [
3147 + Array [
3148 + 2,
3149 + Object {
3150 + "context": null,
3151 + "didHooksChange": false,
3152 + "hooks": null,
3153 + "isFirstMount": false,
3154 + "props": Array [
3155 + "count",
3156 + ],
3157 + "state": null,
3158 + },
3159 + ],
3160 + ],
3161 + "duration": 10,
3162 + "effectDuration": 0,
3163 + "fiberActualDurations": Array [
3164 + Array [
3165 + 2,
3166 + 10,
3167 + ],
3168 + Array [
3169 + 1,
3170 + 10,
3171 + ],
3172 + ],
3173 + "fiberSelfDurations": Array [
3174 + Array [
3175 + 2,
3176 + 10,
3177 + ],
3178 + Array [
3179 + 1,
3180 + 0,
3181 + ],
3182 + ],
3183 + "passiveEffectDuration": 0,
3184 + "priorityLevel": "Immediate",
3185 + "timestamp": 44,
3186 + "updaters": Array [
3187 + Object {
3188 + "displayName": "render()",
3189 + "hocDisplayNames": null,
3190 + "id": 1,
3191 + "key": null,
3192 + "type": 11,
3193 + },
3194 + ],
3195 + },
3196 + ],
3197 + "displayName": "Parent",
3198 + "initialTreeBaseDurations": Array [
3199 + Array [
3200 + 1,
3201 + 12,
3202 + ],
3203 + Array [
3204 + 2,
3205 + 12,
3206 + ],
3207 + Array [
3208 + 4,
3209 + 0,
3210 + ],
3211 + Array [
3212 + 5,
3213 + 1,
3214 + ],
3215 + Array [
3216 + 6,
3217 + 1,
3218 + ],
3219 + ],
3220 + "operations": Array [
3221 + Array [
3222 + 1,
3223 + 1,
3224 + 8,
3225 + 5,
3226 + 67,
3227 + 104,
3228 + 105,
3229 + 108,
3230 + 100,
3231 + 1,
3232 + 50,
3233 + 1,
3234 + 12,
3235 + 5,
3236 + 2,
3237 + 2,
3238 + 1,
3239 + 2,
3240 + 4,
3241 + 12,
3242 + 2000,
3243 + 4,
3244 + 2,
3245 + 14000,
3246 + 3,
3247 + 2,
3248 + 4,
3249 + 4,
3250 + 5,
3251 + 12,
3252 + 6,
3253 + 4,
3254 + 1,
3255 + 14000,
3256 + ],
3257 + Array [
3258 + 1,
3259 + 1,
3260 + 0,
3261 + 2,
3262 + 2,
3263 + 12,
3264 + 5,
3265 + 4,
3266 + 2,
3267 + 11000,
3268 + 3,
3269 + 2,
3270 + 2,
3271 + 4,
3272 + 6,
3273 + 4,
3274 + 1,
3275 + 11000,
3276 + ],
3277 + Array [
3278 + 1,
3279 + 1,
3280 + 0,
3281 + 2,
3282 + 1,
3283 + 4,
3284 + ],
3285 + ],
3286 + "rootID": 1,
3287 + "snapshots": Array [
3288 + Array [
3289 + 1,
3290 + Object {
3291 + "children": Array [
3292 + 2,
3293 + ],
3294 + "displayName": null,
3295 + "hocDisplayNames": null,
3296 + "id": 1,
3297 + "key": null,
3298 + "type": 11,
3299 + },
3300 + ],
3301 + Array [
3302 + 2,
3303 + Object {
3304 + "children": Array [
3305 + 4,
3306 + 5,
3307 + 6,
3308 + ],
3309 + "displayName": "Parent",
3310 + "hocDisplayNames": null,
3311 + "id": 2,
3312 + "key": null,
3313 + "type": 5,
3314 + },
3315 + ],
3316 + Array [
3317 + 4,
3318 + Object {
3319 + "children": Array [],
3320 + "displayName": "Child",
3321 + "hocDisplayNames": null,
3322 + "id": 4,
3323 + "key": "0",
3324 + "type": 5,
3325 + },
3326 + ],
3327 + Array [
3328 + 5,
3329 + Object {
3330 + "children": Array [],
3331 + "displayName": "Child",
3332 + "hocDisplayNames": null,
3333 + "id": 5,
3334 + "key": "1",
3335 + "type": 5,
3336 + },
3337 + ],
3338 + Array [
3339 + 6,
3340 + Object {
3341 + "children": Array [],
3342 + "displayName": "Child",
3343 + "hocDisplayNames": Array [
3344 + "Memo",
3345 + ],
3346 + "id": 6,
3347 + "key": null,
3348 + "type": 8,
3349 + },
3350 + ],
3351 + ],
3352 + },
3353 Object {
3354 "commitData": Array [
3355 Object {
3356 "changeDescriptions": Array [
3357 Array [
1717 - 4,
1718 - Object {
1719 - "context": null,
1720 - "didHooksChange": false,
1721 - "hooks": null,
1722 - "isFirstMount": false,
1723 - "props": Array [],
1724 - "state": null,
1725 - },
1726 - ],
1727 - Array [
1728 - 5,
3358 + 14,
3359 Object {
3360 "context": null,
3361 "didHooksChange": false,
1732 - "hooks": null,
1733 - "isFirstMount": false,
1734 - "props": Array [],
3362 + "isFirstMount": true,
3363 + "props": null,
3364 "state": null,
3365 },
3366 ],
3367 Array [
1739 - 12,
3368 + 16,
3369 Object {
3370 "context": null,
3371 "didHooksChange": false,
@@ -1746,509 +3375,902 @@ Object {
3375 },
3376 ],
3377 Array [
1749 - 2,
3378 + 17,
3379 Object {
3380 "context": null,
3381 "didHooksChange": false,
1753 - "hooks": null,
1754 - "isFirstMount": false,
1755 - "props": Array [
1756 - "count",
1757 - ],
3382 + "isFirstMount": true,
3383 + "props": null,
3384 "state": null,
3385 },
3386 ],
3387 ],
1762 - "duration": 13,
3388 + "duration": 11,
3389 "effectDuration": null,
3390 "fiberActualDurations": Array [
3391 Array [
1766 - 4,
1767 - 0,
1768 - ],
1769 - Array [
1770 - 5,
1771 - 1,
3392 + 13,
3393 + 11,
3394 ],
3395 Array [
1774 - 12,
1775 - 2,
3396 + 14,
3397 + 11,
3398 ],
3399 Array [
1778 - 2,
1779 - 13,
3400 + 16,
3401 + 0,
3402 ],
3403 Array [
3404 + 17,
3405 1,
1783 - 13,
3406 ],
3407 ],
3408 "fiberSelfDurations": Array [
3409 Array [
1788 - 4,
3410 + 13,
3411 0,
3412 ],
3413 Array [
1792 - 5,
1793 - 1,
1794 - ],
1795 - Array [
1796 - 12,
1797 - 2,
3414 + 14,
3415 + 10,
3416 ],
3417 Array [
1800 - 2,
1801 - 10,
3418 + 16,
3419 + 0,
3420 ],
3421 Array [
3422 + 17,
3423 1,
1805 - 0,
3424 ],
3425 ],
3426 "passiveEffectDuration": null,
3427 "priorityLevel": "Immediate",
1810 - "timestamp": 13,
3428 + "timestamp": 24,
3429 "updaters": Array [
3430 Object {
3431 "displayName": "render()",
3432 "hocDisplayNames": null,
1815 - "id": 1,
3433 + "id": 13,
3434 "key": null,
3435 "type": 11,
3436 },
3437 ],
3438 },
1821 - Object {
1822 - "changeDescriptions": Array [
1823 - Array [
1824 - 4,
1825 - Object {
1826 - "context": null,
1827 - "didHooksChange": false,
1828 - "hooks": null,
1829 - "isFirstMount": false,
1830 - "props": Array [],
1831 - "state": null,
1832 - },
1833 - ],
1834 - Array [
1835 - 2,
1836 - Object {
1837 - "context": null,
1838 - "didHooksChange": false,
1839 - "hooks": null,
1840 - "isFirstMount": false,
1841 - "props": Array [
1842 - "count",
1843 - ],
1844 - "state": null,
1845 - },
1846 - ],
3439 + ],
3440 + "displayName": "Parent",
3441 + "initialTreeBaseDurations": Array [],
3442 + "operations": Array [
3443 + Array [
3444 + 1,
3445 + 13,
3446 + 15,
3447 + 6,
3448 + 80,
3449 + 97,
3450 + 114,
3451 + 101,
3452 + 110,
3453 + 116,
3454 + 5,
3455 + 67,
3456 + 104,
3457 + 105,
3458 + 108,
3459 + 100,
3460 + 1,
3461 + 48,
3462 + 1,
3463 + 13,
3464 + 11,
3465 + 0,
3466 + 3,
3467 + 1,
3468 + 1,
3469 + 4,
3470 + 13,
3471 + 11000,
3472 + 1,
3473 + 14,
3474 + 5,
3475 + 13,
3476 + 0,
3477 + 1,
3478 + 0,
3479 + 4,
3480 + 14,
3481 + 11000,
3482 + 1,
3483 + 16,
3484 + 5,
3485 + 14,
3486 + 14,
3487 + 2,
3488 + 3,
3489 + 4,
3490 + 16,
3491 + 0,
3492 + 1,
3493 + 17,
3494 + 8,
3495 + 14,
3496 + 14,
3497 + 2,
3498 + 0,
3499 + 4,
3500 + 17,
3501 + 1000,
3502 + ],
3503 + ],
3504 + "rootID": 13,
3505 + "snapshots": Array [],
3506 + },
3507 + ],
3508 + "timelineData": Array [
3509 + Object {
3510 + "batchUIDToMeasuresKeyValueArray": Array [
3511 + Array [
3512 + 1,
3513 + Array [
3514 + Object {
3515 + "batchUID": 1,
3516 + "depth": 0,
3517 + "duration": 13,
3518 + "lanes": "0b0000000000000000000000000000001",
3519 + "timestamp": 10,
3520 + "type": "render-idle",
3521 + },
3522 + Object {
3523 + "batchUID": 1,
3524 + "depth": 0,
3525 + "duration": 13,
3526 + "lanes": "0b0000000000000000000000000000001",
3527 + "timestamp": 10,
3528 + "type": "render",
3529 + },
3530 + Object {
3531 + "batchUID": 1,
3532 + "depth": 0,
3533 + "duration": 0,
3534 + "lanes": "0b0000000000000000000000000000001",
3535 + "timestamp": 23,
3536 + "type": "commit",
3537 + },
3538 + Object {
3539 + "batchUID": 1,
3540 + "depth": 1,
3541 + "duration": 0,
3542 + "lanes": "0b0000000000000000000000000000001",
3543 + "timestamp": 23,
3544 + "type": "layout-effects",
3545 + },
3546 ],
1848 - "duration": 10,
1849 - "effectDuration": 0,
1850 - "fiberActualDurations": Array [
1851 - Array [
1852 - 4,
1853 - 0,
1854 - ],
1855 - Array [
1856 - 2,
1857 - 10,
1858 - ],
1859 - Array [
1860 - 1,
1861 - 10,
1862 - ],
3547 + ],
3548 + Array [
3549 + 2,
3550 + Array [
3551 + Object {
3552 + "batchUID": 2,
3553 + "depth": 0,
3554 + "duration": 11,
3555 + "lanes": "0b0000000000000000000000000000001",
3556 + "timestamp": 23,
3557 + "type": "render-idle",
3558 + },
3559 + Object {
3560 + "batchUID": 2,
3561 + "depth": 0,
3562 + "duration": 11,
3563 + "lanes": "0b0000000000000000000000000000001",
3564 + "timestamp": 23,
3565 + "type": "render",
3566 + },
3567 + Object {
3568 + "batchUID": 2,
3569 + "depth": 0,
3570 + "duration": 0,
3571 + "lanes": "0b0000000000000000000000000000001",
3572 + "timestamp": 34,
3573 + "type": "commit",
3574 + },
3575 + Object {
3576 + "batchUID": 2,
3577 + "depth": 1,
3578 + "duration": 0,
3579 + "lanes": "0b0000000000000000000000000000001",
3580 + "timestamp": 34,
3581 + "type": "layout-effects",
3582 + },
3583 ],
1864 - "fiberSelfDurations": Array [
1865 - Array [
1866 - 4,
1867 - 0,
1868 - ],
1869 - Array [
1870 - 2,
1871 - 10,
1872 - ],
1873 - Array [
1874 - 1,
1875 - 0,
1876 - ],
3584 + ],
3585 + Array [
3586 + 3,
3587 + Array [
3588 + Object {
3589 + "batchUID": 3,
3590 + "depth": 0,
3591 + "duration": 10,
3592 + "lanes": "0b0000000000000000000000000000001",
3593 + "timestamp": 34,
3594 + "type": "render-idle",
3595 + },
3596 + Object {
3597 + "batchUID": 3,
3598 + "depth": 0,
3599 + "duration": 10,
3600 + "lanes": "0b0000000000000000000000000000001",
3601 + "timestamp": 34,
3602 + "type": "render",
3603 + },
3604 + Object {
3605 + "batchUID": 3,
3606 + "depth": 0,
3607 + "duration": 0,
3608 + "lanes": "0b0000000000000000000000000000001",
3609 + "timestamp": 44,
3610 + "type": "commit",
3611 + },
3612 + Object {
3613 + "batchUID": 3,
3614 + "depth": 1,
3615 + "duration": 0,
3616 + "lanes": "0b0000000000000000000000000000001",
3617 + "timestamp": 44,
3618 + "type": "layout-effects",
3619 + },
3620 + Object {
3621 + "batchUID": 3,
3622 + "depth": 0,
3623 + "duration": 0,
3624 + "lanes": "0b0000000000000000000000000000001",
3625 + "timestamp": 44,
3626 + "type": "passive-effects",
3627 + },
3628 ],
1878 - "passiveEffectDuration": 0,
1879 - "priorityLevel": "Immediate",
1880 - "timestamp": 34,
1881 - "updaters": Array [
3629 + ],
3630 + Array [
3631 + 4,
3632 + Array [
3633 Object {
1883 - "displayName": "render()",
1884 - "hocDisplayNames": null,
1885 - "id": 1,
1886 - "key": null,
1887 - "type": 11,
3634 + "batchUID": 4,
3635 + "depth": 0,
3636 + "duration": 0,
3637 + "lanes": "0b0000000000000000000000000000001",
3638 + "timestamp": 44,
3639 + "type": "render-idle",
3640 + },
3641 + Object {
3642 + "batchUID": 4,
3643 + "depth": 0,
3644 + "duration": 0,
3645 + "lanes": "0b0000000000000000000000000000001",
3646 + "timestamp": 44,
3647 + "type": "render",
3648 + },
3649 + Object {
3650 + "batchUID": 4,
3651 + "depth": 0,
3652 + "duration": 0,
3653 + "lanes": "0b0000000000000000000000000000001",
3654 + "timestamp": 44,
3655 + "type": "commit",
3656 + },
3657 + Object {
3658 + "batchUID": 4,
3659 + "depth": 1,
3660 + "duration": 0,
3661 + "lanes": "0b0000000000000000000000000000001",
3662 + "timestamp": 44,
3663 + "type": "layout-effects",
3664 + },
3665 + Object {
3666 + "batchUID": 4,
3667 + "depth": 0,
3668 + "duration": 0,
3669 + "lanes": "0b0000000000000000000000000000001",
3670 + "timestamp": 44,
3671 + "type": "passive-effects",
3672 + },
3673 + ],
3674 + ],
3675 + Array [
3676 + 5,
3677 + Array [
3678 + Object {
3679 + "batchUID": 5,
3680 + "depth": 0,
3681 + "duration": 10,
3682 + "lanes": "0b0000000000000000000000000000001",
3683 + "timestamp": 44,
3684 + "type": "render-idle",
3685 + },
3686 + Object {
3687 + "batchUID": 5,
3688 + "depth": 0,
3689 + "duration": 10,
3690 + "lanes": "0b0000000000000000000000000000001",
3691 + "timestamp": 44,
3692 + "type": "render",
3693 + },
3694 + Object {
3695 + "batchUID": 5,
3696 + "depth": 0,
3697 + "duration": 0,
3698 + "lanes": "0b0000000000000000000000000000001",
3699 + "timestamp": 54,
3700 + "type": "commit",
3701 + },
3702 + Object {
3703 + "batchUID": 5,
3704 + "depth": 1,
3705 + "duration": 0,
3706 + "lanes": "0b0000000000000000000000000000001",
3707 + "timestamp": 54,
3708 + "type": "layout-effects",
3709 + },
3710 + Object {
3711 + "batchUID": 5,
3712 + "depth": 0,
3713 + "duration": 0,
3714 + "lanes": "0b0000000000000000000000000000001",
3715 + "timestamp": 54,
3716 + "type": "passive-effects",
3717 },
3718 ],
3719 + ],
3720 + ],
3721 + "componentMeasures": Array [
3722 + Object {
3723 + "componentName": "Parent",
3724 + "duration": 10,
3725 + "timestamp": 10,
3726 + "type": "render",
3727 + "warning": null,
3728 },
3729 Object {
1892 - "changeDescriptions": Array [
1893 - Array [
1894 - 2,
1895 - Object {
1896 - "context": null,
1897 - "didHooksChange": false,
1898 - "hooks": null,
1899 - "isFirstMount": false,
1900 - "props": Array [
1901 - "count",
1902 - ],
1903 - "state": null,
1904 - },
1905 - ],
1906 - ],
3730 + "componentName": "Child",
3731 + "duration": 0,
3732 + "timestamp": 20,
3733 + "type": "render",
3734 + "warning": null,
3735 + },
3736 + Object {
3737 + "componentName": "Child",
3738 + "duration": 1,
3739 + "timestamp": 20,
3740 + "type": "render",
3741 + "warning": null,
3742 + },
3743 + Object {
3744 + "componentName": "Child",
3745 + "duration": 2,
3746 + "timestamp": 21,
3747 + "type": "render",
3748 + "warning": null,
3749 + },
3750 + Object {
3751 + "componentName": "Parent",
3752 "duration": 10,
1908 - "effectDuration": 0,
1909 - "fiberActualDurations": Array [
1910 - Array [
1911 - 2,
1912 - 10,
1913 - ],
1914 - Array [
1915 - 1,
1916 - 10,
1917 - ],
1918 - ],
1919 - "fiberSelfDurations": Array [
1920 - Array [
1921 - 2,
1922 - 10,
1923 - ],
1924 - Array [
1925 - 1,
1926 - 0,
1927 - ],
1928 - ],
1929 - "passiveEffectDuration": 0,
1930 - "priorityLevel": "Immediate",
3753 + "timestamp": 23,
3754 + "type": "render",
3755 + "warning": null,
3756 + },
3757 + Object {
3758 + "componentName": "Child",
3759 + "duration": 0,
3760 + "timestamp": 33,
3761 + "type": "render",
3762 + "warning": null,
3763 + },
3764 + Object {
3765 + "componentName": "Child",
3766 + "duration": 1,
3767 + "timestamp": 33,
3768 + "type": "render",
3769 + "warning": null,
3770 + },
3771 + Object {
3772 + "componentName": "Parent",
3773 + "duration": 10,
3774 + "timestamp": 34,
3775 + "type": "render",
3776 + "warning": null,
3777 + },
3778 + Object {
3779 + "componentName": "Child",
3780 + "duration": 0,
3781 "timestamp": 44,
1932 - "updaters": Array [
1933 - Object {
1934 - "displayName": "render()",
1935 - "hocDisplayNames": null,
1936 - "id": 1,
1937 - "key": null,
1938 - "type": 11,
1939 - },
1940 - ],
3782 + "type": "render",
3783 + "warning": null,
3784 + },
3785 + Object {
3786 + "componentName": "Parent",
3787 + "duration": 10,
3788 + "timestamp": 44,
3789 + "type": "render",
3790 + "warning": null,
3791 },
3792 ],
1943 - "displayName": "Parent",
1944 - "initialTreeBaseDurations": Array [
3793 + "duration": 64,
3794 + "flamechart": Array [],
3795 + "internalModuleSourceToRanges": Array [],
3796 + "laneToLabelKeyValueArray": Array [
3797 Array [
3798 1,
1947 - 12,
3799 + "Sync",
3800 ],
3801 Array [
3802 2,
1951 - 12,
3803 + "InputContinuousHydration",
3804 ],
3805 Array [
3806 4,
1955 - 0,
3807 + "InputContinuous",
3808 ],
3809 Array [
1958 - 5,
1959 - 1,
3810 + 8,
3811 + "DefaultHydration",
3812 ],
3813 Array [
1962 - 6,
1963 - 1,
3814 + 16,
3815 + "Default",
3816 + ],
3817 + Array [
3818 + 32,
3819 + "TransitionHydration",
3820 + ],
3821 + Array [
3822 + 64,
3823 + "Transition",
3824 + ],
3825 + Array [
3826 + 128,
3827 + "Transition",
3828 + ],
3829 + Array [
3830 + 256,
3831 + "Transition",
3832 + ],
3833 + Array [
3834 + 512,
3835 + "Transition",
3836 + ],
3837 + Array [
3838 + 1024,
3839 + "Transition",
3840 + ],
3841 + Array [
3842 + 2048,
3843 + "Transition",
3844 + ],
3845 + Array [
3846 + 4096,
3847 + "Transition",
3848 + ],
3849 + Array [
3850 + 8192,
3851 + "Transition",
3852 + ],
3853 + Array [
3854 + 16384,
3855 + "Transition",
3856 + ],
3857 + Array [
3858 + 32768,
3859 + "Transition",
3860 + ],
3861 + Array [
3862 + 65536,
3863 + "Transition",
3864 + ],
3865 + Array [
3866 + 131072,
3867 + "Transition",
3868 + ],
3869 + Array [
3870 + 262144,
3871 + "Transition",
3872 + ],
3873 + Array [
3874 + 524288,
3875 + "Transition",
3876 + ],
3877 + Array [
3878 + 1048576,
3879 + "Transition",
3880 + ],
3881 + Array [
3882 + 2097152,
3883 + "Transition",
3884 + ],
3885 + Array [
3886 + 4194304,
3887 + "Retry",
3888 + ],
3889 + Array [
3890 + 8388608,
3891 + "Retry",
3892 + ],
3893 + Array [
3894 + 16777216,
3895 + "Retry",
3896 + ],
3897 + Array [
3898 + 33554432,
3899 + "Retry",
3900 + ],
3901 + Array [
3902 + 67108864,
3903 + "Retry",
3904 + ],
3905 + Array [
3906 + 134217728,
3907 + "SelectiveHydration",
3908 + ],
3909 + Array [
3910 + 268435456,
3911 + "IdleHydration",
3912 + ],
3913 + Array [
3914 + 536870912,
3915 + "Idle",
3916 + ],
3917 + Array [
3918 + 1073741824,
3919 + "Offscreen",
3920 ],
3921 ],
1966 - "operations": Array [
3922 + "laneToReactMeasureKeyValueArray": Array [
3923 Array [
3924 1,
1969 - 1,
1970 - 8,
1971 - 5,
1972 - 67,
1973 - 104,
1974 - 105,
1975 - 108,
1976 - 100,
1977 - 1,
1978 - 50,
1979 - 1,
1980 - 12,
1981 - 5,
1982 - 2,
1983 - 2,
1984 - 1,
1985 - 2,
1986 - 4,
1987 - 12,
1988 - 2000,
1989 - 4,
1990 - 2,
1991 - 14000,
1992 - 3,
1993 - 2,
1994 - 4,
1995 - 4,
1996 - 5,
1997 - 12,
1998 - 6,
1999 - 4,
2000 - 1,
2001 - 14000,
3925 + Array [
3926 + Object {
3927 + "batchUID": 1,
3928 + "depth": 0,
3929 + "duration": 13,
3930 + "lanes": "0b0000000000000000000000000000001",
3931 + "timestamp": 10,
3932 + "type": "render-idle",
3933 + },
3934 + Object {
3935 + "batchUID": 1,
3936 + "depth": 0,
3937 + "duration": 13,
3938 + "lanes": "0b0000000000000000000000000000001",
3939 + "timestamp": 10,
3940 + "type": "render",
3941 + },
3942 + Object {
3943 + "batchUID": 1,
3944 + "depth": 0,
3945 + "duration": 0,
3946 + "lanes": "0b0000000000000000000000000000001",
3947 + "timestamp": 23,
3948 + "type": "commit",
3949 + },
3950 + Object {
3951 + "batchUID": 1,
3952 + "depth": 1,
3953 + "duration": 0,
3954 + "lanes": "0b0000000000000000000000000000001",
3955 + "timestamp": 23,
3956 + "type": "layout-effects",
3957 + },
3958 + Object {
3959 + "batchUID": 2,
3960 + "depth": 0,
3961 + "duration": 11,
3962 + "lanes": "0b0000000000000000000000000000001",
3963 + "timestamp": 23,
3964 + "type": "render-idle",
3965 + },
3966 + Object {
3967 + "batchUID": 2,
3968 + "depth": 0,
3969 + "duration": 11,
3970 + "lanes": "0b0000000000000000000000000000001",
3971 + "timestamp": 23,
3972 + "type": "render",
3973 + },
3974 + Object {
3975 + "batchUID": 2,
3976 + "depth": 0,
3977 + "duration": 0,
3978 + "lanes": "0b0000000000000000000000000000001",
3979 + "timestamp": 34,
3980 + "type": "commit",
3981 + },
3982 + Object {
3983 + "batchUID": 2,
3984 + "depth": 1,
3985 + "duration": 0,
3986 + "lanes": "0b0000000000000000000000000000001",
3987 + "timestamp": 34,
3988 + "type": "layout-effects",
3989 + },
3990 + Object {
3991 + "batchUID": 3,
3992 + "depth": 0,
3993 + "duration": 10,
3994 + "lanes": "0b0000000000000000000000000000001",
3995 + "timestamp": 34,
3996 + "type": "render-idle",
3997 + },
3998 + Object {
3999 + "batchUID": 3,
4000 + "depth": 0,
4001 + "duration": 10,
4002 + "lanes": "0b0000000000000000000000000000001",
4003 + "timestamp": 34,
4004 + "type": "render",
4005 + },
4006 + Object {
4007 + "batchUID": 3,
4008 + "depth": 0,
4009 + "duration": 0,
4010 + "lanes": "0b0000000000000000000000000000001",
4011 + "timestamp": 44,
4012 + "type": "commit",
4013 + },
4014 + Object {
4015 + "batchUID": 3,
4016 + "depth": 1,
4017 + "duration": 0,
4018 + "lanes": "0b0000000000000000000000000000001",
4019 + "timestamp": 44,
4020 + "type": "layout-effects",
4021 + },
4022 + Object {
4023 + "batchUID": 3,
4024 + "depth": 0,
4025 + "duration": 0,
4026 + "lanes": "0b0000000000000000000000000000001",
4027 + "timestamp": 44,
4028 + "type": "passive-effects",
4029 + },
4030 + Object {
4031 + "batchUID": 4,
4032 + "depth": 0,
4033 + "duration": 0,
4034 + "lanes": "0b0000000000000000000000000000001",
4035 + "timestamp": 44,
4036 + "type": "render-idle",
4037 + },
4038 + Object {
4039 + "batchUID": 4,
4040 + "depth": 0,
4041 + "duration": 0,
4042 + "lanes": "0b0000000000000000000000000000001",
4043 + "timestamp": 44,
4044 + "type": "render",
4045 + },
4046 + Object {
4047 + "batchUID": 4,
4048 + "depth": 0,
4049 + "duration": 0,
4050 + "lanes": "0b0000000000000000000000000000001",
4051 + "timestamp": 44,
4052 + "type": "commit",
4053 + },
4054 + Object {
4055 + "batchUID": 4,
4056 + "depth": 1,
4057 + "duration": 0,
4058 + "lanes": "0b0000000000000000000000000000001",
4059 + "timestamp": 44,
4060 + "type": "layout-effects",
4061 + },
4062 + Object {
4063 + "batchUID": 4,
4064 + "depth": 0,
4065 + "duration": 0,
4066 + "lanes": "0b0000000000000000000000000000001",
4067 + "timestamp": 44,
4068 + "type": "passive-effects",
4069 + },
4070 + Object {
4071 + "batchUID": 5,
4072 + "depth": 0,
4073 + "duration": 10,
4074 + "lanes": "0b0000000000000000000000000000001",
4075 + "timestamp": 44,
4076 + "type": "render-idle",
4077 + },
4078 + Object {
4079 + "batchUID": 5,
4080 + "depth": 0,
4081 + "duration": 10,
4082 + "lanes": "0b0000000000000000000000000000001",
4083 + "timestamp": 44,
4084 + "type": "render",
4085 + },
4086 + Object {
4087 + "batchUID": 5,
4088 + "depth": 0,
4089 + "duration": 0,
4090 + "lanes": "0b0000000000000000000000000000001",
4091 + "timestamp": 54,
4092 + "type": "commit",
4093 + },
4094 + Object {
4095 + "batchUID": 5,
4096 + "depth": 1,
4097 + "duration": 0,
4098 + "lanes": "0b0000000000000000000000000000001",
4099 + "timestamp": 54,
4100 + "type": "layout-effects",
4101 + },
4102 + Object {
4103 + "batchUID": 5,
4104 + "depth": 0,
4105 + "duration": 0,
4106 + "lanes": "0b0000000000000000000000000000001",
4107 + "timestamp": 54,
4108 + "type": "passive-effects",
4109 + },
4110 + ],
4111 ],
4112 Array [
2004 - 1,
2005 - 1,
2006 - 0,
2007 - 2,
2008 - 2,
2009 - 12,
2010 - 5,
2011 - 4,
2012 - 2,
2013 - 11000,
2014 - 3,
2015 - 2,
4113 2,
2017 - 4,
2018 - 6,
2019 - 4,
2020 - 1,
2021 - 11000,
4114 + Array [],
4115 ],
4116 Array [
2024 - 1,
2025 - 1,
2026 - 0,
2027 - 2,
2028 - 1,
4117 4,
4118 + Array [],
4119 ],
2031 - ],
2032 - "rootID": 1,
2033 - "snapshots": Array [
4120 Array [
2035 - 1,
2036 - Object {
2037 - "children": Array [
2038 - 2,
2039 - ],
2040 - "displayName": null,
2041 - "hocDisplayNames": null,
2042 - "id": 1,
2043 - "key": null,
2044 - "type": 11,
2045 - },
4121 + 8,
4122 + Array [],
4123 ],
4124 Array [
2048 - 2,
2049 - Object {
2050 - "children": Array [
2051 - 4,
2052 - 5,
2053 - 6,
2054 - ],
2055 - "displayName": "Parent",
2056 - "hocDisplayNames": null,
2057 - "id": 2,
2058 - "key": null,
2059 - "type": 5,
2060 - },
4125 + 16,
4126 + Array [],
4127 ],
4128 Array [
2063 - 4,
2064 - Object {
2065 - "children": Array [],
2066 - "displayName": "Child",
2067 - "hocDisplayNames": null,
2068 - "id": 4,
2069 - "key": "0",
2070 - "type": 5,
2071 - },
4129 + 32,
4130 + Array [],
4131 ],
4132 Array [
2074 - 5,
2075 - Object {
2076 - "children": Array [],
2077 - "displayName": "Child",
2078 - "hocDisplayNames": null,
2079 - "id": 5,
2080 - "key": "1",
2081 - "type": 5,
2082 - },
4133 + 64,
4134 + Array [],
4135 ],
4136 Array [
2085 - 6,
2086 - Object {
2087 - "children": Array [],
2088 - "displayName": "Child",
2089 - "hocDisplayNames": Array [
2090 - "Memo",
2091 - ],
2092 - "id": 6,
2093 - "key": null,
2094 - "type": 8,
2095 - },
4137 + 128,
4138 + Array [],
4139 ],
2097 - ],
2098 - },
2099 - Object {
2100 - "commitData": Array [
2101 - Object {
2102 - "changeDescriptions": Array [
2103 - Array [
2104 - 14,
2105 - Object {
2106 - "context": null,
2107 - "didHooksChange": false,
2108 - "isFirstMount": true,
2109 - "props": null,
2110 - "state": null,
2111 - },
2112 - ],
2113 - Array [
2114 - 16,
2115 - Object {
2116 - "context": null,
2117 - "didHooksChange": false,
2118 - "isFirstMount": true,
2119 - "props": null,
2120 - "state": null,
2121 - },
2122 - ],
2123 - Array [
2124 - 17,
2125 - Object {
2126 - "context": null,
2127 - "didHooksChange": false,
2128 - "isFirstMount": true,
2129 - "props": null,
2130 - "state": null,
2131 - },
2132 - ],
2133 - ],
2134 - "duration": 11,
2135 - "effectDuration": null,
2136 - "fiberActualDurations": Array [
2137 - Array [
2138 - 13,
2139 - 11,
2140 - ],
2141 - Array [
2142 - 14,
2143 - 11,
2144 - ],
2145 - Array [
2146 - 16,
2147 - 0,
2148 - ],
2149 - Array [
2150 - 17,
2151 - 1,
2152 - ],
2153 - ],
2154 - "fiberSelfDurations": Array [
2155 - Array [
2156 - 13,
2157 - 0,
2158 - ],
2159 - Array [
2160 - 14,
2161 - 10,
2162 - ],
2163 - Array [
2164 - 16,
2165 - 0,
2166 - ],
2167 - Array [
2168 - 17,
2169 - 1,
2170 - ],
2171 - ],
2172 - "passiveEffectDuration": null,
2173 - "priorityLevel": "Immediate",
2174 - "timestamp": 24,
2175 - "updaters": Array [
2176 - Object {
2177 - "displayName": "render()",
2178 - "hocDisplayNames": null,
2179 - "id": 13,
2180 - "key": null,
2181 - "type": 11,
2182 - },
2183 - ],
2184 - },
2185 - ],
2186 - "displayName": "Parent",
2187 - "initialTreeBaseDurations": Array [],
2188 - "operations": Array [
4140 Array [
2190 - 1,
2191 - 13,
2192 - 15,
2193 - 6,
2194 - 80,
2195 - 97,
2196 - 114,
2197 - 101,
2198 - 110,
2199 - 116,
2200 - 5,
2201 - 67,
2202 - 104,
2203 - 105,
2204 - 108,
2205 - 100,
2206 - 1,
2207 - 48,
2208 - 1,
2209 - 13,
2210 - 11,
2211 - 0,
2212 - 1,
2213 - 1,
2214 - 1,
2215 - 4,
2216 - 13,
2217 - 11000,
2218 - 1,
2219 - 14,
2220 - 5,
2221 - 13,
2222 - 0,
2223 - 1,
2224 - 0,
2225 - 4,
2226 - 14,
2227 - 11000,
2228 - 1,
2229 - 16,
2230 - 5,
2231 - 14,
2232 - 14,
2233 - 2,
2234 - 3,
2235 - 4,
2236 - 16,
2237 - 0,
2238 - 1,
2239 - 17,
2240 - 8,
2241 - 14,
2242 - 14,
2243 - 2,
2244 - 0,
2245 - 4,
2246 - 17,
2247 - 1000,
4141 + 256,
4142 + Array [],
4143 + ],
4144 + Array [
4145 + 512,
4146 + Array [],
4147 + ],
4148 + Array [
4149 + 1024,
4150 + Array [],
4151 + ],
4152 + Array [
4153 + 2048,
4154 + Array [],
4155 + ],
4156 + Array [
4157 + 4096,
4158 + Array [],
4159 + ],
4160 + Array [
4161 + 8192,
4162 + Array [],
4163 + ],
4164 + Array [
4165 + 16384,
4166 + Array [],
4167 + ],
4168 + Array [
4169 + 32768,
4170 + Array [],
4171 + ],
4172 + Array [
4173 + 65536,
4174 + Array [],
4175 + ],
4176 + Array [
4177 + 131072,
4178 + Array [],
4179 + ],
4180 + Array [
4181 + 262144,
4182 + Array [],
4183 + ],
4184 + Array [
4185 + 524288,
4186 + Array [],
4187 + ],
4188 + Array [
4189 + 1048576,
4190 + Array [],
4191 + ],
4192 + Array [
4193 + 2097152,
4194 + Array [],
4195 + ],
4196 + Array [
4197 + 4194304,
4198 + Array [],
4199 + ],
4200 + Array [
4201 + 8388608,
4202 + Array [],
4203 + ],
4204 + Array [
4205 + 16777216,
4206 + Array [],
4207 + ],
4208 + Array [
4209 + 33554432,
4210 + Array [],
4211 + ],
4212 + Array [
4213 + 67108864,
4214 + Array [],
4215 + ],
4216 + Array [
4217 + 134217728,
4218 + Array [],
4219 + ],
4220 + Array [
4221 + 268435456,
4222 + Array [],
4223 + ],
4224 + Array [
4225 + 536870912,
4226 + Array [],
4227 + ],
4228 + Array [
4229 + 1073741824,
4230 + Array [],
4231 ],
4232 ],
2250 - "rootID": 13,
4233 + "nativeEvents": Array [],
4234 + "networkMeasures": Array [],
4235 + "otherUserTimingMarks": Array [],
4236 + "reactVersion": "<filtered-version>",
4237 + "schedulingEvents": Array [
4238 + Object {
4239 + "lanes": "0b0000000000000000000000000000001",
4240 + "timestamp": 10,
4241 + "type": "schedule-render",
4242 + "warning": null,
4243 + },
4244 + Object {
4245 + "lanes": "0b0000000000000000000000000000001",
4246 + "timestamp": 23,
4247 + "type": "schedule-render",
4248 + "warning": null,
4249 + },
4250 + Object {
4251 + "lanes": "0b0000000000000000000000000000001",
4252 + "timestamp": 34,
4253 + "type": "schedule-render",
4254 + "warning": null,
4255 + },
4256 + Object {
4257 + "lanes": "0b0000000000000000000000000000001",
4258 + "timestamp": 44,
4259 + "type": "schedule-render",
4260 + "warning": null,
4261 + },
4262 + Object {
4263 + "lanes": "0b0000000000000000000000000000001",
4264 + "timestamp": 44,
4265 + "type": "schedule-render",
4266 + "warning": null,
4267 + },
4268 + ],
4269 + "snapshotHeight": 0,
4270 "snapshots": Array [],
4271 + "startTime": 13,
4272 + "suspenseEvents": Array [],
4273 + "thrownErrors": Array [],
4274 },
4275 ],
4276 "version": 5,
@@ -2304,7 +4326,7 @@ Object {
4326 1,
4327 11,
4328 0,
2307 - 1,
4329 + 3,
4330 1,
4331 1,
4332 1,
@@ -2954,7 +4976,7 @@ Object {
4976 1,
4977 11,
4978 0,
2957 - 1,
4979 + 3,
4980 1,
4981 1,
4982 1,
@@ -2983,39 +5005,758 @@ Object {
5005 3,
5006 0,
5007 2,
2986 - 0,
2987 - 4,
5008 + 0,
5009 + 4,
5010 + 4,
5011 + 0,
5012 + ],
5013 + Array [
5014 + 1,
5015 + 1,
5016 + 0,
5017 + ],
5018 + Array [
5019 + 1,
5020 + 1,
5021 + 0,
5022 + ],
5023 + Array [
5024 + 1,
5025 + 1,
5026 + 0,
5027 + ],
5028 + Array [
5029 + 1,
5030 + 1,
5031 + 0,
5032 + ],
5033 + Array [
5034 + 1,
5035 + 1,
5036 + 0,
5037 + ],
5038 + ],
5039 + "rootID": 1,
5040 + "snapshots": Array [],
5041 + },
5042 + ],
5043 + "timelineData": Array [
5044 + Object {
5045 + "batchUIDToMeasuresKeyValueArray": Array [
5046 + Array [
5047 + 1,
5048 + Array [
5049 + Object {
5050 + "batchUID": 1,
5051 + "depth": 0,
5052 + "duration": 0,
5053 + "lanes": "0b0000000000000000000000000000001",
5054 + "timestamp": 10,
5055 + "type": "render-idle",
5056 + },
5057 + Object {
5058 + "batchUID": 1,
5059 + "depth": 0,
5060 + "duration": 0,
5061 + "lanes": "0b0000000000000000000000000000001",
5062 + "timestamp": 10,
5063 + "type": "render",
5064 + },
5065 + Object {
5066 + "batchUID": 1,
5067 + "depth": 0,
5068 + "duration": 0,
5069 + "lanes": "0b0000000000000000000000000000001",
5070 + "timestamp": 10,
5071 + "type": "commit",
5072 + },
5073 + Object {
5074 + "batchUID": 1,
5075 + "depth": 1,
5076 + "duration": 0,
5077 + "lanes": "0b0000000000000000000000000000001",
5078 + "timestamp": 10,
5079 + "type": "layout-effects",
5080 + },
5081 + Object {
5082 + "batchUID": 1,
5083 + "depth": 0,
5084 + "duration": 0,
5085 + "lanes": "0b0000000000000000000000000000001",
5086 + "timestamp": 10,
5087 + "type": "passive-effects",
5088 + },
5089 + ],
5090 + ],
5091 + Array [
5092 + 2,
5093 + Array [
5094 + Object {
5095 + "batchUID": 2,
5096 + "depth": 0,
5097 + "duration": 0,
5098 + "lanes": "0b0000000000000000000000000000001",
5099 + "timestamp": 10,
5100 + "type": "render-idle",
5101 + },
5102 + Object {
5103 + "batchUID": 2,
5104 + "depth": 0,
5105 + "duration": 0,
5106 + "lanes": "0b0000000000000000000000000000001",
5107 + "timestamp": 10,
5108 + "type": "render",
5109 + },
5110 + Object {
5111 + "batchUID": 2,
5112 + "depth": 0,
5113 + "duration": 0,
5114 + "lanes": "0b0000000000000000000000000000001",
5115 + "timestamp": 10,
5116 + "type": "commit",
5117 + },
5118 + ],
5119 + ],
5120 + Array [
5121 + 3,
5122 + Array [
5123 + Object {
5124 + "batchUID": 3,
5125 + "depth": 0,
5126 + "duration": 0,
5127 + "lanes": "0b0000000000000000000000000000001",
5128 + "timestamp": 10,
5129 + "type": "render-idle",
5130 + },
5131 + Object {
5132 + "batchUID": 3,
5133 + "depth": 0,
5134 + "duration": 0,
5135 + "lanes": "0b0000000000000000000000000000001",
5136 + "timestamp": 10,
5137 + "type": "render",
5138 + },
5139 + Object {
5140 + "batchUID": 3,
5141 + "depth": 0,
5142 + "duration": 0,
5143 + "lanes": "0b0000000000000000000000000000001",
5144 + "timestamp": 10,
5145 + "type": "commit",
5146 + },
5147 + ],
5148 + ],
5149 + Array [
5150 + 4,
5151 + Array [
5152 + Object {
5153 + "batchUID": 4,
5154 + "depth": 0,
5155 + "duration": 0,
5156 + "lanes": "0b0000000000000000000000000000001",
5157 + "timestamp": 10,
5158 + "type": "render-idle",
5159 + },
5160 + Object {
5161 + "batchUID": 4,
5162 + "depth": 0,
5163 + "duration": 0,
5164 + "lanes": "0b0000000000000000000000000000001",
5165 + "timestamp": 10,
5166 + "type": "render",
5167 + },
5168 + Object {
5169 + "batchUID": 4,
5170 + "depth": 0,
5171 + "duration": 0,
5172 + "lanes": "0b0000000000000000000000000000001",
5173 + "timestamp": 10,
5174 + "type": "commit",
5175 + },
5176 + ],
5177 + ],
5178 + Array [
5179 + 5,
5180 + Array [
5181 + Object {
5182 + "batchUID": 5,
5183 + "depth": 0,
5184 + "duration": 0,
5185 + "lanes": "0b0000000000000000000000000000001",
5186 + "timestamp": 10,
5187 + "type": "render-idle",
5188 + },
5189 + Object {
5190 + "batchUID": 5,
5191 + "depth": 0,
5192 + "duration": 0,
5193 + "lanes": "0b0000000000000000000000000000001",
5194 + "timestamp": 10,
5195 + "type": "render",
5196 + },
5197 + Object {
5198 + "batchUID": 5,
5199 + "depth": 0,
5200 + "duration": 0,
5201 + "lanes": "0b0000000000000000000000000000001",
5202 + "timestamp": 10,
5203 + "type": "commit",
5204 + },
5205 + ],
5206 + ],
5207 + Array [
5208 + 6,
5209 + Array [
5210 + Object {
5211 + "batchUID": 6,
5212 + "depth": 0,
5213 + "duration": 0,
5214 + "lanes": "0b0000000000000000000000000000001",
5215 + "timestamp": 10,
5216 + "type": "render-idle",
5217 + },
5218 + Object {
5219 + "batchUID": 6,
5220 + "depth": 0,
5221 + "duration": 0,
5222 + "lanes": "0b0000000000000000000000000000001",
5223 + "timestamp": 10,
5224 + "type": "render",
5225 + },
5226 + Object {
5227 + "batchUID": 6,
5228 + "depth": 0,
5229 + "duration": 0,
5230 + "lanes": "0b0000000000000000000000000000001",
5231 + "timestamp": 10,
5232 + "type": "commit",
5233 + },
5234 + ],
5235 + ],
5236 + ],
5237 + "componentMeasures": Array [
5238 + Object {
5239 + "componentName": "Component",
5240 + "duration": 0,
5241 + "timestamp": 10,
5242 + "type": "render",
5243 + "warning": null,
5244 + },
5245 + Object {
5246 + "componentName": "Component",
5247 + "duration": 0,
5248 + "timestamp": 10,
5249 + "type": "layout-effect-mount",
5250 + "warning": null,
5251 + },
5252 + Object {
5253 + "componentName": "Component",
5254 + "duration": 0,
5255 + "timestamp": 10,
5256 + "type": "passive-effect-mount",
5257 + "warning": null,
5258 + },
5259 + Object {
5260 + "componentName": "Component",
5261 + "duration": 0,
5262 + "timestamp": 10,
5263 + "type": "render",
5264 + "warning": null,
5265 + },
5266 + Object {
5267 + "componentName": "Component",
5268 + "duration": 0,
5269 + "timestamp": 10,
5270 + "type": "render",
5271 + "warning": null,
5272 + },
5273 + Object {
5274 + "componentName": "Component",
5275 + "duration": 0,
5276 + "timestamp": 10,
5277 + "type": "render",
5278 + "warning": null,
5279 + },
5280 + Object {
5281 + "componentName": "Component",
5282 + "duration": 0,
5283 + "timestamp": 10,
5284 + "type": "render",
5285 + "warning": null,
5286 + },
5287 + Object {
5288 + "componentName": "Component",
5289 + "duration": 0,
5290 + "timestamp": 10,

This file is too large to show in full.

packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+2135 -1687
@@ -15,1915 +15,2363 @@ describe('Timeline profiler', () => {
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++;
18 + describe('User Timing API', () => {
19 + let clearedMarks;
20 + let featureDetectionMarkName = null;
21 + let marks;
22 + let setPerformanceMock;
23 +
24 + function createUserTimingPolyfill() {
25 + featureDetectionMarkName = null;
26 +
27 + clearedMarks = [];
28 + marks = [];
29 +
30 + // Remove file-system specific bits or version-specific bits of information from the module range marks.
31 + function filterMarkData(markName) {
32 + if (markName.startsWith('--react-internal-module-start')) {
33 + return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
34 + } else if (markName.startsWith('--react-internal-module-stop')) {
35 + return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
36 + } else if (markName.startsWith('--react-version')) {
37 + return '--react-version-<filtered-version>';
38 + } else {
39 + return markName;
40 }
64 - },
65 - };
66 - }
67 -
68 - function clearPendingMarks() {
69 - clearedMarks.splice(0);
70 - }
71 -
72 - beforeEach(() => {
73 - utils = require('./utils');
74 - utils.beforeEachProfiling();
41 + }
42
76 - React = require('react');
77 - ReactDOM = require('react-dom');
78 - Scheduler = require('scheduler');
43 + // This is not a true polyfill, but it gives us enough to capture marks.
44 + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
45 + return {
46 + clearMarks(markName) {
47 + markName = filterMarkData(markName);
48
80 - setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
81 - .setPerformanceMock_ONLY_FOR_TESTING;
82 - setPerformanceMock(createUserTimingPolyfill());
49 + clearedMarks.push(markName);
50 + marks = marks.filter(mark => mark !== markName);
51 + },
52 + mark(markName, markOptions) {
53 + markName = filterMarkData(markName);
54
84 - global.IS_REACT_ACT_ENVIRONMENT = true;
85 - });
55 + if (featureDetectionMarkName === null) {
56 + featureDetectionMarkName = markName;
57 + }
58
87 - afterEach(() => {
88 - // Verify all logged marks also get cleared.
89 - expect(marks).toHaveLength(0);
59 + marks.push(markName);
60
91 - setPerformanceMock(null);
92 - });
61 + if (markOptions != null) {
62 + // This is triggers the feature detection.
63 + markOptions.startTime++;
64 + }
65 + },
66 + };
67 + }
68
94 - describe('getLanesFromTransportDecimalBitmask', () => {
95 - let getLanesFromTransportDecimalBitmask;
69 + function clearPendingMarks() {
70 + clearedMarks.splice(0);
71 + }
72
73 beforeEach(() => {
98 - getLanesFromTransportDecimalBitmask = require('react-devtools-timeline/src/import-worker/preprocessData')
99 - .getLanesFromTransportDecimalBitmask;
100 - });
74 + utils = require('./utils');
75 + utils.beforeEachProfiling();
76
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 - });
77 + React = require('react');
78 + ReactDOM = require('react-dom');
79 + Scheduler = require('scheduler');
80
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 - });
81 + setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks')
82 + .setPerformanceMock_ONLY_FOR_TESTING;
83 + setPerformanceMock(createUserTimingPolyfill());
84
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;
85 + const store = global.store;
86
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);
87 + // Start profiling so that data will actually be recorded.
88 + utils.act(() => store.profilerStore.startProfiling());
89
135 - expect(
136 - getLanesFromTransportDecimalBitmask(
137 - '4294967297', // 2^32 + 1
138 - ),
139 - ).toEqual([0]);
90 + global.IS_REACT_ACT_ENVIRONMENT = true;
91 });
141 - });
92
143 - describe('preprocessData', () => {
144 - let preprocessData;
93 + afterEach(() => {
94 + // Verify all logged marks also get cleared.
95 + expect(marks).toHaveLength(0);
96
146 - beforeEach(() => {
147 - preprocessData = require('react-devtools-timeline/src/import-worker/preprocessData')
148 - .default;
97 + setPerformanceMock(null);
98 });
99
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 - }
100 + describe('getLanesFromTransportDecimalBitmask', () => {
101 + let getLanesFromTransportDecimalBitmask;
102
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,
103 + beforeEach(() => {
104 + getLanesFromTransportDecimalBitmask = require('react-devtools-timeline/src/import-worker/preprocessData')
105 + .getLanesFromTransportDecimalBitmask;
106 });
173 - }
107
175 - function createReactVersionEntry() {
176 - return createUserTimingEntry({
177 - cat: 'blink.user_timing',
178 - name: '--react-version-0.0.0',
108 + it('should return array of lane numbers from bitmask string', () => {
109 + expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
110 + expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
111 + expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
112 + expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
113 + 1,
114 + 4,
115 + 6,
116 + 7,
117 + 10,
118 + ]); // 2 + 16 + 64 + 128 + 1024
119 + expect(
120 + getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
121 + ).toEqual([30]);
122 + expect(
123 + getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
124 + ).toEqual(Array.from(Array(31).keys()));
125 });
180 - }
126
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',
127 + it('should return empty array if laneBitmaskString is not a bitmask', () => {
128 + expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
129 + expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
130 + expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
131 + expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
132 });
188 - }
133
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 - }
134 + it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
135 + const REACT_TOTAL_NUM_LANES = require('react-devtools-timeline/src/constants')
136 + .REACT_TOTAL_NUM_LANES;
137
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 - }
138 + // Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
139 + expect(REACT_TOTAL_NUM_LANES).toBe(31);
140
210 - function createBoilerplateEntries() {
211 - return [
212 - createProfilerVersionEntry(),
213 - createReactVersionEntry(),
214 - createLaneLabelsEntry(),
215 - ];
216 - }
141 + expect(
142 + getLanesFromTransportDecimalBitmask(
143 + '4294967297', // 2^32 + 1
144 + ),
145 + ).toEqual([0]);
146 + });
147 + });
148
218 - function createUserTimingData(sampleMarks) {
219 - const cpuProfilerSample = creactCpuProfilerSample();
149 + describe('preprocessData', () => {
150 + let preprocessData;
151
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: {},
152 + beforeEach(() => {
153 + preprocessData = require('react-devtools-timeline/src/import-worker/preprocessData')
154 + .default;
155 });
156
230 - const userTimingData = [cpuProfilerSample, randomSample];
157 + // These should be dynamic to mimic a real profile,
158 + // but reprooducible between test runs.
159 + let pid = 0;
160 + let tid = 0;
161 + let startTime = 0;
162
232 - sampleMarks.forEach(markName => {
233 - userTimingData.push({
163 + function createUserTimingEntry(data) {
164 + return {
165 pid: ++pid,
166 tid: ++tid,
167 ts: ++startTime,
237 - args: {data: {}},
168 + ...data,
169 + };
170 + }
171 +
172 + function createProfilerVersionEntry() {
173 + const SCHEDULING_PROFILER_VERSION = require('react-devtools-timeline/src/constants')
174 + .SCHEDULING_PROFILER_VERSION;
175 + return createUserTimingEntry({
176 cat: 'blink.user_timing',
239 - name: markName,
240 - ph: 'R',
177 + name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
178 });
242 - });
179 + }
180
244 - return userTimingData;
245 - }
181 + function createReactVersionEntry() {
182 + return createUserTimingEntry({
183 + cat: 'blink.user_timing',
184 + name: '--react-version-<filtered-version>',
185 + });
186 + }
187
247 - beforeEach(() => {
248 - tid = 0;
249 - pid = 0;
250 - startTime = 0;
251 - });
188 + function createLaneLabelsEntry() {
189 + return createUserTimingEntry({
190 + cat: 'blink.user_timing',
191 + name:
192 + '--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',
193 + });
194 + }
195
253 - it('should throw given an empty timeline', async () => {
254 - await expect(async () => preprocessData([])).rejects.toThrow();
255 - });
196 + function createNativeEventEntry(type, duration) {
197 + return createUserTimingEntry({
198 + cat: 'devtools.timeline',
199 + name: 'EventDispatch',
200 + args: {data: {type}},
201 + dur: duration,
202 + tdur: duration,
203 + });
204 + }
205
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 - });
206 + function creactCpuProfilerSample() {
207 + return createUserTimingEntry({
208 + args: {data: {startTime: ++startTime}},
209 + cat: 'disabled-by-default-v8.cpu_profiler',
210 + id: '0x1',
211 + name: 'Profile',
212 + ph: 'P',
213 + });
214 + }
215
267 - await expect(async () =>
268 - preprocessData([randomSample]),
269 - ).rejects.toThrow();
270 - });
216 + function createBoilerplateEntries() {
217 + return [
218 + createProfilerVersionEntry(),
219 + createReactVersionEntry(),
220 + createLaneLabelsEntry(),
221 + ];
222 + }
223
272 - it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
273 - const cpuProfilerSample = creactCpuProfilerSample();
224 + function createUserTimingData(sampleMarks) {
225 + const cpuProfilerSample = creactCpuProfilerSample();
226
275 - await expect(
276 - async () => await preprocessData([cpuProfilerSample]),
277 - ).rejects.toThrow(
278 - 'Please provide profiling data from an React application',
279 - );
280 - });
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
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-',
236 + const userTimingData = [cpuProfilerSample, randomSample];
237 +
238 + sampleMarks.forEach(markName => {
239 + userTimingData.push({
240 + pid: ++pid,
241 + tid: ++tid,
242 + ts: ++startTime,
243 + args: {data: {}},
244 + cat: 'blink.user_timing',
245 + name: markName,
246 + ph: 'R',
247 + });
248 + });
249 +
250 + return userTimingData;
251 + }
252 +
253 + beforeEach(() => {
254 + tid = 0;
255 + pid = 0;
256 + startTime = 0;
257 });
288 - const samples = [cpuProfilerSample, scheduleRenderSample];
258
290 - await expect(async () => await preprocessData(samples)).rejects.toThrow(
291 - 'This version of profiling data is not supported',
292 - );
293 - });
259 + it('should throw given an empty timeline', async () => {
260 + await expect(async () => preprocessData([])).rejects.toThrow();
261 + });
262 +
263 + it('should throw given a timeline with no Profile event', async () => {
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
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: {},
273 + await expect(async () =>
274 + preprocessData([randomSample]),
275 + ).rejects.toThrow();
276 });
277
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 - });
278 + it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
279 + const cpuProfilerSample = creactCpuProfilerSample();
280
398 - it('should process legacy data format (before lane labels were added)', async () => {
399 - const cpuProfilerSample = creactCpuProfilerSample();
281 + await expect(
282 + async () => await preprocessData([cpuProfilerSample]),
283 + ).rejects.toThrow(
284 + 'Please provide profiling data from an React application',
285 + );
286 + });
287
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({
288 + it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
289 + const cpuProfilerSample = creactCpuProfilerSample();
290 + const scheduleRenderSample = createUserTimingEntry({
291 cat: 'blink.user_timing',
292 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.005,
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.003,
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.001,
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.005,
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.003,
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.001,
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 - });
293 + });
294 + const samples = [cpuProfilerSample, scheduleRenderSample];
295
615 - it('should process a sample legacy render sequence', async () => {
616 - utils.legacyRender(<div />, document.createElement('div'));
296 + await expect(async () => await preprocessData(samples)).rejects.toThrow(
297 + 'This version of profiling data is not supported',
298 + );
299 + });
300
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,
301 + it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
302 + const cpuProfilerSample = creactCpuProfilerSample();
303 + const randomSample = createUserTimingEntry({
304 + dur: 100,
305 + tdur: 200,
306 + ph: 'X',
307 + cat: 'disabled-by-default-devtools.timeline',
308 + name: 'RunTask',
309 + args: {},
310 + });
311 +
312 + const data = await preprocessData([
313 + ...createBoilerplateEntries(),
314 + cpuProfilerSample,
315 + randomSample,
316 + ]);
317 + expect(data).toMatchInlineSnapshot(`
318 + Object {
319 + "batchUIDToMeasuresMap": Map {},
320 + "componentMeasures": Array [],
321 + "duration": 0.005,
322 + "flamechart": Array [],
323 + "internalModuleSourceToRanges": Map {},
324 + "laneToLabelMap": Map {
325 + 0 => "Sync",
326 + 1 => "InputContinuousHydration",
327 + 2 => "InputContinuous",
328 + 3 => "DefaultHydration",
329 + 4 => "Default",
330 + 5 => "TransitionHydration",
331 + 6 => "Transition",
332 + 7 => "Transition",
333 + 8 => "Transition",
334 + 9 => "Transition",
335 + 10 => "Transition",
336 + 11 => "Transition",
337 + 12 => "Transition",
338 + 13 => "Transition",
339 + 14 => "Transition",
340 + 15 => "Transition",
341 + 16 => "Transition",
342 + 17 => "Transition",
343 + 18 => "Transition",
344 + 19 => "Transition",
345 + 20 => "Transition",
346 + 21 => "Transition",
347 + 22 => "Retry",
348 + 23 => "Retry",
349 + 24 => "Retry",
350 + 25 => "Retry",
351 + 26 => "Retry",
352 + 27 => "SelectiveHydration",
353 + 28 => "IdleHydration",
354 + 29 => "Idle",
355 + 30 => "Offscreen",
356 + },
357 + "laneToReactMeasureMap": Map {
358 + 0 => Array [],
359 + 1 => Array [],
360 + 2 => Array [],
361 + 3 => Array [],
362 + 4 => Array [],
363 + 5 => Array [],
364 + 6 => Array [],
365 + 7 => Array [],
366 + 8 => Array [],
367 + 9 => Array [],
368 + 10 => Array [],
369 + 11 => Array [],
370 + 12 => Array [],
371 + 13 => Array [],
372 + 14 => Array [],
373 + 15 => Array [],
374 + 16 => Array [],
375 + 17 => Array [],
376 + 18 => Array [],
377 + 19 => Array [],
378 + 20 => Array [],
379 + 21 => Array [],
380 + 22 => Array [],
381 + 23 => Array [],
382 + 24 => Array [],
383 + 25 => Array [],
384 + 26 => Array [],
385 + 27 => Array [],
386 + 28 => Array [],
387 + 29 => Array [],
388 + 30 => Array [],
389 + },
390 + "nativeEvents": Array [],
391 + "networkMeasures": Array [],
392 + "otherUserTimingMarks": Array [],
393 + "reactVersion": "<filtered-version>",
394 + "schedulingEvents": Array [],
395 + "snapshotHeight": 0,
396 + "snapshots": Array [],
397 + "startTime": 1,
398 + "suspenseEvents": Array [],
399 + "thrownErrors": Array [],
400 + }
401 + `);
402 + });
403 +
404 + it('should process legacy data format (before lane labels were added)', async () => {
405 + const cpuProfilerSample = creactCpuProfilerSample();
406 +
407 + // Data below is hard-coded based on an older profile sample.
408 + // Should be fine since this is explicitly a legacy-format test.
409 + const data = await preprocessData([
410 + ...createBoilerplateEntries(),
411 + cpuProfilerSample,
412 + createUserTimingEntry({
413 + cat: 'blink.user_timing',
414 + name: '--schedule-render-512-',
415 + }),
416 + createUserTimingEntry({
417 + cat: 'blink.user_timing',
418 + name: '--render-start-512',
419 + }),
420 + createUserTimingEntry({
421 + cat: 'blink.user_timing',
422 + name: '--render-stop',
423 + }),
424 + createUserTimingEntry({
425 + cat: 'blink.user_timing',
426 + name: '--commit-start-512',
427 + }),
428 + createUserTimingEntry({
429 + cat: 'blink.user_timing',
430 + name: '--layout-effects-start-512',
431 + }),
432 + createUserTimingEntry({
433 + cat: 'blink.user_timing',
434 + name: '--layout-effects-stop',
435 + }),
436 + createUserTimingEntry({
437 + cat: 'blink.user_timing',
438 + name: '--commit-stop',
439 + }),
440 + ]);
441 + expect(data).toMatchInlineSnapshot(`
442 + Object {
443 + "batchUIDToMeasuresMap": Map {
444 + 0 => Array [
445 + Object {
446 + "batchUID": 0,
447 + "depth": 0,
448 + "duration": 0.005,
449 + "lanes": "0b0000000000000000000000000001001",
450 + "timestamp": 0.006,
451 + "type": "render-idle",
452 + },
453 + Object {
454 + "batchUID": 0,
455 + "depth": 0,
456 + "duration": 0.001,
457 + "lanes": "0b0000000000000000000000000001001",
458 + "timestamp": 0.006,
459 + "type": "render",
460 + },
461 + Object {
462 + "batchUID": 0,
463 + "depth": 0,
464 + "duration": 0.003,
465 + "lanes": "0b0000000000000000000000000001001",
466 + "timestamp": 0.008,
467 + "type": "commit",
468 + },
469 + Object {
470 + "batchUID": 0,
471 + "depth": 1,
472 + "duration": 0.001,
473 + "lanes": "0b0000000000000000000000000001001",
474 + "timestamp": 0.009,
475 + "type": "layout-effects",
476 + },
477 ],
643 - "timestamp": 0.004,
644 - "type": "render",
478 },
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",
479 + "componentMeasures": Array [],
480 + "duration": 0.011,
481 + "flamechart": Array [],
482 + "internalModuleSourceToRanges": Map {},
483 + "laneToLabelMap": Map {
484 + 0 => "Sync",
485 + 1 => "InputContinuousHydration",
486 + 2 => "InputContinuous",
487 + 3 => "DefaultHydration",
488 + 4 => "Default",
489 + 5 => "TransitionHydration",
490 + 6 => "Transition",
491 + 7 => "Transition",
492 + 8 => "Transition",
493 + 9 => "Transition",
494 + 10 => "Transition",
495 + 11 => "Transition",
496 + 12 => "Transition",
497 + 13 => "Transition",
498 + 14 => "Transition",
499 + 15 => "Transition",
500 + 16 => "Transition",
501 + 17 => "Transition",
502 + 18 => "Transition",
503 + 19 => "Transition",
504 + 20 => "Transition",
505 + 21 => "Transition",
506 + 22 => "Retry",
507 + 23 => "Retry",
508 + 24 => "Retry",
509 + 25 => "Retry",
510 + 26 => "Retry",
511 + 27 => "SelectiveHydration",
512 + 28 => "IdleHydration",
513 + 29 => "Idle",
514 + 30 => "Offscreen",
515 },
656 - Object {
657 - "batchUID": 0,
658 - "depth": 1,
659 - "duration": 0.001,
660 - "lanes": Array [
661 - 0,
516 + "laneToReactMeasureMap": Map {
517 + 0 => Array [],
518 + 1 => Array [],
519 + 2 => Array [],
520 + 3 => Array [],
521 + 4 => Array [],
522 + 5 => Array [],
523 + 6 => Array [],
524 + 7 => Array [],
525 + 8 => Array [],
526 + 9 => Array [
527 + Object {
528 + "batchUID": 0,
529 + "depth": 0,
530 + "duration": 0.005,
531 + "lanes": "0b0000000000000000000000000001001",
532 + "timestamp": 0.006,
533 + "type": "render-idle",
534 + },
535 + Object {
536 + "batchUID": 0,
537 + "depth": 0,
538 + "duration": 0.001,
539 + "lanes": "0b0000000000000000000000000001001",
540 + "timestamp": 0.006,
541 + "type": "render",
542 + },
543 + Object {
544 + "batchUID": 0,
545 + "depth": 0,
546 + "duration": 0.003,
547 + "lanes": "0b0000000000000000000000000001001",
548 + "timestamp": 0.008,
549 + "type": "commit",
550 + },
551 + Object {
552 + "batchUID": 0,
553 + "depth": 1,
554 + "duration": 0.001,
555 + "lanes": "0b0000000000000000000000000001001",
556 + "timestamp": 0.009,
557 + "type": "layout-effects",
558 + },
559 ],
663 - "timestamp": 0.012,
664 - "type": "layout-effects",
560 + 10 => Array [],
561 + 11 => Array [],
562 + 12 => Array [],
563 + 13 => Array [],
564 + 14 => Array [],
565 + 15 => Array [],
566 + 16 => Array [],
567 + 17 => Array [],
568 + 18 => Array [],
569 + 19 => Array [],
570 + 20 => Array [],
571 + 21 => Array [],
572 + 22 => Array [],
573 + 23 => Array [],
574 + 24 => Array [],
575 + 25 => Array [],
576 + 26 => Array [],
577 + 27 => Array [],
578 + 28 => Array [],
579 + 29 => Array [],
580 + 30 => Array [],
581 },
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 - },
582 + "nativeEvents": Array [],
583 + "networkMeasures": Array [],
584 + "otherUserTimingMarks": Array [],
585 + "reactVersion": "<filtered-version>",
586 + "schedulingEvents": Array [
587 Object {
678 - "functionName": "dule-stop-<filtered-file-system-path>",
588 + "lanes": "0b0000000000000000000000000001001",
589 + "timestamp": 0.005,
590 + "type": "schedule-render",
591 + "warning": null,
592 },
593 ],
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,
594 + "snapshotHeight": 0,
595 + "snapshots": Array [],
596 + "startTime": 1,
597 + "suspenseEvents": Array [],
598 + "thrownErrors": Array [],
599 + }
600 + `);
601 + });
602 +
603 + it('should process a sample legacy render sequence', async () => {
604 + utils.legacyRender(<div />, document.createElement('div'));
605 +
606 + const data = await preprocessData([
607 + ...createBoilerplateEntries(),
608 + ...createUserTimingData(clearedMarks),
609 + ]);
610 + expect(data).toMatchInlineSnapshot(`
611 + Object {
612 + "batchUIDToMeasuresMap": Map {
613 + 0 => Array [
614 + Object {
615 + "batchUID": 0,
616 + "depth": 0,
617 + "duration": 0.01,
618 + "lanes": "0b0000000000000000000000000000000",
619 + "timestamp": 0.006,
620 + "type": "render-idle",
621 + },
622 + Object {
623 + "batchUID": 0,
624 + "depth": 0,
625 + "duration": 0.001,
626 + "lanes": "0b0000000000000000000000000000000",
627 + "timestamp": 0.006,
628 + "type": "render",
629 + },
630 + Object {
631 + "batchUID": 0,
632 + "depth": 0,
633 + "duration": 0.008,
634 + "lanes": "0b0000000000000000000000000000000",
635 + "timestamp": 0.008,
636 + "type": "commit",
637 + },
638 + Object {
639 + "batchUID": 0,
640 + "depth": 1,
641 + "duration": 0.001,
642 + "lanes": "0b0000000000000000000000000000000",
643 + "timestamp": 0.014,
644 + "type": "layout-effects",
645 + },
646 ],
725 - "timestamp": 0.004,
726 - "type": "render-idle",
647 },
728 - Object {
729 - "batchUID": 0,
730 - "depth": 0,
731 - "duration": 0.001,
732 - "lanes": Array [
733 - 0,
648 + "componentMeasures": Array [],
649 + "duration": 0.016,
650 + "flamechart": Array [],
651 + "internalModuleSourceToRanges": Map {
652 + undefined => Array [
653 + Array [
654 + Object {
655 + "columnNumber": 0,
656 + "functionName": "filtered",
657 + "lineNumber": 0,
658 + "source": " at filtered (<anonymous>:0:0)",
659 + },
660 + Object {
661 + "columnNumber": 1,
662 + "functionName": "filtered",
663 + "lineNumber": 1,
664 + "source": " at filtered (<anonymous>:1:1)",
665 + },
666 + ],
667 ],
735 - "timestamp": 0.004,
736 - "type": "render",
668 },
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",
669 + "laneToLabelMap": Map {
670 + 0 => "Sync",
671 + 1 => "InputContinuousHydration",
672 + 2 => "InputContinuous",
673 + 3 => "DefaultHydration",
674 + 4 => "Default",
675 + 5 => "TransitionHydration",
676 + 6 => "Transition",
677 + 7 => "Transition",
678 + 8 => "Transition",
679 + 9 => "Transition",
680 + 10 => "Transition",
681 + 11 => "Transition",
682 + 12 => "Transition",
683 + 13 => "Transition",
684 + 14 => "Transition",
685 + 15 => "Transition",
686 + 16 => "Transition",
687 + 17 => "Transition",
688 + 18 => "Transition",
689 + 19 => "Transition",
690 + 20 => "Transition",
691 + 21 => "Transition",
692 + 22 => "Retry",
693 + 23 => "Retry",
694 + 24 => "Retry",
695 + 25 => "Retry",
696 + 26 => "Retry",
697 + 27 => "SelectiveHydration",
698 + 28 => "IdleHydration",
699 + 29 => "Idle",
700 + 30 => "Offscreen",
701 },
748 - Object {
749 - "batchUID": 0,
750 - "depth": 1,
751 - "duration": 0.001,
752 - "lanes": Array [
753 - 0,
702 + "laneToReactMeasureMap": Map {
703 + 0 => Array [
704 + Object {
705 + "batchUID": 0,
706 + "depth": 0,
707 + "duration": 0.01,
708 + "lanes": "0b0000000000000000000000000000000",
709 + "timestamp": 0.006,
710 + "type": "render-idle",
711 + },
712 + Object {
713 + "batchUID": 0,
714 + "depth": 0,
715 + "duration": 0.001,
716 + "lanes": "0b0000000000000000000000000000000",
717 + "timestamp": 0.006,
718 + "type": "render",
719 + },
720 + Object {
721 + "batchUID": 0,
722 + "depth": 0,
723 + "duration": 0.008,
724 + "lanes": "0b0000000000000000000000000000000",
725 + "timestamp": 0.008,
726 + "type": "commit",
727 + },
728 + Object {
729 + "batchUID": 0,
730 + "depth": 1,
731 + "duration": 0.001,
732 + "lanes": "0b0000000000000000000000000000000",
733 + "timestamp": 0.014,
734 + "type": "layout-effects",
735 + },
736 ],
755 - "timestamp": 0.012,
756 - "type": "layout-effects",
737 + 1 => Array [],
738 + 2 => Array [],
739 + 3 => Array [],
740 + 4 => Array [],
741 + 5 => Array [],
742 + 6 => Array [],
743 + 7 => Array [],
744 + 8 => Array [],
745 + 9 => Array [],
746 + 10 => Array [],
747 + 11 => Array [],
748 + 12 => Array [],
749 + 13 => Array [],
750 + 14 => Array [],
751 + 15 => Array [],
752 + 16 => Array [],
753 + 17 => Array [],
754 + 18 => Array [],
755 + 19 => Array [],
756 + 20 => Array [],
757 + 21 => Array [],
758 + 22 => Array [],
759 + 23 => Array [],
760 + 24 => Array [],
761 + 25 => Array [],
762 + 26 => Array [],
763 + 27 => Array [],
764 + 28 => Array [],
765 + 29 => Array [],
766 + 30 => Array [],
767 },
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,
768 + "nativeEvents": Array [],
769 + "networkMeasures": Array [],
770 + "otherUserTimingMarks": Array [],
771 + "reactVersion": "<filtered-version>",
772 + "schedulingEvents": Array [
773 + Object {
774 + "lanes": "0b0000000000000000000000000000000",
775 + "timestamp": 0.005,
776 + "type": "schedule-render",
777 + "warning": null,
778 + },
779 ],
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);
780 + "snapshotHeight": 0,
781 + "snapshots": Array [],
782 + "startTime": 4,
783 + "suspenseEvents": Array [],
784 + "thrownErrors": Array [],
785 }
820 - });
821 - return true;
822 - }
786 + `);
787 + });
788
824 - const root = ReactDOM.createRoot(document.createElement('div'));
825 - utils.act(() => root.render(<App />));
789 + it('should process a sample createRoot render sequence', async () => {
790 + function App() {
791 + const [didMount, setDidMount] = React.useState(false);
792 + React.useEffect(() => {
793 + if (!didMount) {
794 + setDidMount(true);
795 + }
796 + });
797 + return true;
798 + }
799
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.001,
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.012,
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.003,
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.008,
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.001,
921 - "lanes": Array [
922 - 4,
800 + const root = ReactDOM.createRoot(document.createElement('div'));
801 + utils.act(() => root.render(<App />));
802 +
803 + const data = await preprocessData([
804 + ...createBoilerplateEntries(),
805 + ...createUserTimingData(clearedMarks),
806 + ]);
807 + expect(data).toMatchInlineSnapshot(`
808 + Object {
809 + "batchUIDToMeasuresMap": Map {
810 + 0 => Array [
811 + Object {
812 + "batchUID": 0,
813 + "depth": 0,
814 + "duration": 0.012,
815 + "lanes": "0b0000000000000000000000000000100",
816 + "timestamp": 0.006,
817 + "type": "render-idle",
818 + },
819 + Object {
820 + "batchUID": 0,
821 + "depth": 0,
822 + "duration": 0.003,
823 + "lanes": "0b0000000000000000000000000000100",
824 + "timestamp": 0.006,
825 + "type": "render",
826 + },
827 + Object {
828 + "batchUID": 0,
829 + "depth": 0,
830 + "duration": 0.008,
831 + "lanes": "0b0000000000000000000000000000100",
832 + "timestamp": 0.01,
833 + "type": "commit",
834 + },
835 + Object {
836 + "batchUID": 0,
837 + "depth": 1,
838 + "duration": 0.001,
839 + "lanes": "0b0000000000000000000000000000100",
840 + "timestamp": 0.016,
841 + "type": "layout-effects",
842 + },
843 + Object {
844 + "batchUID": 0,
845 + "depth": 0,
846 + "duration": 0.004,
847 + "lanes": "0b0000000000000000000000000000100",
848 + "timestamp": 0.019,
849 + "type": "passive-effects",
850 + },
851 ],
924 - "timestamp": 0.032,
925 - "type": "layout-effects",
926 - },
927 - Object {
928 - "batchUID": 1,
929 - "depth": 0,
930 - "duration": 0.003,
931 - "lanes": Array [
932 - 4,
852 + 1 => Array [
853 + Object {
854 + "batchUID": 1,
855 + "depth": 0,
856 + "duration": 0.012,
857 + "lanes": "0b0000000000000000000000000000100",
858 + "timestamp": 0.024,
859 + "type": "render-idle",
860 + },
861 + Object {
862 + "batchUID": 1,
863 + "depth": 0,
864 + "duration": 0.003,
865 + "lanes": "0b0000000000000000000000000000100",
866 + "timestamp": 0.024,
867 + "type": "render",
868 + },
869 + Object {
870 + "batchUID": 1,
871 + "depth": 0,
872 + "duration": 0.008,
873 + "lanes": "0b0000000000000000000000000000100",
874 + "timestamp": 0.028,
875 + "type": "commit",
876 + },
877 + Object {
878 + "batchUID": 1,
879 + "depth": 1,
880 + "duration": 0.001,
881 + "lanes": "0b0000000000000000000000000000100",
882 + "timestamp": 0.034,
883 + "type": "layout-effects",
884 + },
885 + Object {
886 + "batchUID": 1,
887 + "depth": 0,
888 + "duration": 0.003,
889 + "lanes": "0b0000000000000000000000000000100",
890 + "timestamp": 0.037,
891 + "type": "passive-effects",
892 + },
893 ],
934 - "timestamp": 0.035,
935 - "type": "passive-effects",
894 },
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.002,
950 - "timestamp": 0.018,
951 - "type": "passive-effect-mount",
952 - "warning": null,
953 - },
954 - Object {
955 - "componentName": "App",
956 - "duration": 0.001,
957 - "timestamp": 0.023,
958 - "type": "render",
959 - "warning": null,
960 - },
961 - Object {
962 - "componentName": "App",
963 - "duration": 0.001,
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 [
895 + "componentMeasures": Array [
896 Object {
975 - "functionName": "<filtered-file-system-path>",
897 + "componentName": "App",
898 + "duration": 0.001,
899 + "timestamp": 0.007,
900 + "type": "render",
901 + "warning": null,
902 },
903 Object {
978 - "functionName": "dule-stop-<filtered-file-system-path>",
904 + "componentName": "App",
905 + "duration": 0.002,
906 + "timestamp": 0.02,
907 + "type": "passive-effect-mount",
908 + "warning": null,
909 + },
910 + Object {
911 + "componentName": "App",
912 + "duration": 0.001,
913 + "timestamp": 0.025,
914 + "type": "render",
915 + "warning": null,
916 + },
917 + Object {
918 + "componentName": "App",
919 + "duration": 0.001,
920 + "timestamp": 0.038,
921 + "type": "passive-effect-mount",
922 + "warning": null,
923 },
924 ],
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.001,
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.012,
1076 - "lanes": Array [
1077 - 4,
925 + "duration": 0.04,
926 + "flamechart": Array [],
927 + "internalModuleSourceToRanges": Map {
928 + undefined => Array [
929 + Array [
930 + Object {
931 + "columnNumber": 0,
932 + "functionName": "filtered",
933 + "lineNumber": 0,
934 + "source": " at filtered (<anonymous>:0:0)",
935 + },
936 + Object {
937 + "columnNumber": 1,
938 + "functionName": "filtered",
939 + "lineNumber": 1,
940 + "source": " at filtered (<anonymous>:1:1)",
941 + },
942 + ],
943 ],
1079 - "timestamp": 0.022,
1080 - "type": "render-idle",
944 },
1082 - Object {
1083 - "batchUID": 1,
1084 - "depth": 0,
1085 - "duration": 0.003,
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.008,
1096 - "lanes": Array [
1097 - 4,
1098 - ],
1099 - "timestamp": 0.026,
1100 - "type": "commit",
945 + "laneToLabelMap": Map {
946 + 0 => "Sync",
947 + 1 => "InputContinuousHydration",
948 + 2 => "InputContinuous",
949 + 3 => "DefaultHydration",
950 + 4 => "Default",
951 + 5 => "TransitionHydration",
952 + 6 => "Transition",
953 + 7 => "Transition",
954 + 8 => "Transition",
955 + 9 => "Transition",
956 + 10 => "Transition",
957 + 11 => "Transition",
958 + 12 => "Transition",
959 + 13 => "Transition",
960 + 14 => "Transition",
961 + 15 => "Transition",
962 + 16 => "Transition",
963 + 17 => "Transition",
964 + 18 => "Transition",
965 + 19 => "Transition",
966 + 20 => "Transition",
967 + 21 => "Transition",
968 + 22 => "Retry",
969 + 23 => "Retry",
970 + 24 => "Retry",
971 + 25 => "Retry",
972 + 26 => "Retry",
973 + 27 => "SelectiveHydration",
974 + 28 => "IdleHydration",
975 + 29 => "Idle",
976 + 30 => "Offscreen",
977 },
1102 - Object {
1103 - "batchUID": 1,
1104 - "depth": 1,
1105 - "duration": 0.001,
1106 - "lanes": Array [
1107 - 4,
978 + "laneToReactMeasureMap": Map {
979 + 0 => Array [],
980 + 1 => Array [],
981 + 2 => Array [],
982 + 3 => Array [],
983 + 4 => Array [
984 + Object {
985 + "batchUID": 0,
986 + "depth": 0,
987 + "duration": 0.012,
988 + "lanes": "0b0000000000000000000000000000100",
989 + "timestamp": 0.006,
990 + "type": "render-idle",
991 + },
992 + Object {
993 + "batchUID": 0,
994 + "depth": 0,
995 + "duration": 0.003,
996 + "lanes": "0b0000000000000000000000000000100",
997 + "timestamp": 0.006,
998 + "type": "render",
999 + },
1000 + Object {
1001 + "batchUID": 0,
1002 + "depth": 0,
1003 + "duration": 0.008,
1004 + "lanes": "0b0000000000000000000000000000100",
1005 + "timestamp": 0.01,
1006 + "type": "commit",
1007 + },
1008 + Object {
1009 + "batchUID": 0,
1010 + "depth": 1,
1011 + "duration": 0.001,
1012 + "lanes": "0b0000000000000000000000000000100",
1013 + "timestamp": 0.016,
1014 + "type": "layout-effects",
1015 + },
1016 + Object {
1017 + "batchUID": 0,
1018 + "depth": 0,
1019 + "duration": 0.004,
1020 + "lanes": "0b0000000000000000000000000000100",
1021 + "timestamp": 0.019,
1022 + "type": "passive-effects",
1023 + },
1024 + Object {
1025 + "batchUID": 1,
1026 + "depth": 0,
1027 + "duration": 0.012,
1028 + "lanes": "0b0000000000000000000000000000100",
1029 + "timestamp": 0.024,
1030 + "type": "render-idle",
1031 + },
1032 + Object {
1033 + "batchUID": 1,
1034 + "depth": 0,
1035 + "duration": 0.003,
1036 + "lanes": "0b0000000000000000000000000000100",
1037 + "timestamp": 0.024,
1038 + "type": "render",
1039 + },
1040 + Object {
1041 + "batchUID": 1,
1042 + "depth": 0,
1043 + "duration": 0.008,
1044 + "lanes": "0b0000000000000000000000000000100",
1045 + "timestamp": 0.028,
1046 + "type": "commit",
1047 + },
1048 + Object {
1049 + "batchUID": 1,
1050 + "depth": 1,
1051 + "duration": 0.001,
1052 + "lanes": "0b0000000000000000000000000000100",
1053 + "timestamp": 0.034,
1054 + "type": "layout-effects",
1055 + },
1056 + Object {
1057 + "batchUID": 1,
1058 + "depth": 0,
1059 + "duration": 0.003,
1060 + "lanes": "0b0000000000000000000000000000100",
1061 + "timestamp": 0.037,
1062 + "type": "passive-effects",
1063 + },
1064 ],
1109 - "timestamp": 0.032,
1110 - "type": "layout-effects",
1065 + 5 => Array [],
1066 + 6 => Array [],
1067 + 7 => Array [],
1068 + 8 => Array [],
1069 + 9 => Array [],
1070 + 10 => Array [],
1071 + 11 => Array [],
1072 + 12 => Array [],
1073 + 13 => Array [],
1074 + 14 => Array [],
1075 + 15 => Array [],
1076 + 16 => Array [],
1077 + 17 => Array [],
1078 + 18 => Array [],
1079 + 19 => Array [],
1080 + 20 => Array [],
1081 + 21 => Array [],
1082 + 22 => Array [],
1083 + 23 => Array [],
1084 + 24 => Array [],
1085 + 25 => Array [],
1086 + 26 => Array [],
1087 + 27 => Array [],
1088 + 28 => Array [],
1089 + 29 => Array [],
1090 + 30 => Array [],
1091 },
1112 - Object {
1113 - "batchUID": 1,
1114 - "depth": 0,
1115 - "duration": 0.003,
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,
1092 + "nativeEvents": Array [],
1093 + "networkMeasures": Array [],
1094 + "otherUserTimingMarks": Array [],
1095 + "reactVersion": "<filtered-version>",
1096 + "schedulingEvents": Array [
1097 + Object {
1098 + "lanes": "0b0000000000000000000000000000100",
1099 + "timestamp": 0.005,
1100 + "type": "schedule-render",
1101 + "warning": null,
1102 + },
1103 + Object {
1104 + "componentName": "App",
1105 + "lanes": "0b0000000000000000000000000000100",
1106 + "timestamp": 0.021,
1107 + "type": "schedule-state-update",
1108 + "warning": null,
1109 + },
1110 ],
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 - });
1111 + "snapshotHeight": 0,
1112 + "snapshots": Array [],
1113 + "startTime": 4,
1114 + "suspenseEvents": Array [],
1115 + "thrownErrors": Array [],
1116 + }
1117 + `);
1118 + });
1119
1196 - it('should error if work is completed without being started', async () => {
1197 - const container = document.createElement('div');
1198 - utils.legacyRender(<div />, container);
1120 + it('should error if events and measures are incomplete', async () => {
1121 + const container = document.createElement('div');
1122 + utils.legacyRender(<div />, container);
1123 +
1124 + const invalidMarks = clearedMarks.filter(
1125 + mark => !mark.includes('render-stop'),
1126 + );
1127 + const invalidUserTimingData = createUserTimingData(invalidMarks);
1128 +
1129 + const error = spyOn(console, 'error');
1130 + preprocessData([
1131 + ...createBoilerplateEntries(),
1132 + ...invalidUserTimingData,
1133 + ]);
1134 + expect(error).toHaveBeenCalled();
1135 + });
1136
1200 - const invalidMarks = clearedMarks.filter(
1201 - mark => !mark.includes('render-start'),
1202 - );
1203 - const invalidUserTimingData = createUserTimingData(invalidMarks);
1137 + it('should error if work is completed without being started', async () => {
1138 + const container = document.createElement('div');
1139 + utils.legacyRender(<div />, container);
1140 +
1141 + const invalidMarks = clearedMarks.filter(
1142 + mark => !mark.includes('render-start'),
1143 + );
1144 + const invalidUserTimingData = createUserTimingData(invalidMarks);
1145 +
1146 + const error = spyOn(console, 'error');
1147 + preprocessData([
1148 + ...createBoilerplateEntries(),
1149 + ...invalidUserTimingData,
1150 + ]);
1151 + expect(error).toHaveBeenCalled();
1152 + });
1153
1205 - const error = spyOn(console, 'error');
1206 - preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
1207 - expect(error).toHaveBeenCalled();
1208 - });
1154 + it('should populate other user timing marks', async () => {
1155 + const userTimingData = createUserTimingData([]);
1156 + userTimingData.push(
1157 + createUserTimingEntry({
1158 + args: {},
1159 + cat: 'blink.user_timing',
1160 + id: '0xcdf75f7c',
1161 + name: 'VCWithoutImage: root',
1162 + ph: 'n',
1163 + scope: 'blink.user_timing',
1164 + }),
1165 + );
1166 + userTimingData.push(
1167 + createUserTimingEntry({
1168 + cat: 'blink.user_timing',
1169 + name: '--a-mark-that-looks-like-one-of-ours',
1170 + ph: 'R',
1171 + }),
1172 + );
1173 + userTimingData.push(
1174 + createUserTimingEntry({
1175 + cat: 'blink.user_timing',
1176 + name: 'Some other mark',
1177 + ph: 'R',
1178 + }),
1179 + );
1180 +
1181 + const data = await preprocessData([
1182 + ...createBoilerplateEntries(),
1183 + ...userTimingData,
1184 + ]);
1185 + expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
1186 + Array [
1187 + Object {
1188 + "name": "VCWithoutImage: root",
1189 + "timestamp": 0.003,
1190 + },
1191 + Object {
1192 + "name": "--a-mark-that-looks-like-one-of-ours",
1193 + "timestamp": 0.004,
1194 + },
1195 + Object {
1196 + "name": "Some other mark",
1197 + "timestamp": 0.005,
1198 + },
1199 + ]
1200 + `);
1201 + });
1202
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 - });
1203 + it('should include a suspended resource "displayName" if one is set', async () => {
1204 + let promise = null;
1205 + let resolvedValue = null;
1206 + function readValue(value) {
1207 + if (resolvedValue !== null) {
1208 + return resolvedValue;
1209 + } else if (promise === null) {
1210 + promise = Promise.resolve(true).then(() => {
1211 + resolvedValue = value;
1212 + });
1213 + promise.displayName = 'Testing displayName';
1214 + }
1215 + throw promise;
1216 + }
1217
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';
1218 + function Component() {
1219 + const value = readValue(123);
1220 + return value;
1221 }
1271 - throw promise;
1272 - }
1222
1274 - function Component() {
1275 - const value = readValue(123);
1276 - return value;
1277 - }
1223 + const testMarks = [creactCpuProfilerSample()];
1224
1279 - const testMarks = [creactCpuProfilerSample()];
1225 + const root = ReactDOM.createRoot(document.createElement('div'));
1226 + utils.act(() =>
1227 + root.render(
1228 + <React.Suspense fallback="Loading...">
1229 + <Component />
1230 + </React.Suspense>,
1231 + ),
1232 + );
1233
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);
1234 + testMarks.push(...createUserTimingData(clearedMarks));
1235 +
1236 + let data;
1237 + await utils.actAsync(async () => {
1238 + data = await preprocessData(testMarks);
1239 + });
1240 + expect(data.suspenseEvents).toHaveLength(1);
1241 + expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1242 });
1296 - expect(data.suspenseEvents).toHaveLength(1);
1297 - expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1298 - });
1243
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 - }
1244 + describe('warnings', () => {
1245 + describe('long event handlers', () => {
1246 + it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
1247 + function App() {
1248 + return null;
1249 + }
1250
1307 - const testMarks = [
1308 - creactCpuProfilerSample(),
1309 - ...createBoilerplateEntries(),
1310 - createNativeEventEntry('click', 5),
1311 - ];
1251 + const testMarks = [
1252 + creactCpuProfilerSample(),
1253 + ...createBoilerplateEntries(),
1254 + createNativeEventEntry('click', 5),
1255 + ];
1256
1313 - clearPendingMarks();
1257 + clearPendingMarks();
1258
1315 - utils.legacyRender(<App />, document.createElement('div'));
1259 + utils.legacyRender(<App />, document.createElement('div'));
1260
1317 - testMarks.push(...createUserTimingData(clearedMarks));
1261 + testMarks.push(...createUserTimingData(clearedMarks));
1262
1319 - const data = await preprocessData(testMarks);
1320 - const event = data.nativeEvents.find(({type}) => type === 'click');
1321 - expect(event.warning).toBe(null);
1322 - });
1263 + const data = await preprocessData(testMarks);
1264 + const event = data.nativeEvents.find(({type}) => type === 'click');
1265 + expect(event.warning).toBe(null);
1266 + });
1267
1324 - it('should not warn about long events if the cause was non-React JavaScript', async () => {
1325 - function App() {
1326 - return null;
1327 - }
1268 + it('should not warn about long events if the cause was non-React JavaScript', async () => {
1269 + function App() {
1270 + return null;
1271 + }
1272
1329 - const testMarks = [
1330 - creactCpuProfilerSample(),
1331 - ...createBoilerplateEntries(),
1332 - createNativeEventEntry('click', 25000),
1333 - ];
1273 + const testMarks = [
1274 + creactCpuProfilerSample(),
1275 + ...createBoilerplateEntries(),
1276 + createNativeEventEntry('click', 25000),
1277 + ];
1278
1335 - startTime += 2000;
1279 + startTime += 2000;
1280
1337 - clearPendingMarks();
1281 + clearPendingMarks();
1282
1339 - utils.legacyRender(<App />, document.createElement('div'));
1283 + utils.legacyRender(<App />, document.createElement('div'));
1284
1341 - testMarks.push(...createUserTimingData(clearedMarks));
1285 + testMarks.push(...createUserTimingData(clearedMarks));
1286
1343 - const data = await preprocessData(testMarks);
1344 - const event = data.nativeEvents.find(({type}) => type === 'click');
1345 - expect(event.warning).toBe(null);
1346 - });
1287 + const data = await preprocessData(testMarks);
1288 + const event = data.nativeEvents.find(({type}) => type === 'click');
1289 + expect(event.warning).toBe(null);
1290 + });
1291
1348 - it('should warn when React scedules a long (sync) update inside of an event', async () => {
1349 - function App() {
1350 - return null;
1351 - }
1292 + it('should warn when React scedules a long (sync) update inside of an event', async () => {
1293 + function App() {
1294 + return null;
1295 + }
1296
1353 - const testMarks = [
1354 - creactCpuProfilerSample(),
1355 - ...createBoilerplateEntries(),
1356 - createNativeEventEntry('click', 25000),
1357 - ];
1297 + const testMarks = [
1298 + creactCpuProfilerSample(),
1299 + ...createBoilerplateEntries(),
1300 + createNativeEventEntry('click', 25000),
1301 + ];
1302
1359 - clearPendingMarks();
1303 + clearPendingMarks();
1304
1361 - utils.legacyRender(<App />, document.createElement('div'));
1305 + utils.legacyRender(<App />, document.createElement('div'));
1306
1363 - clearedMarks.forEach(markName => {
1364 - if (markName === '--render-stop') {
1365 - // Fake a long running render
1366 - startTime += 20000;
1367 - }
1307 + clearedMarks.forEach(markName => {
1308 + if (markName === '--render-stop') {
1309 + // Fake a long running render
1310 + startTime += 20000;
1311 + }
1312
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',
1313 + testMarks.push({
1314 + pid: ++pid,
1315 + tid: ++tid,
1316 + ts: ++startTime,
1317 + args: {data: {}},
1318 + cat: 'blink.user_timing',
1319 + name: markName,
1320 + ph: 'R',
1321 + });
1322 });
1378 - });
1323
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 - });
1324 + const data = await preprocessData(testMarks);
1325 + const event = data.nativeEvents.find(({type}) => type === 'click');
1326 + expect(event.warning).toMatchInlineSnapshot(
1327 + `"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
1328 + );
1329 + });
1330
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 - }
1331 + it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
1332 + function Yield({id, value}) {
1333 + Scheduler.unstable_yieldValue(`${id}:${value}`);
1334 + return null;
1335 + }
1336
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']);
1337 + const testMarks = [
1338 + creactCpuProfilerSample(),
1339 + ...createBoilerplateEntries(),
1340 + ];
1341
1415 - testMarks.push(...createUserTimingData(clearedMarks));
1416 - clearPendingMarks();
1342 + // Advance the clock by some arbitrary amount.
1343 + startTime += 50000;
1344
1418 - // Advance the clock some more to make the pending React update seem long.
1419 - startTime += 20000;
1345 + const root = ReactDOM.createRoot(document.createElement('div'));
1346
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(() => {
1347 + // Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
1348 + global.IS_REACT_ACT_ENVIRONMENT = false;
1349 + React.startTransition(() => {
1350 + // Start rendering an async update (but don't finish).
1351 root.render(
1352 <>
1427 - <Yield id="A" value={2} />
1428 - <Yield id="B" value={2} />
1353 + <Yield id="A" value={1} />
1354 + <Yield id="B" value={1} />
1355 </>,
1356 );
1431 - });
1432 - });
1433 -
1434 - expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
1357 + expect(Scheduler).toFlushAndYieldThrough(['A:1']);
1358
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 - });
1359 + testMarks.push(...createUserTimingData(clearedMarks));
1360 + clearPendingMarks();
1361
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 - }
1362 + // Advance the clock some more to make the pending React update seem long.
1363 + startTime += 20000;
1364
1457 - const root = ReactDOM.createRoot(document.createElement('div'));
1458 - utils.act(() => {
1459 - root.render(<Component />);
1460 - });
1365 + // Fake a long "click" event in the middle
1366 + // and schedule a sync update that will also flush the previous work.
1367 + testMarks.push(createNativeEventEntry('click', 25000));
1368 + ReactDOM.flushSync(() => {
1369 + root.render(
1370 + <>
1371 + <Yield id="A" value={2} />
1372 + <Yield id="B" value={2} />
1373 + </>,
1374 + );
1375 + });
1376 + });
1377
1462 - expect(Scheduler).toHaveYielded([
1463 - 'Component mount',
1464 - 'Component update',
1465 - ]);
1378 + expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
1379
1467 - const data = await preprocessData([
1468 - ...createBoilerplateEntries(),
1469 - ...createUserTimingData(clearedMarks),
1470 - ]);
1380 + testMarks.push(...createUserTimingData(clearedMarks));
1381
1472 - const event = data.schedulingEvents.find(
1473 - ({type}) => type === 'schedule-state-update',
1474 - );
1475 - expect(event.warning).toBe(null);
1382 + const data = await preprocessData(testMarks);
1383 + const event = data.nativeEvents.find(({type}) => type === 'click');
1384 + expect(event.warning).toBe(null);
1385 + });
1386 });
1387
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() {
1388 + describe('nested updates', () => {
1389 + it('should not warn about short nested (state) updates during layout effects', async () => {
1390 + function Component() {
1391 + const [didMount, setDidMount] = React.useState(false);
1392 Scheduler.unstable_yieldValue(
1487 - `Component ${this._didMount ? 'update' : 'mount'}`,
1393 + `Component ${didMount ? 'update' : 'mount'}`,
1394 );
1489 - return null;
1395 + React.useLayoutEffect(() => {
1396 + setDidMount(true);
1397 + }, []);
1398 + return didMount;
1399 }
1491 - }
1492 -
1493 - const root = ReactDOM.createRoot(document.createElement('div'));
1494 - utils.act(() => {
1495 - root.render(<Component />);
1496 - });
1400
1498 - expect(Scheduler).toHaveYielded([
1499 - 'Component mount',
1500 - 'Component update',
1501 - ]);
1401 + const root = ReactDOM.createRoot(document.createElement('div'));
1402 + utils.act(() => {
1403 + root.render(<Component />);
1404 + });
1405
1503 - const data = await preprocessData([
1504 - ...createBoilerplateEntries(),
1505 - ...createUserTimingData(clearedMarks),
1506 - ]);
1406 + expect(Scheduler).toHaveYielded([
1407 + 'Component mount',
1408 + 'Component update',
1409 + ]);
1410
1508 - const event = data.schedulingEvents.find(
1509 - ({type}) => type === 'schedule-force-update',
1510 - );
1511 - expect(event.warning).toBe(null);
1512 - });
1411 + const data = await preprocessData([
1412 + ...createBoilerplateEntries(),
1413 + ...createUserTimingData(clearedMarks),
1414 + ]);
1415
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'}`,
1416 + const event = data.schedulingEvents.find(
1417 + ({type}) => type === 'schedule-state-update',
1418 );
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 />);
1419 + expect(event.warning).toBe(null);
1420 });
1421
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;
1422 + it('should not warn about short (forced) updates during layout effects', async () => {
1423 + class Component extends React.Component {
1424 + _didMount: boolean = false;
1425 + componentDidMount() {
1426 + this._didMount = true;
1427 + this.forceUpdate();
1428 + }
1429 + render() {
1430 + Scheduler.unstable_yieldValue(
1431 + `Component ${this._didMount ? 'update' : 'mount'}`,
1432 + );
1433 + return null;
1434 + }
1435 }
1436
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',
1437 + const root = ReactDOM.createRoot(document.createElement('div'));
1438 + utils.act(() => {
1439 + root.render(<Component />);
1440 });
1556 - });
1441
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 - });
1442 + expect(Scheduler).toHaveYielded([
1443 + 'Component mount',
1444 + 'Component update',
1445 + ]);
1446
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() {
1447 + const data = await preprocessData([
1448 + ...createBoilerplateEntries(),
1449 + ...createUserTimingData(clearedMarks),
1450 + ]);
1451 +
1452 + const event = data.schedulingEvents.find(
1453 + ({type}) => type === 'schedule-force-update',
1454 + );
1455 + expect(event.warning).toBe(null);
1456 + });
1457 +
1458 + it('should warn about long nested (state) updates during layout effects', async () => {
1459 + function Component() {
1460 + const [didMount, setDidMount] = React.useState(false);
1461 Scheduler.unstable_yieldValue(
1581 - `Component ${this._didMount ? 'update' : 'mount'}`,
1462 + `Component ${didMount ? 'update' : 'mount'}`,
1463 );
1583 - return null;
1464 + // Fake a long render
1465 + startTime += 20000;
1466 + React.useLayoutEffect(() => {
1467 + setDidMount(true);
1468 + }, []);
1469 + return didMount;
1470 }
1585 - }
1471
1587 - const cpuProfilerSample = creactCpuProfilerSample();
1472 + const cpuProfilerSample = creactCpuProfilerSample();
1473
1589 - const root = ReactDOM.createRoot(document.createElement('div'));
1590 - utils.act(() => {
1591 - root.render(<Component />);
1592 - });
1474 + const root = ReactDOM.createRoot(document.createElement('div'));
1475 + utils.act(() => {
1476 + root.render(<Component />);
1477 + });
1478
1594 - expect(Scheduler).toHaveYielded([
1595 - 'Component mount',
1596 - 'Component update',
1597 - ]);
1479 + expect(Scheduler).toHaveYielded([
1480 + 'Component mount',
1481 + 'Component update',
1482 + ]);
1483
1599 - const testMarks = [];
1600 - clearedMarks.forEach(markName => {
1601 - if (markName === '--component-render-start-Component') {
1602 - // Fake a long running render
1603 - startTime += 20000;
1604 - }
1484 + const testMarks = [];
1485 + clearedMarks.forEach(markName => {
1486 + if (markName === '--component-render-start-Component') {
1487 + // Fake a long running render
1488 + startTime += 20000;
1489 + }
1490
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',
1491 + testMarks.push({
1492 + pid: ++pid,
1493 + tid: ++tid,
1494 + ts: ++startTime,
1495 + args: {data: {}},
1496 + cat: 'blink.user_timing',
1497 + name: markName,
1498 + ph: 'R',
1499 + });
1500 });
1615 - });
1501
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();
1502 + const data = await preprocessData([
1503 + cpuProfilerSample,
1504 + ...createBoilerplateEntries(),
1505 + ...testMarks,
1506 + ]);
1507
1637 - Scheduler.unstable_yieldValue(
1638 - `Component rendered with value ${value}`,
1508 + const event = data.schedulingEvents.find(
1509 + ({type}) => type === 'schedule-state-update',
1510 + );
1511 + expect(event.warning).toMatchInlineSnapshot(
1512 + `"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)."`,
1513 );
1514 + });
1515
1641 - // Fake a long render
1642 - if (value !== 0) {
1643 - Scheduler.unstable_yieldValue('Long render');
1644 - startTime += 20000;
1516 + it('should warn about long nested (forced) updates during layout effects', async () => {
1517 + class Component extends React.Component {
1518 + _didMount: boolean = false;
1519 + componentDidMount() {
1520 + this._didMount = true;
1521 + this.forceUpdate();
1522 + }
1523 + render() {
1524 + Scheduler.unstable_yieldValue(
1525 + `Component ${this._didMount ? 'update' : 'mount'}`,
1526 + );
1527 + return null;
1528 + }
1529 }
1530
1647 - React.useLayoutEffect(() => {
1648 - startTransition(() => {
1649 - setValue(1);
1650 - });
1651 - }, []);
1652 -
1653 - return value;
1654 - }
1531 + const cpuProfilerSample = creactCpuProfilerSample();
1532
1656 - const cpuProfilerSample = creactCpuProfilerSample();
1533 + const root = ReactDOM.createRoot(document.createElement('div'));
1534 + utils.act(() => {
1535 + root.render(<Component />);
1536 + });
1537
1658 - const root = ReactDOM.createRoot(document.createElement('div'));
1659 - utils.act(() => {
1660 - root.render(<Component />);
1661 - });
1538 + expect(Scheduler).toHaveYielded([
1539 + 'Component mount',
1540 + 'Component update',
1541 + ]);
1542
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 - }
1543 + const testMarks = [];
1544 + clearedMarks.forEach(markName => {
1545 + if (markName === '--component-render-start-Component') {
1546 + // Fake a long running render
1547 + startTime += 20000;
1548 + }
1549
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',
1550 + testMarks.push({
1551 + pid: ++pid,
1552 + tid: ++tid,
1553 + ts: ++startTime,
1554 + args: {data: {}},
1555 + cat: 'blink.user_timing',
1556 + name: markName,
1557 + ph: 'R',
1558 + });
1559 });
1686 - });
1560
1688 - const data = await preprocessData([
1689 - cpuProfilerSample,
1690 - ...createBoilerplateEntries(),
1691 - ...testMarks,
1692 - ]);
1561 + const data = await preprocessData([
1562 + cpuProfilerSample,
1563 + ...createBoilerplateEntries(),
1564 + ...testMarks,
1565 + ]);
1566
1694 - data.schedulingEvents.forEach(event => {
1695 - expect(event.warning).toBeNull();
1567 + const event = data.schedulingEvents.find(
1568 + ({type}) => type === 'schedule-force-update',
1569 + );
1570 + expect(event.warning).toMatchInlineSnapshot(
1571 + `"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)."`,
1572 + );
1573 });
1697 - });
1574
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);
1575 + it('should not warn about transition updates scheduled during commit phase', async () => {
1576 + function Component() {
1577 + const [value, setValue] = React.useState(0);
1578 + // eslint-disable-next-line no-unused-vars
1579 + const [isPending, startTransition] = React.useTransition();
1580
1704 - Scheduler.unstable_yieldValue(
1705 - `Component rendered with value ${value} and deferredValue ${deferredValue}`,
1706 - );
1581 + Scheduler.unstable_yieldValue(
1582 + `Component rendered with value ${value}`,
1583 + );
1584
1708 - // Fake a long render
1709 - if (deferredValue !== 0) {
1710 - Scheduler.unstable_yieldValue('Long render');
1711 - startTime += 20000;
1585 + // Fake a long render
1586 + if (value !== 0) {
1587 + Scheduler.unstable_yieldValue('Long render');
1588 + startTime += 20000;
1589 + }
1590 +
1591 + React.useLayoutEffect(() => {
1592 + startTransition(() => {
1593 + setValue(1);
1594 + });
1595 + }, []);
1596 +
1597 + return value;
1598 }
1599
1714 - React.useLayoutEffect(() => {
1715 - setValue(1);
1716 - }, []);
1600 + const cpuProfilerSample = creactCpuProfilerSample();
1601
1718 - return value + deferredValue;
1719 - }
1602 + const root = ReactDOM.createRoot(document.createElement('div'));
1603 + utils.act(() => {
1604 + root.render(<Component />);
1605 + });
1606 +
1607 + expect(Scheduler).toHaveYielded([
1608 + 'Component rendered with value 0',
1609 + 'Component rendered with value 0',
1610 + 'Component rendered with value 1',
1611 + 'Long render',
1612 + ]);
1613 +
1614 + const testMarks = [];
1615 + clearedMarks.forEach(markName => {
1616 + if (markName === '--component-render-start-Component') {
1617 + // Fake a long running render
1618 + startTime += 20000;
1619 + }
1620 +
1621 + testMarks.push({
1622 + pid: ++pid,
1623 + tid: ++tid,
1624 + ts: ++startTime,
1625 + args: {data: {}},
1626 + cat: 'blink.user_timing',
1627 + name: markName,
1628 + ph: 'R',
1629 + });
1630 + });
1631
1721 - const cpuProfilerSample = creactCpuProfilerSample();
1632 + const data = await preprocessData([
1633 + cpuProfilerSample,
1634 + ...createBoilerplateEntries(),
1635 + ...testMarks,
1636 + ]);
1637
1723 - const root = ReactDOM.createRoot(document.createElement('div'));
1724 - utils.act(() => {
1725 - root.render(<Component />);
1638 + data.schedulingEvents.forEach(event => {
1639 + expect(event.warning).toBeNull();
1640 + });
1641 });
1642
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;
1643 + it('should not warn about deferred value updates scheduled during commit phase', async () => {
1644 + function Component() {
1645 + const [value, setValue] = React.useState(0);
1646 + const deferredValue = React.useDeferredValue(value);
1647 +
1648 + Scheduler.unstable_yieldValue(
1649 + `Component rendered with value ${value} and deferredValue ${deferredValue}`,
1650 + );
1651 +
1652 + // Fake a long render
1653 + if (deferredValue !== 0) {
1654 + Scheduler.unstable_yieldValue('Long render');
1655 + startTime += 20000;
1656 + }
1657 +
1658 + React.useLayoutEffect(() => {
1659 + setValue(1);
1660 + }, []);
1661 +
1662 + return value + deferredValue;
1663 }
1664
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',
1665 + const cpuProfilerSample = creactCpuProfilerSample();
1666 +
1667 + const root = ReactDOM.createRoot(document.createElement('div'));
1668 + utils.act(() => {
1669 + root.render(<Component />);
1670 + });
1671 +
1672 + expect(Scheduler).toHaveYielded([
1673 + 'Component rendered with value 0 and deferredValue 0',
1674 + 'Component rendered with value 1 and deferredValue 0',
1675 + 'Component rendered with value 1 and deferredValue 1',
1676 + 'Long render',
1677 + ]);
1678 +
1679 + const testMarks = [];
1680 + clearedMarks.forEach(markName => {
1681 + if (markName === '--component-render-start-Component') {
1682 + // Fake a long running render
1683 + startTime += 20000;
1684 + }
1685 +
1686 + testMarks.push({
1687 + pid: ++pid,
1688 + tid: ++tid,
1689 + ts: ++startTime,
1690 + args: {data: {}},
1691 + cat: 'blink.user_timing',
1692 + name: markName,
1693 + ph: 'R',
1694 + });
1695 });
1751 - });
1696
1753 - const data = await preprocessData([
1754 - cpuProfilerSample,
1755 - ...createBoilerplateEntries(),
1756 - ...testMarks,
1757 - ]);
1697 + const data = await preprocessData([
1698 + cpuProfilerSample,
1699 + ...createBoilerplateEntries(),
1700 + ...testMarks,
1701 + ]);
1702
1759 - data.schedulingEvents.forEach(event => {
1760 - expect(event.warning).toBeNull();
1703 + data.schedulingEvents.forEach(event => {
1704 + expect(event.warning).toBeNull();
1705 + });
1706 });
1707 });
1763 - });
1708
1765 - describe('errors thrown while rendering', () => {
1766 - it('shoult parse Errors thrown during render', async () => {
1767 - spyOn(console, 'error');
1709 + describe('errors thrown while rendering', () => {
1710 + it('shoult parse Errors thrown during render', async () => {
1711 + spyOn(console, 'error');
1712
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;
1713 + class ErrorBoundary extends React.Component {
1714 + state = {error: null};
1715 + componentDidCatch(error) {
1716 + this.setState({error});
1717 + }
1718 + render() {
1719 + if (this.state.error) {
1720 + return null;
1721 + }
1722 + return this.props.children;
1723 }
1778 - return this.props.children;
1724 }
1780 - }
1725
1782 - function ExampleThatThrows() {
1783 - throw Error('Expected error');
1784 - }
1726 + function ExampleThatThrows() {
1727 + throw Error('Expected error');
1728 + }
1729
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 - );
1730 + const testMarks = [creactCpuProfilerSample()];
1731 +
1732 + // Mount and commit the app
1733 + const root = ReactDOM.createRoot(document.createElement('div'));
1734 + utils.act(() =>
1735 + root.render(
1736 + <ErrorBoundary>
1737 + <ExampleThatThrows />
1738 + </ErrorBoundary>,
1739 + ),
1740 + );
1741 +
1742 + testMarks.push(...createUserTimingData(clearedMarks));
1743 +
1744 + const data = await preprocessData(testMarks);
1745 + expect(data.thrownErrors).toHaveLength(2);
1746 + expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
1747 + '"Expected error"',
1748 + );
1749 + });
1750 });
1806 - });
1751
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 - });
1752 + describe('suspend during an update', () => {
1753 + // This also tests an edge case where the a component suspends while profiling
1754 + // before the first commit is logged (so the lane-to-labels map will not yet exist).
1755 + it('should warn about suspending during an udpate', async () => {
1756 + let promise = null;
1757 + let resolvedValue = null;
1758 + function readValue(value) {
1759 + if (resolvedValue !== null) {
1760 + return resolvedValue;
1761 + } else if (promise === null) {
1762 + promise = Promise.resolve(true).then(() => {
1763 + resolvedValue = value;
1764 + });
1765 + }
1766 + throw promise;
1767 }
1822 - throw promise;
1823 - }
1768
1825 - function Component({shouldSuspend}) {
1826 - Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1827 - if (shouldSuspend) {
1828 - readValue(123);
1769 + function Component({shouldSuspend}) {
1770 + Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1771 + if (shouldSuspend) {
1772 + readValue(123);
1773 + }
1774 + return null;
1775 }
1830 - return null;
1831 - }
1776
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);
1777 + // Mount and commit the app
1778 + const root = ReactDOM.createRoot(document.createElement('div'));
1779 + utils.act(() =>
1780 + root.render(
1781 + <React.Suspense fallback="Loading...">
1782 + <Component shouldSuspend={false} />
1783 + </React.Suspense>,
1784 + ),
1785 + );
1786 +
1787 + const testMarks = [creactCpuProfilerSample()];
1788 +
1789 + // Start profiling and suspend during a render.
1790 + utils.act(() =>
1791 + root.render(
1792 + <React.Suspense fallback="Loading...">
1793 + <Component shouldSuspend={true} />
1794 + </React.Suspense>,
1795 + ),
1796 + );
1797 +
1798 + testMarks.push(...createUserTimingData(clearedMarks));
1799 +
1800 + let data;
1801 + await utils.actAsync(async () => {
1802 + data = await preprocessData(testMarks);
1803 + });
1804 + expect(data.suspenseEvents).toHaveLength(1);
1805 + expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
1806 + `"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."`,
1807 + );
1808 });
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 - });
1809
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 - });
1810 + it('should not warn about suspending during an transition', async () => {
1811 + let promise = null;
1812 + let resolvedValue = null;
1813 + function readValue(value) {
1814 + if (resolvedValue !== null) {
1815 + return resolvedValue;
1816 + } else if (promise === null) {
1817 + promise = Promise.resolve(true).then(() => {
1818 + resolvedValue = value;
1819 + });
1820 + }
1821 + throw promise;
1822 }
1879 - throw promise;
1880 - }
1823
1882 - function Component({shouldSuspend}) {
1883 - Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1884 - if (shouldSuspend) {
1885 - readValue(123);
1824 + function Component({shouldSuspend}) {
1825 + Scheduler.unstable_yieldValue(`Component ${shouldSuspend}`);
1826 + if (shouldSuspend) {
1827 + readValue(123);
1828 + }
1829 + return null;
1830 }
1887 - return null;
1888 - }
1831
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(() =>
1832 + // Mount and commit the app
1833 + const root = ReactDOM.createRoot(document.createElement('div'));
1834 + utils.act(() =>
1835 root.render(
1836 <React.Suspense fallback="Loading...">
1907 - <Component shouldSuspend={true} />
1837 + <Component shouldSuspend={false} />
1838 </React.Suspense>,
1839 ),
1910 - ),
1911 - );
1840 + );
1841 +
1842 + const testMarks = [creactCpuProfilerSample()];
1843 +
1844 + // Start profiling and suspend during a render.
1845 + await utils.actAsync(async () =>
1846 + React.startTransition(() =>
1847 + root.render(
1848 + <React.Suspense fallback="Loading...">
1849 + <Component shouldSuspend={true} />
1850 + </React.Suspense>,
1851 + ),
1852 + ),
1853 + );
1854
1913 - testMarks.push(...createUserTimingData(clearedMarks));
1855 + testMarks.push(...createUserTimingData(clearedMarks));
1856
1915 - let data;
1916 - await utils.actAsync(async () => {
1917 - data = await preprocessData(testMarks);
1857 + let data;
1858 + await utils.actAsync(async () => {
1859 + data = await preprocessData(testMarks);
1860 + });
1861 + expect(data.suspenseEvents).toHaveLength(1);
1862 + expect(data.suspenseEvents[0].warning).toBe(null);
1863 });
1919 - expect(data.suspenseEvents).toHaveLength(1);
1920 - expect(data.suspenseEvents[0].warning).toBe(null);
1864 });
1865 });
1866 +
1867 + // TODO: Add test for snapshot base64 parsing
1868 +
1869 + // TODO: Add test for flamechart parsing
1870 + });
1871 + });
1872 +
1873 + // Note the in-memory tests vary slightly (e.g. timestamp values, lane numbers) from the above tests.
1874 + // That's okay; the important thing is the the lane-to-label matches the subsequent events/measures.
1875 + describe('DevTools hook (in memory)', () => {
1876 + let store;
1877 +
1878 + beforeEach(() => {
1879 + utils = require('./utils');
1880 + utils.beforeEachProfiling();
1881 +
1882 + React = require('react');
1883 + ReactDOM = require('react-dom');
1884 + Scheduler = require('scheduler');
1885 +
1886 + store = global.store;
1887 +
1888 + // Start profiling so that data will actually be recorded.
1889 + utils.act(() => store.profilerStore.startProfiling());
1890 +
1891 + global.IS_REACT_ACT_ENVIRONMENT = true;
1892 + });
1893 +
1894 + it('should process a sample legacy render sequence', async () => {
1895 + utils.legacyRender(<div />, document.createElement('div'));
1896 + utils.act(() => store.profilerStore.stopProfiling());
1897 +
1898 + const data = store.profilerStore.profilingData?.timelineData;
1899 + expect(data).toHaveLength(1);
1900 + const timelineData = data[0];
1901 + expect(timelineData).toMatchInlineSnapshot(`
1902 + Object {
1903 + "batchUIDToMeasuresMap": Map {
1904 + 1 => Array [
1905 + Object {
1906 + "batchUID": 1,
1907 + "depth": 0,
1908 + "duration": 0,
1909 + "lanes": "0b0000000000000000000000000000001",
1910 + "timestamp": 10,
1911 + "type": "render-idle",
1912 + },
1913 + Object {
1914 + "batchUID": 1,
1915 + "depth": 0,
1916 + "duration": 0,
1917 + "lanes": "0b0000000000000000000000000000001",
1918 + "timestamp": 10,
1919 + "type": "render",
1920 + },
1921 + Object {
1922 + "batchUID": 1,
1923 + "depth": 0,
1924 + "duration": 0,
1925 + "lanes": "0b0000000000000000000000000000001",
1926 + "timestamp": 10,
1927 + "type": "commit",
1928 + },
1929 + Object {
1930 + "batchUID": 1,
1931 + "depth": 1,
1932 + "duration": 0,
1933 + "lanes": "0b0000000000000000000000000000001",
1934 + "timestamp": 10,
1935 + "type": "layout-effects",
1936 + },
1937 + ],
1938 + },
1939 + "componentMeasures": Array [],
1940 + "duration": 20,
1941 + "flamechart": Array [],
1942 + "internalModuleSourceToRanges": Map {},
1943 + "laneToLabelMap": Map {
1944 + 1 => "Sync",
1945 + 2 => "InputContinuousHydration",
1946 + 4 => "InputContinuous",
1947 + 8 => "DefaultHydration",
1948 + 16 => "Default",
1949 + 32 => "TransitionHydration",
1950 + 64 => "Transition",
1951 + 128 => "Transition",
1952 + 256 => "Transition",
1953 + 512 => "Transition",
1954 + 1024 => "Transition",
1955 + 2048 => "Transition",
1956 + 4096 => "Transition",
1957 + 8192 => "Transition",
1958 + 16384 => "Transition",
1959 + 32768 => "Transition",
1960 + 65536 => "Transition",
1961 + 131072 => "Transition",
1962 + 262144 => "Transition",
1963 + 524288 => "Transition",
1964 + 1048576 => "Transition",
1965 + 2097152 => "Transition",
1966 + 4194304 => "Retry",
1967 + 8388608 => "Retry",
1968 + 16777216 => "Retry",
1969 + 33554432 => "Retry",
1970 + 67108864 => "Retry",
1971 + 134217728 => "SelectiveHydration",
1972 + 268435456 => "IdleHydration",
1973 + 536870912 => "Idle",
1974 + 1073741824 => "Offscreen",
1975 + },
1976 + "laneToReactMeasureMap": Map {
1977 + 1 => Array [
1978 + Object {
1979 + "batchUID": 1,
1980 + "depth": 0,
1981 + "duration": 0,
1982 + "lanes": "0b0000000000000000000000000000001",
1983 + "timestamp": 10,
1984 + "type": "render-idle",
1985 + },
1986 + Object {
1987 + "batchUID": 1,
1988 + "depth": 0,
1989 + "duration": 0,
1990 + "lanes": "0b0000000000000000000000000000001",
1991 + "timestamp": 10,
1992 + "type": "render",
1993 + },
1994 + Object {
1995 + "batchUID": 1,
1996 + "depth": 0,
1997 + "duration": 0,
1998 + "lanes": "0b0000000000000000000000000000001",
1999 + "timestamp": 10,
2000 + "type": "commit",
2001 + },
2002 + Object {
2003 + "batchUID": 1,
2004 + "depth": 1,
2005 + "duration": 0,
2006 + "lanes": "0b0000000000000000000000000000001",
2007 + "timestamp": 10,
2008 + "type": "layout-effects",
2009 + },
2010 + ],
2011 + 2 => Array [],
2012 + 4 => Array [],
2013 + 8 => Array [],
2014 + 16 => Array [],
2015 + 32 => Array [],
2016 + 64 => Array [],
2017 + 128 => Array [],
2018 + 256 => Array [],
2019 + 512 => Array [],
2020 + 1024 => Array [],
2021 + 2048 => Array [],
2022 + 4096 => Array [],
2023 + 8192 => Array [],
2024 + 16384 => Array [],
2025 + 32768 => Array [],
2026 + 65536 => Array [],
2027 + 131072 => Array [],
2028 + 262144 => Array [],
2029 + 524288 => Array [],
2030 + 1048576 => Array [],
2031 + 2097152 => Array [],
2032 + 4194304 => Array [],
2033 + 8388608 => Array [],
2034 + 16777216 => Array [],
2035 + 33554432 => Array [],
2036 + 67108864 => Array [],
2037 + 134217728 => Array [],
2038 + 268435456 => Array [],
2039 + 536870912 => Array [],
2040 + 1073741824 => Array [],
2041 + },
2042 + "nativeEvents": Array [],
2043 + "networkMeasures": Array [],
2044 + "otherUserTimingMarks": Array [],
2045 + "reactVersion": "<filtered-version>",
2046 + "schedulingEvents": Array [
2047 + Object {
2048 + "lanes": "0b0000000000000000000000000000001",
2049 + "timestamp": 10,
2050 + "type": "schedule-render",
2051 + "warning": null,
2052 + },
2053 + ],
2054 + "snapshotHeight": 0,
2055 + "snapshots": Array [],
2056 + "startTime": -10,
2057 + "suspenseEvents": Array [],
2058 + "thrownErrors": Array [],
2059 + }
2060 + `);
2061 });
2062
1925 - // TODO: Add test for snapshot base64 parsing
2063 + it('should process a sample createRoot render sequence', async () => {
2064 + function App() {
2065 + const [didMount, setDidMount] = React.useState(false);
2066 + React.useEffect(() => {
2067 + if (!didMount) {
2068 + setDidMount(true);
2069 + }
2070 + });
2071 + return true;
2072 + }
2073
1927 - // TODO: Add test for flamechart parsing
2074 + const root = ReactDOM.createRoot(document.createElement('div'));
2075 + utils.act(() => root.render(<App />));
2076 + utils.act(() => store.profilerStore.stopProfiling());
2077 +
2078 + const data = store.profilerStore.profilingData?.timelineData;
2079 + expect(data).toHaveLength(1);
2080 + const timelineData = data[0];
2081 + expect(timelineData).toMatchInlineSnapshot(`
2082 + Object {
2083 + "batchUIDToMeasuresMap": Map {
2084 + 1 => Array [
2085 + Object {
2086 + "batchUID": 1,
2087 + "depth": 0,
2088 + "duration": 0,
2089 + "lanes": "0b0000000000000000000000000010000",
2090 + "timestamp": 10,
2091 + "type": "render-idle",
2092 + },
2093 + Object {
2094 + "batchUID": 1,
2095 + "depth": 0,
2096 + "duration": 0,
2097 + "lanes": "0b0000000000000000000000000010000",
2098 + "timestamp": 10,
2099 + "type": "render",
2100 + },
2101 + Object {
2102 + "batchUID": 1,
2103 + "depth": 0,
2104 + "duration": 0,
2105 + "lanes": "0b0000000000000000000000000010000",
2106 + "timestamp": 10,
2107 + "type": "commit",
2108 + },
2109 + Object {
2110 + "batchUID": 1,
2111 + "depth": 1,
2112 + "duration": 0,
2113 + "lanes": "0b0000000000000000000000000010000",
2114 + "timestamp": 10,
2115 + "type": "layout-effects",
2116 + },
2117 + Object {
2118 + "batchUID": 1,
2119 + "depth": 0,
2120 + "duration": 0,
2121 + "lanes": "0b0000000000000000000000000010000",
2122 + "timestamp": 10,
2123 + "type": "passive-effects",
2124 + },
2125 + ],
2126 + 2 => Array [
2127 + Object {
2128 + "batchUID": 2,
2129 + "depth": 0,
2130 + "duration": 0,
2131 + "lanes": "0b0000000000000000000000000010000",
2132 + "timestamp": 10,
2133 + "type": "render-idle",
2134 + },
2135 + Object {
2136 + "batchUID": 2,
2137 + "depth": 0,
2138 + "duration": 0,
2139 + "lanes": "0b0000000000000000000000000010000",
2140 + "timestamp": 10,
2141 + "type": "render",
2142 + },
2143 + Object {
2144 + "batchUID": 2,
2145 + "depth": 0,
2146 + "duration": 0,
2147 + "lanes": "0b0000000000000000000000000010000",
2148 + "timestamp": 10,
2149 + "type": "commit",
2150 + },
2151 + Object {
2152 + "batchUID": 2,
2153 + "depth": 1,
2154 + "duration": 0,
2155 + "lanes": "0b0000000000000000000000000010000",
2156 + "timestamp": 10,
2157 + "type": "layout-effects",
2158 + },
2159 + Object {
2160 + "batchUID": 2,
2161 + "depth": 0,
2162 + "duration": 0,
2163 + "lanes": "0b0000000000000000000000000010000",
2164 + "timestamp": 10,
2165 + "type": "passive-effects",
2166 + },
2167 + ],
2168 + },
2169 + "componentMeasures": Array [
2170 + Object {
2171 + "componentName": "App",
2172 + "duration": 0,
2173 + "timestamp": 10,
2174 + "type": "render",
2175 + "warning": null,
2176 + },
2177 + Object {
2178 + "componentName": "App",
2179 + "duration": 0,
2180 + "timestamp": 10,
2181 + "type": "passive-effect-mount",
2182 + "warning": null,
2183 + },
2184 + Object {
2185 + "componentName": "App",
2186 + "duration": 0,
2187 + "timestamp": 10,
2188 + "type": "render",
2189 + "warning": null,
2190 + },
2191 + Object {
2192 + "componentName": "App",
2193 + "duration": 0,
2194 + "timestamp": 10,
2195 + "type": "passive-effect-mount",
2196 + "warning": null,
2197 + },
2198 + ],
2199 + "duration": 20,
2200 + "flamechart": Array [],
2201 + "internalModuleSourceToRanges": Map {},
2202 + "laneToLabelMap": Map {
2203 + 1 => "Sync",
2204 + 2 => "InputContinuousHydration",
2205 + 4 => "InputContinuous",
2206 + 8 => "DefaultHydration",
2207 + 16 => "Default",
2208 + 32 => "TransitionHydration",
2209 + 64 => "Transition",
2210 + 128 => "Transition",
2211 + 256 => "Transition",
2212 + 512 => "Transition",
2213 + 1024 => "Transition",
2214 + 2048 => "Transition",
2215 + 4096 => "Transition",
2216 + 8192 => "Transition",
2217 + 16384 => "Transition",
2218 + 32768 => "Transition",
2219 + 65536 => "Transition",
2220 + 131072 => "Transition",
2221 + 262144 => "Transition",
2222 + 524288 => "Transition",
2223 + 1048576 => "Transition",
2224 + 2097152 => "Transition",
2225 + 4194304 => "Retry",
2226 + 8388608 => "Retry",
2227 + 16777216 => "Retry",
2228 + 33554432 => "Retry",
2229 + 67108864 => "Retry",
2230 + 134217728 => "SelectiveHydration",
2231 + 268435456 => "IdleHydration",
2232 + 536870912 => "Idle",
2233 + 1073741824 => "Offscreen",
2234 + },
2235 + "laneToReactMeasureMap": Map {
2236 + 1 => Array [],
2237 + 2 => Array [],
2238 + 4 => Array [],
2239 + 8 => Array [],
2240 + 16 => Array [
2241 + Object {
2242 + "batchUID": 1,
2243 + "depth": 0,
2244 + "duration": 0,
2245 + "lanes": "0b0000000000000000000000000010000",
2246 + "timestamp": 10,
2247 + "type": "render-idle",
2248 + },
2249 + Object {
2250 + "batchUID": 1,
2251 + "depth": 0,
2252 + "duration": 0,
2253 + "lanes": "0b0000000000000000000000000010000",
2254 + "timestamp": 10,
2255 + "type": "render",
2256 + },
2257 + Object {
2258 + "batchUID": 1,
2259 + "depth": 0,
2260 + "duration": 0,
2261 + "lanes": "0b0000000000000000000000000010000",
2262 + "timestamp": 10,
2263 + "type": "commit",
2264 + },
2265 + Object {
2266 + "batchUID": 1,
2267 + "depth": 1,
2268 + "duration": 0,
2269 + "lanes": "0b0000000000000000000000000010000",
2270 + "timestamp": 10,
2271 + "type": "layout-effects",
2272 + },
2273 + Object {
2274 + "batchUID": 1,
2275 + "depth": 0,
2276 + "duration": 0,
2277 + "lanes": "0b0000000000000000000000000010000",
2278 + "timestamp": 10,
2279 + "type": "passive-effects",
2280 + },
2281 + Object {
2282 + "batchUID": 2,
2283 + "depth": 0,
2284 + "duration": 0,
2285 + "lanes": "0b0000000000000000000000000010000",
2286 + "timestamp": 10,
2287 + "type": "render-idle",
2288 + },
2289 + Object {
2290 + "batchUID": 2,
2291 + "depth": 0,
2292 + "duration": 0,
2293 + "lanes": "0b0000000000000000000000000010000",
2294 + "timestamp": 10,
2295 + "type": "render",
2296 + },
2297 + Object {
2298 + "batchUID": 2,
2299 + "depth": 0,
2300 + "duration": 0,
2301 + "lanes": "0b0000000000000000000000000010000",
2302 + "timestamp": 10,
2303 + "type": "commit",
2304 + },
2305 + Object {
2306 + "batchUID": 2,
2307 + "depth": 1,
2308 + "duration": 0,
2309 + "lanes": "0b0000000000000000000000000010000",
2310 + "timestamp": 10,
2311 + "type": "layout-effects",
2312 + },
2313 + Object {
2314 + "batchUID": 2,
2315 + "depth": 0,
2316 + "duration": 0,
2317 + "lanes": "0b0000000000000000000000000010000",
2318 + "timestamp": 10,
2319 + "type": "passive-effects",
2320 + },
2321 + ],
2322 + 32 => Array [],
2323 + 64 => Array [],
2324 + 128 => Array [],
2325 + 256 => Array [],
2326 + 512 => Array [],
2327 + 1024 => Array [],
2328 + 2048 => Array [],
2329 + 4096 => Array [],
2330 + 8192 => Array [],
2331 + 16384 => Array [],
2332 + 32768 => Array [],
2333 + 65536 => Array [],
2334 + 131072 => Array [],
2335 + 262144 => Array [],
2336 + 524288 => Array [],
2337 + 1048576 => Array [],
2338 + 2097152 => Array [],
2339 + 4194304 => Array [],
2340 + 8388608 => Array [],
2341 + 16777216 => Array [],
2342 + 33554432 => Array [],
2343 + 67108864 => Array [],
2344 + 134217728 => Array [],
2345 + 268435456 => Array [],
2346 + 536870912 => Array [],
2347 + 1073741824 => Array [],
2348 + },
2349 + "nativeEvents": Array [],
2350 + "networkMeasures": Array [],
2351 + "otherUserTimingMarks": Array [],
2352 + "reactVersion": "<filtered-version>",
2353 + "schedulingEvents": Array [
2354 + Object {
2355 + "lanes": "0b0000000000000000000000000010000",
2356 + "timestamp": 10,
2357 + "type": "schedule-render",
2358 + "warning": null,
2359 + },
2360 + Object {
2361 + "componentName": "App",
2362 + "lanes": "0b0000000000000000000000000010000",
2363 + "timestamp": 10,
2364 + "type": "schedule-state-update",
2365 + "warning": null,
2366 + },
2367 + ],
2368 + "snapshotHeight": 0,
2369 + "snapshots": Array [],
2370 + "startTime": -10,
2371 + "suspenseEvents": Array [],
2372 + "thrownErrors": Array [],
2373 + }
2374 + `);
2375 + });
2376 });
2377 });
packages/react-devtools-shared/src/__tests__/store-test.js
+4 -4
@@ -915,17 +915,17 @@ describe('Store', () => {
915 const containerA = document.createElement('div');
916 const containerB = document.createElement('div');
917
918 - expect(store.supportsProfiling).toBe(false);
918 + expect(store.rootSupportsBasicProfiling).toBe(false);
919
920 act(() => legacyRender(<Component />, containerA));
921 - expect(store.supportsProfiling).toBe(true);
921 + expect(store.rootSupportsBasicProfiling).toBe(true);
922
923 act(() => legacyRender(<Component />, containerB));
924 act(() => ReactDOM.unmountComponentAtNode(containerA));
925 - expect(store.supportsProfiling).toBe(true);
925 + expect(store.rootSupportsBasicProfiling).toBe(true);
926
927 act(() => ReactDOM.unmountComponentAtNode(containerB));
928 - expect(store.supportsProfiling).toBe(false);
928 + expect(store.rootSupportsBasicProfiling).toBe(false);
929 });
930
931 it('should properly serialize non-string key values', () => {
packages/react-devtools-shared/src/__tests__/utils.js
+3
@@ -227,6 +227,9 @@ export function exportImportHelper(bridge: FrontendBridge, store: Store): void {
227 expect(profilingDataFrontendInitial.dataForRoots).toEqual(
228 profilingDataFrontend.dataForRoots,
229 );
230 + expect(profilingDataFrontendInitial.timelineData).toEqual(
231 + profilingDataFrontend.timelineData,
232 + );
233
234 // Snapshot the JSON-parsed object, rather than the raw string, because Jest formats the diff nicer.
235 expect(parsedProfilingDataExport).toMatchSnapshot('imported data');
packages/react-devtools-shared/src/backend/legacy/renderer.js
+1 -1
@@ -387,7 +387,7 @@ export function attach(
387 pushOperation(id);
388 pushOperation(ElementTypeRoot);
389 pushOperation(0); // StrictMode compliant?
390 - pushOperation(0); // Profiling supported?
390 + pushOperation(0); // Profiling flag
391 pushOperation(0); // StrictMode supported?
392 pushOperation(hasOwnerMetadata ? 1 : 0);
393 } else {
packages/react-devtools-shared/src/backend/profilingHooks.js
+627 -98
@@ -14,9 +14,25 @@ import type {
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 +import type {
18 + BatchUID,
19 + LaneToLabelMap,
20 + ReactComponentMeasure,
21 + ReactMeasure,
22 + ReactMeasureType,
23 + TimelineData,
24 + SuspenseEvent,
25 +} from 'react-devtools-timeline/src/types';
26
27 import isArray from 'shared/isArray';
19 -import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-timeline/src/constants';
28 +import {
29 + REACT_TOTAL_NUM_LANES,
30 + SCHEDULING_PROFILER_VERSION,
31 +} from 'react-devtools-timeline/src/constants';
32 +
33 +// Add padding to the start/stop time of the profile.
34 +// This makes the UI nicer to use.
35 +const TIME_OFFSET = 10;
36
37 let performanceTarget: Performance | null = null;
38
@@ -53,6 +69,12 @@ if (supportsUserTimingV3) {
69 performanceTarget = performance;
70 }
71
72 +// Some environments (e.g. React Native / Hermes) don't support the performance API yet.
73 +const getCurrentTime =
74 + typeof performance === 'object' && typeof performance.now === 'function'
75 + ? () => performance.now()
76 + : () => Date.now();
77 +
78 // Mocking the Performance Object (and User Timing APIs) for testing is fragile.
79 // This API allows tests to directly override the User Timing APIs.
80 export function setPerformanceMock_ONLY_FOR_TESTING(
@@ -63,25 +85,48 @@ export function setPerformanceMock_ONLY_FOR_TESTING(
85 supportsUserTimingV3 = performanceMock !== null;
86 }
87
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 -}
88 +export type GetTimelineData = () => TimelineData | null;
89 +export type ToggleProfilingStatus = (value: boolean) => void;
90 +
91 +type Response = {|
92 + getTimelineData: GetTimelineData,
93 + profilingHooks: DevToolsProfilingHooks,
94 + toggleProfilingStatus: ToggleProfilingStatus,
95 +|};
96
97 export function createProfilingHooks({
98 getDisplayNameForFiber,
99 + getIsProfiling,
100 getLaneLabelMap,
101 reactVersion,
102 }: {|
103 getDisplayNameForFiber: (fiber: Fiber) => string | null,
104 + getIsProfiling: () => boolean,
105 getLaneLabelMap?: () => Map<Lane, string> | null,
106 reactVersion: string,
80 -|}): DevToolsProfilingHooks {
81 - function markMetadata() {
82 - markAndClear(`--react-version-${reactVersion}`);
83 - markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
107 +|}): Response {
108 + let currentBatchUID: BatchUID = 0;
109 + let currentReactComponentMeasure: ReactComponentMeasure | null = null;
110 + let currentReactMeasuresStack: Array<ReactMeasure> = [];
111 + let currentTimelineData: TimelineData | null = null;
112 + let isProfiling: boolean = false;
113 + let nextRenderShouldStartNewBatch: boolean = false;
114 +
115 + function getRelativeTime() {
116 + const currentTime = getCurrentTime();
117 +
118 + if (currentTimelineData) {
119 + if (currentTimelineData.startTime === 0) {
120 + currentTimelineData.startTime = currentTime - TIME_OFFSET;
121 + }
122 +
123 + return currentTime - currentTimelineData.startTime;
124 + }
125 +
126 + return 0;
127 + }
128
129 + function getInternalModuleRanges() {
130 /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
131 if (
132 typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
@@ -89,122 +134,396 @@ export function createProfilingHooks({
134 'function'
135 ) {
136 // Ask the DevTools hook for module ranges that may have been reported by the current renderer(s).
137 + // Don't do this eagerly like the laneToLabelMap,
138 + // because some modules might not yet have registered their boundaries when the renderer is injected.
139 const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();
140
141 // This check would not be required,
142 // except that it's possible for things to override __REACT_DEVTOOLS_GLOBAL_HOOK__.
143 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];
144 + return ranges;
145 + }
146 + }
147
102 - markAndClear(`--react-internal-module-start-${startStackFrame}`);
103 - markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
104 - }
148 + return null;
149 + }
150 +
151 + function getTimelineData(): TimelineData | null {
152 + return currentTimelineData;
153 + }
154 +
155 + function laneToLanesArray(lanes: Lane) {
156 + const lanesArray = [];
157 +
158 + let lane = 1;
159 + for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
160 + if (lane & lanes) {
161 + lanesArray.push(lane);
162 + }
163 + lane *= 2;
164 + }
165 +
166 + return lanesArray;
167 + }
168 +
169 + const laneToLabelMap: LaneToLabelMap | null =
170 + typeof getLaneLabelMap === 'function' ? getLaneLabelMap() : null;
171 +
172 + function markMetadata() {
173 + markAndClear(`--react-version-${reactVersion}`);
174 + markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
175 +
176 + const ranges = getInternalModuleRanges();
177 + if (ranges) {
178 + for (let i = 0; i < ranges.length; i++) {
179 + const range = ranges[i];
180 + if (isArray(range) && range.length === 2) {
181 + const [startStackFrame, stopStackFrame] = ranges[i];
182 +
183 + markAndClear(`--react-internal-module-start-${startStackFrame}`);
184 + markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
185 }
186 }
187 }
188
109 - if (typeof getLaneLabelMap === 'function') {
110 - const map = getLaneLabelMap();
111 - if (map != null) {
112 - const labels = Array.from(map.values()).join(',');
113 - markAndClear(`--react-lane-labels-${labels}`);
189 + if (laneToLabelMap != null) {
190 + const labels = Array.from(laneToLabelMap.values()).join(',');
191 + markAndClear(`--react-lane-labels-${labels}`);
192 + }
193 + }
194 +
195 + function markAndClear(markName) {
196 + // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
197 + ((performanceTarget: any): Performance).mark(markName);
198 + ((performanceTarget: any): Performance).clearMarks(markName);
199 + }
200 +
201 + function recordReactMeasureStarted(
202 + type: ReactMeasureType,
203 + lanes: Lanes,
204 + ): void {
205 + // Decide what depth thi work should be rendered at, based on what's on the top of the stack.
206 + // It's okay to render over top of "idle" work but everything else should be on its own row.
207 + let depth = 0;
208 + if (currentReactMeasuresStack.length > 0) {
209 + const top =
210 + currentReactMeasuresStack[currentReactMeasuresStack.length - 1];
211 + depth = top.type === 'render-idle' ? top.depth : top.depth + 1;
212 + }
213 +
214 + const lanesArray = laneToLanesArray(lanes);
215 +
216 + const reactMeasure: ReactMeasure = {
217 + type,
218 + batchUID: currentBatchUID,
219 + depth,
220 + lanes: lanesArray,
221 + timestamp: getRelativeTime(),
222 + duration: 0,
223 + };
224 +
225 + currentReactMeasuresStack.push(reactMeasure);
226 +
227 + if (currentTimelineData) {
228 + const {
229 + batchUIDToMeasuresMap,
230 + laneToReactMeasureMap,
231 + } = currentTimelineData;
232 +
233 + let reactMeasures = batchUIDToMeasuresMap.get(currentBatchUID);
234 + if (reactMeasures != null) {
235 + reactMeasures.push(reactMeasure);
236 + } else {
237 + batchUIDToMeasuresMap.set(currentBatchUID, [reactMeasure]);
238 }
239 +
240 + lanesArray.forEach(lane => {
241 + reactMeasures = laneToReactMeasureMap.get(lane);
242 + if (reactMeasures) {
243 + reactMeasures.push(reactMeasure);
244 + }
245 + });
246 + }
247 + }
248 +
249 + function recordReactMeasureCompleted(type: ReactMeasureType): void {
250 + const currentTime = getRelativeTime();
251 +
252 + if (currentReactMeasuresStack.length === 0) {
253 + console.error(
254 + 'Unexpected type "%s" completed at %sms while currentReactMeasuresStack is empty.',
255 + type,
256 + currentTime,
257 + );
258 + // Ignore work "completion" user timing mark that doesn't complete anything
259 + return;
260 + }
261 +
262 + const top = currentReactMeasuresStack.pop();
263 + if (top.type !== type) {
264 + console.error(
265 + 'Unexpected type "%s" completed at %sms before "%s" completed.',
266 + type,
267 + currentTime,
268 + top.type,
269 + );
270 + }
271 +
272 + // $FlowFixMe This property should not be writable outside of this function.
273 + top.duration = currentTime - top.timestamp;
274 +
275 + if (currentTimelineData) {
276 + currentTimelineData.duration = getRelativeTime() + TIME_OFFSET;
277 }
278 }
279
280 function markCommitStarted(lanes: Lanes): void {
281 + if (isProfiling) {
282 + recordReactMeasureStarted('commit', lanes);
283 +
284 + // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering.
285 + // This issue applies to the User Timing data also.
286 + nextRenderShouldStartNewBatch = true;
287 + }
288 +
289 if (supportsUserTimingV3) {
290 markAndClear(`--commit-start-${lanes}`);
291
122 - // Certain types of metadata should be logged infrequently.
123 - // Normally we would log this during module init,
124 - // but there's no guarantee a user is profiling at that time.
125 - // Commits happen infrequently (less than renders or state updates)
126 - // so we log this extra information along with a commit.
127 - // It will likely be logged more than once but that's okay.
128 - //
129 - // TODO (timeline) Only log this once, when profiling starts.
130 - // For the first phase– refactoring– we'll match the previous behavior.
292 + // Some metadata only needs to be logged once per session,
293 + // but if profiling information is being recorded via the Performance tab,
294 + // DevTools has no way of knowing when the recording starts.
295 + // Because of that, we log thie type of data periodically (once per commit).
296 markMetadata();
297 }
298 }
299
300 function markCommitStopped(): void {
301 + if (isProfiling) {
302 + recordReactMeasureCompleted('commit');
303 + recordReactMeasureCompleted('render-idle');
304 + }
305 +
306 if (supportsUserTimingV3) {
307 markAndClear('--commit-stop');
308 }
309 }
310
311 function markComponentRenderStarted(fiber: Fiber): void {
142 - if (supportsUserTimingV3) {
312 + if (isProfiling || supportsUserTimingV3) {
313 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
144 - // TODO (timeline) Record and cache component stack
145 - markAndClear(`--component-render-start-${componentName}`);
314 +
315 + if (isProfiling) {
316 + // TODO (timeline) Record and cache component stack
317 + if (isProfiling) {
318 + currentReactComponentMeasure = {
319 + componentName,
320 + duration: 0,
321 + timestamp: getRelativeTime(),
322 + type: 'render',
323 + warning: null,
324 + };
325 + }
326 + }
327 +
328 + if (supportsUserTimingV3) {
329 + markAndClear(`--component-render-start-${componentName}`);
330 + }
331 }
332 }
333
334 function markComponentRenderStopped(): void {
335 + if (isProfiling) {
336 + if (currentReactComponentMeasure) {
337 + if (currentTimelineData) {
338 + currentTimelineData.componentMeasures.push(
339 + currentReactComponentMeasure,
340 + );
341 + }
342 +
343 + currentReactComponentMeasure.duration =
344 + getRelativeTime() - currentReactComponentMeasure.timestamp;
345 + currentReactComponentMeasure = null;
346 + }
347 + }
348 +
349 if (supportsUserTimingV3) {
350 markAndClear('--component-render-stop');
351 }
352 }
353
155 - function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
156 - if (supportsUserTimingV3) {
354 + function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
355 + if (isProfiling || supportsUserTimingV3) {
356 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
158 - // TODO (timeline) Record and cache component stack
159 - markAndClear(`--component-passive-effect-mount-start-${componentName}`);
357 +
358 + if (isProfiling) {
359 + // TODO (timeline) Record and cache component stack
360 + if (isProfiling) {
361 + currentReactComponentMeasure = {
362 + componentName,
363 + duration: 0,
364 + timestamp: getRelativeTime(),
365 + type: 'layout-effect-mount',
366 + warning: null,
367 + };
368 + }
369 + }
370 +
371 + if (supportsUserTimingV3) {
372 + markAndClear(`--component-layout-effect-mount-start-${componentName}`);
373 + }
374 }
375 }
376
163 - function markComponentPassiveEffectMountStopped(): void {
377 + function markComponentLayoutEffectMountStopped(): void {
378 + if (isProfiling) {
379 + if (currentReactComponentMeasure) {
380 + if (currentTimelineData) {
381 + currentTimelineData.componentMeasures.push(
382 + currentReactComponentMeasure,
383 + );
384 + }
385 +
386 + currentReactComponentMeasure.duration =
387 + getRelativeTime() - currentReactComponentMeasure.timestamp;
388 + currentReactComponentMeasure = null;
389 + }
390 + }
391 +
392 if (supportsUserTimingV3) {
165 - markAndClear('--component-passive-effect-mount-stop');
393 + markAndClear('--component-layout-effect-mount-stop');
394 }
395 }
396
169 - function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
170 - if (supportsUserTimingV3) {
397 + function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
398 + if (isProfiling || supportsUserTimingV3) {
399 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
172 - // TODO (timeline) Record and cache component stack
173 - markAndClear(`--component-passive-effect-unmount-start-${componentName}`);
400 +
401 + if (isProfiling) {
402 + // TODO (timeline) Record and cache component stack
403 + if (isProfiling) {
404 + currentReactComponentMeasure = {
405 + componentName,
406 + duration: 0,
407 + timestamp: getRelativeTime(),
408 + type: 'layout-effect-unmount',
409 + warning: null,
410 + };
411 + }
412 + }
413 +
414 + if (supportsUserTimingV3) {
415 + markAndClear(
416 + `--component-layout-effect-unmount-start-${componentName}`,
417 + );
418 + }
419 }
420 }
421
177 - function markComponentPassiveEffectUnmountStopped(): void {
422 + function markComponentLayoutEffectUnmountStopped(): void {
423 + if (isProfiling) {
424 + if (currentReactComponentMeasure) {
425 + if (currentTimelineData) {
426 + currentTimelineData.componentMeasures.push(
427 + currentReactComponentMeasure,
428 + );
429 + }
430 +
431 + currentReactComponentMeasure.duration =
432 + getRelativeTime() - currentReactComponentMeasure.timestamp;
433 + currentReactComponentMeasure = null;
434 + }
435 + }
436 +
437 if (supportsUserTimingV3) {
179 - markAndClear('--component-passive-effect-unmount-stop');
438 + markAndClear('--component-layout-effect-unmount-stop');
439 }
440 }
441
183 - function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
184 - if (supportsUserTimingV3) {
442 + function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
443 + if (isProfiling || supportsUserTimingV3) {
444 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
186 - // TODO (timeline) Record and cache component stack
187 - markAndClear(`--component-layout-effect-mount-start-${componentName}`);
445 +
446 + if (isProfiling) {
447 + // TODO (timeline) Record and cache component stack
448 + if (isProfiling) {
449 + currentReactComponentMeasure = {
450 + componentName,
451 + duration: 0,
452 + timestamp: getRelativeTime(),
453 + type: 'passive-effect-mount',
454 + warning: null,
455 + };
456 + }
457 + }
458 +
459 + if (supportsUserTimingV3) {
460 + markAndClear(`--component-passive-effect-mount-start-${componentName}`);
461 + }
462 }
463 }
464
191 - function markComponentLayoutEffectMountStopped(): void {
465 + function markComponentPassiveEffectMountStopped(): void {
466 + if (isProfiling) {
467 + if (currentReactComponentMeasure) {
468 + if (currentTimelineData) {
469 + currentTimelineData.componentMeasures.push(
470 + currentReactComponentMeasure,
471 + );
472 + }
473 +
474 + currentReactComponentMeasure.duration =
475 + getRelativeTime() - currentReactComponentMeasure.timestamp;
476 + currentReactComponentMeasure = null;
477 + }
478 + }
479 +
480 if (supportsUserTimingV3) {
193 - markAndClear('--component-layout-effect-mount-stop');
481 + markAndClear('--component-passive-effect-mount-stop');
482 }
483 }
484
197 - function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
198 - if (supportsUserTimingV3) {
485 + function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
486 + if (isProfiling || supportsUserTimingV3) {
487 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
200 - // TODO (timeline) Record and cache component stack
201 - markAndClear(`--component-layout-effect-unmount-start-${componentName}`);
488 +
489 + if (isProfiling) {
490 + // TODO (timeline) Record and cache component stack
491 + if (isProfiling) {
492 + currentReactComponentMeasure = {
493 + componentName,
494 + duration: 0,
495 + timestamp: getRelativeTime(),
496 + type: 'passive-effect-unmount',
497 + warning: null,
498 + };
499 + }
500 + }
501 +
502 + if (supportsUserTimingV3) {
503 + markAndClear(
504 + `--component-passive-effect-unmount-start-${componentName}`,
505 + );
506 + }
507 }
508 }
509
205 - function markComponentLayoutEffectUnmountStopped(): void {
510 + function markComponentPassiveEffectUnmountStopped(): void {
511 + if (isProfiling) {
512 + if (currentReactComponentMeasure) {
513 + if (currentTimelineData) {
514 + currentTimelineData.componentMeasures.push(
515 + currentReactComponentMeasure,
516 + );
517 + }
518 +
519 + currentReactComponentMeasure.duration =
520 + getRelativeTime() - currentReactComponentMeasure.timestamp;
521 + currentReactComponentMeasure = null;
522 + }
523 + }
524 +
525 if (supportsUserTimingV3) {
207 - markAndClear('--component-layout-effect-unmount-stop');
526 + markAndClear('--component-passive-effect-unmount-stop');
527 }
528 }
529
@@ -213,7 +532,7 @@ export function createProfilingHooks({
532 thrownValue: mixed,
533 lanes: Lanes,
534 ): void {
216 - if (supportsUserTimingV3) {
535 + if (isProfiling || supportsUserTimingV3) {
536 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
537 const phase = fiber.alternate === null ? 'mount' : 'update';
538
@@ -228,8 +547,22 @@ export function createProfilingHooks({
547 message = thrownValue;
548 }
549
231 - // TODO (timeline) Record and cache component stack
232 - markAndClear(`--error-${componentName}-${phase}-${message}`);
550 + if (isProfiling) {
551 + // TODO (timeline) Record and cache component stack
552 + if (currentTimelineData) {
553 + currentTimelineData.thrownErrors.push({
554 + componentName,
555 + message,
556 + phase,
557 + timestamp: getRelativeTime(),
558 + type: 'thrown-error',
559 + });
560 + }
561 + }
562 +
563 + if (supportsUserTimingV3) {
564 + markAndClear(`--error-${componentName}-${phase}-${message}`);
565 + }
566 }
567 }
568
@@ -250,7 +583,7 @@ export function createProfilingHooks({
583 wakeable: Wakeable,
584 lanes: Lanes,
585 ): void {
253 - if (supportsUserTimingV3) {
586 + if (isProfiling || supportsUserTimingV3) {
587 const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend';
588 const id = getWakeableID(wakeable);
589 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
@@ -262,105 +595,301 @@ export function createProfilingHooks({
595 // When this is available we should pass it along to the Timeline.
596 const displayName = (wakeable: any).displayName || '';
597
265 - // TODO (timeline) Record and cache component stack
266 - markAndClear(
267 - `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
268 - );
598 + let suspenseEvent: SuspenseEvent | null = null;
599 + if (isProfiling) {
600 + // TODO (timeline) Record and cache component stack
601 + suspenseEvent = {
602 + componentName,
603 + depth: 0,
604 + duration: 0,
605 + id: `${id}`,
606 + phase,
607 + promiseName: displayName,
608 + resolution: 'unresolved',
609 + timestamp: getRelativeTime(),
610 + type: 'suspense',
611 + warning: null,
612 + };
613 +
614 + if (currentTimelineData) {
615 + currentTimelineData.suspenseEvents.push(suspenseEvent);
616 + }
617 + }
618 +
619 + if (supportsUserTimingV3) {
620 + markAndClear(
621 + `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
622 + );
623 + }
624 +
625 wakeable.then(
270 - () => markAndClear(`--suspense-resolved-${id}-${componentName}`),
271 - () => markAndClear(`--suspense-rejected-${id}-${componentName}`),
626 + () => {
627 + if (suspenseEvent) {
628 + suspenseEvent.duration =
629 + getRelativeTime() - suspenseEvent.timestamp;
630 + suspenseEvent.resolution = 'resolved';
631 + }
632 +
633 + if (supportsUserTimingV3) {
634 + markAndClear(`--suspense-resolved-${id}-${componentName}`);
635 + }
636 + },
637 + () => {
638 + if (suspenseEvent) {
639 + suspenseEvent.duration =
640 + getRelativeTime() - suspenseEvent.timestamp;
641 + suspenseEvent.resolution = 'rejected';
642 + }
643 +
644 + if (supportsUserTimingV3) {
645 + markAndClear(`--suspense-rejected-${id}-${componentName}`);
646 + }
647 + },
648 );
649 }
650 }
651
652 function markLayoutEffectsStarted(lanes: Lanes): void {
653 + if (isProfiling) {
654 + recordReactMeasureStarted('layout-effects', lanes);
655 + }
656 +
657 if (supportsUserTimingV3) {
658 markAndClear(`--layout-effects-start-${lanes}`);
659 }
660 }
661
662 function markLayoutEffectsStopped(): void {
663 + if (isProfiling) {
664 + recordReactMeasureCompleted('layout-effects');
665 + }
666 +
667 if (supportsUserTimingV3) {
668 markAndClear('--layout-effects-stop');
669 }
670 }
671
672 function markPassiveEffectsStarted(lanes: Lanes): void {
673 + if (isProfiling) {
674 + recordReactMeasureStarted('passive-effects', lanes);
675 + }
676 +
677 if (supportsUserTimingV3) {
678 markAndClear(`--passive-effects-start-${lanes}`);
679 }
680 }
681
682 function markPassiveEffectsStopped(): void {
683 + if (isProfiling) {
684 + recordReactMeasureCompleted('passive-effects');
685 + }
686 +
687 if (supportsUserTimingV3) {
688 markAndClear('--passive-effects-stop');
689 }
690 }
691
692 function markRenderStarted(lanes: Lanes): void {
693 + if (isProfiling) {
694 + if (nextRenderShouldStartNewBatch) {
695 + nextRenderShouldStartNewBatch = false;
696 + currentBatchUID++;
697 + }
698 +
699 + // If this is a new batch of work, wrap an "idle" measure around it.
700 + // Log it before the "render" measure to preserve the stack ordering.
701 + if (
702 + currentReactMeasuresStack.length === 0 ||
703 + currentReactMeasuresStack[currentReactMeasuresStack.length - 1].type !==
704 + 'render-idle'
705 + ) {
706 + recordReactMeasureStarted('render-idle', lanes);
707 + }
708 +
709 + recordReactMeasureStarted('render', lanes);
710 + }
711 +
712 if (supportsUserTimingV3) {
713 markAndClear(`--render-start-${lanes}`);
714 }
715 }
716
717 function markRenderYielded(): void {
718 + if (isProfiling) {
719 + recordReactMeasureCompleted('render');
720 + }
721 +
722 if (supportsUserTimingV3) {
723 markAndClear('--render-yield');
724 }
725 }
726
727 function markRenderStopped(): void {
728 + if (isProfiling) {
729 + recordReactMeasureCompleted('render');
730 + }
731 +
732 if (supportsUserTimingV3) {
733 markAndClear('--render-stop');
734 }
735 }
736
737 function markRenderScheduled(lane: Lane): void {
738 + if (isProfiling) {
739 + if (currentTimelineData) {
740 + currentTimelineData.schedulingEvents.push({
741 + lanes: laneToLanesArray(lane),
742 + timestamp: getRelativeTime(),
743 + type: 'schedule-render',
744 + warning: null,
745 + });
746 + }
747 + }
748 +
749 if (supportsUserTimingV3) {
750 markAndClear(`--schedule-render-${lane}`);
751 }
752 }
753
754 function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
325 - if (supportsUserTimingV3) {
755 + if (isProfiling || supportsUserTimingV3) {
756 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
327 - // TODO (timeline) Record and cache component stack
328 - markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
757 +
758 + if (isProfiling) {
759 + // TODO (timeline) Record and cache component stack
760 + if (currentTimelineData) {
761 + currentTimelineData.schedulingEvents.push({
762 + componentName,
763 + lanes: laneToLanesArray(lane),
764 + timestamp: getRelativeTime(),
765 + type: 'schedule-force-update',
766 + warning: null,
767 + });
768 + }
769 + }
770 +
771 + if (supportsUserTimingV3) {
772 + markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
773 + }
774 }
775 }
776
777 function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
333 - if (supportsUserTimingV3) {
778 + if (isProfiling || supportsUserTimingV3) {
779 const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
335 - // TODO (timeline) Record and cache component stack
336 - markAndClear(`--schedule-state-update-${lane}-${componentName}`);
780 +
781 + if (isProfiling) {
782 + // TODO (timeline) Record and cache component stack
783 + if (currentTimelineData) {
784 + currentTimelineData.schedulingEvents.push({
785 + componentName,
786 + lanes: laneToLanesArray(lane),
787 + timestamp: getRelativeTime(),
788 + type: 'schedule-state-update',
789 + warning: null,
790 + });
791 + }
792 + }
793 +
794 + if (supportsUserTimingV3) {
795 + markAndClear(`--schedule-state-update-${lane}-${componentName}`);
796 + }
797 + }
798 + }
799 +
800 + function toggleProfilingStatus(value: boolean) {
801 + if (isProfiling !== value) {
802 + isProfiling = value;
803 +
804 + if (isProfiling) {
805 + const internalModuleSourceToRanges = new Map();
806 +
807 + if (supportsUserTimingV3) {
808 + const ranges = getInternalModuleRanges();
809 + if (ranges) {
810 + for (let i = 0; i < ranges.length; i++) {
811 + const range = ranges[i];
812 + if (isArray(range) && range.length === 2) {
813 + const [startStackFrame, stopStackFrame] = ranges[i];
814 +
815 + markAndClear(
816 + `--react-internal-module-start-${startStackFrame}`,
817 + );
818 + markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
819 + }
820 + }
821 + }
822 + }
823 +
824 + const laneToReactMeasureMap = new Map();
825 + let lane = 1;
826 + for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
827 + laneToReactMeasureMap.set(lane, []);
828 + lane *= 2;
829 + }
830 +
831 + currentBatchUID = 0;
832 + currentReactComponentMeasure = null;
833 + currentReactMeasuresStack = [];
834 + currentTimelineData = {
835 + // Session wide metadata; only collected once.
836 + internalModuleSourceToRanges,
837 + laneToLabelMap: laneToLabelMap || new Map(),
838 + reactVersion,
839 +
840 + // Data logged by React during profiling session.
841 + componentMeasures: [],
842 + schedulingEvents: [],
843 + suspenseEvents: [],
844 + thrownErrors: [],
845 +
846 + // Data inferred based on what React logs.
847 + batchUIDToMeasuresMap: new Map(),
848 + duration: 0,
849 + laneToReactMeasureMap,
850 + startTime: 0,
851 +
852 + // Data only available in Chrome profiles.
853 + flamechart: [],
854 + nativeEvents: [],
855 + networkMeasures: [],
856 + otherUserTimingMarks: [],
857 + snapshots: [],
858 + snapshotHeight: 0,
859 + };
860 + nextRenderShouldStartNewBatch = true;
861 + }
862 }
863 }
864
865 return {
341 - markCommitStarted,
342 - markCommitStopped,
343 - markComponentRenderStarted,
344 - markComponentRenderStopped,
345 - markComponentPassiveEffectMountStarted,
346 - markComponentPassiveEffectMountStopped,
347 - markComponentPassiveEffectUnmountStarted,
348 - markComponentPassiveEffectUnmountStopped,
349 - markComponentLayoutEffectMountStarted,
350 - markComponentLayoutEffectMountStopped,
351 - markComponentLayoutEffectUnmountStarted,
352 - markComponentLayoutEffectUnmountStopped,
353 - markComponentErrored,
354 - markComponentSuspended,
355 - markLayoutEffectsStarted,
356 - markLayoutEffectsStopped,
357 - markPassiveEffectsStarted,
358 - markPassiveEffectsStopped,
359 - markRenderStarted,
360 - markRenderYielded,
361 - markRenderStopped,
362 - markRenderScheduled,
363 - markForceUpdateScheduled,
364 - markStateUpdateScheduled,
866 + getTimelineData,
867 + profilingHooks: {
868 + markCommitStarted,
869 + markCommitStopped,
870 + markComponentRenderStarted,
871 + markComponentRenderStopped,
872 + markComponentPassiveEffectMountStarted,
873 + markComponentPassiveEffectMountStopped,
874 + markComponentPassiveEffectUnmountStarted,
875 + markComponentPassiveEffectUnmountStopped,
876 + markComponentLayoutEffectMountStarted,
877 + markComponentLayoutEffectMountStopped,
878 + markComponentLayoutEffectUnmountStarted,
879 + markComponentLayoutEffectUnmountStopped,
880 + markComponentErrored,
881 + markComponentSuspended,
882 + markLayoutEffectsStarted,
883 + markLayoutEffectsStopped,
884 + markPassiveEffectsStarted,
885 + markPassiveEffectsStopped,
886 + markRenderStarted,
887 + markRenderYielded,
888 + markRenderStopped,
889 + markRenderScheduled,
890 + markForceUpdateScheduled,
891 + markStateUpdateScheduled,
892 + },
893 + toggleProfilingStatus,
894 };
895 }
packages/react-devtools-shared/src/backend/renderer.js
+71 -8
@@ -47,6 +47,8 @@ import {
47 } from './utils';
48 import {
49 __DEBUG__,
50 + PROFILING_FLAG_BASIC_SUPPORT,
51 + PROFILING_FLAG_TIMELINE_SUPPORT,
52 SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
53 SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
54 TREE_OPERATION_ADD,
@@ -94,6 +96,7 @@ import hasOwnProperty from 'shared/hasOwnProperty';
96 import {getStyleXData} from './StyleX/utils';
97 import {createProfilingHooks} from './profilingHooks';
98
99 +import type {GetTimelineData, ToggleProfilingStatus} from './profilingHooks';
100 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
101 import type {
102 ChangeDescription,
@@ -627,14 +630,22 @@ export function attach(
630 };
631 }
632
633 + let getTimelineData: null | GetTimelineData = null;
634 + let toggleProfilingStatus: null | ToggleProfilingStatus = null;
635 if (typeof injectProfilingHooks === 'function') {
631 - injectProfilingHooks(
632 - createProfilingHooks({
633 - getDisplayNameForFiber,
634 - getLaneLabelMap,
635 - reactVersion: version,
636 - }),
637 - );
636 + const response = createProfilingHooks({
637 + getDisplayNameForFiber,
638 + getIsProfiling: () => isProfiling,
639 + getLaneLabelMap,
640 + reactVersion: version,
641 + });
642 +
643 + // Pass the Profiling hooks to the reconciler for it to call during render.
644 + injectProfilingHooks(response.profilingHooks);
645 +
646 + // Hang onto this toggle so we can notify the external methods of profiling status changes.
647 + getTimelineData = response.getTimelineData;
648 + toggleProfilingStatus = response.toggleProfilingStatus;
649 }
650
651 // Tracks Fibers with recently changed number of error/warning messages.
@@ -1910,12 +1921,22 @@ export function attach(
1921 const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
1922 const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
1923
1924 + // Adding a new field here would require a bridge protocol version bump (a backwads breaking change).
1925 + // Instead let's re-purpose a pre-existing field to carry more information.
1926 + let profilingFlags = 0;
1927 + if (isProfilingSupported) {
1928 + profilingFlags = PROFILING_FLAG_BASIC_SUPPORT;
1929 + if (typeof injectProfilingHooks === 'function') {
1930 + profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT;
1931 + }
1932 + }
1933 +
1934 if (isRoot) {
1935 pushOperation(TREE_OPERATION_ADD);
1936 pushOperation(id);
1937 pushOperation(ElementTypeRoot);
1938 pushOperation((fiber.mode & StrictModeBits) !== 0 ? 1 : 0);
1918 - pushOperation(isProfilingSupported ? 1 : 0);
1939 + pushOperation(profilingFlags);
1940 pushOperation(StrictModeBits !== 0 ? 1 : 0);
1941 pushOperation(hasOwnerMetadata ? 1 : 0);
1942
@@ -3959,9 +3980,43 @@ export function attach(
3980 },
3981 );
3982
3983 + let timelineData = null;
3984 + if (typeof getTimelineData === 'function') {
3985 + const currentTimelineData = getTimelineData();
3986 + if (currentTimelineData) {
3987 + const {
3988 + batchUIDToMeasuresMap,
3989 + internalModuleSourceToRanges,
3990 + laneToLabelMap,
3991 + laneToReactMeasureMap,
3992 + ...rest
3993 + } = currentTimelineData;
3994 +
3995 + timelineData = {
3996 + ...rest,
3997 +
3998 + // Most of the data is safe to parse as-is,
3999 + // but we need to convert the nested Arrays back to Maps.
4000 + // Most of the data is safe to serialize as-is,
4001 + // but we need to convert the Maps to nested Arrays.
4002 + batchUIDToMeasuresKeyValueArray: Array.from(
4003 + batchUIDToMeasuresMap.entries(),
4004 + ),
4005 + internalModuleSourceToRanges: Array.from(
4006 + internalModuleSourceToRanges.entries(),
4007 + ),
4008 + laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
4009 + laneToReactMeasureKeyValueArray: Array.from(
4010 + laneToReactMeasureMap.entries(),
4011 + ),
4012 + };
4013 + }
4014 + }
4015 +
4016 return {
4017 dataForRoots,
4018 rendererID,
4019 + timelineData,
4020 };
4021 }
4022
@@ -3999,11 +4054,19 @@ export function attach(
4054 isProfiling = true;
4055 profilingStartTime = getCurrentTime();
4056 rootToCommitProfilingMetadataMap = new Map();
4057 +
4058 + if (toggleProfilingStatus !== null) {
4059 + toggleProfilingStatus(true);
4060 + }
4061 }
4062
4063 function stopProfiling() {
4064 isProfiling = false;
4065 recordChangeDescriptions = false;
4066 +
4067 + if (toggleProfilingStatus !== null) {
4068 + toggleProfilingStatus(false);
4069 + }
4070 }
4071
4072 // Automatically start profiling so that we don't miss timing info from initial "mount".
packages/react-devtools-shared/src/backend/types.js
+2
@@ -16,6 +16,7 @@ import type {
16 Plugins,
17 } from 'react-devtools-shared/src/types';
18 import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
19 +import type {TimelineDataExport} from 'react-devtools-timeline/src/types';
20
21 type BundleType =
22 | 0 // PROD
@@ -195,6 +196,7 @@ export type ProfilingDataForRootBackend = {|
196 export type ProfilingDataBackend = {|
197 dataForRoots: Array<ProfilingDataForRootBackend>,
198 rendererID: number,
199 + timelineData: TimelineDataExport | null,
200 |};
201
202 export type PathFrame = {|
packages/react-devtools-shared/src/constants.js
+3
@@ -25,6 +25,9 @@ export const TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS = 5;
25 export const TREE_OPERATION_REMOVE_ROOT = 6;
26 export const TREE_OPERATION_SET_SUBTREE_MODE = 7;
27
28 +export const PROFILING_FLAG_BASIC_SUPPORT = 0b01;
29 +export const PROFILING_FLAG_TIMELINE_SUPPORT = 0b10;
30 +
31 export const LOCAL_STORAGE_DEFAULT_TAB_KEY = 'React::DevTools::defaultTab';
32
33 export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
packages/react-devtools-shared/src/devtools/store.js
+50 -12
@@ -10,6 +10,8 @@
10 import EventEmitter from '../events';
11 import {inspect} from 'util';
12 import {
13 + PROFILING_FLAG_BASIC_SUPPORT,
14 + PROFILING_FLAG_TIMELINE_SUPPORT,
15 TREE_OPERATION_ADD,
16 TREE_OPERATION_REMOVE,
17 TREE_OPERATION_REMOVE_ROOT,
@@ -72,9 +74,10 @@ type Config = {|
74 |};
75
76 export type Capabilities = {|
77 + supportsBasicProfiling: boolean,
78 hasOwnerMetadata: boolean,
76 - supportsProfiling: boolean,
79 supportsStrictMode: boolean,
80 + supportsTimeline: boolean,
81 |};
82
83 /**
@@ -88,8 +91,9 @@ export default class Store extends EventEmitter<{|
91 mutated: [[Array<number>, Map<number, number>]],
92 recordChangeDescriptions: [],
93 roots: [],
94 + rootSupportsBasicProfiling: [],
95 + rootSupportsTimelineProfiling: [],
96 supportsNativeStyleEditor: [],
92 - supportsProfiling: [],
97 supportsReloadAndProfile: [],
98 unsupportedBridgeProtocolDetected: [],
99 unsupportedRendererVersionDetected: [],
@@ -161,13 +165,16 @@ export default class Store extends EventEmitter<{|
165 _rootIDToRendererID: Map<number, number> = new Map();
166
167 // These options may be initially set by a confiugraiton option when constructing the Store.
164 - // In the case of "supportsProfiling", the option may be updated based on the injected renderers.
168 _supportsNativeInspection: boolean = true;
169 _supportsProfiling: boolean = false;
170 _supportsReloadAndProfile: boolean = false;
171 _supportsTimeline: boolean = false;
172 _supportsTraceUpdates: boolean = false;
173
174 + // These options default to false but may be updated as roots are added and removed.
175 + _rootSupportsBasicProfiling: boolean = false;
176 + _rootSupportsTimelineProfiling: boolean = false;
177 +
178 _unsupportedBridgeProtocol: BridgeProtocol | null = null;
179 _unsupportedRendererVersionDetected: boolean = false;
180
@@ -402,6 +409,16 @@ export default class Store extends EventEmitter<{|
409 return this._roots;
410 }
411
412 + // At least one of the currently mounted roots support the Legacy profiler.
413 + get rootSupportsBasicProfiling(): boolean {
414 + return this._rootSupportsBasicProfiling;
415 + }
416 +
417 + // At least one of the currently mounted roots support the Timeline profiler.
418 + get rootSupportsTimelineProfiling(): boolean {
419 + return this._rootSupportsTimelineProfiling;
420 + }
421 +
422 get supportsNativeInspection(): boolean {
423 return this._supportsNativeInspection;
424 }
@@ -410,6 +427,8 @@ export default class Store extends EventEmitter<{|
427 return this._isNativeStyleEditorSupported;
428 }
429
430 + // This build of DevTools supports the legacy profiler.
431 + // This is a static flag, controled by the Store config.
432 get supportsProfiling(): boolean {
433 return this._supportsProfiling;
434 }
@@ -425,6 +444,8 @@ export default class Store extends EventEmitter<{|
444 );
445 }
446
447 + // This build of DevTools supports the Timeline profiler.
448 + // This is a static flag, controled by the Store config.
449 get supportsTimeline(): boolean {
450 return this._supportsTimeline;
451 }
@@ -903,7 +924,10 @@ export default class Store extends EventEmitter<{|
924 const isStrictModeCompliant = operations[i] > 0;
925 i++;
926
906 - const supportsProfiling = operations[i] > 0;
927 + const supportsBasicProfiling =
928 + (operations[i] & PROFILING_FLAG_BASIC_SUPPORT) !== 0;
929 + const supportsTimeline =
930 + (operations[i] & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0;
931 i++;
932
933 const supportsStrictMode = operations[i] > 0;
@@ -915,9 +939,10 @@ export default class Store extends EventEmitter<{|
939 this._roots = this._roots.concat(id);
940 this._rootIDToRendererID.set(id, rendererID);
941 this._rootIDToCapabilities.set(id, {
942 + supportsBasicProfiling,
943 hasOwnerMetadata,
919 - supportsProfiling,
944 supportsStrictMode,
945 + supportsTimeline,
946 });
947
948 // Not all roots support StrictMode;
@@ -1224,25 +1249,38 @@ export default class Store extends EventEmitter<{|
1249 }
1250
1251 if (haveRootsChanged) {
1227 - const prevSupportsProfiling = this._supportsProfiling;
1252 + const prevRootSupportsProfiling = this._rootSupportsBasicProfiling;
1253 + const prevRootSupportsTimelineProfiling = this
1254 + ._rootSupportsTimelineProfiling;
1255
1256 this._hasOwnerMetadata = false;
1230 - this._supportsProfiling = false;
1257 + this._rootSupportsBasicProfiling = false;
1258 + this._rootSupportsTimelineProfiling = false;
1259 this._rootIDToCapabilities.forEach(
1232 - ({hasOwnerMetadata, supportsProfiling}) => {
1260 + ({supportsBasicProfiling, hasOwnerMetadata, supportsTimeline}) => {
1261 + if (supportsBasicProfiling) {
1262 + this._rootSupportsBasicProfiling = true;
1263 + }
1264 if (hasOwnerMetadata) {
1265 this._hasOwnerMetadata = true;
1266 }
1236 - if (supportsProfiling) {
1237 - this._supportsProfiling = true;
1267 + if (supportsTimeline) {
1268 + this._rootSupportsTimelineProfiling = true;
1269 }
1270 },
1271 );
1272
1273 this.emit('roots');
1274
1244 - if (this._supportsProfiling !== prevSupportsProfiling) {
1245 - this.emit('supportsProfiling');
1275 + if (this._rootSupportsBasicProfiling !== prevRootSupportsProfiling) {
1276 + this.emit('rootSupportsBasicProfiling');
1277 + }
1278 +
1279 + if (
1280 + this._rootSupportsTimelineProfiling !==
1281 + prevRootSupportsTimelineProfiling
1282 + ) {
1283 + this.emit('rootSupportsTimelineProfiling');
1284 }
1285 }
1286
packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
+10 -13
@@ -17,30 +17,27 @@ import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
17
18 export default function ClearProfilingDataButton() {
19 const store = useContext(StoreContext);
20 - const {didRecordCommits, isProfiling, selectedTabID} = useContext(
21 - ProfilerContext,
22 - );
20 + const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
21 const {file, setFile} = useContext(TimelineContext);
22 const {profilerStore} = store;
23
26 - let doesHaveData = false;
27 - if (selectedTabID === 'timeline') {
28 - doesHaveData = file !== null;
29 - } else {
30 - doesHaveData = didRecordCommits;
31 - }
24 + const doesHaveInMemoryData = didRecordCommits;
25 + const doesHaveUserTimingData = file !== null;
26
27 const clear = () => {
34 - if (selectedTabID === 'timeline') {
35 - setFile(null);
36 - } else {
28 + if (doesHaveInMemoryData) {
29 profilerStore.clear();
30 }
31 + if (doesHaveUserTimingData) {
32 + setFile(null);
33 + }
34 };
35
36 return (
37 <Button
43 - disabled={isProfiling || !doesHaveData}
38 + disabled={
39 + isProfiling || !(doesHaveInMemoryData || doesHaveUserTimingData)
40 + }
41 onClick={clear}
42 title="Clear profiling data">
43 <ButtonIcon type="clear" />
packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
+1 -1
@@ -194,7 +194,7 @@ function updateTree(
194
195 if (type === ElementTypeRoot) {
196 i++; // isStrictModeCompliant
197 - i++; // supportsProfiling flag
197 + i++; // Profiling flag
198 i++; // supportsStrictMode flag
199 i++; // hasOwnerMetadata flag
200
packages/react-devtools-shared/src/devtools/views/Profiler/NoProfilingData.js new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +import RecordToggle from './RecordToggle';
12 +
13 +import styles from './Profiler.css';
14 +
15 +export default function NoProfilingData() {
16 + return (
17 + <div className={styles.Column}>
18 + <div className={styles.Header}>No profiling data has been recorded.</div>
19 + <div className={styles.Row}>
20 + Click the record button <RecordToggle /> to start recording.
21 + </div>
22 + <div className={`${styles.Row} ${styles.LearnMoreRow}`}>
23 + Click{' '}
24 + <a
25 + className={styles.LearnMoreLink}
26 + href="https://fb.me/react-devtools-profiling"
27 + rel="noopener noreferrer"
28 + target="_blank">
29 + here
30 + </a>{' '}
31 + to learn more about profiling.
32 + </div>
33 + </div>
34 + );
35 +}
packages/react-devtools-shared/src/devtools/views/Profiler/ProcessingData.js new
+21
@@ -0,0 +1,21 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +
12 +import styles from './Profiler.css';
13 +
14 +export default function ProcessingData() {
15 + return (
16 + <div className={styles.Column}>
17 + <div className={styles.Header}>Processing data...</div>
18 + <div className={styles.Row}>This should only take a minute.</div>
19 + </div>
20 + );
21 +}
packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css
+12
@@ -61,6 +61,12 @@
61 justify-content: center;
62 }
63
64 +.LearnMoreRow {
65 + margin-top: 1rem;
66 + color: var(--color-dim);
67 + font-size: var(--font-size-sans-small);
68 +}
69 +
70 .Header {
71 font-size: var(--font-size-sans-large);
72 margin-bottom: 0.5rem;
@@ -121,3 +127,9 @@
127 display: flex;
128 align-items: center;
129 }
130 +
131 +.LearnMoreLink {
132 + color: var(--color-link);
133 + margin-left: 0.25rem;
134 + margin-right: 0.25rem;
135 +}
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+19 -50
@@ -22,6 +22,10 @@ import ReloadAndProfileButton from './ReloadAndProfileButton';
22 import ProfilingImportExportButtons from './ProfilingImportExportButtons';
23 import SnapshotSelector from './SnapshotSelector';
24 import SidebarCommitInfo from './SidebarCommitInfo';
25 +import NoProfilingData from './NoProfilingData';
26 +import RecordingInProgress from './RecordingInProgress';
27 +import ProcessingData from './ProcessingData';
28 +import ProfilingNotSupported from './ProfilingNotSupported';
29 import SidebarSelectedFiberInfo from './SidebarSelectedFiberInfo';
30 import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
31 import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
@@ -44,7 +48,9 @@ function Profiler(_: {||}) {
48 supportsProfiling,
49 } = useContext(ProfilerContext);
50
47 - const {searchInputContainerRef} = useContext(TimelineContext);
51 + const {file: timelineTraceEventData, searchInputContainerRef} = useContext(
52 + TimelineContext,
53 + );
54
55 const {supportsTimeline} = useContext(StoreContext);
56
@@ -69,6 +75,8 @@ function Profiler(_: {||}) {
75 view = <RecordingInProgress />;
76 } else if (isProcessingData) {
77 view = <ProcessingData />;
78 + } else if (timelineTraceEventData) {
79 + view = <OnlyTimelineData />;
80 } else if (supportsProfiling) {
81 view = <NoProfilingData />;
82 } else {
@@ -104,9 +112,7 @@ function Profiler(_: {||}) {
112 <div className={styles.Profiler}>
113 <div className={styles.LeftColumn}>
114 <div className={styles.Toolbar}>
107 - <RecordToggle
108 - disabled={!supportsProfiling || selectedTabID === 'timeline'}
109 - />
115 + <RecordToggle disabled={!supportsProfiling} />
116 <ReloadAndProfileButton
117 disabled={selectedTabID === 'timeline' || !supportsProfiling}
118 />
@@ -150,6 +156,15 @@ function Profiler(_: {||}) {
156 );
157 }
158
159 +const OnlyTimelineData = () => (
160 + <div className={styles.Column}>
161 + <div className={styles.Header}>Timeline only</div>
162 + <div className={styles.Row}>
163 + The current profile contains only Timeline data.
164 + </div>
165 + </div>
166 +);
167 +
168 const tabs = [
169 {
170 id: 'flame-chart',
@@ -176,50 +191,4 @@ const tabsWithTimeline = [
191 },
192 ];
193
179 -const NoProfilingData = () => (
180 - <div className={styles.Column}>
181 - <div className={styles.Header}>No profiling data has been recorded.</div>
182 - <div className={styles.Row}>
183 - Click the record button <RecordToggle /> to start recording.
184 - </div>
185 - </div>
186 -);
187 -
188 -const ProfilingNotSupported = () => (
189 - <div className={styles.Column}>
190 - <div className={styles.Header}>Profiling not supported.</div>
191 - <p className={styles.Paragraph}>
192 - Profiling support requires either a development or production-profiling
193 - build of React v16.5+.
194 - </p>
195 - <p className={styles.Paragraph}>
196 - Learn more at{' '}
197 - <a
198 - className={styles.Link}
199 - href="https://reactjs.org/link/profiling"
200 - rel="noopener noreferrer"
201 - target="_blank">
202 - reactjs.org/link/profiling
203 - </a>
204 - .
205 - </p>
206 - </div>
207 -);
208 -
209 -const ProcessingData = () => (
210 - <div className={styles.Column}>
211 - <div className={styles.Header}>Processing data...</div>
212 - <div className={styles.Row}>This should only take a minute.</div>
213 - </div>
214 -);
215 -
216 -const RecordingInProgress = () => (
217 - <div className={styles.Column}>
218 - <div className={styles.Header}>Profiling is in progress...</div>
219 - <div className={styles.Row}>
220 - Click the record button <RecordToggle /> to stop recording.
221 - </div>
222 - </div>
223 -);
224 -
194 export default portaledContent(Profiler);
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+3 -4
@@ -19,7 +19,6 @@ import {StoreContext} from '../context';
19
20 import type {ProfilingDataFrontend} from './types';
21
22 -// TODO (timeline) Should this be its own context?
22 export type TabID = 'flame-chart' | 'ranked-chart' | 'timeline';
23
24 export type Context = {|
@@ -96,18 +95,18 @@ function ProfilerContextController({children}: Props) {
95 isProcessingData: profilerStore.isProcessingData,
96 isProfiling: profilerStore.isProfiling,
97 profilingData: profilerStore.profilingData,
99 - supportsProfiling: store.supportsProfiling,
98 + supportsProfiling: store.rootSupportsBasicProfiling,
99 }),
100 subscribe: (callback: Function) => {
101 profilerStore.addListener('profilingData', callback);
102 profilerStore.addListener('isProcessingData', callback);
103 profilerStore.addListener('isProfiling', callback);
105 - store.addListener('supportsProfiling', callback);
104 + store.addListener('rootSupportsBasicProfiling', callback);
105 return () => {
106 profilerStore.removeListener('profilingData', callback);
107 profilerStore.removeListener('isProcessingData', callback);
108 profilerStore.removeListener('isProfiling', callback);
110 - store.removeListener('supportsProfiling', callback);
109 + store.removeListener('rootSupportsBasicProfiling', callback);
110 };
111 },
112 }),
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js
+49 -46
@@ -20,15 +20,15 @@ import {
20 } from './utils';
21 import {downloadFile} from '../utils';
22 import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
23 +import isArray from 'shared/isArray';
24 +import hasOwnProperty from 'shared/hasOwnProperty';
25
26 import styles from './ProfilingImportExportButtons.css';
27
28 import type {ProfilingDataExport} from './types';
29
30 export default function ProfilingImportExportButtons() {
29 - const {isProfiling, profilingData, rootID, selectedTabID} = useContext(
30 - ProfilerContext,
31 - );
31 + const {isProfiling, profilingData, rootID} = useContext(ProfilerContext);
32 const {setFile} = useContext(TimelineContext);
33 const store = useContext(StoreContext);
34 const {profilerStore} = store;
@@ -38,6 +38,8 @@ export default function ProfilingImportExportButtons() {
38
39 const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
40
41 + const doesHaveInMemoryData = profilerStore.didRecordCommits;
42 +
43 const downloadData = useCallback(() => {
44 if (rootID === null) {
45 return;
@@ -74,45 +76,54 @@ export default function ProfilingImportExportButtons() {
76 }
77 }, []);
78
77 - const importProfilerData = useCallback(() => {
79 + // TODO (profiling) We should probably use a transition for this and suspend while loading the file.
80 + // Local files load so fast it's probably not very noticeable though.
81 + const handleChange = () => {
82 const input = inputRef.current;
83 if (input !== null && input.files.length > 0) {
84 + const file = input.files[0];
85 +
86 + // TODO (profiling) Handle fileReader errors.
87 const fileReader = new FileReader();
88 fileReader.addEventListener('load', () => {
82 - try {
83 - const raw = ((fileReader.result: any): string);
84 - const profilingDataExport = ((JSON.parse(
85 - raw,
86 - ): any): ProfilingDataExport);
87 - profilerStore.profilingData = prepareProfilingDataFrontendFromExport(
88 - profilingDataExport,
89 - );
90 - } catch (error) {
91 - modalDialogDispatch({
92 - id: 'ProfilingImportExportButtons',
93 - type: 'SHOW',
94 - title: 'Import failed',
95 - content: (
96 - <Fragment>
97 - <div>The profiling data you selected cannot be imported.</div>
98 - {error !== null && (
99 - <div className={styles.ErrorMessage}>{error.message}</div>
100 - )}
101 - </Fragment>
102 - ),
103 - });
89 + const raw = ((fileReader.result: any): string);
90 + const json = JSON.parse(raw);
91 +
92 + if (!isArray(json) && hasOwnProperty.call(json, 'version')) {
93 + // This looks like React profiling data.
94 + // But first, clear any User Timing marks; we should only have one type open at a time.
95 + setFile(null);
96 +
97 + try {
98 + const profilingDataExport = ((json: any): ProfilingDataExport);
99 + profilerStore.profilingData = prepareProfilingDataFrontendFromExport(
100 + profilingDataExport,
101 + );
102 + } catch (error) {
103 + modalDialogDispatch({
104 + id: 'ProfilingImportExportButtons',
105 + type: 'SHOW',
106 + title: 'Import failed',
107 + content: (
108 + <Fragment>
109 + <div>The profiling data you selected cannot be imported.</div>
110 + {error !== null && (
111 + <div className={styles.ErrorMessage}>{error.message}</div>
112 + )}
113 + </Fragment>
114 + ),
115 + });
116 + }
117 + } else {
118 + // Otherwise let's assume this is Trace Event data and pass it to the Timeline preprocessor.
119 + // But first, clear React profiling data; we should only have one type open at a time.
120 + profilerStore.clear();
121 +
122 + // TODO (timeline) We shouldn't need to re-open the File but we'll need to refactor to avoid this.
123 + setFile(file);
124 }
125 });
106 - // TODO (profiling) Handle fileReader errors.
107 - fileReader.readAsText(input.files[0]);
108 - }
109 - }, [modalDialogDispatch, profilerStore]);
110 -
111 - const importTimelineDataWrapper = event => {
112 - const input = inputRef.current;
113 - if (input !== null && input.files.length > 0) {
114 - const file = input.files[0];
115 - setFile(file);
126 + fileReader.readAsText(file);
127 }
128 };
129
@@ -124,11 +135,7 @@ export default function ProfilingImportExportButtons() {
135 className={styles.Input}
136 type="file"
137 accept=".json"
127 - onChange={
128 - selectedTabID === 'timeline'
129 - ? importTimelineDataWrapper
130 - : importProfilerData
131 - }
138 + onChange={handleChange}
139 tabIndex={-1}
140 />
141 <a ref={downloadRef} className={styles.Input} />
@@ -139,11 +146,7 @@ export default function ProfilingImportExportButtons() {
146 <ButtonIcon type="import" />
147 </Button>
148 <Button
142 - disabled={
143 - isProfiling ||
144 - !profilerStore.didRecordCommits ||
145 - selectedTabID === 'timeline'
146 - }
149 + disabled={isProfiling || !doesHaveInMemoryData}
150 onClick={downloadData}
151 title="Save profile...">
152 <ButtonIcon type="export" />
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingNotSupported.js new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +
12 +import styles from './Profiler.css';
13 +
14 +export default function ProfilingNotSupported() {
15 + return (
16 + <div className={styles.Column}>
17 + <div className={styles.Header}>Profiling not supported.</div>
18 + <p className={styles.Paragraph}>
19 + Profiling support requires either a development or profiling build of
20 + React v16.5+.
21 + </p>
22 + <p className={styles.Paragraph}>
23 + Learn more at{' '}
24 + <a
25 + className={styles.Link}
26 + href="https://fb.me/react-devtools-profiling"
27 + rel="noopener noreferrer"
28 + target="_blank">
29 + reactjs.org/link/profiling
30 + </a>
31 + .
32 + </p>
33 + </div>
34 + );
35 +}
packages/react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress.js new
+24
@@ -0,0 +1,24 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +import RecordToggle from './RecordToggle';
12 +
13 +import styles from './Profiler.css';
14 +
15 +export default function RecordingInProgress() {
16 + return (
17 + <div className={styles.Column}>
18 + <div className={styles.Header}>Profiling is in progress...</div>
19 + <div className={styles.Row}>
20 + Click the record button <RecordToggle /> to stop recording.
21 + </div>
22 + </div>
23 + );
24 +}
packages/react-devtools-shared/src/devtools/views/Profiler/types.js
+18 -1
@@ -9,6 +9,10 @@
9
10 import type {ElementType} from 'react-devtools-shared/src/types';
11 import type {SerializedElement} from '../Components/types';
12 +import type {
13 + TimelineData,
14 + TimelineDataExport,
15 +} from 'react-devtools-timeline/src/types';
16
17 export type CommitTreeNode = {|
18 id: number,
@@ -103,8 +107,15 @@ export type ProfilingDataForRootFrontend = {|
107
108 // Combination of profiling data collected by the renderer interface (backend) and Store (frontend).
109 export type ProfilingDataFrontend = {|
106 - // Profiling data per root.
110 + // Legacy profiling data is per renderer + root.
111 dataForRoots: Map<number, ProfilingDataForRootFrontend>,
112 +
113 + // Timeline data is per rederer.
114 + timelineData: Array<TimelineData>,
115 +
116 + // Some functionality should be disabled for imported data.
117 + // e.g. DevTools should not try to sync selection between Components and Profiler tabs,
118 + // even if there are Fibers with the same IDs.
119 imported: boolean,
120 |};
121
@@ -135,5 +146,11 @@ export type ProfilingDataForRootExport = {|
146 // Serializable version of ProfilingDataFrontend data.
147 export type ProfilingDataExport = {|
148 version: 5,
149 +
150 + // Legacy profiling data is per renderer + root.
151 dataForRoots: Array<ProfilingDataForRootExport>,
152 +
153 + // Timeline data is per rederer.
154 + // Note that old exported profiles won't contain this key.
155 + timelineData?: Array<TimelineDataExport>,
156 |};
packages/react-devtools-shared/src/devtools/views/Profiler/utils.js
+127 -2
@@ -18,6 +18,10 @@ import type {
18 ProfilingDataFrontend,
19 SnapshotNode,
20 } from './types';
21 +import type {
22 + TimelineData,
23 + TimelineDataExport,
24 +} from 'react-devtools-timeline/src/types';
25
26 const commitGradient = [
27 'var(--color-commit-gradient-0)',
@@ -41,7 +45,31 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
45 ): ProfilingDataFrontend {
46 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
47
48 + const timelineDataArray = [];
49 +
50 dataBackends.forEach(dataBackend => {
51 + const {timelineData} = dataBackend;
52 + if (timelineData != null) {
53 + const {
54 + batchUIDToMeasuresKeyValueArray,
55 + internalModuleSourceToRanges,
56 + laneToLabelKeyValueArray,
57 + laneToReactMeasureKeyValueArray,
58 + ...rest
59 + } = timelineData;
60 +
61 + timelineDataArray.push({
62 + ...rest,
63 +
64 + // Most of the data is safe to parse as-is,
65 + // but we need to convert the nested Arrays back to Maps.
66 + batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
67 + internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
68 + laneToLabelMap: new Map(laneToLabelKeyValueArray),
69 + laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
70 + });
71 + }
72 +
73 dataBackend.dataForRoots.forEach(
74 ({commitData, displayName, initialTreeBaseDurations, rootID}) => {
75 const operations = operationsByRootID.get(rootID);
@@ -108,7 +136,7 @@ export function prepareProfilingDataFrontendFromBackendAndStore(
136 );
137 });
138
111 - return {dataForRoots, imported: false};
139 + return {dataForRoots, imported: false, timelineData: timelineDataArray};
140 }
141
142 // Converts a Profiling data export into the format required by the Store.
@@ -123,6 +151,50 @@ export function prepareProfilingDataFrontendFromExport(
151 );
152 }
153
154 + const timelineData: Array<TimelineData> = profilingDataExport.timelineData
155 + ? profilingDataExport.timelineData.map(
156 + ({
157 + batchUIDToMeasuresKeyValueArray,
158 + componentMeasures,
159 + duration,
160 + flamechart,
161 + internalModuleSourceToRanges,
162 + laneToLabelKeyValueArray,
163 + laneToReactMeasureKeyValueArray,
164 + nativeEvents,
165 + networkMeasures,
166 + otherUserTimingMarks,
167 + reactVersion,
168 + schedulingEvents,
169 + snapshots,
170 + snapshotHeight,
171 + startTime,
172 + suspenseEvents,
173 + thrownErrors,
174 + }) => ({
175 + // Most of the data is safe to parse as-is,
176 + // but we need to convert the nested Arrays back to Maps.
177 + batchUIDToMeasuresMap: new Map(batchUIDToMeasuresKeyValueArray),
178 + componentMeasures,
179 + duration,
180 + flamechart,
181 + internalModuleSourceToRanges: new Map(internalModuleSourceToRanges),
182 + laneToLabelMap: new Map(laneToLabelKeyValueArray),
183 + laneToReactMeasureMap: new Map(laneToReactMeasureKeyValueArray),
184 + nativeEvents,
185 + networkMeasures,
186 + otherUserTimingMarks,
187 + reactVersion,
188 + schedulingEvents,
189 + snapshots,
190 + snapshotHeight,
191 + startTime,
192 + suspenseEvents,
193 + thrownErrors,
194 + }),
195 + )
196 + : [];
197 +
198 const dataForRoots: Map<number, ProfilingDataForRootFrontend> = new Map();
199 profilingDataExport.dataForRoots.forEach(
200 ({
@@ -167,13 +239,65 @@ export function prepareProfilingDataFrontendFromExport(
239 },
240 );
241
170 - return {dataForRoots, imported: true};
242 + return {
243 + dataForRoots,
244 + imported: true,
245 + timelineData,
246 + };
247 }
248
249 // Converts a Store Profiling data into a format that can be safely (JSON) serialized for export.
250 export function prepareProfilingDataExport(
251 profilingDataFrontend: ProfilingDataFrontend,
252 ): ProfilingDataExport {
253 + const timelineData: Array<TimelineDataExport> = profilingDataFrontend.timelineData.map(
254 + ({
255 + batchUIDToMeasuresMap,
256 + componentMeasures,
257 + duration,
258 + flamechart,
259 + internalModuleSourceToRanges,
260 + laneToLabelMap,
261 + laneToReactMeasureMap,
262 + nativeEvents,
263 + networkMeasures,
264 + otherUserTimingMarks,
265 + reactVersion,
266 + schedulingEvents,
267 + snapshots,
268 + snapshotHeight,
269 + startTime,
270 + suspenseEvents,
271 + thrownErrors,
272 + }) => ({
273 + // Most of the data is safe to serialize as-is,
274 + // but we need to convert the Maps to nested Arrays.
275 + batchUIDToMeasuresKeyValueArray: Array.from(
276 + batchUIDToMeasuresMap.entries(),
277 + ),
278 + componentMeasures: componentMeasures,
279 + duration,
280 + flamechart,
281 + internalModuleSourceToRanges: Array.from(
282 + internalModuleSourceToRanges.entries(),
283 + ),
284 + laneToLabelKeyValueArray: Array.from(laneToLabelMap.entries()),
285 + laneToReactMeasureKeyValueArray: Array.from(
286 + laneToReactMeasureMap.entries(),
287 + ),
288 + nativeEvents,
289 + networkMeasures,
290 + otherUserTimingMarks,
291 + reactVersion,
292 + schedulingEvents,
293 + snapshots,
294 + snapshotHeight,
295 + startTime,
296 + suspenseEvents,
297 + thrownErrors,
298 + }),
299 + );
300 +
301 const dataForRoots: Array<ProfilingDataForRootExport> = [];
302 profilingDataFrontend.dataForRoots.forEach(
303 ({
@@ -225,6 +349,7 @@ export function prepareProfilingDataExport(
349 return {
350 version: PROFILER_EXPORT_VERSION,
351 dataForRoots,
352 + timelineData,
353 };
354 }
355
packages/react-devtools-timeline/src/CanvasPage.js
+62 -41
@@ -10,7 +10,7 @@
10 import type {Point} from './view-base';
11 import type {
12 ReactHoverContextInfo,
13 - ReactProfilerData,
13 + TimelineData,
14 ReactMeasure,
15 ViewState,
16 } from './types';
@@ -69,7 +69,7 @@ import styles from './CanvasPage.css';
69 const CONTEXT_MENU_ID = 'canvas';
70
71 type Props = {|
72 - profilerData: ReactProfilerData,
72 + profilerData: TimelineData,
73 viewState: ViewState,
74 |};
75
@@ -92,7 +92,7 @@ function CanvasPage({profilerData, viewState}: Props) {
92 );
93 }
94
95 -const copySummary = (data: ReactProfilerData, measure: ReactMeasure) => {
95 +const copySummary = (data: TimelineData, measure: ReactMeasure) => {
96 const {batchUID, duration, timestamp, type} = measure;
97
98 const [startTime, stopTime] = getBatchRange(batchUID, data);
@@ -108,7 +108,7 @@ const copySummary = (data: ReactProfilerData, measure: ReactMeasure) => {
108 };
109
110 const zoomToBatch = (
111 - data: ReactProfilerData,
111 + data: TimelineData,
112 measure: ReactMeasure,
113 viewState: ViewState,
114 width: number,
@@ -145,7 +145,7 @@ const EMPTY_CONTEXT_INFO: ReactHoverContextInfo = {
145 };
146
147 type AutoSizedCanvasProps = {|
148 - data: ReactProfilerData,
148 + data: TimelineData,
149 height: number,
150 viewState: ViewState,
151 width: number,
@@ -295,14 +295,21 @@ function AutoSizedCanvas({
295 );
296 }
297
298 - const nativeEventsView = new NativeEventsView(surface, defaultFrame, data);
299 - nativeEventsViewRef.current = nativeEventsView;
300 - const nativeEventsViewWrapper = createViewHelper(
301 - nativeEventsView,
302 - 'events',
303 - true,
304 - true,
305 - );
298 + let nativeEventsViewWrapper = null;
299 + if (data.nativeEvents.length > 0) {
300 + const nativeEventsView = new NativeEventsView(
301 + surface,
302 + defaultFrame,
303 + data,
304 + );
305 + nativeEventsViewRef.current = nativeEventsView;
306 + nativeEventsViewWrapper = createViewHelper(
307 + nativeEventsView,
308 + 'events',
309 + true,
310 + true,
311 + );
312 + }
313
314 let thrownErrorsViewWrapper = null;
315 if (data.thrownErrors.length > 0) {
@@ -318,16 +325,19 @@ function AutoSizedCanvas({
325 );
326 }
327
321 - const schedulingEventsView = new SchedulingEventsView(
322 - surface,
323 - defaultFrame,
324 - data,
325 - );
326 - schedulingEventsViewRef.current = schedulingEventsView;
327 - const schedulingEventsViewWrapper = createViewHelper(
328 - schedulingEventsView,
329 - 'react updates',
330 - );
328 + let schedulingEventsViewWrapper = null;
329 + if (data.schedulingEvents.length > 0) {
330 + const schedulingEventsView = new SchedulingEventsView(
331 + surface,
332 + defaultFrame,
333 + data,
334 + );
335 + schedulingEventsViewRef.current = schedulingEventsView;
336 + schedulingEventsViewWrapper = createViewHelper(
337 + schedulingEventsView,
338 + 'react updates',
339 + );
340 + }
341
342 let suspenseEventsViewWrapper = null;
343 if (data.suspenseEvents.length > 0) {
@@ -401,20 +411,23 @@ function AutoSizedCanvas({
411 );
412 }
413
404 - const flamechartView = new FlamechartView(
405 - surface,
406 - defaultFrame,
407 - data.flamechart,
408 - data.internalModuleSourceToRanges,
409 - data.duration,
410 - );
411 - flamechartViewRef.current = flamechartView;
412 - const flamechartViewWrapper = createViewHelper(
413 - flamechartView,
414 - 'flamechart',
415 - true,
416 - true,
417 - );
414 + let flamechartViewWrapper = null;
415 + if (data.flamechart.length > 0) {
416 + const flamechartView = new FlamechartView(
417 + surface,
418 + defaultFrame,
419 + data.flamechart,
420 + data.internalModuleSourceToRanges,
421 + data.duration,
422 + );
423 + flamechartViewRef.current = flamechartView;
424 + flamechartViewWrapper = createViewHelper(
425 + flamechartView,
426 + 'flamechart',
427 + true,
428 + true,
429 + );
430 + }
431
432 // Root view contains all of the sub views defined above.
433 // The order we add them below determines their vertical position.
@@ -429,15 +442,21 @@ function AutoSizedCanvas({
442 if (userTimingMarksViewWrapper !== null) {
443 rootView.addSubview(userTimingMarksViewWrapper);
444 }
432 - rootView.addSubview(nativeEventsViewWrapper);
433 - rootView.addSubview(schedulingEventsViewWrapper);
445 + if (nativeEventsViewWrapper !== null) {
446 + rootView.addSubview(nativeEventsViewWrapper);
447 + }
448 + if (schedulingEventsViewWrapper !== null) {
449 + rootView.addSubview(schedulingEventsViewWrapper);
450 + }
451 if (thrownErrorsViewWrapper !== null) {
452 rootView.addSubview(thrownErrorsViewWrapper);
453 }
454 if (suspenseEventsViewWrapper !== null) {
455 rootView.addSubview(suspenseEventsViewWrapper);
456 }
440 - rootView.addSubview(reactMeasuresViewWrapper);
457 + if (reactMeasuresViewWrapper !== null) {
458 + rootView.addSubview(reactMeasuresViewWrapper);
459 + }
460 if (componentMeasuresViewWrapper !== null) {
461 rootView.addSubview(componentMeasuresViewWrapper);
462 }
@@ -447,7 +466,9 @@ function AutoSizedCanvas({
466 if (networkMeasuresViewWrapper !== null) {
467 rootView.addSubview(networkMeasuresViewWrapper);
468 }
450 - rootView.addSubview(flamechartViewWrapper);
469 + if (flamechartViewWrapper !== null) {
470 + rootView.addSubview(flamechartViewWrapper);
471 + }
472
473 const verticalScrollOverflowView = new VerticalScrollOverflowView(
474 surface,
packages/react-devtools-timeline/src/EventTooltip.js
+4 -4
@@ -15,7 +15,7 @@ import type {
15 ReactComponentMeasure,
16 ReactHoverContextInfo,
17 ReactMeasure,
18 - ReactProfilerData,
18 + TimelineData,
19 SchedulingEvent,
20 Snapshot,
21 SuspenseEvent,
@@ -33,7 +33,7 @@ const MAX_TOOLTIP_TEXT_LENGTH = 60;
33
34 type Props = {|
35 canvasRef: {|current: HTMLCanvasElement | null|},
36 - data: ReactProfilerData,
36 + data: TimelineData,
37 height: number,
38 hoveredEvent: ReactHoverContextInfo | null,
39 origin: Point,
@@ -278,7 +278,7 @@ const TooltipSchedulingEvent = ({
278 data,
279 schedulingEvent,
280 }: {|
281 - data: ReactProfilerData,
281 + data: TimelineData,
282 schedulingEvent: SchedulingEvent,
283 |}) => {
284 const label = getSchedulingEventLabel(schedulingEvent);
@@ -433,7 +433,7 @@ const TooltipReactMeasure = ({
433 data,
434 measure,
435 }: {|
436 - data: ReactProfilerData,
436 + data: TimelineData,
437 measure: ReactMeasure,
438 |}) => {
439 const label = getReactMeasureLabel(measure.type);
packages/react-devtools-timeline/src/ImportButton.css deleted
-10
@@ -1,10 +0,0 @@
1 -/**
2 - * https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
3 - */
4 -.Input {
5 - position: absolute !important;
6 - height: 1px;
7 - width: 1px;
8 - overflow: hidden;
9 - clip: rect(1px, 1px, 1px, 1px);
10 -}
packages/react-devtools-timeline/src/ImportButton.js deleted
-59
@@ -1,59 +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 * as React from 'react';
11 -import {useCallback, useRef} from 'react';
12 -import Button from 'react-devtools-shared/src/devtools/views/Button';
13 -import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
14 -
15 -import styles from './ImportButton.css';
16 -
17 -type Props = {|
18 - children?: mixed,
19 - onFileSelect: (file: File) => void,
20 -|};
21 -
22 -export default function ImportButton({children = null, onFileSelect}: Props) {
23 - const inputRef = useRef<HTMLInputElement | null>(null);
24 -
25 - const handleFiles = useCallback(() => {
26 - const input = inputRef.current;
27 - if (input === null) {
28 - return;
29 - }
30 - if (input.files.length > 0) {
31 - onFileSelect(input.files[0]);
32 - }
33 - // Reset input element to allow the same file to be re-imported
34 - input.value = '';
35 - }, [onFileSelect]);
36 -
37 - const uploadData = useCallback(() => {
38 - if (inputRef.current !== null) {
39 - inputRef.current.click();
40 - }
41 - }, []);
42 -
43 - return (
44 - <>
45 - <input
46 - ref={inputRef}
47 - className={styles.Input}
48 - type="file"
49 - accept=".json"
50 - onChange={handleFiles}
51 - tabIndex={-1}
52 - />
53 - <Button onClick={uploadData} title="Load profile...">
54 - <ButtonIcon type="import" />
55 - {children}
56 - </Button>
57 - </>
58 - );
59 -}
packages/react-devtools-timeline/src/Timeline.js
+56 -60
@@ -9,7 +9,6 @@
9
10 import type {ViewState} from './types';
11
12 -import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
12 import * as React from 'react';
13 import {
14 Suspense,
@@ -20,18 +19,28 @@ import {
19 useState,
20 } from 'react';
21 import {SettingsContext} from 'react-devtools-shared/src/devtools/views/Settings/SettingsContext';
22 +import {ProfilerContext} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext';
23 +import NoProfilingData from 'react-devtools-shared/src/devtools/views/Profiler/NoProfilingData';
24 +import RecordingInProgress from 'react-devtools-shared/src/devtools/views/Profiler/RecordingInProgress';
25 import {updateColorsToMatchTheme} from './content-views/constants';
26 import {TimelineContext} from './TimelineContext';
25 -import ImportButton from './ImportButton';
27 import CanvasPage from './CanvasPage';
28 import {importFile} from './timelineCache';
29 import TimelineSearchInput from './TimelineSearchInput';
30 +import TimelineNotSupported from './TimelineNotSupported';
31 import {TimelineSearchContextController} from './TimelineSearchContext';
32
33 import styles from './Timeline.css';
34
35 export function Timeline(_: {||}) {
34 - const {file, setFile, viewState} = useContext(TimelineContext);
36 + const {
37 + file,
38 + inMemoryTimelineData,
39 + isTimelineSupported,
40 + setFile,
41 + viewState,
42 + } = useContext(TimelineContext);
43 + const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
44
45 const ref = useRef(null);
46
@@ -60,66 +69,47 @@ export function Timeline(_: {||}) {
69 };
70 }, [deferredTheme]);
71
72 + let content = null;
73 + if (isProfiling) {
74 + content = <RecordingInProgress />;
75 + } else if (inMemoryTimelineData && inMemoryTimelineData.length > 0) {
76 + // TODO (timeline) Support multiple renderers.
77 + const timelineData = inMemoryTimelineData[0];
78 +
79 + content = (
80 + <TimelineSearchContextController
81 + profilerData={timelineData}
82 + viewState={viewState}>
83 + <TimelineSearchInput />
84 + <CanvasPage profilerData={timelineData} viewState={viewState} />
85 + </TimelineSearchContextController>
86 + );
87 + } else if (file) {
88 + content = (
89 + <Suspense fallback={<ProcessingData />}>
90 + <FileLoader
91 + file={file}
92 + key={key}
93 + onFileSelect={setFile}
94 + viewState={viewState}
95 + />
96 + </Suspense>
97 + );
98 + } else if (didRecordCommits) {
99 + content = <NoTimelineData />;
100 + } else if (isTimelineSupported) {
101 + content = <NoProfilingData />;
102 + } else {
103 + content = <TimelineNotSupported />;
104 + }
105 +
106 return (
107 <div className={styles.Content} ref={ref}>
65 - {file ? (
66 - <Suspense fallback={<ProcessingData />}>
67 - <FileLoader
68 - file={file}
69 - key={key}
70 - onFileSelect={setFile}
71 - viewState={viewState}
72 - />
73 - </Suspense>
74 - ) : (
75 - <Welcome onFileSelect={setFile} />
76 - )}
108 + {content}
109 </div>
110 );
111 }
112
81 -const Welcome = ({onFileSelect}: {|onFileSelect: (file: File) => void|}) => (
82 - <ol className={styles.WelcomeInstructionsList}>
83 - {isInternalFacebookBuild && (
84 - <li className={styles.WelcomeInstructionsListItem}>
85 - Enable the
86 - <a
87 - className={styles.WelcomeInstructionsListItemLink}
88 - href="https://fburl.com/react-devtools-scheduling-profiler-gk"
89 - rel="noopener noreferrer"
90 - target="_blank">
91 - <code>react_enable_scheduling_profiler</code> GK
92 - </a>
93 - .
94 - </li>
95 - )}
96 - <li className={styles.WelcomeInstructionsListItem}>
97 - Open a website that's built with the
98 - <a
99 - className={styles.WelcomeInstructionsListItemLink}
100 - href="https://reactjs.org/link/profiling"
101 - rel="noopener noreferrer"
102 - target="_blank">
103 - profiling build of ReactDOM
104 - </a>
105 - (version 18 or newer).
106 - </li>
107 - <li className={styles.WelcomeInstructionsListItem}>
108 - Open the "Performance" tab in Chrome and record some performance data.
109 - </li>
110 - <li className={styles.WelcomeInstructionsListItem}>
111 - Click the "Save profile..." button in Chrome to export the data.
112 - </li>
113 - <li className={styles.WelcomeInstructionsListItem}>
114 - Import the data into the profiler:
115 - <br />
116 - <ImportButton onFileSelect={onFileSelect}>
117 - <span className={styles.ImportButtonLabel}>Import</span>
118 - </ImportButton>
119 - </li>
120 - </ol>
121 -);
122 -
113 const ProcessingData = () => (
114 <div className={styles.EmptyStateContainer}>
115 <div className={styles.Header}>Processing data...</div>
@@ -136,9 +126,15 @@ const CouldNotLoadProfile = ({error, onFileSelect}) => (
126 </div>
127 )}
128 <div className={styles.Row}>
139 - Try importing
140 - <ImportButton onFileSelect={onFileSelect} />
141 - another Chrome performance profile.
129 + Try importing another Chrome performance profile.
130 + </div>
131 + </div>
132 +);
133 +
134 +const NoTimelineData = () => (
135 + <div className={styles.EmptyStateContainer}>
136 + <div className={styles.Row}>
137 + This current profile does not contain timeline data.
138 </div>
139 </div>
140 );
packages/react-devtools-timeline/src/TimelineContext.js
+43 -2
@@ -8,10 +8,19 @@
8 */
9
10 import * as React from 'react';
11 -import {createContext, useMemo, useRef, useState} from 'react';
11 +import {
12 + createContext,
13 + useContext,
14 + useMemo,
15 + useRef,
16 + useState,
17 + useSyncExternalStore,
18 +} from 'react';
19 +import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
20
21 import type {
22 HorizontalScrollStateChangeCallback,
23 + TimelineData,
24 SearchRegExpStateChangeCallback,
25 ViewState,
26 } from './types';
@@ -19,6 +28,8 @@ import type {RefObject} from 'shared/ReactTypes';
28
29 export type Context = {|
30 file: File | null,
31 + inMemoryTimelineData: Array<TimelineData> | null,
32 + isTimelineSupported: boolean,
33 searchInputContainerRef: RefObject,
34 setFile: (file: File | null) => void,
35 viewState: ViewState,
@@ -35,6 +46,34 @@ function TimelineContextController({children}: Props) {
46 const searchInputContainerRef = useRef(null);
47 const [file, setFile] = useState<string | null>(null);
48
49 + const store = useContext(StoreContext);
50 +
51 + const isTimelineSupported = useSyncExternalStore<boolean>(
52 + function subscribe(callback) {
53 + store.addListener('rootSupportsTimelineProfiling', callback);
54 + return function unsubscribe() {
55 + store.removeListener('rootSupportsTimelineProfiling', callback);
56 + };
57 + },
58 + function getState() {
59 + return store.rootSupportsTimelineProfiling;
60 + },
61 + );
62 +
63 + const inMemoryTimelineData = useSyncExternalStore<Array<TimelineData> | null>(
64 + function subscribe(callback) {
65 + store.profilerStore.addListener('isProcessingData', callback);
66 + store.profilerStore.addListener('profilingData', callback);
67 + return function unsubscribe() {
68 + store.profilerStore.removeListener('isProcessingData', callback);
69 + store.profilerStore.removeListener('profilingData', callback);
70 + };
71 + },
72 + function getState() {
73 + return store.profilerStore.profilingData?.timelineData || null;
74 + },
75 + );
76 +
77 // Recreate view state any time new profiling data is imported.
78 const viewState = useMemo<ViewState>(() => {
79 const horizontalScrollStateChangeCallbacks: Set<HorizontalScrollStateChangeCallback> = new Set();
@@ -85,11 +124,13 @@ function TimelineContextController({children}: Props) {
124 const value = useMemo(
125 () => ({
126 file,
127 + inMemoryTimelineData,
128 + isTimelineSupported,
129 searchInputContainerRef,
130 setFile,
131 viewState,
132 }),
92 - [file, setFile, viewState],
133 + [file, inMemoryTimelineData, isTimelineSupported, setFile, viewState],
134 );
135
136 return (
packages/react-devtools-timeline/src/TimelineNotSupported.css new
+38
@@ -0,0 +1,38 @@
1 +.Column {
2 + display: flex;
3 + flex-direction: column;
4 + align-items: center;
5 + justify-content: center;
6 + padding: 0 1rem;
7 +}
8 +
9 +.Header {
10 + font-size: var(--font-size-sans-large);
11 + margin-bottom: 0.5rem;
12 +}
13 +
14 +.Paragraph {
15 + text-align: center;
16 + margin: 0;
17 +}
18 +
19 +.Link {
20 + color: var(--color-link);
21 +}
22 +
23 +.LearnMoreRow {
24 + margin-top: 1rem;
25 + color: var(--color-dim);
26 + font-size: var(--font-size-sans-small);
27 +}
28 +
29 +.Code {
30 + color: var(--color-bridge-version-number);
31 +}
32 +
33 +.MetaGKRow {
34 + background: var(--color-background-hover);
35 + padding: 0.25rem 0.5rem;
36 + border-radius: 0.25rem;
37 + margin-top: 1rem;
38 +}
\ No newline at end of file
packages/react-devtools-timeline/src/TimelineNotSupported.js new
+52
@@ -0,0 +1,52 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
12 +
13 +import styles from './TimelineNotSupported.css';
14 +
15 +export default function TimelineNotSupported() {
16 + return (
17 + <div className={styles.Column}>
18 + <div className={styles.Header}>Timeline profiling not supported.</div>
19 + <p className={styles.Paragraph}>
20 + <span>
21 + Timeline profiler requires a development or profiling build of{' '}
22 + <code className={styles.Code}>react-dom@^18</code>.
23 + </span>
24 + </p>
25 + <div className={styles.LearnMoreRow}>
26 + Click{' '}
27 + <a
28 + className={styles.Link}
29 + href="https://fb.me/react-devtools-profiling"
30 + rel="noopener noreferrer"
31 + target="_blank">
32 + here
33 + </a>{' '}
34 + to learn more about profiling.
35 + </div>
36 +
37 + {isInternalFacebookBuild && (
38 + <div className={styles.MetaGKRow}>
39 + <strong>Meta only</strong>: Enable the{' '}
40 + <a
41 + className={styles.Link}
42 + href="https://fburl.com/react-devtools-scheduling-profiler-gk"
43 + rel="noopener noreferrer"
44 + target="_blank">
45 + react_enable_scheduling_profiler GK
46 + </a>
47 + .
48 + </div>
49 + )}
50 + </div>
51 + );
52 +}
packages/react-devtools-timeline/src/TimelineSearchContext.js
+4 -8
@@ -10,14 +10,10 @@
10 import * as React from 'react';
11 import {createContext, useMemo, useReducer} from 'react';
12
13 -import type {
14 - ReactComponentMeasure,
15 - ReactProfilerData,
16 - ViewState,
17 -} from './types';
13 +import type {ReactComponentMeasure, TimelineData, ViewState} from './types';
14
15 type State = {|
20 - profilerData: ReactProfilerData,
16 + profilerData: TimelineData,
17 searchIndex: number,
18 searchRegExp: RegExp | null,
19 searchResults: Array<ReactComponentMeasure>,
@@ -116,7 +112,7 @@ function reducer(state: State, action: Action): State {
112 }
113
114 export type Context = {|
119 - profilerData: ReactProfilerData,
115 + profilerData: TimelineData,
116
117 // Search state
118 dispatch: Dispatch,
@@ -131,7 +127,7 @@ TimelineSearchContext.displayName = 'TimelineSearchContext';
127
128 type Props = {|
129 children: React$Node,
134 - profilerData: ReactProfilerData,
130 + profilerData: TimelineData,
131 viewState: ViewState,
132 |};
133
packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js
+3 -7
@@ -7,11 +7,7 @@
7 * @flow
8 */
9
10 -import type {
11 - ReactComponentMeasure,
12 - ReactProfilerData,
13 - ViewState,
14 -} from '../types';
10 +import type {ReactComponentMeasure, TimelineData, ViewState} from '../types';
11 import type {
12 Interaction,
13 IntrinsicSize,
@@ -44,7 +40,7 @@ export class ComponentMeasuresView extends View {
40 _cachedSearchRegExp: RegExp | null = null;
41 _hoveredComponentMeasure: ReactComponentMeasure | null = null;
42 _intrinsicSize: IntrinsicSize;
47 - _profilerData: ReactProfilerData;
43 + _profilerData: TimelineData;
44 _viewState: ViewState;
45
46 onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
@@ -52,7 +48,7 @@ export class ComponentMeasuresView extends View {
48 constructor(
49 surface: Surface,
50 frame: Rect,
55 - profilerData: ReactProfilerData,
51 + profilerData: TimelineData,
52 viewState: ViewState,
53 ) {
54 super(surface, frame);
packages/react-devtools-timeline/src/content-views/NativeEventsView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {NativeEvent, ReactProfilerData} from '../types';
10 +import type {NativeEvent, TimelineData} from '../types';
11 import type {
12 Interaction,
13 IntrinsicSize,
@@ -40,11 +40,11 @@ export class NativeEventsView extends View {
40 _hoveredEvent: NativeEvent | null = null;
41 _intrinsicSize: IntrinsicSize;
42 _maxDepth: number = 0;
43 - _profilerData: ReactProfilerData;
43 + _profilerData: TimelineData;
44
45 onHover: ((event: NativeEvent | null) => void) | null = null;
46
47 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
47 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
48 super(surface, frame);
49
50 this._profilerData = profilerData;
packages/react-devtools-timeline/src/content-views/NetworkMeasuresView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {NetworkMeasure, ReactProfilerData} from '../types';
10 +import type {NetworkMeasure, TimelineData} from '../types';
11 import type {
12 Interaction,
13 IntrinsicSize,
@@ -43,11 +43,11 @@ export class NetworkMeasuresView extends View {
43 _hoveredNetworkMeasure: NetworkMeasure | null = null;
44 _intrinsicSize: IntrinsicSize;
45 _maxDepth: number = 0;
46 - _profilerData: ReactProfilerData;
46 + _profilerData: TimelineData;
47
48 onHover: ((event: NetworkMeasure | null) => void) | null = null;
49
50 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
50 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
51 super(surface, frame);
52
53 this._profilerData = profilerData;
packages/react-devtools-timeline/src/content-views/ReactMeasuresView.js
+7 -9
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {ReactLane, ReactMeasure, ReactProfilerData} from '../types';
10 +import type {ReactLane, ReactMeasure, TimelineData} from '../types';
11 import type {
12 Interaction,
13 IntrinsicSize,
@@ -33,7 +33,6 @@ import {
33 } from '../view-base';
34
35 import {COLORS, BORDER_SIZE, REACT_MEASURE_HEIGHT} from './constants';
36 -import {REACT_TOTAL_NUM_LANES} from '../constants';
36
37 const REACT_LANE_HEIGHT = REACT_MEASURE_HEIGHT + BORDER_SIZE;
38 const MAX_ROWS_TO_SHOW_INITIALLY = 5;
@@ -41,12 +40,12 @@ const MAX_ROWS_TO_SHOW_INITIALLY = 5;
40 export class ReactMeasuresView extends View {
41 _intrinsicSize: IntrinsicSize;
42 _lanesToRender: ReactLane[];
44 - _profilerData: ReactProfilerData;
43 + _profilerData: TimelineData;
44 _hoveredMeasure: ReactMeasure | null = null;
45
46 onHover: ((measure: ReactMeasure | null) => void) | null = null;
47
49 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
48 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
49 super(surface, frame);
50 this._profilerData = profilerData;
51 this._performPreflightComputations();
@@ -55,12 +54,11 @@ export class ReactMeasuresView extends View {
54 _performPreflightComputations() {
55 this._lanesToRender = [];
56
58 - for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
59 - const measuresForLane = this._profilerData.laneToReactMeasureMap.get(
60 - lane,
61 - );
57 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
58 + for (const [lane, measuresForLane] of this._profilerData
59 + .laneToReactMeasureMap) {
60 // Only show lanes with measures
63 - if (measuresForLane != null && measuresForLane.length > 0) {
61 + if (measuresForLane.length > 0) {
62 this._lanesToRender.push(lane);
63 }
64 }
packages/react-devtools-timeline/src/content-views/SchedulingEventsView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {SchedulingEvent, ReactProfilerData} from '../types';
10 +import type {SchedulingEvent, TimelineData} from '../types';
11 import type {
12 Interaction,
13 MouseMoveInteraction,
@@ -40,13 +40,13 @@ const EVENT_ROW_HEIGHT_FIXED =
40 TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
41
42 export class SchedulingEventsView extends View {
43 - _profilerData: ReactProfilerData;
43 + _profilerData: TimelineData;
44 _intrinsicSize: Size;
45
46 _hoveredEvent: SchedulingEvent | null = null;
47 onHover: ((event: SchedulingEvent | null) => void) | null = null;
48
49 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
49 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
50 super(surface, frame);
51 this._profilerData = profilerData;
52
packages/react-devtools-timeline/src/content-views/SnapshotsView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Snapshot, ReactProfilerData} from '../types';
10 +import type {Snapshot, TimelineData} from '../types';
11 import type {
12 Interaction,
13 Point,
@@ -31,11 +31,11 @@ type OnHover = (node: Snapshot | null) => void;
31 export class SnapshotsView extends View {
32 _hoverLocation: Point | null = null;
33 _intrinsicSize: Size;
34 - _profilerData: ReactProfilerData;
34 + _profilerData: TimelineData;
35
36 onHover: OnHover | null = null;
37
38 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
38 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
39 super(surface, frame);
40
41 this._intrinsicSize = {
packages/react-devtools-timeline/src/content-views/SuspenseEventsView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {SuspenseEvent, ReactProfilerData} from '../types';
10 +import type {SuspenseEvent, TimelineData} from '../types';
11 import type {
12 Interaction,
13 IntrinsicSize,
@@ -47,11 +47,11 @@ export class SuspenseEventsView extends View {
47 _hoveredEvent: SuspenseEvent | null = null;
48 _intrinsicSize: IntrinsicSize;
49 _maxDepth: number = 0;
50 - _profilerData: ReactProfilerData;
50 + _profilerData: TimelineData;
51
52 onHover: ((event: SuspenseEvent | null) => void) | null = null;
53
54 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
54 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
55 super(surface, frame);
56
57 this._profilerData = profilerData;
packages/react-devtools-timeline/src/content-views/ThrownErrorsView.js
+3 -3
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {ThrownError, ReactProfilerData} from '../types';
10 +import type {ThrownError, TimelineData} from '../types';
11 import type {
12 Interaction,
13 MouseMoveInteraction,
@@ -40,12 +40,12 @@ const EVENT_ROW_HEIGHT_FIXED =
40 TOP_ROW_PADDING + REACT_EVENT_DIAMETER + TOP_ROW_PADDING;
41
42 export class ThrownErrorsView extends View {
43 - _profilerData: ReactProfilerData;
43 + _profilerData: TimelineData;
44 _intrinsicSize: Size;
45 _hoveredEvent: ThrownError | null = null;
46 onHover: ((event: ThrownError | null) => void) | null = null;
47
48 - constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
48 + constructor(surface: Surface, frame: Rect, profilerData: TimelineData) {
49 super(surface, frame);
50 this._profilerData = profilerData;
51
packages/react-devtools-timeline/src/createDataResourceFromImportedFile.js
+3 -3
@@ -11,17 +11,17 @@ import {createResource} from 'react-devtools-shared/src/devtools/cache';
11 import {importFile} from './import-worker';
12
13 import type {Resource} from 'react-devtools-shared/src/devtools/cache';
14 -import type {ReactProfilerData} from './types';
14 +import type {TimelineData} from './types';
15 import type {ImportWorkerOutputData} from './import-worker/index';
16
17 -export type DataResource = Resource<void, File, ReactProfilerData | Error>;
17 +export type DataResource = Resource<void, File, TimelineData | Error>;
18
19 export default function createDataResourceFromImportedFile(
20 file: File,
21 ): DataResource {
22 return createResource(
23 () => {
24 - return new Promise<ReactProfilerData | Error>((resolve, reject) => {
24 + return new Promise<TimelineData | Error>((resolve, reject) => {
25 const promise = ((importFile(
26 file,
27 ): any): Promise<ImportWorkerOutputData>);
packages/react-devtools-timeline/src/import-worker/index.js
+2 -2
@@ -13,7 +13,7 @@
13 import * as importFileModule from './importFile';
14 import WorkerizedImportFile from './importFile.worker';
15
16 -import type {ReactProfilerData} from '../types';
16 +import type {TimelineData} from '../types';
17
18 type ImportFileModule = typeof importFileModule;
19
@@ -22,7 +22,7 @@ const workerizedImportFile: ImportFileModule = window.Worker
22 : importFileModule;
23
24 export type ImportWorkerOutputData =
25 - | {|status: 'SUCCESS', processedData: ReactProfilerData|}
25 + | {|status: 'SUCCESS', processedData: TimelineData|}
26 | {|status: 'INVALID_PROFILE_ERROR', error: Error|}
27 | {|status: 'UNEXPECTED_ERROR', error: Error|};
28
packages/react-devtools-timeline/src/import-worker/preprocessData.js
+23 -72
@@ -25,7 +25,7 @@ import type {
25 ReactComponentMeasureType,
26 ReactMeasure,
27 ReactMeasureType,
28 - ReactProfilerData,
28 + TimelineData,
29 SchedulingEvent,
30 SuspenseEvent,
31 } from '../types';
@@ -105,7 +105,7 @@ export function getLanesFromTransportDecimalBitmask(
105 }
106
107 function updateLaneToLabelMap(
108 - profilerData: ReactProfilerData,
108 + profilerData: TimelineData,
109 laneLabelTuplesString: string,
110 ): void {
111 // These marks appear multiple times in the data;
@@ -143,7 +143,7 @@ function markWorkStarted(
143 type: ReactMeasureType,
144 startTime: Milliseconds,
145 lanes: ReactLane[],
146 - currentProfilerData: ReactProfilerData,
146 + currentProfilerData: TimelineData,
147 state: ProcessorState,
148 ) {
149 const {batchUID, measureStack} = state;
@@ -179,7 +179,7 @@ function markWorkStarted(
179 function markWorkCompleted(
180 type: ReactMeasureType,
181 stopTime: Milliseconds,
182 - currentProfilerData: ReactProfilerData,
182 + currentProfilerData: TimelineData,
183 stack: $PropertyType<ProcessorState, 'measureStack'>,
184 ) {
185 if (stack.length === 0) {
@@ -229,7 +229,7 @@ function throwIfIncomplete(
229 function processEventDispatch(
230 event: TimelineEvent,
231 timestamp: Milliseconds,
232 - profilerData: ReactProfilerData,
232 + profilerData: TimelineData,
233 state: ProcessorState,
234 ) {
235 const data = event.args.data;
@@ -292,7 +292,7 @@ function processEventDispatch(
292 function processResourceFinish(
293 event: TimelineEvent,
294 timestamp: Milliseconds,
295 - profilerData: ReactProfilerData,
295 + profilerData: TimelineData,
296 state: ProcessorState,
297 ) {
298 const requestId = event.args.data.requestId;
@@ -314,7 +314,7 @@ function processResourceFinish(
314 function processResourceReceivedData(
315 event: TimelineEvent,
316 timestamp: Milliseconds,
317 - profilerData: ReactProfilerData,
317 + profilerData: TimelineData,
318 state: ProcessorState,
319 ) {
320 const requestId = event.args.data.requestId;
@@ -331,7 +331,7 @@ function processResourceReceivedData(
331 function processResourceReceiveResponse(
332 event: TimelineEvent,
333 timestamp: Milliseconds,
334 - profilerData: ReactProfilerData,
334 + profilerData: TimelineData,
335 state: ProcessorState,
336 ) {
337 const requestId = event.args.data.requestId;
@@ -344,7 +344,7 @@ function processResourceReceiveResponse(
344 function processScreenshot(
345 event: TimelineEvent,
346 timestamp: Milliseconds,
347 - profilerData: ReactProfilerData,
347 + profilerData: TimelineData,
348 state: ProcessorState,
349 ) {
350 const encodedSnapshot = event.args.snapshot; // Base 64 encoded
@@ -385,7 +385,7 @@ function processScreenshot(
385 function processResourceSendRequest(
386 event: TimelineEvent,
387 timestamp: Milliseconds,
388 - profilerData: ReactProfilerData,
388 + profilerData: TimelineData,
389 state: ProcessorState,
390 ) {
391 const data = event.args.data;
@@ -428,7 +428,7 @@ function processResourceSendRequest(
428 function processTimelineEvent(
429 event: TimelineEvent,
430 /** Finalized profiler data up to `event`. May be mutated. */
431 - currentProfilerData: ReactProfilerData,
431 + currentProfilerData: TimelineData,
432 /** Intermediate processor state. May be mutated. */
433 state: ProcessorState,
434 ) {
@@ -551,10 +551,7 @@ function processTimelineEvent(
551 timestamp: startTime,
552 type: 'thrown-error',
553 });
554 - } // eslint-disable-line brace-style
555 -
556 - // React Events - suspense
557 - else if (name.startsWith('--suspense-suspend-')) {
554 + } else if (name.startsWith('--suspense-suspend-')) {
555 const [
556 id,
557 componentName,
@@ -592,7 +589,6 @@ function processTimelineEvent(
589 phase: ((phase: any): Phase),
590 promiseName: promiseName || null,
591 resolution: 'unresolved',
595 - resuspendTimestamps: null,
592 timestamp: startTime,
593 type: 'suspense',
594 warning: null,
@@ -611,16 +607,6 @@ function processTimelineEvent(
607
608 currentProfilerData.suspenseEvents.push(suspenseEvent);
609 state.unresolvedSuspenseEvents.set(id, suspenseEvent);
614 - } else if (name.startsWith('--suspense-resuspend-')) {
615 - const [id] = name.substr(21).split('-');
616 - const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
617 - if (suspenseEvent != null) {
618 - if (suspenseEvent.resuspendTimestamps === null) {
619 - suspenseEvent.resuspendTimestamps = [startTime];
620 - } else {
621 - suspenseEvent.resuspendTimestamps.push(startTime);
622 - }
623 - }
610 } else if (name.startsWith('--suspense-resolved-')) {
611 const [id] = name.substr(20).split('-');
612 const suspenseEvent = state.unresolvedSuspenseEvents.get(id);
@@ -639,10 +625,7 @@ function processTimelineEvent(
625 suspenseEvent.duration = startTime - suspenseEvent.timestamp;
626 suspenseEvent.resolution = 'rejected';
627 }
642 - } // eslint-disable-line brace-style
643 -
644 - // React Measures - render
645 - else if (name.startsWith('--render-start-')) {
628 + } else if (name.startsWith('--render-start-')) {
629 if (state.nextRenderShouldGenerateNewBatchID) {
630 state.nextRenderShouldGenerateNewBatchID = false;
631 state.batchUID = ((state.uidCounter++: any): BatchUID);
@@ -701,24 +684,7 @@ function processTimelineEvent(
684 currentProfilerData,
685 state.measureStack,
686 );
704 - } else if (name.startsWith('--render-cancel')) {
705 - state.nextRenderShouldGenerateNewBatchID = true;
706 - markWorkCompleted(
707 - 'render',
708 - startTime,
709 - currentProfilerData,
710 - state.measureStack,
711 - );
712 - markWorkCompleted(
713 - 'render-idle',
714 - startTime,
715 - currentProfilerData,
716 - state.measureStack,
717 - );
718 - } // eslint-disable-line brace-style
719 -
720 - // React Measures - commits
721 - else if (name.startsWith('--commit-start-')) {
687 + } else if (name.startsWith('--commit-start-')) {
688 state.nextRenderShouldGenerateNewBatchID = true;
689 const [laneBitmaskString] = name.substr(15).split('-');
690
@@ -742,10 +708,7 @@ function processTimelineEvent(
708 currentProfilerData,
709 state.measureStack,
710 );
745 - } // eslint-disable-line brace-style
746 -
747 - // React Measures - layout effects
748 - else if (name.startsWith('--layout-effects-start-')) {
711 + } else if (name.startsWith('--layout-effects-start-')) {
712 const [laneBitmaskString] = name.substr(23).split('-');
713
714 markWorkStarted(
@@ -762,10 +725,7 @@ function processTimelineEvent(
725 currentProfilerData,
726 state.measureStack,
727 );
765 - } // eslint-disable-line brace-style
766 -
767 - // React Measures - passive effects
768 - else if (name.startsWith('--passive-effects-start-')) {
728 + } else if (name.startsWith('--passive-effects-start-')) {
729 const [laneBitmaskString] = name.substr(24).split('-');
730
731 markWorkStarted(
@@ -782,10 +742,7 @@ function processTimelineEvent(
742 currentProfilerData,
743 state.measureStack,
744 );
785 - } // eslint-disable-line brace-style
786 -
787 - // Internal module ranges
788 - else if (name.startsWith('--react-internal-module-start-')) {
745 + } else if (name.startsWith('--react-internal-module-start-')) {
746 const stackFrameStart = name.substr(30);
747
748 if (!state.internalModuleStackStringSet.has(stackFrameStart)) {
@@ -796,7 +753,7 @@ function processTimelineEvent(
753 state.internalModuleCurrentStackFrame = parsedStackFrameStart;
754 }
755 } else if (name.startsWith('--react-internal-module-stop-')) {
799 - const stackFrameStop = name.substr(19);
756 + const stackFrameStop = name.substr(29);
757
758 if (!state.internalModuleStackStringSet.has(stackFrameStop)) {
759 state.internalModuleStackStringSet.add(stackFrameStop);
@@ -825,10 +782,7 @@ function processTimelineEvent(
782 }
783 }
784 }
828 - } // eslint-disable-line brace-style
829 -
830 - // Other user timing marks/measures
831 - else if (ph === 'R' || ph === 'n') {
785 + } else if (ph === 'R' || ph === 'n') {
786 // User Timing mark
787 currentProfilerData.otherUserTimingMarks.push({
788 name,
@@ -841,10 +795,7 @@ function processTimelineEvent(
795 } else if (ph === 'i' || ph === 'I') {
796 // Instant events.
797 // Note that the capital "I" is a deprecated value that exists in Chrome Canary traces.
844 - } // eslint-disable-line brace-style
845 -
846 - // Unrecognized event
847 - else {
798 + } else {
799 throw new InvalidProfileError(
800 `Unrecognized event ${JSON.stringify(
801 event,
@@ -882,7 +833,7 @@ function assertCurrentComponentMeasureType(
833 function processReactComponentMeasure(
834 name: string,
835 startTime: Milliseconds,
885 - currentProfilerData: ReactProfilerData,
836 + currentProfilerData: TimelineData,
837 state: ProcessorState,
838 ): void {
839 if (name.startsWith('--component-render-start-')) {
@@ -1048,7 +999,7 @@ function parseStackFrame(stackFrame: string): ErrorStackFrame | null {
999
1000 export default async function preprocessData(
1001 timeline: TimelineEvent[],
1051 -): Promise<ReactProfilerData> {
1002 +): Promise<TimelineData> {
1003 const flamechart = preprocessFlamechart(timeline);
1004
1005 const laneToReactMeasureMap = new Map();
@@ -1056,7 +1007,7 @@ export default async function preprocessData(
1007 laneToReactMeasureMap.set(lane, []);
1008 }
1009
1059 - const profilerData: ReactProfilerData = {
1010 + const profilerData: TimelineData = {
1011 batchUIDToMeasuresMap: new Map(),
1012 componentMeasures: [],
1013 duration: 0,
packages/react-devtools-timeline/src/timelineCache.js
+5 -8
@@ -8,7 +8,7 @@
8 */
9
10 import type {Wakeable} from 'shared/ReactTypes';
11 -import type {ReactProfilerData} from './types';
11 +import type {TimelineData} from './types';
12
13 import {importFile as importFileWorker} from './import-worker';
14
@@ -36,10 +36,7 @@ type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
36 // This is intentionally a module-level Map, rather than a React-managed one.
37 // Otherwise, refreshing the inspected element cache would also clear this cache.
38 // Profiler file contents are static anyway.
39 -const fileNameToProfilerDataMap: Map<
40 - string,
41 - Record<ReactProfilerData>,
42 -> = new Map();
39 +const fileNameToProfilerDataMap: Map<string, Record<TimelineData>> = new Map();
40
41 function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
42 if (record.status === Resolved) {
@@ -53,7 +50,7 @@ function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
50 }
51 }
52
56 -export function importFile(file: File): ReactProfilerData | Error {
53 +export function importFile(file: File): TimelineData | Error {
54 const fileName = file.name;
55 let record = fileNameToProfilerDataMap.get(fileName);
56
@@ -74,7 +71,7 @@ export function importFile(file: File): ReactProfilerData | Error {
71 callbacks.clear();
72 };
73
77 - const newRecord: Record<ReactProfilerData> = (record = {
74 + const newRecord: Record<TimelineData> = (record = {
75 status: Pending,
76 value: wakeable,
77 });
@@ -82,7 +79,7 @@ export function importFile(file: File): ReactProfilerData | Error {
79 importFileWorker(file).then(data => {
80 switch (data.status) {
81 case 'SUCCESS':
85 - const resolvedRecord = ((newRecord: any): ResolvedRecord<ReactProfilerData>);
82 + const resolvedRecord = ((newRecord: any): ResolvedRecord<TimelineData>);
83 resolvedRecord.status = Resolved;
84 resolvedRecord.value = data.processedData;
85 break;
packages/react-devtools-timeline/src/types.js
+26 -3
@@ -68,7 +68,6 @@ export type SuspenseEvent = {|
68 +phase: Phase | null,
69 promiseName: string | null,
70 resolution: 'rejected' | 'resolved' | 'unresolved',
71 - resuspendTimestamps: Array<number> | null,
71 +type: 'suspense',
72 |};
73
@@ -196,13 +195,15 @@ export type InternalModuleSourceToRanges = Map<
195 Array<[ErrorStackFrame, ErrorStackFrame]>,
196 >;
197
199 -export type ReactProfilerData = {|
198 +export type LaneToLabelMap = Map<ReactLane, string>;
199 +
200 +export type TimelineData = {|
201 batchUIDToMeasuresMap: Map<BatchUID, ReactMeasure[]>,
202 componentMeasures: ReactComponentMeasure[],
203 duration: number,
204 flamechart: Flamechart,
205 internalModuleSourceToRanges: InternalModuleSourceToRanges,
205 - laneToLabelMap: Map<ReactLane, string>,
206 + laneToLabelMap: LaneToLabelMap,
207 laneToReactMeasureMap: Map<ReactLane, ReactMeasure[]>,
208 nativeEvents: NativeEvent[],
209 networkMeasures: NetworkMeasure[],
@@ -216,6 +217,28 @@ export type ReactProfilerData = {|
217 thrownErrors: ThrownError[],
218 |};
219
220 +export type TimelineDataExport = {|
221 + batchUIDToMeasuresKeyValueArray: Array<[BatchUID, ReactMeasure[]]>,
222 + componentMeasures: ReactComponentMeasure[],
223 + duration: number,
224 + flamechart: Flamechart,
225 + internalModuleSourceToRanges: Array<
226 + [string, Array<[ErrorStackFrame, ErrorStackFrame]>],
227 + >,
228 + laneToLabelKeyValueArray: Array<[ReactLane, string]>,
229 + laneToReactMeasureKeyValueArray: Array<[ReactLane, ReactMeasure[]]>,
230 + nativeEvents: NativeEvent[],
231 + networkMeasures: NetworkMeasure[],
232 + otherUserTimingMarks: UserTimingMark[],
233 + reactVersion: string | null,
234 + schedulingEvents: SchedulingEvent[],
235 + snapshots: Snapshot[],
236 + snapshotHeight: number,
237 + startTime: number,
238 + suspenseEvents: SuspenseEvent[],
239 + thrownErrors: ThrownError[],
240 +|};
241 +
242 export type ReactHoverContextInfo = {|
243 componentMeasure: ReactComponentMeasure | null,
244 flamechartStackFrame: FlamechartStackFrame | null,
packages/react-devtools-timeline/src/utils/getBatchRange.js
+2 -2
@@ -13,12 +13,12 @@ import type {
13 BatchUID,
14 Milliseconds,
15 ReactMeasure,
16 - ReactProfilerData,
16 + TimelineData,
17 } from '../types';
18
19 function unmemoizedGetBatchRange(
20 batchUID: BatchUID,
21 - data: ReactProfilerData,
21 + data: TimelineData,
22 minStartTime?: number = 0,
23 ): [Milliseconds, Milliseconds] {
24 const measures = data.batchUIDToMeasuresMap.get(batchUID);
scripts/jest/config.build-devtools.js
+6
@@ -70,9 +70,15 @@ module.exports = Object.assign({}, baseConfig, {
70 require.resolve(
71 '../../packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js'
72 ),
73 + require.resolve(
74 + '../../packages/react-devtools-shared/src/__tests__/__serializers__/profilingSerializer.js'
75 + ),
76 require.resolve(
77 '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
78 ),
79 + require.resolve(
80 + '../../packages/react-devtools-shared/src/__tests__/__serializers__/timelineDataSerializer.js'
81 + ),
82 require.resolve(
83 '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
84 ),