Skip to content

Commit c32780e

Browse files
authored
[Fiber] Highlight hydration and offscreen render phases (facebook#31752)
This highlights the render phase as the tertiary color (green) when we're render a hydration lane or offscreen lane. I call the "Render" phase "Hydrated" instead in this case. For the offscreen case we don't currently have a differentiation between hydrated or activity. I just called that "Prepared". Even for the hydration case where there's no discovered client rendered boundaries it's more like it's preparing for an interaction rather than blocking one. Where as for the other lanes the hydration might block something. <img width="1173" alt="Screenshot 2024-12-12 at 11 23 14 PM" src="https://github.com/user-attachments/assets/49ab1508-840f-4188-a085-18fe94b14187" /> In a follow up I'd like to color the components in the Components tree green if they were hydrated but not the ones that was actually client rendered e.g. due to a mismatch or forced client rendering so you can tell the difference. Unfortunately, the current signals we have for this get reset earlier in the commit phase than when we log these. Another thing is that a failed hydration should probably be colored red even though it ends up committing successfully. I.e. a recoverable error.
1 parent d1dd7fe commit c32780e

File tree

3 files changed

+94
-17
lines changed

3 files changed

+94
-17
lines changed

packages/react-reconciler/src/ReactFiberLane.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,18 @@ export function includesTransitionLane(lanes: Lanes): boolean {
621621
return (lanes & TransitionLanes) !== NoLanes;
622622
}
623623

624+
export function includesOnlyHydrationLanes(lanes: Lanes): boolean {
625+
return (lanes & HydrationLanes) === lanes;
626+
}
627+
628+
export function includesOnlyOffscreenLanes(lanes: Lanes): boolean {
629+
return (lanes & OffscreenLane) === lanes;
630+
}
631+
632+
export function includesOnlyHydrationOrOffscreenLanes(lanes: Lanes): boolean {
633+
return (lanes & (HydrationLanes | OffscreenLane)) === lanes;
634+
}
635+
624636
export function includesBlockingLane(lanes: Lanes): boolean {
625637
const SyncDefaultLanes =
626638
InputContinuousHydrationLane |

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@
99

1010
import type {Fiber} from './ReactInternalTypes';
1111

12+
import type {Lanes} from './ReactFiberLane';
13+
1214
import getComponentNameFromFiber from './getComponentNameFromFiber';
1315

14-
import {getGroupNameOfHighestPriorityLane} from './ReactFiberLane';
16+
import {
17+
getGroupNameOfHighestPriorityLane,
18+
includesOnlyHydrationLanes,
19+
includesOnlyOffscreenLanes,
20+
includesOnlyHydrationOrOffscreenLanes,
21+
} from './ReactFiberLane';
1522

1623
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
1724

@@ -51,7 +58,7 @@ const reusableLaneOptions = {
5158
},
5259
};
5360

54-
export function setCurrentTrackFromLanes(lanes: number): void {
61+
export function setCurrentTrackFromLanes(lanes: Lanes): void {
5562
reusableLaneDevToolDetails.track = getGroupNameOfHighestPriorityLane(lanes);
5663
}
5764

@@ -223,6 +230,7 @@ export function logBlockingStart(
223230
eventType: null | string,
224231
eventIsRepeat: boolean,
225232
renderStartTime: number,
233+
lanes: Lanes,
226234
): void {
227235
if (supportsUserTiming) {
228236
reusableLaneDevToolDetails.track = 'Blocking';
@@ -240,7 +248,11 @@ export function logBlockingStart(
240248
}
241249
if (updateTime > 0) {
242250
// Log the time from when we called setState until we started rendering.
243-
reusableLaneDevToolDetails.color = 'primary-light';
251+
reusableLaneDevToolDetails.color = includesOnlyHydrationOrOffscreenLanes(
252+
lanes,
253+
)
254+
? 'tertiary-light'
255+
: 'primary-light';
244256
reusableLaneOptions.start = updateTime;
245257
reusableLaneOptions.end = renderStartTime;
246258
performance.measure('Blocked', reusableLaneOptions);
@@ -292,33 +304,65 @@ export function logTransitionStart(
292304
}
293305
}
294306

295-
export function logRenderPhase(startTime: number, endTime: number): void {
307+
export function logRenderPhase(
308+
startTime: number,
309+
endTime: number,
310+
lanes: Lanes,
311+
): void {
296312
if (supportsUserTiming) {
297-
reusableLaneDevToolDetails.color = 'primary-dark';
313+
reusableLaneDevToolDetails.color = includesOnlyHydrationOrOffscreenLanes(
314+
lanes,
315+
)
316+
? 'tertiary-dark'
317+
: 'primary-dark';
298318
reusableLaneOptions.start = startTime;
299319
reusableLaneOptions.end = endTime;
300-
performance.measure('Render', reusableLaneOptions);
320+
performance.measure(
321+
includesOnlyOffscreenLanes(lanes)
322+
? 'Prepared'
323+
: includesOnlyHydrationLanes(lanes)
324+
? 'Hydrated'
325+
: 'Render',
326+
reusableLaneOptions,
327+
);
301328
}
302329
}
303330

304331
export function logInterruptedRenderPhase(
305332
startTime: number,
306333
endTime: number,
334+
lanes: Lanes,
307335
): void {
308336
if (supportsUserTiming) {
309-
reusableLaneDevToolDetails.color = 'primary-dark';
337+
reusableLaneDevToolDetails.color = includesOnlyHydrationOrOffscreenLanes(
338+
lanes,
339+
)
340+
? 'tertiary-dark'
341+
: 'primary-dark';
310342
reusableLaneOptions.start = startTime;
311343
reusableLaneOptions.end = endTime;
312-
performance.measure('Interrupted Render', reusableLaneOptions);
344+
performance.measure(
345+
includesOnlyOffscreenLanes(lanes)
346+
? 'Prewarm'
347+
: includesOnlyHydrationLanes(lanes)
348+
? 'Interrupted Hydration'
349+
: 'Interrupted Render',
350+
reusableLaneOptions,
351+
);
313352
}
314353
}
315354

316355
export function logSuspendedRenderPhase(
317356
startTime: number,
318357
endTime: number,
358+
lanes: Lanes,
319359
): void {
320360
if (supportsUserTiming) {
321-
reusableLaneDevToolDetails.color = 'primary-dark';
361+
reusableLaneDevToolDetails.color = includesOnlyHydrationOrOffscreenLanes(
362+
lanes,
363+
)
364+
? 'tertiary-dark'
365+
: 'primary-dark';
322366
reusableLaneOptions.start = startTime;
323367
reusableLaneOptions.end = endTime;
324368
performance.measure('Prewarm', reusableLaneOptions);
@@ -328,10 +372,15 @@ export function logSuspendedRenderPhase(
328372
export function logSuspendedWithDelayPhase(
329373
startTime: number,
330374
endTime: number,
375+
lanes: Lanes,
331376
): void {
332377
// This means the render was suspended and cannot commit until it gets unblocked.
333378
if (supportsUserTiming) {
334-
reusableLaneDevToolDetails.color = 'primary-dark';
379+
reusableLaneDevToolDetails.color = includesOnlyHydrationOrOffscreenLanes(
380+
lanes,
381+
)
382+
? 'tertiary-dark'
383+
: 'primary-dark';
335384
reusableLaneOptions.start = startTime;
336385
reusableLaneOptions.end = endTime;
337386
performance.measure('Suspended', reusableLaneOptions);
@@ -341,6 +390,7 @@ export function logSuspendedWithDelayPhase(
341390
export function logErroredRenderPhase(
342391
startTime: number,
343392
endTime: number,
393+
lanes: Lanes,
344394
): void {
345395
if (supportsUserTiming) {
346396
reusableLaneDevToolDetails.color = 'error';

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,7 @@ export function performWorkOnRoot(
10351035
if (errorRetryLanes !== NoLanes) {
10361036
if (enableProfilerTimer && enableComponentPerformanceTrack) {
10371037
setCurrentTrackFromLanes(lanes);
1038-
logErroredRenderPhase(renderStartTime, renderEndTime);
1038+
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
10391039
finalizeRender(lanes, renderEndTime);
10401040
}
10411041
lanes = errorRetryLanes;
@@ -1066,7 +1066,7 @@ export function performWorkOnRoot(
10661066
if (exitStatus === RootFatalErrored) {
10671067
if (enableProfilerTimer && enableComponentPerformanceTrack) {
10681068
setCurrentTrackFromLanes(lanes);
1069-
logErroredRenderPhase(renderStartTime, renderEndTime);
1069+
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
10701070
finalizeRender(lanes, renderEndTime);
10711071
}
10721072
prepareFreshStack(root, NoLanes);
@@ -1207,7 +1207,7 @@ function finishConcurrentRender(
12071207
// until we receive more data.
12081208
if (enableProfilerTimer && enableComponentPerformanceTrack) {
12091209
setCurrentTrackFromLanes(lanes);
1210-
logSuspendedRenderPhase(renderStartTime, renderEndTime);
1210+
logSuspendedRenderPhase(renderStartTime, renderEndTime, lanes);
12111211
finalizeRender(lanes, renderEndTime);
12121212
trackSuspendedTime(lanes, renderEndTime);
12131213
}
@@ -1757,9 +1757,17 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
17571757
// then this is considered a prewarm and not an interrupted render because
17581758
// we couldn't have shown anything anyway so it's not a bad thing that we
17591759
// got interrupted.
1760-
logSuspendedRenderPhase(previousRenderStartTime, renderStartTime);
1760+
logSuspendedRenderPhase(
1761+
previousRenderStartTime,
1762+
renderStartTime,
1763+
lanes,
1764+
);
17611765
} else {
1762-
logInterruptedRenderPhase(previousRenderStartTime, renderStartTime);
1766+
logInterruptedRenderPhase(
1767+
previousRenderStartTime,
1768+
renderStartTime,
1769+
lanes,
1770+
);
17631771
}
17641772
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
17651773
}
@@ -1783,6 +1791,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
17831791
: clampedUpdateTime >= 0
17841792
? clampedUpdateTime
17851793
: renderStartTime,
1794+
lanes,
17861795
);
17871796
}
17881797
logBlockingStart(
@@ -1791,6 +1800,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
17911800
blockingEventType,
17921801
blockingEventIsRepeat,
17931802
renderStartTime,
1803+
lanes,
17941804
);
17951805
clearBlockingTimers();
17961806
}
@@ -1817,6 +1827,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
18171827
: clampedUpdateTime >= 0
18181828
? clampedUpdateTime
18191829
: renderStartTime,
1830+
lanes,
18201831
);
18211832
}
18221833
logTransitionStart(
@@ -3202,9 +3213,13 @@ function commitRootImpl(
32023213
// Log the previous render phase once we commit. I.e. we weren't interrupted.
32033214
setCurrentTrackFromLanes(lanes);
32043215
if (exitStatus === RootErrored) {
3205-
logErroredRenderPhase(completedRenderStartTime, completedRenderEndTime);
3216+
logErroredRenderPhase(
3217+
completedRenderStartTime,
3218+
completedRenderEndTime,
3219+
lanes,
3220+
);
32063221
} else {
3207-
logRenderPhase(completedRenderStartTime, completedRenderEndTime);
3222+
logRenderPhase(completedRenderStartTime, completedRenderEndTime, lanes);
32083223
}
32093224
}
32103225

0 commit comments

Comments
 (0)