Log Mount/Unmount/Reconnect/Disconnect in the Component Track (#32816)

Stacked on #32815.

To be able to differentiate mounted subtrees from updated subtrees. This
adds a yellow entry above the component subtree that mounted. This is
added both to the render phase, mutation effect phase, layout effect
phase and passive effect phase.

<img width="962" alt="Screenshot 2025-04-03 at 10 41 02 PM"
src="https://github.com/user-attachments/assets/13777347-07e8-458c-9127-8675ef08b54f"
/>

Ideally we could probably give an annotation to the component instead of
adding a whole other line which is also a color that's kind of
distracting. However, not all components are included and keeping track
of which one is the first one below is kind of annoying. Adding a marker
to all components is kind of noisy. So this is a compromise. It's only
one per depth so it won't make it too deep even on larger trees.

If this is an unmount, those are added to the mutation effect phase for
the layout unmounts and passive unmount effect phase. Since these never
have a render, they're not in the render phase.

<img width="1010" alt="Screenshot 2025-04-03 at 11 05 57 PM"
src="https://github.com/user-attachments/assets/ab39f27e-13be-4281-94fa-9391bb293fd2"
/>

For showing / hiding `<Activity>` the terminology "Reconnect" and
"Disconnect" is used instead.
This commit is contained in:
Sebastian Markbåge 2025-04-03 23:33:29 -04:00 committed by GitHub
parent c0f08ae74a
commit 540cd65252
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 310 additions and 29 deletions

View File

@ -132,6 +132,10 @@ import {
logComponentRender,
logComponentErrored,
logComponentEffect,
logComponentMount,
logComponentUnmount,
logComponentReappeared,
logComponentDisappeared,
} from './ReactFiberPerformanceTrack';
import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
@ -287,6 +291,25 @@ export let shouldFireAfterActiveInstanceBlur: boolean = false;
let viewTransitionContextChanged: boolean = false;
let rootViewTransitionAffected: boolean = false;
function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {
if (finishedWork.tag === SuspenseComponent) {
const prevState: SuspenseState | null = current.memoizedState;
const nextState: SuspenseState | null = finishedWork.memoizedState;
return (
prevState !== null &&
prevState.dehydrated !== null &&
(nextState === null || nextState.dehydrated === null)
);
} else if (finishedWork.tag === HostRoot) {
return (
(current.memoizedState: RootState).isDehydrated &&
(finishedWork.flags & ForceClientRender) === NoFlags
);
} else {
return false;
}
}
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
@ -735,6 +758,21 @@ function commitLayoutEffectOnFiber(
finishedWork,
includeWorkInProgressEffects,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
@ -796,16 +834,36 @@ function commitLayoutEffectOnFiber(
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectDuration > 0.05
componentEffectEndTime >= 0
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
if (componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (
// Insertion
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
const isHydration = isHydratingParent(
finishedWork.return.alternate,
finishedWork.return,
);
if (!isHydration) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
@ -1220,6 +1278,8 @@ function commitDeletionEffects(
returnFiber: Fiber,
deletedFiber: Fiber,
) {
const prevEffectStart = pushComponentEffectStart();
if (supportsMutation) {
// We only have the top Fiber that was deleted but we need to recurse down its
// children to find all the terminal nodes.
@ -1282,6 +1342,23 @@ function commitDeletionEffects(
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(deletedFiber.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentUnmount(
deletedFiber,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
detachFiberMutation(deletedFiber);
}
@ -2224,6 +2301,27 @@ function commitMutationEffectsOnFiber(
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
if (
// If this was the root of the reappear.
wasHidden &&
!isHidden &&
!prevOffscreenSubtreeIsHidden &&
!prevOffscreenSubtreeWasHidden &&
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
}
@ -2259,6 +2357,22 @@ function commitMutationEffectsOnFiber(
) {
// Disappear the layout effects of all the children
recursivelyTraverseDisappearLayoutEffects(finishedWork);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentDisappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
}
@ -2371,16 +2485,36 @@ function commitMutationEffectsOnFiber(
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectDuration > 0.05
componentEffectEndTime >= 0
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
if (componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (
// Insertion
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
const isHydration = isHydratingParent(
finishedWork.return.alternate,
finishedWork.return,
);
if (!isHydration) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
@ -3608,6 +3742,31 @@ function commitPassiveMountOnFiber(
includeWorkInProgressEffects,
endTime,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
!inHydratedSubtree
) {
// Log the reappear in the render phase.
const startTime = ((finishedWork.actualStartTime: any): number);
if (endTime - startTime > 0.05) {
logComponentReappeared(finishedWork, startTime, endTime);
}
if (
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
}
@ -3688,18 +3847,38 @@ function commitPassiveMountOnFiber(
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectDuration > 0.05
(finishedWork.mode & ProfileMode) !== NoMode
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
const isMount =
!inHydratedSubtree &&
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null;
if (isMount) {
// Log the mount in the render phase.
const startTime = ((finishedWork.actualStartTime: any): number);
if (endTime - startTime > 0.05) {
logComponentMount(finishedWork, startTime, endTime);
}
}
if (componentEffectStartTime >= 0 && componentEffectEndTime >= 0) {
if (componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (isMount && componentEffectEndTime - componentEffectStartTime > 0.05) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
@ -4244,12 +4423,29 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
const prevEffectStart = pushComponentEffectStart();
// TODO: Convert this to use recursion
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(childToDelete.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentUnmount(
childToDelete,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
}
}
detachAlternateSiblings(parentFiber);
@ -4328,7 +4524,24 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
// effects. Then if the tree reappears before the delay has elapsed, we
// can skip toggling the effects entirely.
instance._visibility &= ~OffscreenPassiveEffectsConnected;
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentDisappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraversePassiveUnmountEffects(finishedWork);
}
@ -4373,12 +4586,34 @@ function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
const prevEffectStart = pushComponentEffectStart();
// TODO: Convert this to use recursion
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(childToDelete.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
// While this is inside the disconnect path. This is a deletion within the
// disconnected tree. We currently log this for deletions in the mutation
// phase since it's shared by the disappear path.
logComponentUnmount(
childToDelete,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
}
}
detachAlternateSiblings(parentFiber);

View File

@ -123,6 +123,52 @@ export function markAllLanesInOrder() {
}
}
function logComponentTrigger(
fiber: Fiber,
startTime: number,
endTime: number,
trigger: string,
) {
if (supportsUserTiming) {
reusableComponentDevToolDetails.color = 'warning';
reusableComponentOptions.start = startTime;
reusableComponentOptions.end = endTime;
performance.measure(trigger, reusableComponentOptions);
}
}
export function logComponentMount(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Mount');
}
export function logComponentUnmount(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Unmount');
}
export function logComponentReappeared(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Reconnect');
}
export function logComponentDisappeared(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Disconnect');
}
export function logComponentRender(
fiber: Fiber,
startTime: number,