@samitouri / QOS-React-2 / commits / e3049bb850

DevTools scheduling profiler: Add React component measures (#22013)

Brian Vaughn committed Aug 3, 2021 at 13:03 UTC e3049bb850d8d6395f67d4171870a686e08c3dbd
24 files changed +603 -39
packages/react-devtools-scheduling-profiler/src/CanvasPage.js
+53
@@ -44,6 +44,7 @@ import {
44 zeroPoint,
45 } from './view-base';
46 import {
47 + ComponentMeasuresView,
48 FlamechartView,
49 NativeEventsView,
50 ReactMeasuresView,
@@ -132,6 +133,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
133 const nativeEventsViewRef = useRef(null);
134 const schedulingEventsViewRef = useRef(null);
135 const suspenseEventsViewRef = useRef(null);
136 + const componentMeasuresViewRef = useRef(null);
137 const reactMeasuresViewRef = useRef(null);
138 const flamechartViewRef = useRef(null);
139 const syncedHorizontalPanAndZoomViewsRef = useRef<HorizontalPanAndZoomView[]>(
@@ -259,6 +261,17 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
261 true,
262 );
263
264 + let componentMeasuresViewWrapper = null;
265 + if (data.componentMeasures.length > 0) {
266 + const componentMeasuresView = new ComponentMeasuresView(
267 + surface,
268 + defaultFrame,
269 + data,
270 + );
271 + componentMeasuresViewRef.current = componentMeasuresView;
272 + componentMeasuresViewWrapper = createViewHelper(componentMeasuresView);
273 + }
274 +
275 const flamechartView = new FlamechartView(
276 surface,
277 defaultFrame,
@@ -293,6 +306,9 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
306 rootView.addSubview(suspenseEventsViewWrapper);
307 }
308 rootView.addSubview(reactMeasuresViewWrapper);
309 + if (componentMeasuresViewWrapper !== null) {
310 + rootView.addSubview(componentMeasuresViewWrapper);
311 + }
312 rootView.addSubview(flamechartViewWrapper);
313
314 // If subviews are less than the available height, fill remaining height with a solid color.
@@ -323,6 +339,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
339 if (prevHoverEvent === null) {
340 return prevHoverEvent;
341 } else if (
342 + prevHoverEvent.componentMeasure !== null ||
343 prevHoverEvent.flamechartStackFrame !== null ||
344 prevHoverEvent.measure !== null ||
345 prevHoverEvent.nativeEvent !== null ||
@@ -331,6 +348,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
348 prevHoverEvent.userTimingMark !== null
349 ) {
350 return {
351 + componentMeasure: null,
352 data: prevHoverEvent.data,
353 flamechartStackFrame: null,
354 measure: null,
@@ -378,6 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
396 userTimingMarksView.onHover = userTimingMark => {
397 if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
398 setHoveredEvent({
399 + componentMeasure: null,
400 data,
401 flamechartStackFrame: null,
402 measure: null,
@@ -395,6 +414,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
414 nativeEventsView.onHover = nativeEvent => {
415 if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
416 setHoveredEvent({
417 + componentMeasure: null,
418 data,
419 flamechartStackFrame: null,
420 measure: null,
@@ -412,6 +432,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
432 schedulingEventsView.onHover = schedulingEvent => {
433 if (!hoveredEvent || hoveredEvent.schedulingEvent !== schedulingEvent) {
434 setHoveredEvent({
435 + componentMeasure: null,
436 data,
437 flamechartStackFrame: null,
438 measure: null,
@@ -429,6 +450,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
450 suspenseEventsView.onHover = suspenseEvent => {
451 if (!hoveredEvent || hoveredEvent.suspenseEvent !== suspenseEvent) {
452 setHoveredEvent({
453 + componentMeasure: null,
454 data,
455 flamechartStackFrame: null,
456 measure: null,
@@ -446,6 +468,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
468 reactMeasuresView.onHover = measure => {
469 if (!hoveredEvent || hoveredEvent.measure !== measure) {
470 setHoveredEvent({
471 + componentMeasure: null,
472 data,
473 flamechartStackFrame: null,
474 measure,
@@ -458,6 +481,27 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
481 };
482 }
483
484 + const {current: componentMeasuresView} = componentMeasuresViewRef;
485 + if (componentMeasuresView) {
486 + componentMeasuresView.onHover = componentMeasure => {
487 + if (
488 + !hoveredEvent ||
489 + hoveredEvent.componentMeasure !== componentMeasure
490 + ) {
491 + setHoveredEvent({
492 + componentMeasure,
493 + data,
494 + flamechartStackFrame: null,
495 + measure: null,
496 + nativeEvent: null,
497 + schedulingEvent: null,
498 + suspenseEvent: null,
499 + userTimingMark: null,
500 + });
501 + }
502 + };
503 + }
504 +
505 const {current: flamechartView} = flamechartViewRef;
506 if (flamechartView) {
507 flamechartView.setOnHover(flamechartStackFrame => {
@@ -466,6 +510,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
510 hoveredEvent.flamechartStackFrame !== flamechartStackFrame
511 ) {
512 setHoveredEvent({
513 + componentMeasure: null,
514 data,
515 flamechartStackFrame,
516 measure: null,
@@ -540,6 +585,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
585 return null;
586 }
587 const {
588 + componentMeasure,
589 flamechartStackFrame,
590 measure,
591 schedulingEvent,
@@ -547,6 +593,13 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
593 } = contextData.hoveredEvent;
594 return (
595 <Fragment>
596 + {componentMeasure !== null && (
597 + <ContextMenuItem
598 + onClick={() => copy(componentMeasure.componentName)}
599 + title="Copy component name">
600 + Copy component name
601 + </ContextMenuItem>
602 + )}
603 {schedulingEvent !== null && (
604 <ContextMenuItem
605 onClick={() => copy(schedulingEvent.componentName)}
packages/react-devtools-scheduling-profiler/src/EventTooltip.js
+42 -1
@@ -11,6 +11,7 @@ import type {Point} from './view-base';
11 import type {
12 FlamechartStackFrame,
13 NativeEvent,
14 + ReactComponentMeasure,
15 ReactHoverContextInfo,
16 ReactMeasure,
17 ReactProfilerData,
@@ -81,6 +82,7 @@ export default function EventTooltip({
82 }
83
84 const {
85 + componentMeasure,
86 flamechartStackFrame,
87 measure,
88 nativeEvent,
@@ -89,7 +91,14 @@ export default function EventTooltip({
91 userTimingMark,
92 } = hoveredEvent;
93
92 - if (nativeEvent !== null) {
94 + if (componentMeasure !== null) {
95 + return (
96 + <TooltipReactComponentMeasure
97 + componentMeasure={componentMeasure}
98 + tooltipRef={tooltipRef}
99 + />
100 + );
101 + } else if (nativeEvent !== null) {
102 return (
103 <TooltipNativeEvent nativeEvent={nativeEvent} tooltipRef={tooltipRef} />
104 );
@@ -130,6 +139,38 @@ export default function EventTooltip({
139 return null;
140 }
141
142 +const TooltipReactComponentMeasure = ({
143 + componentMeasure,
144 + tooltipRef,
145 +}: {
146 + componentMeasure: ReactComponentMeasure,
147 + tooltipRef: Return<typeof useRef>,
148 +}) => {
149 + const {componentName, duration, timestamp, warning} = componentMeasure;
150 +
151 + const label = `${componentName} rendered`;
152 +
153 + return (
154 + <div className={styles.Tooltip} ref={tooltipRef}>
155 + <div className={styles.TooltipSection}>
156 + {trimString(label, 768)}
157 + <div className={styles.Divider} />
158 + <div className={styles.DetailsGrid}>
159 + <div className={styles.DetailsGridLabel}>Timestamp:</div>
160 + <div>{formatTimestamp(timestamp)}</div>
161 + <div className={styles.DetailsGridLabel}>Duration:</div>
162 + <div>{formatDuration(duration)}</div>
163 + </div>
164 + </div>
165 + {warning !== null && (
166 + <div className={styles.TooltipWarningSection}>
167 + <div className={styles.WarningText}>{warning}</div>
168 + </div>
169 + )}
170 + </div>
171 + );
172 +};
173 +
174 const TooltipFlamechartNode = ({
175 stackFrame,
176 tooltipRef,
packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js
+7
@@ -14,6 +14,7 @@ import createDataResourceFromImportedFile from './createDataResourceFromImported
14 import type {DataResource} from './createDataResourceFromImportedFile';
15
16 export type Context = {|
17 + clearSchedulingProfilerData: () => void,
18 importSchedulingProfilerData: (file: File) => void,
19 schedulingProfilerData: DataResource | null,
20 |};
@@ -33,6 +34,10 @@ function SchedulingProfilerContextController({children}: Props) {
34 setSchedulingProfilerData,
35 ] = useState<DataResource | null>(null);
36
37 + const clearSchedulingProfilerData = useCallback(() => {
38 + setSchedulingProfilerData(null);
39 + }, []);
40 +
41 const importSchedulingProfilerData = useCallback((file: File) => {
42 setSchedulingProfilerData(createDataResourceFromImportedFile(file));
43 }, []);
@@ -41,11 +46,13 @@ function SchedulingProfilerContextController({children}: Props) {
46
47 const value = useMemo(
48 () => ({
49 + clearSchedulingProfilerData,
50 importSchedulingProfilerData,
51 schedulingProfilerData,
52 // TODO (scheduling profiler)
53 }),
54 [
55 + clearSchedulingProfilerData,
56 importSchedulingProfilerData,
57 schedulingProfilerData,
58 // TODO (scheduling profiler)
packages/react-devtools-scheduling-profiler/src/content-views/ComponentMeasuresView.js new
+222
@@ -0,0 +1,222 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactComponentMeasure, ReactProfilerData} from '../types';
11 +import type {
12 + Interaction,
13 + IntrinsicSize,
14 + MouseMoveInteraction,
15 + Rect,
16 + ViewRefs,
17 +} from '../view-base';
18 +
19 +import {
20 + durationToWidth,
21 + positioningScaleFactor,
22 + positionToTimestamp,
23 + timestampToPosition,
24 +} from './utils/positioning';
25 +import {drawText} from './utils/text';
26 +import {formatDuration} from '../utils/formatting';
27 +import {
28 + View,
29 + Surface,
30 + rectContainsPoint,
31 + rectIntersectsRect,
32 + intersectionOfRects,
33 +} from '../view-base';
34 +import {COLORS, NATIVE_EVENT_HEIGHT, BORDER_SIZE} from './constants';
35 +
36 +const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
37 +
38 +export class ComponentMeasuresView extends View {
39 + _hoveredComponentMeasure: ReactComponentMeasure | null = null;
40 + _intrinsicSize: IntrinsicSize;
41 + _profilerData: ReactProfilerData;
42 +
43 + onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
44 +
45 + constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
46 + super(surface, frame);
47 +
48 + this._profilerData = profilerData;
49 +
50 + this._intrinsicSize = {
51 + width: profilerData.duration,
52 + height: ROW_WITH_BORDER_HEIGHT,
53 + };
54 + }
55 +
56 + desiredSize() {
57 + return this._intrinsicSize;
58 + }
59 +
60 + setHoveredEvent(hoveredEvent: ReactComponentMeasure | null) {
61 + if (this._hoveredComponentMeasure === hoveredEvent) {
62 + return;
63 + }
64 + this._hoveredComponentMeasure = hoveredEvent;
65 + this.setNeedsDisplay();
66 + }
67 +
68 + /**
69 + * Draw a single `ReactComponentMeasure` as a box/span with text inside of it.
70 + */
71 + _drawSingleReactComponentMeasure(
72 + context: CanvasRenderingContext2D,
73 + rect: Rect,
74 + componentMeasure: ReactComponentMeasure,
75 + scaleFactor: number,
76 + showHoverHighlight: boolean,
77 + ): boolean {
78 + const {frame} = this;
79 + const {componentName, duration, timestamp, warning} = componentMeasure;
80 +
81 + const xStart = timestampToPosition(timestamp, scaleFactor, frame);
82 + const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
83 + const componentMeasureRect: Rect = {
84 + origin: {
85 + x: xStart,
86 + y: frame.origin.y,
87 + },
88 + size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
89 + };
90 + if (!rectIntersectsRect(componentMeasureRect, rect)) {
91 + return false; // Not in view
92 + }
93 +
94 + const width = durationToWidth(duration, scaleFactor);
95 + if (width < 1) {
96 + return false; // Too small to render at this zoom level
97 + }
98 +
99 + const drawableRect = intersectionOfRects(componentMeasureRect, rect);
100 + context.beginPath();
101 + if (warning !== null) {
102 + context.fillStyle = showHoverHighlight
103 + ? COLORS.WARNING_BACKGROUND_HOVER
104 + : COLORS.WARNING_BACKGROUND;
105 + } else {
106 + context.fillStyle = showHoverHighlight
107 + ? COLORS.REACT_COMPONENT_MEASURE_HOVER
108 + : COLORS.REACT_COMPONENT_MEASURE;
109 + }
110 + context.fillRect(
111 + drawableRect.origin.x,
112 + drawableRect.origin.y,
113 + drawableRect.size.width,
114 + drawableRect.size.height,
115 + );
116 +
117 + const label = `${componentName} rendered - ${formatDuration(duration)}`;
118 +
119 + drawText(label, context, componentMeasureRect, drawableRect);
120 +
121 + return true;
122 + }
123 +
124 + draw(context: CanvasRenderingContext2D) {
125 + const {
126 + frame,
127 + _profilerData: {componentMeasures},
128 + _hoveredComponentMeasure,
129 + visibleArea,
130 + } = this;
131 +
132 + context.fillStyle = COLORS.BACKGROUND;
133 + context.fillRect(
134 + visibleArea.origin.x,
135 + visibleArea.origin.y,
136 + visibleArea.size.width,
137 + visibleArea.size.height,
138 + );
139 +
140 + // Draw events
141 + const scaleFactor = positioningScaleFactor(
142 + this._intrinsicSize.width,
143 + frame,
144 + );
145 +
146 + let didDrawMeasure = false;
147 + componentMeasures.forEach(componentMeasure => {
148 + didDrawMeasure =
149 + this._drawSingleReactComponentMeasure(
150 + context,
151 + visibleArea,
152 + componentMeasure,
153 + scaleFactor,
154 + componentMeasure === _hoveredComponentMeasure,
155 + ) || didDrawMeasure;
156 + });
157 +
158 + if (!didDrawMeasure) {
159 + drawText(
160 + '(zoom or pan to see React components)',
161 + context,
162 + visibleArea,
163 + visibleArea,
164 + 'center',
165 + COLORS.TEXT_DIM_COLOR,
166 + );
167 + }
168 +
169 + context.fillStyle = COLORS.PRIORITY_BORDER;
170 + context.fillRect(
171 + visibleArea.origin.x,
172 + visibleArea.origin.y + ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
173 + visibleArea.size.width,
174 + BORDER_SIZE,
175 + );
176 + }
177 +
178 + /**
179 + * @private
180 + */
181 + _handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
182 + const {frame, _intrinsicSize, onHover, visibleArea} = this;
183 + if (!onHover) {
184 + return;
185 + }
186 +
187 + const {location} = interaction.payload;
188 + if (!rectContainsPoint(location, visibleArea)) {
189 + onHover(null);
190 + return;
191 + }
192 +
193 + const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
194 + const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
195 +
196 + const componentMeasures = this._profilerData.componentMeasures;
197 + for (let index = componentMeasures.length - 1; index >= 0; index--) {
198 + const componentMeasure = componentMeasures[index];
199 + const {duration, timestamp} = componentMeasure;
200 +
201 + if (
202 + hoverTimestamp >= timestamp &&
203 + hoverTimestamp <= timestamp + duration
204 + ) {
205 + this.currentCursor = 'context-menu';
206 + viewRefs.hoveredView = this;
207 + onHover(componentMeasure);
208 + return;
209 + }
210 + }
211 +
212 + onHover(null);
213 + }
214 +
215 + handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
216 + switch (interaction.type) {
217 + case 'mousemove':
218 + this._handleMouseMove(interaction, viewRefs);
219 + break;
220 + }
221 + }
222 +}
packages/react-devtools-scheduling-profiler/src/content-views/FlamechartView.js
+24 -2
@@ -128,7 +128,7 @@ class FlamechartStackLayerView extends View {
128 visibleArea,
129 } = this;
130
131 - context.fillStyle = COLORS.BACKGROUND;
131 + context.fillStyle = COLORS.PRIORITY_BACKGROUND;
132 context.fillRect(
133 visibleArea.origin.x,
134 visibleArea.origin.y,
@@ -172,7 +172,29 @@ class FlamechartStackLayerView extends View {
172 drawableRect.size.height,
173 );
174
175 - drawText(name, context, nodeRect, drawableRect, width);
175 + drawText(name, context, nodeRect, drawableRect);
176 + }
177 +
178 + // Render bottom border.
179 + const borderFrame: Rect = {
180 + origin: {
181 + x: frame.origin.x,
182 + y: frame.origin.y + FLAMECHART_FRAME_HEIGHT - BORDER_SIZE,
183 + },
184 + size: {
185 + width: frame.size.width,
186 + height: BORDER_SIZE,
187 + },
188 + };
189 + if (rectIntersectsRect(borderFrame, visibleArea)) {
190 + const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
191 + context.fillStyle = COLORS.PRIORITY_BORDER;
192 + context.fillRect(
193 + borderDrawableRect.origin.x,
194 + borderDrawableRect.origin.y,
195 + borderDrawableRect.size.width,
196 + borderDrawableRect.size.height,
197 + );
198 }
199 }
200
packages/react-devtools-scheduling-profiler/src/content-views/NativeEventsView.js
+1 -1
@@ -143,7 +143,7 @@ export class NativeEventsView extends View {
143
144 const label = `${type} - ${formatDuration(duration)}`;
145
146 - drawText(label, context, eventRect, drawableRect, width);
146 + drawText(label, context, eventRect, drawableRect);
147 }
148
149 draw(context: CanvasRenderingContext2D) {
packages/react-devtools-scheduling-profiler/src/content-views/SuspenseEventsView.js
+1 -1
@@ -231,7 +231,7 @@ export class SuspenseEventsView extends View {
231 label += ` - ${formatDuration(duration)}`;
232 }
233
234 - drawText(label, context, eventRect, drawableRect, width);
234 + drawText(label, context, eventRect, drawableRect);
235 }
236 }
237
packages/react-devtools-scheduling-profiler/src/content-views/constants.js
+12
@@ -50,6 +50,8 @@ export let COLORS = {
50 PRIORITY_LABEL: '',
51 USER_TIMING: '',
52 USER_TIMING_HOVER: '',
53 + REACT_COMPONENT_MEASURE: '',
54 + REACT_COMPONENT_MEASURE_HOVER: '',
55 REACT_IDLE: '',
56 REACT_IDLE_HOVER: '',
57 REACT_RENDER: '',
@@ -75,6 +77,7 @@ export let COLORS = {
77 REACT_WORK_BORDER: '',
78 SCROLL_CARET: '',
79 TEXT_COLOR: '',
80 + TEXT_DIM_COLOR: '',
81 TIME_MARKER_LABEL: '',
82 WARNING_BACKGROUND: '',
83 WARNING_BACKGROUND_HOVER: '',
@@ -111,6 +114,12 @@ export function updateColorsToMatchTheme(element: Element): boolean {
114 USER_TIMING_HOVER: computedStyle.getPropertyValue(
115 '--color-scheduling-profiler-user-timing-hover',
116 ),
117 + REACT_COMPONENT_MEASURE: computedStyle.getPropertyValue(
118 + '--color-scheduling-profiler-react-render',
119 + ),
120 + REACT_COMPONENT_MEASURE_HOVER: computedStyle.getPropertyValue(
121 + '--color-scheduling-profiler-react-render-hover',
122 + ),
123 REACT_IDLE: computedStyle.getPropertyValue(
124 '--color-scheduling-profiler-react-idle',
125 ),
@@ -182,6 +191,9 @@ export function updateColorsToMatchTheme(element: Element): boolean {
191 TEXT_COLOR: computedStyle.getPropertyValue(
192 '--color-scheduling-profiler-text-color',
193 ),
194 + TEXT_DIM_COLOR: computedStyle.getPropertyValue(
195 + '--color-scheduling-profiler-text-dim-color',
196 + ),
197 TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
198 WARNING_BACKGROUND: computedStyle.getPropertyValue(
199 '--color-warning-background',
packages/react-devtools-scheduling-profiler/src/content-views/index.js
+1
@@ -7,6 +7,7 @@
7 * @flow
8 */
9
10 +export * from './ComponentMeasuresView';
11 export * from './FlamechartView';
12 export * from './NativeEventsView';
13 export * from './ReactMeasuresView';
packages/react-devtools-scheduling-profiler/src/content-views/utils/text.js
+3 -4
@@ -41,11 +41,10 @@ export function drawText(
41 context: CanvasRenderingContext2D,
42 fullRect: Rect,
43 drawableRect: Rect,
44 - availableWidth: number,
44 textAlign: 'left' | 'center' = 'left',
45 fillStyle: string = COLORS.TEXT_COLOR,
46 ): void {
48 - if (availableWidth > TEXT_PADDING * 2) {
47 + if (fullRect.size.width > TEXT_PADDING * 2) {
48 context.textAlign = textAlign;
49 context.textBaseline = 'middle';
50 context.font = `${FONT_SIZE}px sans-serif`;
@@ -55,7 +54,7 @@ export function drawText(
54 const trimmedName = trimText(
55 context,
56 text,
58 - availableWidth - TEXT_PADDING * 2 + (x < 0 ? x : 0),
57 + fullRect.size.width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
58 );
59
60 if (trimmedName !== null) {
@@ -81,7 +80,7 @@ export function drawText(
80
81 let textX;
82 if (textAlign === 'center') {
84 - textX = x + availableWidth / 2 + TEXT_PADDING - (x < 0 ? x : 0);
83 + textX = x + fullRect.size.width / 2 + TEXT_PADDING - (x < 0 ? x : 0);
84 } else {
85 textX = x + TEXT_PADDING - (x < 0 ? x : 0);
86 }
packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js
+31 -14
@@ -203,6 +203,7 @@ describe(preprocessData, () => {
203 });
204
205 expect(preprocessData([cpuProfilerSample, randomSample])).toStrictEqual({
206 + componentMeasures: [],
207 duration: 0.002,
208 flamechart: [],
209 measures: [],
@@ -259,6 +260,7 @@ describe(preprocessData, () => {
260 }),
261 ]),
262 ).toStrictEqual({
263 + componentMeasures: [],
264 duration: 0.008,
265 flamechart: [],
266 measures: [
@@ -323,6 +325,7 @@ describe(preprocessData, () => {
325
326 const userTimingData = createUserTimingData(clearedMarks);
327 expect(preprocessData(userTimingData)).toStrictEqual({
328 + componentMeasures: [],
329 duration: 0.011,
330 flamechart: [],
331 measures: [
@@ -405,13 +408,27 @@ describe(preprocessData, () => {
408
409 const userTimingData = createUserTimingData(clearedMarks);
410 expect(preprocessData(userTimingData)).toStrictEqual({
408 - duration: 0.022,
411 + componentMeasures: [
412 + {
413 + componentName: 'App',
414 + duration: 0.001,
415 + timestamp: 0.007,
416 + warning: null,
417 + },
418 + {
419 + componentName: 'App',
420 + duration: 0.0010000000000000009,
421 + timestamp: 0.018,
422 + warning: null,
423 + },
424 + ],
425 + duration: 0.026,
426 flamechart: [],
427 measures: [
428 {
429 batchUID: 0,
430 depth: 0,
414 - duration: 0.004999999999999999,
431 + duration: 0.006999999999999999,
432 laneLabels: ['Default'],
433 lanes: [4],
434 timestamp: 0.006,
@@ -420,7 +437,7 @@ describe(preprocessData, () => {
437 {
438 batchUID: 0,
439 depth: 0,
423 - duration: 0.001,
440 + duration: 0.002999999999999999,
441 laneLabels: ['Default'],
442 lanes: [4],
443 timestamp: 0.006,
@@ -432,7 +449,7 @@ describe(preprocessData, () => {
449 duration: 0.002999999999999999,
450 laneLabels: ['Default'],
451 lanes: [4],
435 - timestamp: 0.008,
452 + timestamp: 0.01,
453 type: 'commit',
454 },
455 {
@@ -441,7 +458,7 @@ describe(preprocessData, () => {
458 duration: 0.0010000000000000009,
459 laneLabels: ['Default'],
460 lanes: [4],
444 - timestamp: 0.009,
461 + timestamp: 0.011,
462 type: 'layout-effects',
463 },
464 {
@@ -450,25 +467,25 @@ describe(preprocessData, () => {
467 duration: 0.002,
468 laneLabels: ['Default'],
469 lanes: [4],
453 - timestamp: 0.012,
470 + timestamp: 0.014,
471 type: 'passive-effects',
472 },
473 {
474 batchUID: 1,
475 depth: 0,
459 - duration: 0.005000000000000001,
476 + duration: 0.006999999999999999,
477 laneLabels: ['Default'],
478 lanes: [4],
462 - timestamp: 0.015,
479 + timestamp: 0.017,
480 type: 'render-idle',
481 },
482 {
483 batchUID: 1,
484 depth: 0,
468 - duration: 0.0010000000000000009,
485 + duration: 0.002999999999999999,
486 laneLabels: ['Default'],
487 lanes: [4],
471 - timestamp: 0.015,
488 + timestamp: 0.017,
489 type: 'render',
490 },
491 {
@@ -477,7 +494,7 @@ describe(preprocessData, () => {
494 duration: 0.002999999999999999,
495 laneLabels: ['Default'],
496 lanes: [4],
480 - timestamp: 0.017,
497 + timestamp: 0.021,
498 type: 'commit',
499 },
500 {
@@ -486,7 +503,7 @@ describe(preprocessData, () => {
503 duration: 0.0010000000000000009,
504 laneLabels: ['Default'],
505 lanes: [4],
489 - timestamp: 0.018,
506 + timestamp: 0.022,
507 type: 'layout-effects',
508 },
509 {
@@ -495,7 +512,7 @@ describe(preprocessData, () => {
512 duration: 0.0009999999999999974,
513 laneLabels: ['Default'],
514 lanes: [4],
498 - timestamp: 0.021,
515 + timestamp: 0.025,
516 type: 'passive-effects',
517 },
518 ],
@@ -522,7 +539,7 @@ describe(preprocessData, () => {
539 componentName: 'App',
540 laneLabels: ['Default'],
541 lanes: [4],
525 - timestamp: 0.013,
542 + timestamp: 0.015,
543 type: 'schedule-state-update',
544 warning: null,
545 },
packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js
+30 -2
@@ -18,6 +18,7 @@ import type {
18 Flamechart,
19 NativeEvent,
20 ReactLane,
21 + ReactComponentMeasure,
22 ReactMeasureType,
23 ReactProfilerData,
24 SuspenseEvent,
@@ -36,6 +37,7 @@ type MeasureStackElement = {|
37
38 type ProcessorState = {|
39 batchUID: BatchUID,
40 + currentReactComponentMeasure: ReactComponentMeasure | null,
41 measureStack: MeasureStackElement[],
42 nativeEventStack: NativeEvent[],
43 nextRenderShouldGenerateNewBatchID: boolean,
@@ -240,8 +242,32 @@ function processTimelineEvent(
242 case 'blink.user_timing':
243 const startTime = (ts - currentProfilerData.startTime) / 1000;
244
243 - // React Events - schedule
244 - if (name.startsWith('--schedule-render-')) {
245 + if (name.startsWith('--component-render-start-')) {
246 + const [componentName] = name.substr(25).split('-');
247 +
248 + if (state.currentReactComponentMeasure !== null) {
249 + console.error(
250 + 'Render started while another render in progress:',
251 + state.currentReactComponentMeasure,
252 + );
253 + }
254 +
255 + state.currentReactComponentMeasure = {
256 + componentName,
257 + timestamp: startTime,
258 + duration: 0,
259 + warning: null,
260 + };
261 + } else if (name === '--component-render-stop') {
262 + if (state.currentReactComponentMeasure !== null) {
263 + const componentMeasure = state.currentReactComponentMeasure;
264 + componentMeasure.duration = startTime - componentMeasure.timestamp;
265 +
266 + state.currentReactComponentMeasure = null;
267 +
268 + currentProfilerData.componentMeasures.push(componentMeasure);
269 + }
270 + } else if (name.startsWith('--schedule-render-')) {
271 const [laneBitmaskString, laneLabels] = name.substr(18).split('-');
272 currentProfilerData.schedulingEvents.push({
273 type: 'schedule-render',
@@ -581,6 +607,7 @@ export default function preprocessData(
607 const flamechart = preprocessFlamechart(timeline);
608
609 const profilerData: ReactProfilerData = {
610 + componentMeasures: [],
611 duration: 0,
612 flamechart,
613 measures: [],
@@ -619,6 +646,7 @@ export default function preprocessData(
646
647 const state: ProcessorState = {
648 batchUID: 0,
649 + currentReactComponentMeasure: null,
650 measureStack: [],
651 nativeEventStack: [],
652 nextRenderShouldGenerateNewBatchID: true,
packages/react-devtools-scheduling-profiler/src/types.js
+9
@@ -91,6 +91,13 @@ export type ReactMeasure = {|
91 +depth: number,
92 |};
93
94 +export type ReactComponentMeasure = {|
95 + +componentName: string,
96 + duration: Milliseconds,
97 + +timestamp: Milliseconds,
98 + warning: string | null,
99 +|};
100 +
101 /**
102 * A flamechart stack frame belonging to a stack trace.
103 */
@@ -117,6 +124,7 @@ export type FlamechartStackLayer = FlamechartStackFrame[];
124 export type Flamechart = FlamechartStackLayer[];
125
126 export type ReactProfilerData = {|
127 + componentMeasures: ReactComponentMeasure[],
128 duration: number,
129 flamechart: Flamechart,
130 measures: ReactMeasure[],
@@ -128,6 +136,7 @@ export type ReactProfilerData = {|
136 |};
137
138 export type ReactHoverContextInfo = {|
139 + componentMeasure: ReactComponentMeasure | null,
140 data: $ReadOnly<ReactProfilerData> | null,
141 flamechartStackFrame: FlamechartStackFrame | null,
142 measure: ReactMeasure | null,
packages/react-devtools-scheduling-profiler/src/view-base/ResizableView.js
-1
@@ -110,7 +110,6 @@ class ResizeBar extends View {
110 context,
111 labelRect,
112 drawableRect,
113 - visibleArea.size.width,
113 'center',
114 COLORS.REACT_RESIZE_BAR_DOT,
115 );
packages/react-devtools-shared/src/constants.js
+3 -1
@@ -164,6 +164,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
164 '--color-scheduling-profiler-react-suspense-unresolved': '#c9cacd',
165 '--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
166 '--color-scheduling-profiler-text-color': '#000000',
167 + '--color-scheduling-profiler-text-dim-color': '#ccc',
168 '--color-scheduling-profiler-react-work-border': '#ffffff',
169 '--color-search-match': 'yellow',
170 '--color-search-match-current': '#f7923b',
@@ -293,7 +294,8 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
294 '--color-scheduling-profiler-react-suspense-resolved-hover': '#89d281',
295 '--color-scheduling-profiler-react-suspense-unresolved': '#c9cacd',
296 '--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
296 - '--color-scheduling-profiler-text-color': '#000000',
297 + '--color-scheduling-profiler-text-color': '#282c34',
298 + '--color-scheduling-profiler-text-dim-color': '#555b66',
299 '--color-scheduling-profiler-react-work-border': '#ffffff',
300 '--color-search-match': 'yellow',
301 '--color-search-match-current': '#f7923b',
packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js
+23 -4
@@ -8,22 +8,41 @@
8 */
9
10 import * as React from 'react';
11 -import {useCallback, useContext} from 'react';
11 +import {useContext} from 'react';
12 import {ProfilerContext} from './ProfilerContext';
13 import Button from '../Button';
14 import ButtonIcon from '../ButtonIcon';
15 import {StoreContext} from '../context';
16 +import {SchedulingProfilerContext} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
17
18 export default function ClearProfilingDataButton() {
19 const store = useContext(StoreContext);
19 - const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
20 + const {didRecordCommits, isProfiling, selectedTabID} = useContext(
21 + ProfilerContext,
22 + );
23 + const {clearSchedulingProfilerData, schedulingProfilerData} = useContext(
24 + SchedulingProfilerContext,
25 + );
26 const {profilerStore} = store;
27
22 - const clear = useCallback(() => profilerStore.clear(), [profilerStore]);
28 + let doesHaveData = false;
29 + if (selectedTabID === 'scheduling-profiler') {
30 + doesHaveData = schedulingProfilerData !== null;
31 + } else {
32 + doesHaveData = didRecordCommits;
33 + }
34 +
35 + const clear = () => {
36 + if (selectedTabID === 'scheduling-profiler') {
37 + clearSchedulingProfilerData();
38 + } else {
39 + profilerStore.clear();
40 + }
41 + };
42
43 return (
44 <Button
26 - disabled={isProfiling || !didRecordCommits}
45 + disabled={isProfiling || !doesHaveData}
46 onClick={clear}
47 title="Clear profiling data">
48 <ButtonIcon type="clear" />
packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js
+10 -2
@@ -103,8 +103,16 @@ function Profiler(_: {||}) {
103 <div className={styles.Profiler}>
104 <div className={styles.LeftColumn}>
105 <div className={styles.Toolbar}>
106 - <RecordToggle disabled={!supportsProfiling} />
107 - <ReloadAndProfileButton />
106 + <RecordToggle
107 + disabled={
108 + !supportsProfiling || selectedTabID === 'scheduling-profiler'
109 + }
110 + />
111 + <ReloadAndProfileButton
112 + disabled={
113 + selectedTabID === 'scheduling-profiler' || !supportsProfiling
114 + }
115 + />
116 <ClearProfilingDataButton />
117 <ProfilingImportExportButtons />
118 <div className={styles.VRule} />
packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js
+6 -2
@@ -19,7 +19,11 @@ type SubscriptionData = {|
19 supportsReloadAndProfile: boolean,
20 |};
21
22 -export default function ReloadAndProfileButton() {
22 +export default function ReloadAndProfileButton({
23 + disabled,
24 +}: {|
25 + disabled: boolean,
26 +|}) {
27 const bridge = useContext(BridgeContext);
28 const store = useContext(StoreContext);
29
@@ -61,7 +65,7 @@ export default function ReloadAndProfileButton() {
65
66 return (
67 <Button
64 - disabled={!store.supportsProfiling}
68 + disabled={disabled}
69 onClick={reloadAndProfile}
70 title="Reload and start profiling">
71 <ButtonIcon type="reload" />
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+36 -1
@@ -31,7 +31,10 @@ import type {
31 import type {UpdateQueue} from './ReactUpdateQueue.new';
32
33 import checkPropTypes from 'shared/checkPropTypes';
34 -
34 +import {
35 + markComponentRenderStarted,
36 + markComponentRenderStopped,
37 +} from './SchedulingProfiler';
38 import {
39 IndeterminateComponent,
40 FunctionComponent,
@@ -85,6 +88,7 @@ import {
88 enableCache,
89 enableLazyContextPropagation,
90 enableSuspenseLayoutEffectSemantics,
91 + enableSchedulingProfiler,
92 } from 'shared/ReactFeatureFlags';
93 import invariant from 'shared/invariant';
94 import isArray from 'shared/isArray';
@@ -357,6 +361,9 @@ function updateForwardRef(
361 // The rest is a fork of updateFunctionComponent
362 let nextChildren;
363 prepareToReadContext(workInProgress, renderLanes);
364 + if (enableSchedulingProfiler) {
365 + markComponentRenderStarted(workInProgress);
366 + }
367 if (__DEV__) {
368 ReactCurrentOwner.current = workInProgress;
369 setIsRendering(true);
@@ -397,6 +404,9 @@ function updateForwardRef(
404 renderLanes,
405 );
406 }
407 + if (enableSchedulingProfiler) {
408 + markComponentRenderStopped();
409 + }
410
411 if (current !== null && !didReceiveUpdate) {
412 bailoutHooks(current, workInProgress, renderLanes);
@@ -958,6 +968,9 @@ function updateFunctionComponent(
968
969 let nextChildren;
970 prepareToReadContext(workInProgress, renderLanes);
971 + if (enableSchedulingProfiler) {
972 + markComponentRenderStarted(workInProgress);
973 + }
974 if (__DEV__) {
975 ReactCurrentOwner.current = workInProgress;
976 setIsRendering(true);
@@ -998,6 +1011,9 @@ function updateFunctionComponent(
1011 renderLanes,
1012 );
1013 }
1014 + if (enableSchedulingProfiler) {
1015 + markComponentRenderStopped();
1016 + }
1017
1018 if (current !== null && !didReceiveUpdate) {
1019 bailoutHooks(current, workInProgress, renderLanes);
@@ -1177,6 +1193,9 @@ function finishClassComponent(
1193 stopProfilerTimerIfRunning(workInProgress);
1194 }
1195 } else {
1196 + if (enableSchedulingProfiler) {
1197 + markComponentRenderStarted(workInProgress);
1198 + }
1199 if (__DEV__) {
1200 setIsRendering(true);
1201 nextChildren = instance.render();
@@ -1195,6 +1214,9 @@ function finishClassComponent(
1214 } else {
1215 nextChildren = instance.render();
1216 }
1217 + if (enableSchedulingProfiler) {
1218 + markComponentRenderStopped();
1219 + }
1220 }
1221
1222 // React DevTools reads this flag.
@@ -1567,6 +1589,9 @@ function mountIndeterminateComponent(
1589 prepareToReadContext(workInProgress, renderLanes);
1590 let value;
1591
1592 + if (enableSchedulingProfiler) {
1593 + markComponentRenderStarted(workInProgress);
1594 + }
1595 if (__DEV__) {
1596 if (
1597 Component.prototype &&
@@ -1610,6 +1635,10 @@ function mountIndeterminateComponent(
1635 renderLanes,
1636 );
1637 }
1638 + if (enableSchedulingProfiler) {
1639 + markComponentRenderStopped();
1640 + }
1641 +
1642 // React DevTools reads this flag.
1643 workInProgress.flags |= PerformedWork;
1644
@@ -3195,6 +3224,9 @@ function updateContextConsumer(
3224
3225 prepareToReadContext(workInProgress, renderLanes);
3226 const newValue = readContext(context);
3227 + if (enableSchedulingProfiler) {
3228 + markComponentRenderStarted(workInProgress);
3229 + }
3230 let newChildren;
3231 if (__DEV__) {
3232 ReactCurrentOwner.current = workInProgress;
@@ -3204,6 +3236,9 @@ function updateContextConsumer(
3236 } else {
3237 newChildren = render(newValue);
3238 }
3239 + if (enableSchedulingProfiler) {
3240 + markComponentRenderStopped();
3241 + }
3242
3243 // React DevTools reads this flag.
3244 workInProgress.flags |= PerformedWork;
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+36 -1
@@ -31,7 +31,10 @@ import type {
31 import type {UpdateQueue} from './ReactUpdateQueue.old';
32
33 import checkPropTypes from 'shared/checkPropTypes';
34 -
34 +import {
35 + markComponentRenderStarted,
36 + markComponentRenderStopped,
37 +} from './SchedulingProfiler';
38 import {
39 IndeterminateComponent,
40 FunctionComponent,
@@ -85,6 +88,7 @@ import {
88 enableCache,
89 enableLazyContextPropagation,
90 enableSuspenseLayoutEffectSemantics,
91 + enableSchedulingProfiler,
92 } from 'shared/ReactFeatureFlags';
93 import invariant from 'shared/invariant';
94 import isArray from 'shared/isArray';
@@ -357,6 +361,9 @@ function updateForwardRef(
361 // The rest is a fork of updateFunctionComponent
362 let nextChildren;
363 prepareToReadContext(workInProgress, renderLanes);
364 + if (enableSchedulingProfiler) {
365 + markComponentRenderStarted(workInProgress);
366 + }
367 if (__DEV__) {
368 ReactCurrentOwner.current = workInProgress;
369 setIsRendering(true);
@@ -397,6 +404,9 @@ function updateForwardRef(
404 renderLanes,
405 );
406 }
407 + if (enableSchedulingProfiler) {
408 + markComponentRenderStopped();
409 + }
410
411 if (current !== null && !didReceiveUpdate) {
412 bailoutHooks(current, workInProgress, renderLanes);
@@ -958,6 +968,9 @@ function updateFunctionComponent(
968
969 let nextChildren;
970 prepareToReadContext(workInProgress, renderLanes);
971 + if (enableSchedulingProfiler) {
972 + markComponentRenderStarted(workInProgress);
973 + }
974 if (__DEV__) {
975 ReactCurrentOwner.current = workInProgress;
976 setIsRendering(true);
@@ -998,6 +1011,9 @@ function updateFunctionComponent(
1011 renderLanes,
1012 );
1013 }
1014 + if (enableSchedulingProfiler) {
1015 + markComponentRenderStopped();
1016 + }
1017
1018 if (current !== null && !didReceiveUpdate) {
1019 bailoutHooks(current, workInProgress, renderLanes);
@@ -1177,6 +1193,9 @@ function finishClassComponent(
1193 stopProfilerTimerIfRunning(workInProgress);
1194 }
1195 } else {
1196 + if (enableSchedulingProfiler) {
1197 + markComponentRenderStarted(workInProgress);
1198 + }
1199 if (__DEV__) {
1200 setIsRendering(true);
1201 nextChildren = instance.render();
@@ -1195,6 +1214,9 @@ function finishClassComponent(
1214 } else {
1215 nextChildren = instance.render();
1216 }
1217 + if (enableSchedulingProfiler) {
1218 + markComponentRenderStopped();
1219 + }
1220 }
1221
1222 // React DevTools reads this flag.
@@ -1567,6 +1589,9 @@ function mountIndeterminateComponent(
1589 prepareToReadContext(workInProgress, renderLanes);
1590 let value;
1591
1592 + if (enableSchedulingProfiler) {
1593 + markComponentRenderStarted(workInProgress);
1594 + }
1595 if (__DEV__) {
1596 if (
1597 Component.prototype &&
@@ -1610,6 +1635,10 @@ function mountIndeterminateComponent(
1635 renderLanes,
1636 );
1637 }
1638 + if (enableSchedulingProfiler) {
1639 + markComponentRenderStopped();
1640 + }
1641 +
1642 // React DevTools reads this flag.
1643 workInProgress.flags |= PerformedWork;
1644
@@ -3195,6 +3224,9 @@ function updateContextConsumer(
3224
3225 prepareToReadContext(workInProgress, renderLanes);
3226 const newValue = readContext(context);
3227 + if (enableSchedulingProfiler) {
3228 + markComponentRenderStarted(workInProgress);
3229 + }
3230 let newChildren;
3231 if (__DEV__) {
3232 ReactCurrentOwner.current = workInProgress;
@@ -3204,6 +3236,9 @@ function updateContextConsumer(
3236 } else {
3237 newChildren = render(newValue);
3238 }
3239 + if (enableSchedulingProfiler) {
3240 + markComponentRenderStopped();
3241 + }
3242
3243 // React DevTools reads this flag.
3244 workInProgress.flags |= PerformedWork;
packages/react-reconciler/src/ReactFiberThrow.new.js
+5 -1
@@ -70,7 +70,10 @@ import {
70 import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.new';
71 import {logCapturedError} from './ReactFiberErrorLogger';
72 import {logComponentSuspended} from './DebugTracing';
73 -import {markComponentSuspended} from './SchedulingProfiler';
73 +import {
74 + markComponentRenderStopped,
75 + markComponentSuspended,
76 +} from './SchedulingProfiler';
77 import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
78 import {
79 SyncLane,
@@ -244,6 +247,7 @@ function throwException(
247 }
248
249 if (enableSchedulingProfiler) {
250 + markComponentRenderStopped();
251 markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
252 }
253
packages/react-reconciler/src/ReactFiberThrow.old.js
+5 -1
@@ -70,7 +70,10 @@ import {
70 import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.old';
71 import {logCapturedError} from './ReactFiberErrorLogger';
72 import {logComponentSuspended} from './DebugTracing';
73 -import {markComponentSuspended} from './SchedulingProfiler';
73 +import {
74 + markComponentRenderStopped,
75 + markComponentSuspended,
76 +} from './SchedulingProfiler';
77 import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
78 import {
79 SyncLane,
@@ -244,6 +247,7 @@ function throwException(
247 }
248
249 if (enableSchedulingProfiler) {
250 + markComponentRenderStopped();
251 markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
252 }
253
packages/react-reconciler/src/SchedulingProfiler.js
+17
@@ -97,6 +97,23 @@ export function markCommitStopped(): void {
97 }
98 }
99
100 +export function markComponentRenderStarted(fiber: Fiber): void {
101 + if (enableSchedulingProfiler) {
102 + if (supportsUserTimingV3) {
103 + const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
104 + markAndClear(`--component-render-start-${componentName}`);
105 + }
106 + }
107 +}
108 +
109 +export function markComponentRenderStopped(): void {
110 + if (enableSchedulingProfiler) {
111 + if (supportsUserTimingV3) {
112 + markAndClear('--component-render-stop');
113 + }
114 + }
115 +}
116 +
117 const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
118
119 // $FlowFixMe: Flow cannot handle polymorphic WeakMaps
packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js
+26
@@ -174,6 +174,8 @@ describe('SchedulingProfiler', () => {
174 `--react-init-${ReactVersion}`,
175 `--schedule-render-${formatLanes(ReactFiberLane.TransitionLane1)}`,
176 `--render-start-${formatLanes(ReactFiberLane.TransitionLane1)}`,
177 + '--component-render-start-Foo',
178 + '--component-render-stop',
179 '--render-yield',
180 ]);
181 } else {
@@ -208,6 +210,8 @@ describe('SchedulingProfiler', () => {
210 `--react-init-${ReactVersion}`,
211 `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
212 `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
213 + '--component-render-start-Example',
214 + '--component-render-stop',
215 '--suspense-suspend-0-Example-mount-1-Sync',
216 '--render-stop',
217 `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
@@ -239,6 +243,8 @@ describe('SchedulingProfiler', () => {
243 `--react-init-${ReactVersion}`,
244 `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
245 `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
246 + '--component-render-start-Example',
247 + '--component-render-stop',
248 '--suspense-suspend-0-Example-mount-1-Sync',
249 '--render-stop',
250 `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
@@ -278,6 +284,8 @@ describe('SchedulingProfiler', () => {
284
285 expectMarksToEqual([
286 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
287 + '--component-render-start-Example',
288 + '--component-render-stop',
289 '--suspense-suspend-0-Example-mount-16-Default',
290 '--render-stop',
291 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
@@ -317,6 +325,8 @@ describe('SchedulingProfiler', () => {
325
326 expectMarksToEqual([
327 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
328 + '--component-render-start-Example',
329 + '--component-render-stop',
330 '--suspense-suspend-0-Example-mount-16-Default',
331 '--render-stop',
332 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
@@ -356,12 +366,16 @@ describe('SchedulingProfiler', () => {
366
367 expectMarksToEqual([
368 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
369 + '--component-render-start-Example',
370 + '--component-render-stop',
371 '--render-stop',
372 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
373 `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
374 `--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
375 '--layout-effects-stop',
376 `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
377 + '--component-render-start-Example',
378 + '--component-render-stop',
379 '--render-stop',
380 `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
381 '--commit-stop',
@@ -393,6 +407,8 @@ describe('SchedulingProfiler', () => {
407
408 expectMarksToEqual([
409 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
410 + '--component-render-start-Example',
411 + '--component-render-stop',
412 '--render-stop',
413 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
414 `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
@@ -401,6 +417,8 @@ describe('SchedulingProfiler', () => {
417 )}-Example`,
418 '--layout-effects-stop',
419 `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
420 + '--component-render-start-Example',
421 + '--component-render-stop',
422 '--render-stop',
423 `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
424 '--commit-stop',
@@ -495,12 +513,16 @@ describe('SchedulingProfiler', () => {
513
514 expectMarksToEqual([
515 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
516 + '--component-render-start-Example',
517 + '--component-render-stop',
518 '--render-stop',
519 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
520 `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
521 `--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
522 '--layout-effects-stop',
523 `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
524 + '--component-render-start-Example',
525 + '--component-render-stop',
526 '--render-stop',
527 `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
528 '--commit-stop',
@@ -528,6 +550,8 @@ describe('SchedulingProfiler', () => {
550 `--react-init-${ReactVersion}`,
551 `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
552 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
553 + '--component-render-start-Example',
554 + '--component-render-stop',
555 '--render-stop',
556 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
557 `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
@@ -539,6 +563,8 @@ describe('SchedulingProfiler', () => {
563 )}-Example`,
564 '--passive-effects-stop',
565 `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
566 + '--component-render-start-Example',
567 + '--component-render-stop',
568 '--render-stop',
569 `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
570 '--commit-stop',