Scheduling Profiler: Misc UX and performance improvements (#22043)
Brian Vaughn committed
Aug 9, 2021 at 17:11 UTC
5634ed16aaba4a507844f0502953abcf40e3a165
12 files changed
+1076
-623
packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+78
-48
@@ -7,19 +7,18 @@
7
* @flow
8
*/
9
10
-import type {
11
- Point,
12
- HorizontalPanAndZoomViewOnChangeCallback,
13
-} from './view-base';
10
+import type {Point} from './view-base';
11
import type {
12
ReactHoverContextInfo,
13
ReactProfilerData,
14
ReactMeasure,
15
+ ViewState,
16
} from './types';
17
18
import * as React from 'react';
19
import {
20
Fragment,
21
+ useContext,
22
useEffect,
23
useLayoutEffect,
24
useRef,
@@ -54,12 +53,14 @@ import {
53
UserTimingMarksView,
54
} from './content-views';
55
import {COLORS} from './content-views/constants';
57
-
56
+import {clampState, moveStateToRange} from './view-base/utils/scrollState';
57
import EventTooltip from './EventTooltip';
58
+import {RegistryContext} from 'react-devtools-shared/src/devtools/ContextMenu/Contexts';
59
import ContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenu';
60
import ContextMenuItem from 'react-devtools-shared/src/devtools/ContextMenu/ContextMenuItem';
61
import useContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/useContextMenu';
62
import {getBatchRange} from './utils/getBatchRange';
63
+import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
64
65
import styles from './CanvasPage.css';
66
@@ -67,16 +68,22 @@ const CONTEXT_MENU_ID = 'canvas';
68
69
type Props = {|
70
profilerData: ReactProfilerData,
71
+ viewState: ViewState,
72
|};
73
72
-function CanvasPage({profilerData}: Props) {
74
+function CanvasPage({profilerData, viewState}: Props) {
75
return (
76
<div
77
className={styles.CanvasPage}
78
style={{backgroundColor: COLORS.BACKGROUND}}>
79
<AutoSizer>
80
{({height, width}: {height: number, width: number}) => (
79
- <AutoSizedCanvas data={profilerData} height={height} width={width} />
81
+ <AutoSizedCanvas
82
+ data={profilerData}
83
+ height={height}
84
+ viewState={viewState}
85
+ width={width}
86
+ />
87
)}
88
</AutoSizer>
89
</div>
@@ -98,27 +105,43 @@ const copySummary = (data: ReactProfilerData, measure: ReactMeasure) => {
105
);
106
};
107
101
-// TODO (scheduling profiler) Why is the "zoom" feature so much slower than normal rendering?
108
const zoomToBatch = (
109
data: ReactProfilerData,
110
measure: ReactMeasure,
105
- syncedHorizontalPanAndZoomViews: HorizontalPanAndZoomView[],
111
+ viewState: ViewState,
112
+ width: number,
113
) => {
114
const {batchUID} = measure;
108
- const [startTime, stopTime] = getBatchRange(batchUID, data);
109
- syncedHorizontalPanAndZoomViews.forEach(syncedView =>
110
- // Using time as range works because the views' intrinsic content size is based on time.
111
- syncedView.zoomToRange(startTime, stopTime),
112
- );
115
+ const [rangeStart, rangeEnd] = getBatchRange(batchUID, data);
116
+
117
+ // Convert from time range to ScrollState
118
+ const scrollState = moveStateToRange({
119
+ state: viewState.horizontalScrollState,
120
+ rangeStart,
121
+ rangeEnd,
122
+ contentLength: data.duration,
123
+
124
+ minContentLength: data.duration * MIN_ZOOM_LEVEL,
125
+ maxContentLength: data.duration * MAX_ZOOM_LEVEL,
126
+ containerLength: width,
127
+ });
128
+
129
+ viewState.updateHorizontalScrollState(scrollState);
130
};
131
132
type AutoSizedCanvasProps = {|
133
data: ReactProfilerData,
134
height: number,
135
+ viewState: ViewState,
136
width: number,
137
|};
138
121
-function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
139
+function AutoSizedCanvas({
140
+ data,
141
+ height,
142
+ viewState,
143
+ width,
144
+}: AutoSizedCanvasProps) {
145
const canvasRef = useRef<HTMLCanvasElement | null>(null);
146
147
const [isContextMenuShown, setIsContextMenuShown] = useState<boolean>(false);
@@ -136,30 +159,31 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
159
const componentMeasuresViewRef = useRef(null);
160
const reactMeasuresViewRef = useRef(null);
161
const flamechartViewRef = useRef(null);
139
- const syncedHorizontalPanAndZoomViewsRef = useRef<HorizontalPanAndZoomView[]>(
140
- [],
141
- );
162
+
163
+ const {hideMenu: hideContextMenu} = useContext(RegistryContext);
164
165
useLayoutEffect(() => {
166
const surface = surfaceRef.current;
167
const defaultFrame = {origin: zeroPoint, size: {width, height}};
168
147
- // Clear synced views
148
- syncedHorizontalPanAndZoomViewsRef.current = [];
169
+ // Auto hide context menu when panning.
170
+ viewState.onHorizontalScrollStateChange(scrollState => {
171
+ hideContextMenu();
172
+ });
173
150
- const syncAllHorizontalPanAndZoomViewStates: HorizontalPanAndZoomViewOnChangeCallback = (
151
- newState,
152
- triggeringView?: HorizontalPanAndZoomView,
153
- ) => {
154
- syncedHorizontalPanAndZoomViewsRef.current.forEach(
155
- syncedView =>
156
- triggeringView !== syncedView && syncedView.setScrollState(newState),
157
- );
158
- };
174
+ // Initialize horizontal view state
175
+ viewState.updateHorizontalScrollState(
176
+ clampState({
177
+ state: viewState.horizontalScrollState,
178
+ minContentLength: data.duration * MIN_ZOOM_LEVEL,
179
+ maxContentLength: data.duration * MAX_ZOOM_LEVEL,
180
+ containerLength: defaultFrame.size.width,
181
+ }),
182
+ );
183
184
function createViewHelper(
185
view: View,
162
- resizeLabel: string = '',
186
+ label: string,
187
shouldScrollVertically: boolean = false,
188
shouldResizeVertically: boolean = false,
189
): View {
@@ -169,6 +193,8 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
193
surface,
194
defaultFrame,
195
view,
196
+ viewState,
197
+ label,
198
);
199
}
200
@@ -177,23 +203,22 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
203
defaultFrame,
204
verticalScrollView !== null ? verticalScrollView : view,
205
data.duration,
180
- syncAllHorizontalPanAndZoomViewStates,
206
+ viewState,
207
);
208
183
- syncedHorizontalPanAndZoomViewsRef.current.push(horizontalPanAndZoomView);
184
-
185
- let viewToReturn = horizontalPanAndZoomView;
209
+ let resizableView = null;
210
if (shouldResizeVertically) {
187
- viewToReturn = new ResizableView(
211
+ resizableView = new ResizableView(
212
surface,
213
defaultFrame,
214
horizontalPanAndZoomView,
215
+ viewState,
216
canvasRef,
192
- resizeLabel,
217
+ label,
218
);
219
}
220
196
- return viewToReturn;
221
+ return resizableView || horizontalPanAndZoomView;
222
}
223
224
const axisMarkersView = new TimeAxisMarkersView(
@@ -201,7 +226,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
226
defaultFrame,
227
data.duration,
228
);
204
- const axisMarkersViewWrapper = createViewHelper(axisMarkersView);
229
+ const axisMarkersViewWrapper = createViewHelper(axisMarkersView, 'time');
230
231
let userTimingMarksViewWrapper = null;
232
if (data.otherUserTimingMarks.length > 0) {
@@ -212,7 +237,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
237
data.duration,
238
);
239
userTimingMarksViewRef.current = userTimingMarksView;
215
- userTimingMarksViewWrapper = createViewHelper(userTimingMarksView);
240
+ userTimingMarksViewWrapper = createViewHelper(
241
+ userTimingMarksView,
242
+ 'user timing api',
243
+ );
244
}
245
246
const nativeEventsView = new NativeEventsView(surface, defaultFrame, data);
@@ -230,7 +258,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
258
data,
259
);
260
schedulingEventsViewRef.current = schedulingEventsView;
233
- const schedulingEventsViewWrapper = createViewHelper(schedulingEventsView);
261
+ const schedulingEventsViewWrapper = createViewHelper(
262
+ schedulingEventsView,
263
+ 'react updates',
264
+ );
265
266
let suspenseEventsViewWrapper = null;
267
if (data.suspenseEvents.length > 0) {
@@ -256,7 +287,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
287
reactMeasuresViewRef.current = reactMeasuresView;
288
const reactMeasuresViewWrapper = createViewHelper(
289
reactMeasuresView,
259
- 'react',
290
+ 'react scheduling',
291
true,
292
true,
293
);
@@ -269,7 +300,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
300
data,
301
);
302
componentMeasuresViewRef.current = componentMeasuresView;
272
- componentMeasuresViewWrapper = createViewHelper(componentMeasuresView);
303
+ componentMeasuresViewWrapper = createViewHelper(
304
+ componentMeasuresView,
305
+ 'react components',
306
+ );
307
}
308
309
const flamechartView = new FlamechartView(
@@ -329,7 +363,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
363
return;
364
}
365
332
- // Wheel events should always hide the current toolltip.
366
+ // Wheel events should always hide the current tooltip.
367
switch (interaction.type) {
368
case 'wheel-control':
369
case 'wheel-meta':
@@ -617,11 +651,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
651
{measure !== null && (
652
<ContextMenuItem
653
onClick={() =>
620
- zoomToBatch(
621
- contextData.data,
622
- measure,
623
- syncedHorizontalPanAndZoomViewsRef.current,
624
- )
654
+ zoomToBatch(contextData.data, measure, viewState, width)
655
}
656
title="Zoom to batch">
657
Zoom to batch
packages/react-devtools-scheduling-profiler/src/SchedulingProfiler.js
+10
-4
@@ -8,6 +8,7 @@
8
*/
9
10
import type {DataResource} from './createDataResourceFromImportedFile';
11
+import type {ViewState} from './types';
12
13
import * as React from 'react';
14
import {
@@ -27,9 +28,11 @@ import CanvasPage from './CanvasPage';
28
import styles from './SchedulingProfiler.css';
29
30
export function SchedulingProfiler(_: {||}) {
30
- const {importSchedulingProfilerData, schedulingProfilerData} = useContext(
31
- SchedulingProfilerContext,
32
- );
31
+ const {
32
+ importSchedulingProfilerData,
33
+ schedulingProfilerData,
34
+ viewState,
35
+ } = useContext(SchedulingProfilerContext);
36
37
const ref = useRef(null);
38
@@ -66,6 +69,7 @@ export function SchedulingProfiler(_: {||}) {
69
dataResource={schedulingProfilerData}
70
key={key}
71
onFileSelect={importSchedulingProfilerData}
72
+ viewState={viewState}
73
/>
74
</Suspense>
75
) : (
@@ -130,9 +134,11 @@ const CouldNotLoadProfile = ({error, onFileSelect}) => (
134
const DataResourceComponent = ({
135
dataResource,
136
onFileSelect,
137
+ viewState,
138
}: {|
139
dataResource: DataResource,
140
onFileSelect: (file: File) => void,
141
+ viewState: ViewState,
142
|}) => {
143
const dataOrError = dataResource.read();
144
if (dataOrError instanceof Error) {
@@ -140,5 +146,5 @@ const DataResourceComponent = ({
146
<CouldNotLoadProfile error={dataOrError} onFileSelect={onFileSelect} />
147
);
148
}
143
- return <CanvasPage profilerData={dataOrError} />;
149
+ return <CanvasPage profilerData={dataOrError} viewState={viewState} />;
150
};
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js
+36
-3
@@ -11,12 +11,14 @@ import * as React from 'react';
11
import {createContext, useCallback, useMemo, useState} from 'react';
12
import createDataResourceFromImportedFile from './createDataResourceFromImportedFile';
13
14
+import type {HorizontalScrollStateChangeCallback, ViewState} from './types';
15
import type {DataResource} from './createDataResourceFromImportedFile';
16
17
export type Context = {|
18
clearSchedulingProfilerData: () => void,
19
importSchedulingProfilerData: (file: File) => void,
20
schedulingProfilerData: DataResource | null,
21
+ viewState: ViewState,
22
|};
23
24
const SchedulingProfilerContext = createContext<Context>(
@@ -42,20 +44,51 @@ function SchedulingProfilerContextController({children}: Props) {
44
setSchedulingProfilerData(createDataResourceFromImportedFile(file));
45
}, []);
46
45
- // TODO (scheduling profiler) Start/stop time ref here?
47
+ // Recreate view state any time new profiling data is imported.
48
+ const viewState = useMemo<ViewState>(() => {
49
+ const horizontalScrollStateChangeCallbacks: Set<HorizontalScrollStateChangeCallback> = new Set();
50
+
51
+ const horizontalScrollState = {
52
+ offset: 0,
53
+ length: 0,
54
+ };
55
+
56
+ return {
57
+ horizontalScrollState,
58
+ onHorizontalScrollStateChange: callback => {
59
+ horizontalScrollStateChangeCallbacks.add(callback);
60
+ },
61
+ updateHorizontalScrollState: scrollState => {
62
+ if (
63
+ horizontalScrollState.offset === scrollState.offset &&
64
+ horizontalScrollState.length === scrollState.length
65
+ ) {
66
+ return;
67
+ }
68
+
69
+ horizontalScrollState.offset = scrollState.offset;
70
+ horizontalScrollState.length = scrollState.length;
71
+
72
+ horizontalScrollStateChangeCallbacks.forEach(callback => {
73
+ callback(scrollState);
74
+ });
75
+ },
76
+ viewToMutableViewStateMap: new Map(),
77
+ };
78
+ }, [schedulingProfilerData]);
79
80
const value = useMemo(
81
() => ({
82
clearSchedulingProfilerData,
83
importSchedulingProfilerData,
84
schedulingProfilerData,
52
- // TODO (scheduling profiler)
85
+ viewState,
86
}),
87
[
88
clearSchedulingProfilerData,
89
importSchedulingProfilerData,
90
schedulingProfilerData,
58
- // TODO (scheduling profiler)
91
+ viewState,
92
],
93
);
94
packages/react-devtools-scheduling-profiler/src/content-views/ReactMeasuresView.js
+9
-21
@@ -38,21 +38,12 @@ import {REACT_TOTAL_NUM_LANES} from '../constants';
38
const REACT_LANE_HEIGHT = REACT_MEASURE_HEIGHT + BORDER_SIZE;
39
const MAX_ROWS_TO_SHOW_INITIALLY = 5;
40
41
-function getMeasuresForLane(
42
- allMeasures: ReactMeasure[],
43
- lane: ReactLane,
44
-): ReactMeasure[] {
45
- return allMeasures.filter(measure => measure.lanes.includes(lane));
46
-}
47
-
41
export class ReactMeasuresView extends View {
49
- _profilerData: ReactProfilerData;
42
_intrinsicSize: IntrinsicSize;
51
-
43
_lanesToRender: ReactLane[];
53
- _laneToMeasures: Map<ReactLane, ReactMeasure[]>;
54
-
44
+ _profilerData: ReactProfilerData;
45
_hoveredMeasure: ReactMeasure | null = null;
46
+
47
onHover: ((measure: ReactMeasure | null) => void) | null = null;
48
49
constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
@@ -63,17 +54,14 @@ export class ReactMeasuresView extends View {
54
55
_performPreflightComputations() {
56
this._lanesToRender = [];
66
- this._laneToMeasures = new Map();
57
58
for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
69
- const measuresForLane = getMeasuresForLane(
70
- this._profilerData.measures,
59
+ const measuresForLane = this._profilerData.laneToReactMeasureMap.get(
60
lane,
61
);
62
// Only show lanes with measures
74
- if (measuresForLane.length) {
63
+ if (measuresForLane != null && measuresForLane.length > 0) {
64
this._lanesToRender.push(lane);
76
- this._laneToMeasures.set(lane, measuresForLane);
65
}
66
}
67
@@ -213,7 +201,7 @@ export class ReactMeasuresView extends View {
201
frame,
202
_hoveredMeasure,
203
_lanesToRender,
216
- _laneToMeasures,
204
+ _profilerData,
205
visibleArea,
206
} = this;
207
@@ -233,7 +221,7 @@ export class ReactMeasuresView extends View {
221
for (let i = 0; i < _lanesToRender.length; i++) {
222
const lane = _lanesToRender[i];
223
const baseY = frame.origin.y + i * REACT_LANE_HEIGHT;
236
- const measuresForLane = _laneToMeasures.get(lane);
224
+ const measuresForLane = _profilerData.laneToReactMeasureMap.get(lane);
225
226
if (!measuresForLane) {
227
throw new Error(
@@ -242,7 +230,7 @@ export class ReactMeasuresView extends View {
230
}
231
232
// Render lane labels
245
- const label = this._profilerData.laneToLabelMap.get(lane);
233
+ const label = _profilerData.laneToLabelMap.get(lane);
234
if (label == null) {
235
console.warn(`Could not find label for lane ${lane}.`);
236
} else {
@@ -316,8 +304,8 @@ export class ReactMeasuresView extends View {
304
frame,
305
_intrinsicSize,
306
_lanesToRender,
319
- _laneToMeasures,
307
onHover,
308
+ _profilerData,
309
visibleArea,
310
} = this;
311
if (!onHover) {
@@ -347,7 +335,7 @@ export class ReactMeasuresView extends View {
335
// This will always be the one on "top" (the one the user is hovering over).
336
const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
337
const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
350
- const measures = _laneToMeasures.get(lane);
338
+ const measures = _profilerData.laneToReactMeasureMap.get(lane);
339
if (!measures) {
340
onHover(null);
341
return;
packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js
+692
-369
@@ -253,6 +253,7 @@ describe(preprocessData, () => {
253
]);
254
expect(data).toMatchInlineSnapshot(`
255
Object {
256
+ "batchUIDToMeasuresMap": Map {},
257
"componentMeasures": Array [],
258
"duration": 0.005,
259
"flamechart": Array [],
@@ -289,7 +290,39 @@ describe(preprocessData, () => {
290
29 => "Idle",
291
30 => "Offscreen",
292
},
292
- "measures": Array [],
293
+ "laneToReactMeasureMap": Map {
294
+ 0 => Array [],
295
+ 1 => Array [],
296
+ 2 => Array [],
297
+ 3 => Array [],
298
+ 4 => Array [],
299
+ 5 => Array [],
300
+ 6 => Array [],
301
+ 7 => Array [],
302
+ 8 => Array [],
303
+ 9 => Array [],
304
+ 10 => Array [],
305
+ 11 => Array [],
306
+ 12 => Array [],
307
+ 13 => Array [],
308
+ 14 => Array [],
309
+ 15 => Array [],
310
+ 16 => Array [],
311
+ 17 => Array [],
312
+ 18 => Array [],
313
+ 19 => Array [],
314
+ 20 => Array [],
315
+ 21 => Array [],
316
+ 22 => Array [],
317
+ 23 => Array [],
318
+ 24 => Array [],
319
+ 25 => Array [],
320
+ 26 => Array [],
321
+ 27 => Array [],
322
+ 28 => Array [],
323
+ 29 => Array [],
324
+ 30 => Array [],
325
+ },
326
"nativeEvents": Array [],
327
"otherUserTimingMarks": Array [],
328
"reactVersion": "17.0.3",
@@ -340,102 +373,178 @@ describe(preprocessData, () => {
373
}),
374
]);
375
expect(data).toMatchInlineSnapshot(`
376
+ Object {
377
+ "batchUIDToMeasuresMap": Map {
378
+ 0 => Array [
379
+ Object {
380
+ "batchUID": 0,
381
+ "depth": 0,
382
+ "duration": 0.004999999999999999,
383
+ "lanes": Array [
384
+ 9,
385
+ ],
386
+ "timestamp": 0.006,
387
+ "type": "render-idle",
388
+ },
389
+ Object {
390
+ "batchUID": 0,
391
+ "depth": 0,
392
+ "duration": 0.001,
393
+ "lanes": Array [
394
+ 9,
395
+ ],
396
+ "timestamp": 0.006,
397
+ "type": "render",
398
+ },
399
+ Object {
400
+ "batchUID": 0,
401
+ "depth": 0,
402
+ "duration": 0.002999999999999999,
403
+ "lanes": Array [
404
+ 9,
405
+ ],
406
+ "timestamp": 0.008,
407
+ "type": "commit",
408
+ },
409
+ Object {
410
+ "batchUID": 0,
411
+ "depth": 1,
412
+ "duration": 0.0010000000000000009,
413
+ "lanes": Array [
414
+ 9,
415
+ ],
416
+ "timestamp": 0.009,
417
+ "type": "layout-effects",
418
+ },
419
+ ],
420
+ },
421
+ "componentMeasures": Array [],
422
+ "duration": 0.011,
423
+ "flamechart": Array [],
424
+ "laneToLabelMap": Map {
425
+ 0 => "Sync",
426
+ 1 => "InputContinuousHydration",
427
+ 2 => "InputContinuous",
428
+ 3 => "DefaultHydration",
429
+ 4 => "Default",
430
+ 5 => "TransitionHydration",
431
+ 6 => "Transition",
432
+ 7 => "Transition",
433
+ 8 => "Transition",
434
+ 9 => "Transition",
435
+ 10 => "Transition",
436
+ 11 => "Transition",
437
+ 12 => "Transition",
438
+ 13 => "Transition",
439
+ 14 => "Transition",
440
+ 15 => "Transition",
441
+ 16 => "Transition",
442
+ 17 => "Transition",
443
+ 18 => "Transition",
444
+ 19 => "Transition",
445
+ 20 => "Transition",
446
+ 21 => "Transition",
447
+ 22 => "Retry",
448
+ 23 => "Retry",
449
+ 24 => "Retry",
450
+ 25 => "Retry",
451
+ 26 => "Retry",
452
+ 27 => "SelectiveHydration",
453
+ 28 => "IdleHydration",
454
+ 29 => "Idle",
455
+ 30 => "Offscreen",
456
+ },
457
+ "laneToReactMeasureMap": Map {
458
+ 0 => Array [],
459
+ 1 => Array [],
460
+ 2 => Array [],
461
+ 3 => Array [],
462
+ 4 => Array [],
463
+ 5 => Array [],
464
+ 6 => Array [],
465
+ 7 => Array [],
466
+ 8 => Array [],
467
+ 9 => Array [
468
+ Object {
469
+ "batchUID": 0,
470
+ "depth": 0,
471
+ "duration": 0.004999999999999999,
472
+ "lanes": Array [
473
+ 9,
474
+ ],
475
+ "timestamp": 0.006,
476
+ "type": "render-idle",
477
+ },
478
Object {
344
- "componentMeasures": Array [],
345
- "duration": 0.011,
346
- "flamechart": Array [],
347
- "laneToLabelMap": Map {
348
- 0 => "Sync",
349
- 1 => "InputContinuousHydration",
350
- 2 => "InputContinuous",
351
- 3 => "DefaultHydration",
352
- 4 => "Default",
353
- 5 => "TransitionHydration",
354
- 6 => "Transition",
355
- 7 => "Transition",
356
- 8 => "Transition",
357
- 9 => "Transition",
358
- 10 => "Transition",
359
- 11 => "Transition",
360
- 12 => "Transition",
361
- 13 => "Transition",
362
- 14 => "Transition",
363
- 15 => "Transition",
364
- 16 => "Transition",
365
- 17 => "Transition",
366
- 18 => "Transition",
367
- 19 => "Transition",
368
- 20 => "Transition",
369
- 21 => "Transition",
370
- 22 => "Retry",
371
- 23 => "Retry",
372
- 24 => "Retry",
373
- 25 => "Retry",
374
- 26 => "Retry",
375
- 27 => "SelectiveHydration",
376
- 28 => "IdleHydration",
377
- 29 => "Idle",
378
- 30 => "Offscreen",
379
- },
380
- "measures": Array [
381
- Object {
382
- "batchUID": 0,
383
- "depth": 0,
384
- "duration": 0.004999999999999999,
385
- "lanes": Array [
386
- 9,
387
- ],
388
- "timestamp": 0.006,
389
- "type": "render-idle",
390
- },
391
- Object {
392
- "batchUID": 0,
393
- "depth": 0,
394
- "duration": 0.001,
395
- "lanes": Array [
396
- 9,
397
- ],
398
- "timestamp": 0.006,
399
- "type": "render",
400
- },
401
- Object {
402
- "batchUID": 0,
403
- "depth": 0,
404
- "duration": 0.002999999999999999,
405
- "lanes": Array [
406
- 9,
407
- ],
408
- "timestamp": 0.008,
409
- "type": "commit",
410
- },
411
- Object {
412
- "batchUID": 0,
413
- "depth": 1,
414
- "duration": 0.0010000000000000009,
415
- "lanes": Array [
416
- 9,
417
- ],
418
- "timestamp": 0.009,
419
- "type": "layout-effects",
420
- },
479
+ "batchUID": 0,
480
+ "depth": 0,
481
+ "duration": 0.001,
482
+ "lanes": Array [
483
+ 9,
484
],
422
- "nativeEvents": Array [],
423
- "otherUserTimingMarks": Array [],
424
- "reactVersion": "17.0.3",
425
- "schedulingEvents": Array [
426
- Object {
427
- "lanes": Array [
428
- 9,
429
- ],
430
- "timestamp": 0.005,
431
- "type": "schedule-render",
432
- "warning": null,
433
- },
485
+ "timestamp": 0.006,
486
+ "type": "render",
487
+ },
488
+ Object {
489
+ "batchUID": 0,
490
+ "depth": 0,
491
+ "duration": 0.002999999999999999,
492
+ "lanes": Array [
493
+ 9,
494
+ ],
495
+ "timestamp": 0.008,
496
+ "type": "commit",
497
+ },
498
+ Object {
499
+ "batchUID": 0,
500
+ "depth": 1,
501
+ "duration": 0.0010000000000000009,
502
+ "lanes": Array [
503
+ 9,
504
],
435
- "startTime": 1,
436
- "suspenseEvents": Array [],
437
- }
438
- `);
505
+ "timestamp": 0.009,
506
+ "type": "layout-effects",
507
+ },
508
+ ],
509
+ 10 => Array [],
510
+ 11 => Array [],
511
+ 12 => Array [],
512
+ 13 => Array [],
513
+ 14 => Array [],
514
+ 15 => Array [],
515
+ 16 => Array [],
516
+ 17 => Array [],
517
+ 18 => Array [],
518
+ 19 => Array [],
519
+ 20 => Array [],
520
+ 21 => Array [],
521
+ 22 => Array [],
522
+ 23 => Array [],
523
+ 24 => Array [],
524
+ 25 => Array [],
525
+ 26 => Array [],
526
+ 27 => Array [],
527
+ 28 => Array [],
528
+ 29 => Array [],
529
+ 30 => Array [],
530
+ },
531
+ "nativeEvents": Array [],
532
+ "otherUserTimingMarks": Array [],
533
+ "reactVersion": "17.0.3",
534
+ "schedulingEvents": Array [
535
+ Object {
536
+ "lanes": Array [
537
+ 9,
538
+ ],
539
+ "timestamp": 0.005,
540
+ "type": "schedule-render",
541
+ "warning": null,
542
+ },
543
+ ],
544
+ "startTime": 1,
545
+ "suspenseEvents": Array [],
546
+ }
547
+ `);
548
}
549
});
550
@@ -448,107 +557,183 @@ describe(preprocessData, () => {
557
...createUserTimingData(clearedMarks),
558
]);
559
expect(data).toMatchInlineSnapshot(`
560
+ Object {
561
+ "batchUIDToMeasuresMap": Map {
562
+ 0 => Array [
563
Object {
452
- "componentMeasures": Array [],
453
- "duration": 0.013,
454
- "flamechart": Array [],
455
- "laneToLabelMap": Map {
456
- 0 => "Sync",
457
- 1 => "InputContinuousHydration",
458
- 2 => "InputContinuous",
459
- 3 => "DefaultHydration",
460
- 4 => "Default",
461
- 5 => "TransitionHydration",
462
- 6 => "Transition",
463
- 7 => "Transition",
464
- 8 => "Transition",
465
- 9 => "Transition",
466
- 10 => "Transition",
467
- 11 => "Transition",
468
- 12 => "Transition",
469
- 13 => "Transition",
470
- 14 => "Transition",
471
- 15 => "Transition",
472
- 16 => "Transition",
473
- 17 => "Transition",
474
- 18 => "Transition",
475
- 19 => "Transition",
476
- 20 => "Transition",
477
- 21 => "Transition",
478
- 22 => "Retry",
479
- 23 => "Retry",
480
- 24 => "Retry",
481
- 25 => "Retry",
482
- 26 => "Retry",
483
- 27 => "SelectiveHydration",
484
- 28 => "IdleHydration",
485
- 29 => "Idle",
486
- 30 => "Offscreen",
487
- },
488
- "measures": Array [
489
- Object {
490
- "batchUID": 0,
491
- "depth": 0,
492
- "duration": 0.008,
493
- "lanes": Array [
494
- 0,
495
- ],
496
- "timestamp": 0.005,
497
- "type": "render-idle",
498
- },
499
- Object {
500
- "batchUID": 0,
501
- "depth": 0,
502
- "duration": 0.001,
503
- "lanes": Array [
504
- 0,
505
- ],
506
- "timestamp": 0.005,
507
- "type": "render",
508
- },
509
- Object {
510
- "batchUID": 0,
511
- "depth": 0,
512
- "duration": 0.005999999999999999,
513
- "lanes": Array [
514
- 0,
515
- ],
516
- "timestamp": 0.007,
517
- "type": "commit",
518
- },
519
- Object {
520
- "batchUID": 0,
521
- "depth": 1,
522
- "duration": 0.0010000000000000009,
523
- "lanes": Array [
524
- 0,
525
- ],
526
- "timestamp": 0.011,
527
- "type": "layout-effects",
528
- },
564
+ "batchUID": 0,
565
+ "depth": 0,
566
+ "duration": 0.008,
567
+ "lanes": Array [
568
+ 0,
569
],
530
- "nativeEvents": Array [],
531
- "otherUserTimingMarks": Array [
532
- Object {
533
- "name": "__v3",
534
- "timestamp": 0.003,
535
- },
570
+ "timestamp": 0.005,
571
+ "type": "render-idle",
572
+ },
573
+ Object {
574
+ "batchUID": 0,
575
+ "depth": 0,
576
+ "duration": 0.001,
577
+ "lanes": Array [
578
+ 0,
579
+ ],
580
+ "timestamp": 0.005,
581
+ "type": "render",
582
+ },
583
+ Object {
584
+ "batchUID": 0,
585
+ "depth": 0,
586
+ "duration": 0.005999999999999999,
587
+ "lanes": Array [
588
+ 0,
589
],
537
- "reactVersion": "17.0.3",
538
- "schedulingEvents": Array [
539
- Object {
540
- "lanes": Array [
541
- 0,
542
- ],
543
- "timestamp": 0.004,
544
- "type": "schedule-render",
545
- "warning": null,
546
- },
590
+ "timestamp": 0.007,
591
+ "type": "commit",
592
+ },
593
+ Object {
594
+ "batchUID": 0,
595
+ "depth": 1,
596
+ "duration": 0.0010000000000000009,
597
+ "lanes": Array [
598
+ 0,
599
],
548
- "startTime": 4,
549
- "suspenseEvents": Array [],
550
- }
551
- `);
600
+ "timestamp": 0.011,
601
+ "type": "layout-effects",
602
+ },
603
+ ],
604
+ },
605
+ "componentMeasures": Array [],
606
+ "duration": 0.013,
607
+ "flamechart": Array [],
608
+ "laneToLabelMap": Map {
609
+ 0 => "Sync",
610
+ 1 => "InputContinuousHydration",
611
+ 2 => "InputContinuous",
612
+ 3 => "DefaultHydration",
613
+ 4 => "Default",
614
+ 5 => "TransitionHydration",
615
+ 6 => "Transition",
616
+ 7 => "Transition",
617
+ 8 => "Transition",
618
+ 9 => "Transition",
619
+ 10 => "Transition",
620
+ 11 => "Transition",
621
+ 12 => "Transition",
622
+ 13 => "Transition",
623
+ 14 => "Transition",
624
+ 15 => "Transition",
625
+ 16 => "Transition",
626
+ 17 => "Transition",
627
+ 18 => "Transition",
628
+ 19 => "Transition",
629
+ 20 => "Transition",
630
+ 21 => "Transition",
631
+ 22 => "Retry",
632
+ 23 => "Retry",
633
+ 24 => "Retry",
634
+ 25 => "Retry",
635
+ 26 => "Retry",
636
+ 27 => "SelectiveHydration",
637
+ 28 => "IdleHydration",
638
+ 29 => "Idle",
639
+ 30 => "Offscreen",
640
+ },
641
+ "laneToReactMeasureMap": Map {
642
+ 0 => Array [
643
+ Object {
644
+ "batchUID": 0,
645
+ "depth": 0,
646
+ "duration": 0.008,
647
+ "lanes": Array [
648
+ 0,
649
+ ],
650
+ "timestamp": 0.005,
651
+ "type": "render-idle",
652
+ },
653
+ Object {
654
+ "batchUID": 0,
655
+ "depth": 0,
656
+ "duration": 0.001,
657
+ "lanes": Array [
658
+ 0,
659
+ ],
660
+ "timestamp": 0.005,
661
+ "type": "render",
662
+ },
663
+ Object {
664
+ "batchUID": 0,
665
+ "depth": 0,
666
+ "duration": 0.005999999999999999,
667
+ "lanes": Array [
668
+ 0,
669
+ ],
670
+ "timestamp": 0.007,
671
+ "type": "commit",
672
+ },
673
+ Object {
674
+ "batchUID": 0,
675
+ "depth": 1,
676
+ "duration": 0.0010000000000000009,
677
+ "lanes": Array [
678
+ 0,
679
+ ],
680
+ "timestamp": 0.011,
681
+ "type": "layout-effects",
682
+ },
683
+ ],
684
+ 1 => Array [],
685
+ 2 => Array [],
686
+ 3 => Array [],
687
+ 4 => Array [],
688
+ 5 => Array [],
689
+ 6 => Array [],
690
+ 7 => Array [],
691
+ 8 => Array [],
692
+ 9 => Array [],
693
+ 10 => Array [],
694
+ 11 => Array [],
695
+ 12 => Array [],
696
+ 13 => Array [],
697
+ 14 => Array [],
698
+ 15 => Array [],
699
+ 16 => Array [],
700
+ 17 => Array [],
701
+ 18 => Array [],
702
+ 19 => Array [],
703
+ 20 => Array [],
704
+ 21 => Array [],
705
+ 22 => Array [],
706
+ 23 => Array [],
707
+ 24 => Array [],
708
+ 25 => Array [],
709
+ 26 => Array [],
710
+ 27 => Array [],
711
+ 28 => Array [],
712
+ 29 => Array [],
713
+ 30 => Array [],
714
+ },
715
+ "nativeEvents": Array [],
716
+ "otherUserTimingMarks": Array [
717
+ Object {
718
+ "name": "__v3",
719
+ "timestamp": 0.003,
720
+ },
721
+ ],
722
+ "reactVersion": "17.0.3",
723
+ "schedulingEvents": Array [
724
+ Object {
725
+ "lanes": Array [
726
+ 0,
727
+ ],
728
+ "timestamp": 0.004,
729
+ "type": "schedule-render",
730
+ "warning": null,
731
+ },
732
+ ],
733
+ "startTime": 4,
734
+ "suspenseEvents": Array [],
735
+ }
736
+ `);
737
}
738
});
739
@@ -572,189 +757,327 @@ describe(preprocessData, () => {
757
...createUserTimingData(clearedMarks),
758
]);
759
expect(data).toMatchInlineSnapshot(`
760
+ Object {
761
+ "batchUIDToMeasuresMap": Map {
762
+ 0 => Array [
763
+ Object {
764
+ "batchUID": 0,
765
+ "depth": 0,
766
+ "duration": 0.009999999999999998,
767
+ "lanes": Array [
768
+ 4,
769
+ ],
770
+ "timestamp": 0.005,
771
+ "type": "render-idle",
772
+ },
773
+ Object {
774
+ "batchUID": 0,
775
+ "depth": 0,
776
+ "duration": 0.003,
777
+ "lanes": Array [
778
+ 4,
779
+ ],
780
+ "timestamp": 0.005,
781
+ "type": "render",
782
+ },
783
+ Object {
784
+ "batchUID": 0,
785
+ "depth": 0,
786
+ "duration": 0.006,
787
+ "lanes": Array [
788
+ 4,
789
+ ],
790
+ "timestamp": 0.009,
791
+ "type": "commit",
792
+ },
793
+ Object {
794
+ "batchUID": 0,
795
+ "depth": 1,
796
+ "duration": 0.0010000000000000009,
797
+ "lanes": Array [
798
+ 4,
799
+ ],
800
+ "timestamp": 0.013,
801
+ "type": "layout-effects",
802
+ },
803
+ Object {
804
+ "batchUID": 0,
805
+ "depth": 0,
806
+ "duration": 0.0019999999999999983,
807
+ "lanes": Array [
808
+ 4,
809
+ ],
810
+ "timestamp": 0.016,
811
+ "type": "passive-effects",
812
+ },
813
+ ],
814
+ 1 => Array [
815
+ Object {
816
+ "batchUID": 1,
817
+ "depth": 0,
818
+ "duration": 0.010000000000000002,
819
+ "lanes": Array [
820
+ 4,
821
+ ],
822
+ "timestamp": 0.019,
823
+ "type": "render-idle",
824
+ },
825
Object {
576
- "componentMeasures": Array [
577
- Object {
578
- "componentName": "App",
579
- "duration": 0.001,
580
- "timestamp": 0.006,
581
- "warning": null,
582
- },
583
- Object {
584
- "componentName": "App",
585
- "duration": 0.0010000000000000009,
586
- "timestamp": 0.02,
587
- "warning": null,
588
- },
826
+ "batchUID": 1,
827
+ "depth": 0,
828
+ "duration": 0.002999999999999999,
829
+ "lanes": Array [
830
+ 4,
831
],
590
- "duration": 0.031,
591
- "flamechart": Array [],
592
- "laneToLabelMap": Map {
593
- 0 => "Sync",
594
- 1 => "InputContinuousHydration",
595
- 2 => "InputContinuous",
596
- 3 => "DefaultHydration",
597
- 4 => "Default",
598
- 5 => "TransitionHydration",
599
- 6 => "Transition",
600
- 7 => "Transition",
601
- 8 => "Transition",
602
- 9 => "Transition",
603
- 10 => "Transition",
604
- 11 => "Transition",
605
- 12 => "Transition",
606
- 13 => "Transition",
607
- 14 => "Transition",
608
- 15 => "Transition",
609
- 16 => "Transition",
610
- 17 => "Transition",
611
- 18 => "Transition",
612
- 19 => "Transition",
613
- 20 => "Transition",
614
- 21 => "Transition",
615
- 22 => "Retry",
616
- 23 => "Retry",
617
- 24 => "Retry",
618
- 25 => "Retry",
619
- 26 => "Retry",
620
- 27 => "SelectiveHydration",
621
- 28 => "IdleHydration",
622
- 29 => "Idle",
623
- 30 => "Offscreen",
624
- },
625
- "measures": Array [
626
- Object {
627
- "batchUID": 0,
628
- "depth": 0,
629
- "duration": 0.009999999999999998,
630
- "lanes": Array [
631
- 4,
632
- ],
633
- "timestamp": 0.005,
634
- "type": "render-idle",
635
- },
636
- Object {
637
- "batchUID": 0,
638
- "depth": 0,
639
- "duration": 0.003,
640
- "lanes": Array [
641
- 4,
642
- ],
643
- "timestamp": 0.005,
644
- "type": "render",
645
- },
646
- Object {
647
- "batchUID": 0,
648
- "depth": 0,
649
- "duration": 0.006,
650
- "lanes": Array [
651
- 4,
652
- ],
653
- "timestamp": 0.009,
654
- "type": "commit",
655
- },
656
- Object {
657
- "batchUID": 0,
658
- "depth": 1,
659
- "duration": 0.0010000000000000009,
660
- "lanes": Array [
661
- 4,
662
- ],
663
- "timestamp": 0.013,
664
- "type": "layout-effects",
665
- },
666
- Object {
667
- "batchUID": 0,
668
- "depth": 0,
669
- "duration": 0.0019999999999999983,
670
- "lanes": Array [
671
- 4,
672
- ],
673
- "timestamp": 0.016,
674
- "type": "passive-effects",
675
- },
676
- Object {
677
- "batchUID": 1,
678
- "depth": 0,
679
- "duration": 0.010000000000000002,
680
- "lanes": Array [
681
- 4,
682
- ],
683
- "timestamp": 0.019,
684
- "type": "render-idle",
685
- },
686
- Object {
687
- "batchUID": 1,
688
- "depth": 0,
689
- "duration": 0.002999999999999999,
690
- "lanes": Array [
691
- 4,
692
- ],
693
- "timestamp": 0.019,
694
- "type": "render",
695
- },
696
- Object {
697
- "batchUID": 1,
698
- "depth": 0,
699
- "duration": 0.006000000000000002,
700
- "lanes": Array [
701
- 4,
702
- ],
703
- "timestamp": 0.023,
704
- "type": "commit",
705
- },
706
- Object {
707
- "batchUID": 1,
708
- "depth": 1,
709
- "duration": 0.0010000000000000009,
710
- "lanes": Array [
711
- 4,
712
- ],
713
- "timestamp": 0.027,
714
- "type": "layout-effects",
715
- },
716
- Object {
717
- "batchUID": 1,
718
- "depth": 0,
719
- "duration": 0.0010000000000000009,
720
- "lanes": Array [
721
- 4,
722
- ],
723
- "timestamp": 0.03,
724
- "type": "passive-effects",
725
- },
832
+ "timestamp": 0.019,
833
+ "type": "render",
834
+ },
835
+ Object {
836
+ "batchUID": 1,
837
+ "depth": 0,
838
+ "duration": 0.006000000000000002,
839
+ "lanes": Array [
840
+ 4,
841
],
727
- "nativeEvents": Array [],
728
- "otherUserTimingMarks": Array [
729
- Object {
730
- "name": "__v3",
731
- "timestamp": 0.003,
732
- },
842
+ "timestamp": 0.023,
843
+ "type": "commit",
844
+ },
845
+ Object {
846
+ "batchUID": 1,
847
+ "depth": 1,
848
+ "duration": 0.0010000000000000009,
849
+ "lanes": Array [
850
+ 4,
851
],
734
- "reactVersion": "17.0.3",
735
- "schedulingEvents": Array [
736
- Object {
737
- "lanes": Array [
738
- 4,
739
- ],
740
- "timestamp": 0.004,
741
- "type": "schedule-render",
742
- "warning": null,
743
- },
744
- Object {
745
- "componentName": "App",
746
- "lanes": Array [
747
- 4,
748
- ],
749
- "timestamp": 0.017,
750
- "type": "schedule-state-update",
751
- "warning": null,
752
- },
852
+ "timestamp": 0.027,
853
+ "type": "layout-effects",
854
+ },
855
+ Object {
856
+ "batchUID": 1,
857
+ "depth": 0,
858
+ "duration": 0.0010000000000000009,
859
+ "lanes": Array [
860
+ 4,
861
],
754
- "startTime": 4,
755
- "suspenseEvents": Array [],
756
- }
757
- `);
862
+ "timestamp": 0.03,
863
+ "type": "passive-effects",
864
+ },
865
+ ],
866
+ },
867
+ "componentMeasures": Array [
868
+ Object {
869
+ "componentName": "App",
870
+ "duration": 0.001,
871
+ "timestamp": 0.006,
872
+ "warning": null,
873
+ },
874
+ Object {
875
+ "componentName": "App",
876
+ "duration": 0.0010000000000000009,
877
+ "timestamp": 0.02,
878
+ "warning": null,
879
+ },
880
+ ],
881
+ "duration": 0.031,
882
+ "flamechart": Array [],
883
+ "laneToLabelMap": Map {
884
+ 0 => "Sync",
885
+ 1 => "InputContinuousHydration",
886
+ 2 => "InputContinuous",
887
+ 3 => "DefaultHydration",
888
+ 4 => "Default",
889
+ 5 => "TransitionHydration",
890
+ 6 => "Transition",
891
+ 7 => "Transition",
892
+ 8 => "Transition",
893
+ 9 => "Transition",
894
+ 10 => "Transition",
895
+ 11 => "Transition",
896
+ 12 => "Transition",
897
+ 13 => "Transition",
898
+ 14 => "Transition",
899
+ 15 => "Transition",
900
+ 16 => "Transition",
901
+ 17 => "Transition",
902
+ 18 => "Transition",
903
+ 19 => "Transition",
904
+ 20 => "Transition",
905
+ 21 => "Transition",
906
+ 22 => "Retry",
907
+ 23 => "Retry",
908
+ 24 => "Retry",
909
+ 25 => "Retry",
910
+ 26 => "Retry",
911
+ 27 => "SelectiveHydration",
912
+ 28 => "IdleHydration",
913
+ 29 => "Idle",
914
+ 30 => "Offscreen",
915
+ },
916
+ "laneToReactMeasureMap": Map {
917
+ 0 => Array [],
918
+ 1 => Array [],
919
+ 2 => Array [],
920
+ 3 => Array [],
921
+ 4 => Array [
922
+ Object {
923
+ "batchUID": 0,
924
+ "depth": 0,
925
+ "duration": 0.009999999999999998,
926
+ "lanes": Array [
927
+ 4,
928
+ ],
929
+ "timestamp": 0.005,
930
+ "type": "render-idle",
931
+ },
932
+ Object {
933
+ "batchUID": 0,
934
+ "depth": 0,
935
+ "duration": 0.003,
936
+ "lanes": Array [
937
+ 4,
938
+ ],
939
+ "timestamp": 0.005,
940
+ "type": "render",
941
+ },
942
+ Object {
943
+ "batchUID": 0,
944
+ "depth": 0,
945
+ "duration": 0.006,
946
+ "lanes": Array [
947
+ 4,
948
+ ],
949
+ "timestamp": 0.009,
950
+ "type": "commit",
951
+ },
952
+ Object {
953
+ "batchUID": 0,
954
+ "depth": 1,
955
+ "duration": 0.0010000000000000009,
956
+ "lanes": Array [
957
+ 4,
958
+ ],
959
+ "timestamp": 0.013,
960
+ "type": "layout-effects",
961
+ },
962
+ Object {
963
+ "batchUID": 0,
964
+ "depth": 0,
965
+ "duration": 0.0019999999999999983,
966
+ "lanes": Array [
967
+ 4,
968
+ ],
969
+ "timestamp": 0.016,
970
+ "type": "passive-effects",
971
+ },
972
+ Object {
973
+ "batchUID": 1,
974
+ "depth": 0,
975
+ "duration": 0.010000000000000002,
976
+ "lanes": Array [
977
+ 4,
978
+ ],
979
+ "timestamp": 0.019,
980
+ "type": "render-idle",
981
+ },
982
+ Object {
983
+ "batchUID": 1,
984
+ "depth": 0,
985
+ "duration": 0.002999999999999999,
986
+ "lanes": Array [
987
+ 4,
988
+ ],
989
+ "timestamp": 0.019,
990
+ "type": "render",
991
+ },
992
+ Object {
993
+ "batchUID": 1,
994
+ "depth": 0,
995
+ "duration": 0.006000000000000002,
996
+ "lanes": Array [
997
+ 4,
998
+ ],
999
+ "timestamp": 0.023,
1000
+ "type": "commit",
1001
+ },
1002
+ Object {
1003
+ "batchUID": 1,
1004
+ "depth": 1,
1005
+ "duration": 0.0010000000000000009,
1006
+ "lanes": Array [
1007
+ 4,
1008
+ ],
1009
+ "timestamp": 0.027,
1010
+ "type": "layout-effects",
1011
+ },
1012
+ Object {
1013
+ "batchUID": 1,
1014
+ "depth": 0,
1015
+ "duration": 0.0010000000000000009,
1016
+ "lanes": Array [
1017
+ 4,
1018
+ ],
1019
+ "timestamp": 0.03,
1020
+ "type": "passive-effects",
1021
+ },
1022
+ ],
1023
+ 5 => Array [],
1024
+ 6 => Array [],
1025
+ 7 => Array [],
1026
+ 8 => Array [],
1027
+ 9 => Array [],
1028
+ 10 => Array [],
1029
+ 11 => Array [],
1030
+ 12 => Array [],
1031
+ 13 => Array [],
1032
+ 14 => Array [],
1033
+ 15 => Array [],
1034
+ 16 => Array [],
1035
+ 17 => Array [],
1036
+ 18 => Array [],
1037
+ 19 => Array [],
1038
+ 20 => Array [],
1039
+ 21 => Array [],
1040
+ 22 => Array [],
1041
+ 23 => Array [],
1042
+ 24 => Array [],
1043
+ 25 => Array [],
1044
+ 26 => Array [],
1045
+ 27 => Array [],
1046
+ 28 => Array [],
1047
+ 29 => Array [],
1048
+ 30 => Array [],
1049
+ },
1050
+ "nativeEvents": Array [],
1051
+ "otherUserTimingMarks": Array [
1052
+ Object {
1053
+ "name": "__v3",
1054
+ "timestamp": 0.003,
1055
+ },
1056
+ ],
1057
+ "reactVersion": "17.0.3",
1058
+ "schedulingEvents": Array [
1059
+ Object {
1060
+ "lanes": Array [
1061
+ 4,
1062
+ ],
1063
+ "timestamp": 0.004,
1064
+ "type": "schedule-render",
1065
+ "warning": null,
1066
+ },
1067
+ Object {
1068
+ "componentName": "App",
1069
+ "lanes": Array [
1070
+ 4,
1071
+ ],
1072
+ "timestamp": 0.017,
1073
+ "type": "schedule-state-update",
1074
+ "warning": null,
1075
+ },
1076
+ ],
1077
+ "startTime": 4,
1078
+ "suspenseEvents": Array [],
1079
+ }
1080
+ `);
1081
}
1082
});
1083
packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js
+42
-16
@@ -13,13 +13,14 @@ import {
13
} from '@elg/speedscope';
14
import type {TimelineEvent} from '@elg/speedscope';
15
import type {
16
- Milliseconds,
16
BatchUID,
17
Flamechart,
18
+ Milliseconds,
19
NativeEvent,
20
Phase,
21
ReactLane,
22
ReactComponentMeasure,
23
+ ReactMeasure,
24
ReactMeasureType,
25
ReactProfilerData,
26
SchedulingEvent,
@@ -33,7 +34,7 @@ import {getBatchRange} from '../utils/getBatchRange';
34
type MeasureStackElement = {|
35
type: ReactMeasureType,
36
depth: number,
36
- index: number,
37
+ measure: ReactMeasure,
38
startTime: Milliseconds,
39
stopTime?: Milliseconds,
40
|};
@@ -92,17 +93,19 @@ export function getLanesFromTransportDecimalBitmask(
93
return lanes;
94
}
95
95
-const laneToLabelMap: Map<number, string> = new Map();
96
-function updateLaneToLabelMap(laneLabelTuplesString: string): void {
96
+function updateLaneToLabelMap(
97
+ profilerData: ReactProfilerData,
98
+ laneLabelTuplesString: string,
99
+): void {
100
// These marks appear multiple times in the data;
101
// We only need to extact them once.
99
- if (laneToLabelMap.size === 0) {
102
+ if (profilerData.laneToLabelMap.size === 0) {
103
const laneLabelTuples = laneLabelTuplesString.split(',');
104
for (let laneIndex = 0; laneIndex < laneLabelTuples.length; laneIndex++) {
105
// The numeric lane value (e.g. 64) isn't important.
106
// The profiler parses and stores the lane's position within the bitmap,
107
// (e.g. lane 1 is index 0, lane 16 is index 4).
105
- laneToLabelMap.set(laneIndex, laneLabelTuples[laneIndex]);
108
+ profilerData.laneToLabelMap.set(laneIndex, laneLabelTuples[laneIndex]);
109
}
110
}
111
}
@@ -133,18 +136,32 @@ function markWorkStarted(
136
state: ProcessorState,
137
) {
138
const {batchUID, measureStack} = state;
136
- const index = currentProfilerData.measures.length;
139
const depth = getDepth(measureStack);
140
139
- state.measureStack.push({depth, index, startTime, type});
140
-
141
- currentProfilerData.measures.push({
141
+ const measure: ReactMeasure = {
142
type,
143
batchUID,
144
depth,
145
lanes,
146
timestamp: startTime,
147
duration: 0,
148
+ };
149
+
150
+ state.measureStack.push({depth, measure, startTime, type});
151
+
152
+ // This array is pre-initialized when the batchUID is generated.
153
+ const measures = currentProfilerData.batchUIDToMeasuresMap.get(batchUID);
154
+ if (measures != null) {
155
+ measures.push(measure);
156
+ } else {
157
+ currentProfilerData.batchUIDToMeasuresMap.set(state.batchUID, [measure]);
158
+ }
159
+
160
+ // This array is pre-initialized before processing starts.
161
+ lanes.forEach(lane => {
162
+ ((currentProfilerData.laneToReactMeasureMap.get(
163
+ lane,
164
+ ): any): ReactMeasure[]).push(measure);
165
});
166
}
167
@@ -174,8 +191,7 @@ function markWorkCompleted(
191
);
192
}
193
177
- const {index, startTime} = stack.pop();
178
- const measure = currentProfilerData.measures[index];
194
+ const {measure, startTime} = stack.pop();
195
if (!measure) {
196
console.error('Could not find matching measure for type "%s".', type);
197
}
@@ -282,7 +298,7 @@ function processTimelineEvent(
298
}
299
} else if (name.startsWith('--react-lane-labels-')) {
300
const [laneLabelTuplesString] = name.substr(20).split('-');
285
- updateLaneToLabelMap(laneLabelTuplesString);
301
+ updateLaneToLabelMap(currentProfilerData, laneLabelTuplesString);
302
} else if (name.startsWith('--component-render-start-')) {
303
const [componentName] = name.substr(25).split('-');
304
@@ -648,12 +664,18 @@ export default function preprocessData(
664
): ReactProfilerData {
665
const flamechart = preprocessFlamechart(timeline);
666
667
+ const laneToReactMeasureMap = new Map();
668
+ for (let lane: ReactLane = 0; lane < REACT_TOTAL_NUM_LANES; lane++) {
669
+ laneToReactMeasureMap.set(lane, []);
670
+ }
671
+
672
const profilerData: ReactProfilerData = {
673
+ batchUIDToMeasuresMap: new Map(),
674
componentMeasures: [],
675
duration: 0,
676
flamechart,
655
- laneToLabelMap,
656
- measures: [],
677
+ laneToLabelMap: new Map(),
678
+ laneToReactMeasureMap,
679
nativeEvents: [],
680
otherUserTimingMarks: [],
681
reactVersion: null,
@@ -739,7 +761,11 @@ export default function preprocessData(
761
state.potentialSuspenseEventsOutsideOfTransition.forEach(
762
([suspenseEvent, lanes]) => {
763
// HACK This is a bit gross but the numeric lane value might change between render versions.
742
- if (!lanes.some(lane => laneToLabelMap.get(lane) === 'Transition')) {
764
+ if (
765
+ !lanes.some(
766
+ lane => profilerData.laneToLabelMap.get(lane) === 'Transition',
767
+ )
768
+ ) {
769
suspenseEvent.warning = WARNING_STRINGS.SUSPEND_DURING_UPATE;
770
}
771
},
packages/react-devtools-scheduling-profiler/src/types.js
+19
-2
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-// Type utilities
10
+import type {ScrollState} from './view-base/utils/scrollState';
11
12
// Source: https://github.com/facebook/flow/issues/4002#issuecomment-323612798
13
// eslint-disable-next-line no-unused-vars
@@ -123,12 +123,29 @@ export type FlamechartStackLayer = FlamechartStackFrame[];
123
124
export type Flamechart = FlamechartStackLayer[];
125
126
+export type HorizontalScrollStateChangeCallback = (
127
+ scrollState: ScrollState,
128
+) => void;
129
+
130
+// Imperative view state that corresponds to profiler data.
131
+// This state lives outside of React's lifecycle
132
+// and should be erased/reset whenever new profiler data is loaded.
133
+export type ViewState = {|
134
+ horizontalScrollState: ScrollState,
135
+ onHorizontalScrollStateChange: (
136
+ callback: HorizontalScrollStateChangeCallback,
137
+ ) => void,
138
+ updateHorizontalScrollState: (scrollState: ScrollState) => void,
139
+ viewToMutableViewStateMap: Map<string, mixed>,
140
+|};
141
+
142
export type ReactProfilerData = {|
143
+ batchUIDToMeasuresMap: Map<BatchUID, ReactMeasure[]>,
144
componentMeasures: ReactComponentMeasure[],
145
duration: number,
146
flamechart: Flamechart,
147
laneToLabelMap: Map<ReactLane, string>,
131
- measures: ReactMeasure[],
148
+ laneToReactMeasureMap: Map<ReactLane, ReactMeasure[]>,
149
nativeEvents: NativeEvent[],
150
otherUserTimingMarks: UserTimingMark[],
151
reactVersion: string | null,
packages/react-devtools-scheduling-profiler/src/utils/getBatchRange.js
+20
-22
@@ -9,37 +9,35 @@
9
10
import memoize from 'memoize-one';
11
12
-import type {BatchUID, Milliseconds, ReactProfilerData} from '../types';
12
+import type {
13
+ BatchUID,
14
+ Milliseconds,
15
+ ReactMeasure,
16
+ ReactProfilerData,
17
+} from '../types';
18
19
function unmemoizedGetBatchRange(
20
batchUID: BatchUID,
21
data: ReactProfilerData,
17
- minStartTime?: ?number,
22
+ minStartTime?: number = 0,
23
): [Milliseconds, Milliseconds] {
19
- const {measures} = data;
20
-
21
- let startTime = 0;
22
- let stopTime = Infinity;
24
+ const measures = data.batchUIDToMeasuresMap.get(batchUID);
25
+ if (measures == null || measures.length === 0) {
26
+ throw Error(`Could not find measures with batch UID "${batchUID}"`);
27
+ }
28
24
- let i = 0;
29
+ const lastMeasure = ((measures[measures.length - 1]: any): ReactMeasure);
30
+ const stopTime = lastMeasure.timestamp + lastMeasure.duration;
31
26
- // Find the first measure in the current batch.
27
- for (i; i < measures.length; i++) {
28
- const measure = measures[i];
29
- if (measure.batchUID === batchUID) {
30
- if (minStartTime == null || measure.timestamp >= minStartTime) {
31
- startTime = measure.timestamp;
32
- break;
33
- }
34
- }
32
+ if (stopTime < minStartTime) {
33
+ return [0, 0];
34
}
35
37
- // Find the last measure in the current batch.
38
- for (i; i < measures.length; i++) {
39
- const measure = measures[i];
40
- if (measure.batchUID === batchUID) {
41
- stopTime = measure.timestamp;
42
- } else {
36
+ let startTime = minStartTime;
37
+ for (let index = 0; index < measures.length; index++) {
38
+ const measure = measures[index];
39
+ if (measure.timestamp >= minStartTime) {
40
+ startTime = measure.timestamp;
41
break;
42
}
43
}
packages/react-devtools-scheduling-profiler/src/view-base/HorizontalPanAndZoomView.js
+58
-89
@@ -18,6 +18,7 @@ import type {
18
import type {Rect} from './geometry';
19
import type {ScrollState} from './utils/scrollState';
20
import type {ViewRefs} from './Surface';
21
+import type {ViewState} from '../types';
22
23
import {Surface} from './Surface';
24
import {View} from './View';
@@ -30,96 +31,84 @@ import {
31
zoomState,
32
} from './utils/scrollState';
33
import {
33
- DEFAULT_ZOOM_LEVEL,
34
MAX_ZOOM_LEVEL,
35
MIN_ZOOM_LEVEL,
36
MOVE_WHEEL_DELTA_THRESHOLD,
37
} from './constants';
38
39
-export type HorizontalPanAndZoomViewOnChangeCallback = (
40
- state: ScrollState,
41
- view: HorizontalPanAndZoomView,
42
-) => void;
43
-
39
export class HorizontalPanAndZoomView extends View {
40
+ _contentView: View;
41
_intrinsicContentWidth: number;
42
_isPanning = false;
47
- _scrollState: ScrollState = {offset: 0, length: 0};
48
- _onStateChange: HorizontalPanAndZoomViewOnChangeCallback = () => {};
43
+ _viewState: ViewState;
44
45
constructor(
46
surface: Surface,
47
frame: Rect,
48
contentView: View,
49
intrinsicContentWidth: number,
55
- onStateChange?: HorizontalPanAndZoomViewOnChangeCallback,
50
+ viewState: ViewState,
51
) {
52
super(surface, frame);
58
- this.addSubview(contentView);
59
- this._intrinsicContentWidth = intrinsicContentWidth;
60
- this._setScrollState({
61
- offset: 0,
62
- length: intrinsicContentWidth * DEFAULT_ZOOM_LEVEL,
63
- });
64
- if (onStateChange) this._onStateChange = onStateChange;
65
- }
53
67
- setFrame(newFrame: Rect) {
68
- super.setFrame(newFrame);
54
+ this._contentView = contentView;
55
+ this._intrinsicContentWidth = intrinsicContentWidth;
56
+ this._viewState = viewState;
57
70
- // Revalidate scrollState
71
- this._setStateAndInformCallbacksIfChanged(this._scrollState);
72
- }
58
+ viewState.onHorizontalScrollStateChange(scrollState => {
59
+ this.zoomToRange(scrollState.offset, scrollState.length);
60
+ });
61
74
- setScrollState(proposedState: ScrollState) {
75
- this._setScrollState(proposedState);
62
+ this.addSubview(contentView);
63
}
64
65
/**
79
- * Just sets scroll state. Use `_setStateAndInformCallbacksIfChanged` if this
80
- * view's callbacks should also be called.
66
+ * Just sets scroll state.
67
+ * Use `_setStateAndInformCallbacksIfChanged` if this view's callbacks should also be called.
68
*
69
* @returns Whether state was changed
70
* @private
71
*/
85
- _setScrollState(proposedState: ScrollState): boolean {
72
+ setScrollState(proposedState: ScrollState) {
73
const clampedState = clampState({
74
state: proposedState,
75
minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
76
maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
77
containerLength: this.frame.size.width,
78
});
92
- if (areScrollStatesEqual(clampedState, this._scrollState)) {
93
- return false;
79
+ if (
80
+ !areScrollStatesEqual(clampedState, this._viewState.horizontalScrollState)
81
+ ) {
82
+ this.setNeedsDisplay();
83
}
95
- this._scrollState = clampedState;
96
- this.setNeedsDisplay();
97
- return true;
84
}
85
86
/**
101
- * @private
87
+ * Zoom to a specific range of the content specified as a range of the
88
+ * content view's intrinsic content size.
89
+ *
90
+ * Does not inform callbacks of state change since this is a public API.
91
*/
103
- _setStateAndInformCallbacksIfChanged(proposedState: ScrollState) {
104
- if (this._setScrollState(proposedState)) {
105
- this._onStateChange(this._scrollState, this);
106
- }
92
+ zoomToRange(rangeStart: number, rangeEnd: number) {
93
+ const newState = moveStateToRange({
94
+ state: this._viewState.horizontalScrollState,
95
+ rangeStart,
96
+ rangeEnd,
97
+ contentLength: this._intrinsicContentWidth,
98
+
99
+ minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
100
+ maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
101
+ containerLength: this.frame.size.width,
102
+ });
103
+ this.setScrollState(newState);
104
}
105
106
desiredSize() {
107
return this._contentView.desiredSize();
108
}
109
113
- /**
114
- * Reference to the content view. This view is also the only view in
115
- * `this.subviews`.
116
- */
117
- get _contentView() {
118
- return this.subviews[0];
119
- }
120
-
110
layoutSubviews() {
122
- const {offset, length} = this._scrollState;
111
+ const {offset, length} = this._viewState.horizontalScrollState;
112
const proposedFrame = {
113
origin: {
114
x: this.frame.origin.x + offset,
@@ -134,24 +123,22 @@ export class HorizontalPanAndZoomView extends View {
123
super.layoutSubviews();
124
}
125
137
- /**
138
- * Zoom to a specific range of the content specified as a range of the
139
- * content view's intrinsic content size.
140
- *
141
- * Does not inform callbacks of state change since this is a public API.
142
- */
143
- zoomToRange(rangeStart: number, rangeEnd: number) {
144
- const newState = moveStateToRange({
145
- state: this._scrollState,
146
- rangeStart,
147
- rangeEnd,
148
- contentLength: this._intrinsicContentWidth,
149
-
150
- minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
151
- maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
152
- containerLength: this.frame.size.width,
153
- });
154
- this._setScrollState(newState);
126
+ handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
127
+ switch (interaction.type) {
128
+ case 'mousedown':
129
+ this._handleMouseDown(interaction, viewRefs);
130
+ break;
131
+ case 'mousemove':
132
+ this._handleMouseMove(interaction, viewRefs);
133
+ break;
134
+ case 'mouseup':
135
+ this._handleMouseUp(interaction, viewRefs);
136
+ break;
137
+ case 'wheel-plain':
138
+ case 'wheel-shift':
139
+ this._handleWheel(interaction);
140
+ break;
141
+ }
142
}
143
144
_handleMouseDown(interaction: MouseDownInteraction, viewRefs: ViewRefs) {
@@ -183,11 +170,11 @@ export class HorizontalPanAndZoomView extends View {
170
return;
171
}
172
const newState = translateState({
186
- state: this._scrollState,
173
+ state: this._viewState.horizontalScrollState,
174
delta: interaction.payload.event.movementX,
175
containerLength: this.frame.size.width,
176
});
190
- this._setStateAndInformCallbacksIfChanged(newState);
177
+ this._viewState.updateHorizontalScrollState(newState);
178
}
179
180
_handleMouseUp(interaction: MouseUpInteraction, viewRefs: ViewRefs) {
@@ -226,44 +213,26 @@ export class HorizontalPanAndZoomView extends View {
213
}
214
215
const newState = zoomState({
229
- state: this._scrollState,
216
+ state: this._viewState.horizontalScrollState,
217
multiplier: 1 + 0.005 * -deltaY,
231
- fixedPoint: location.x - this._scrollState.offset,
218
+ fixedPoint: location.x - this._viewState.horizontalScrollState.offset,
219
220
minContentLength: this._intrinsicContentWidth * MIN_ZOOM_LEVEL,
221
maxContentLength: this._intrinsicContentWidth * MAX_ZOOM_LEVEL,
222
containerLength: this.frame.size.width,
223
});
237
- this._setStateAndInformCallbacksIfChanged(newState);
224
+ this._viewState.updateHorizontalScrollState(newState);
225
} else {
226
if (absDeltaX < MOVE_WHEEL_DELTA_THRESHOLD) {
227
return;
228
}
229
230
const newState = translateState({
244
- state: this._scrollState,
231
+ state: this._viewState.horizontalScrollState,
232
delta: -deltaX,
233
containerLength: this.frame.size.width,
234
});
248
- this._setStateAndInformCallbacksIfChanged(newState);
249
- }
250
- }
251
-
252
- handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
253
- switch (interaction.type) {
254
- case 'mousedown':
255
- this._handleMouseDown(interaction, viewRefs);
256
- break;
257
- case 'mousemove':
258
- this._handleMouseMove(interaction, viewRefs);
259
- break;
260
- case 'mouseup':
261
- this._handleMouseUp(interaction, viewRefs);
262
- break;
263
- case 'wheel-plain':
264
- case 'wheel-shift':
265
- this._handleWheel(interaction);
266
- break;
235
+ this._viewState.updateHorizontalScrollState(newState);
236
}
237
}
238
}
packages/react-devtools-scheduling-profiler/src/view-base/ResizableView.js
+48
-16
@@ -17,6 +17,7 @@ import type {
17
} from './useCanvasInteraction';
18
import type {Rect} from './geometry';
19
import type {ViewRefs} from './Surface';
20
+import type {ViewState} from '../types';
21
22
import {BORDER_SIZE, COLORS} from '../content-views/constants';
23
import {drawText} from '../content-views/utils/text';
@@ -35,10 +36,10 @@ type ResizingState = $ReadOnly<{|
36
mouseY: number,
37
|}>;
38
38
-type LayoutState = $ReadOnly<{|
39
+type LayoutState = {|
40
/** Resize bar's vertical position relative to resize view's frame.origin.y */
41
barOffsetY: number,
41
-|}>;
42
+|};
43
44
const RESIZE_BAR_DOT_RADIUS = 1;
45
const RESIZE_BAR_DOT_SPACING = 4;
@@ -217,36 +218,33 @@ class ResizeBar extends View {
218
export class ResizableView extends View {
219
_canvasRef: {current: HTMLCanvasElement | null};
220
_layoutState: LayoutState;
221
+ _mutableViewStateKey: string;
222
_resizeBar: ResizeBar;
223
_resizingState: ResizingState | null = null;
224
_subview: View;
225
+ _viewState: ViewState;
226
227
constructor(
228
surface: Surface,
229
frame: Rect,
230
subview: View,
231
+ viewState: ViewState,
232
canvasRef: {current: HTMLCanvasElement | null},
233
label: string,
234
) {
235
super(surface, frame, noopLayout);
236
237
this._canvasRef = canvasRef;
234
-
238
+ this._layoutState = {barOffsetY: 0};
239
+ this._mutableViewStateKey = label + ':ResizableView';
240
this._subview = subview;
241
this._resizeBar = new ResizeBar(surface, frame, label);
242
+ this._viewState = viewState;
243
244
this.addSubview(this._subview);
245
this.addSubview(this._resizeBar);
246
241
- const subviewDesiredSize = subview.desiredSize();
242
- this._updateLayoutStateAndResizeBar(
243
- subviewDesiredSize.maxInitialHeight != null
244
- ? Math.min(
245
- subviewDesiredSize.maxInitialHeight,
246
- subviewDesiredSize.height,
247
- )
248
- : subviewDesiredSize.height,
249
- );
247
+ this._restoreMutableViewState();
248
}
249
250
desiredSize() {
@@ -274,6 +272,35 @@ export class ResizableView extends View {
272
super.layoutSubviews();
273
}
274
275
+ _restoreMutableViewState() {
276
+ if (
277
+ this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
278
+ ) {
279
+ this._layoutState = ((this._viewState.viewToMutableViewStateMap.get(
280
+ this._mutableViewStateKey,
281
+ ): any): LayoutState);
282
+
283
+ this._updateLayoutStateAndResizeBar(this._layoutState.barOffsetY);
284
+ } else {
285
+ this._viewState.viewToMutableViewStateMap.set(
286
+ this._mutableViewStateKey,
287
+ this._layoutState,
288
+ );
289
+
290
+ const subviewDesiredSize = this._subview.desiredSize();
291
+ this._updateLayoutStateAndResizeBar(
292
+ subviewDesiredSize.maxInitialHeight != null
293
+ ? Math.min(
294
+ subviewDesiredSize.maxInitialHeight,
295
+ subviewDesiredSize.height,
296
+ )
297
+ : subviewDesiredSize.height,
298
+ );
299
+ }
300
+
301
+ this.setNeedsDisplay();
302
+ }
303
+
304
_shouldRenderResizeBar() {
305
const subviewDesiredSize = this._subview.desiredSize();
306
return subviewDesiredSize.hideScrollBarIfLessThanHeight != null
@@ -287,10 +314,7 @@ export class ResizableView extends View {
314
barOffsetY = 0;
315
}
316
290
- this._layoutState = {
291
- ...this._layoutState,
292
- barOffsetY,
293
- };
317
+ this._layoutState.barOffsetY = barOffsetY;
318
319
this._resizeBar.showLabel = barOffsetY === 0;
320
}
@@ -341,6 +365,10 @@ export class ResizableView extends View {
365
}
366
367
_handleClick(interaction: ClickInteraction) {
368
+ if (!this._shouldRenderResizeBar()) {
369
+ return;
370
+ }
371
+
372
const cursorInView = rectContainsPoint(
373
interaction.payload.location,
374
this.frame,
@@ -356,6 +384,10 @@ export class ResizableView extends View {
384
}
385
386
_handleDoubleClick(interaction: DoubleClickInteraction) {
387
+ if (!this._shouldRenderResizeBar()) {
388
+ return;
389
+ }
390
+
391
const cursorInView = rectContainsPoint(
392
interaction.payload.location,
393
this.frame,
packages/react-devtools-scheduling-profiler/src/view-base/VerticalScrollView.js
+62
-31
@@ -17,6 +17,7 @@ import type {
17
import type {Rect} from './geometry';
18
import type {ScrollState} from './utils/scrollState';
19
import type {ViewRefs} from './Surface';
20
+import type {ViewState} from '../types';
21
22
import {Surface} from './Surface';
23
import {View} from './View';
@@ -34,13 +35,33 @@ const CARET_WIDTH = 5;
35
const CARET_HEIGHT = 3;
36
37
export class VerticalScrollView extends View {
37
- _scrollState: ScrollState = {offset: 0, length: 0};
38
- _isPanning = false;
39
-
40
- constructor(surface: Surface, frame: Rect, contentView: View) {
38
+ _contentView: View;
39
+ _isPanning: boolean;
40
+ _mutableViewStateKey: string;
41
+ _scrollState: ScrollState;
42
+ _viewState: ViewState;
43
+
44
+ constructor(
45
+ surface: Surface,
46
+ frame: Rect,
47
+ contentView: View,
48
+ viewState: ViewState,
49
+ label: string,
50
+ ) {
51
super(surface, frame);
52
+
53
+ this._contentView = contentView;
54
+ this._isPanning = false;
55
+ this._mutableViewStateKey = label + ':VerticalScrollView';
56
+ this._scrollState = {
57
+ offset: 0,
58
+ length: 0,
59
+ };
60
+ this._viewState = viewState;
61
+
62
this.addSubview(contentView);
43
- this._setScrollState(this._scrollState);
63
+
64
+ this._restoreMutableViewState();
65
}
66
67
setFrame(newFrame: Rect) {
@@ -102,14 +123,6 @@ export class VerticalScrollView extends View {
123
}
124
}
125
105
- /**
106
- * Reference to the content view. This view is also the only view in
107
- * `this.subviews`.
108
- */
109
- get _contentView() {
110
- return this.subviews[0];
111
- }
112
-
126
layoutSubviews() {
127
const {offset} = this._scrollState;
128
const desiredSize = this._contentView.desiredSize();
@@ -133,6 +146,23 @@ export class VerticalScrollView extends View {
146
super.layoutSubviews();
147
}
148
149
+ handleInteraction(interaction: Interaction) {
150
+ switch (interaction.type) {
151
+ case 'mousedown':
152
+ this._handleMouseDown(interaction);
153
+ break;
154
+ case 'mousemove':
155
+ this._handleMouseMove(interaction);
156
+ break;
157
+ case 'mouseup':
158
+ this._handleMouseUp(interaction);
159
+ break;
160
+ case 'wheel-shift':
161
+ this._handleWheelShift(interaction);
162
+ break;
163
+ }
164
+ }
165
+
166
_handleMouseDown(interaction: MouseDownInteraction) {
167
if (rectContainsPoint(interaction.payload.location, this.frame)) {
168
this._isPanning = true;
@@ -184,21 +214,21 @@ export class VerticalScrollView extends View {
214
this._setScrollState(newState);
215
}
216
187
- handleInteraction(interaction: Interaction) {
188
- switch (interaction.type) {
189
- case 'mousedown':
190
- this._handleMouseDown(interaction);
191
- break;
192
- case 'mousemove':
193
- this._handleMouseMove(interaction);
194
- break;
195
- case 'mouseup':
196
- this._handleMouseUp(interaction);
197
- break;
198
- case 'wheel-shift':
199
- this._handleWheelShift(interaction);
200
- break;
217
+ _restoreMutableViewState() {
218
+ if (
219
+ this._viewState.viewToMutableViewStateMap.has(this._mutableViewStateKey)
220
+ ) {
221
+ this._scrollState = ((this._viewState.viewToMutableViewStateMap.get(
222
+ this._mutableViewStateKey,
223
+ ): any): ScrollState);
224
+ } else {
225
+ this._viewState.viewToMutableViewStateMap.set(
226
+ this._mutableViewStateKey,
227
+ this._scrollState,
228
+ );
229
}
230
+
231
+ this.setNeedsDisplay();
232
}
233
234
/**
@@ -212,10 +242,11 @@ export class VerticalScrollView extends View {
242
maxContentLength: height,
243
containerLength: this.frame.size.height,
244
});
215
- if (areScrollStatesEqual(clampedState, this._scrollState)) {
216
- return;
245
+ if (!areScrollStatesEqual(clampedState, this._scrollState)) {
246
+ this._scrollState.offset = clampedState.offset;
247
+ this._scrollState.length = clampedState.length;
248
+
249
+ this.setNeedsDisplay();
250
}
218
- this._scrollState = clampedState;
219
- this.setNeedsDisplay();
251
}
252
}
packages/react-devtools-scheduling-profiler/src/view-base/utils/scrollState.js
+2
-2
@@ -18,10 +18,10 @@ import {clamp} from './clamp';
18
* |<-------------------length------------------->|
19
* ```
20
*/
21
-export type ScrollState = $ReadOnly<{|
21
+export type ScrollState = {|
22
offset: number,
23
length: number,
24
-|}>;
24
+|};
25
26
function clampOffset(state: ScrollState, containerLength: number): ScrollState {
27
return {