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
),