Skip to content

Commit 357a613

Browse files
authored
[DevTools][Transition Tracing] Added support for Suspense Boundaries (#23365)
This PR: * During the passive effect complete phase for Offscreen, we add all the transitions that were added to the update queue in the render phase to the transitions set on the memoziedState. We also add the stateNode for the Offscreen component to the root pendingSuspenseBoundaries map if the suspense boundary has gone from shown to fallback. We remove the stateNode if the boundary goes from fallback to shown. * During the passive effect complete phase for the HostRoot, for each transition that was initiated during this commit, we add a pending transitionStart callback. We also add them to the transition set on the memoizedState for the HostRoot. If the root pendingSuspenseBoundaries is empty, we add a pending transitionComplete callback.
1 parent c5e039d commit 357a613

File tree

3 files changed

+497
-18
lines changed

3 files changed

+497
-18
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+158-9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
2222
import type {UpdateQueue} from './ReactUpdateQueue.new';
2323
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
2424
import type {Wakeable} from 'shared/ReactTypes';
25-
import type {OffscreenState} from './ReactFiberOffscreenComponent';
25+
import type {
26+
OffscreenState,
27+
OffscreenInstance,
28+
} from './ReactFiberOffscreenComponent';
2629
import type {HookFlags} from './ReactHookEffectTags';
2730
import type {Cache} from './ReactFiberCacheComponent.new';
2831
import type {RootState} from './ReactFiberRoot.new';
@@ -62,6 +65,7 @@ import {
6265
OffscreenComponent,
6366
LegacyHiddenComponent,
6467
CacheComponent,
68+
TracingMarkerComponent,
6569
} from './ReactWorkTags';
6670
import {detachDeletedInstance} from './ReactFiberHostConfig';
6771
import {
@@ -1001,7 +1005,8 @@ function commitLayoutEffectOnFiber(
10011005
case IncompleteClassComponent:
10021006
case ScopeComponent:
10031007
case OffscreenComponent:
1004-
case LegacyHiddenComponent: {
1008+
case LegacyHiddenComponent:
1009+
case TracingMarkerComponent: {
10051010
break;
10061011
}
10071012

@@ -1066,6 +1071,89 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
10661071
}
10671072
}
10681073

1074+
function commitTransitionProgress(
1075+
finishedRoot: FiberRoot,
1076+
offscreenFiber: Fiber,
1077+
) {
1078+
if (enableTransitionTracing) {
1079+
// This function adds suspense boundaries to the root
1080+
// or tracing marker's pendingSuspenseBoundaries map.
1081+
// When a suspense boundary goes from a resolved to a fallback
1082+
// state we add the boundary to the map, and when it goes from
1083+
// a fallback to a resolved state, we remove the boundary from
1084+
// the map.
1085+
1086+
// We use stateNode on the Offscreen component as a stable object
1087+
// that doesnt change from render to render. This way we can
1088+
// distinguish between different Offscreen instances (vs. the same
1089+
// Offscreen instance with different fibers)
1090+
const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
1091+
1092+
let prevState: SuspenseState | null = null;
1093+
const previousFiber = offscreenFiber.alternate;
1094+
if (previousFiber !== null && previousFiber.memoizedState !== null) {
1095+
prevState = previousFiber.memoizedState;
1096+
}
1097+
const nextState: SuspenseState | null = offscreenFiber.memoizedState;
1098+
1099+
const wasHidden = prevState !== null;
1100+
const isHidden = nextState !== null;
1101+
1102+
const rootState: RootState = finishedRoot.current.memoizedState;
1103+
// TODO(luna) move pendingSuspenseBoundaries and transitions from
1104+
// HostRoot fiber to FiberRoot
1105+
const rootPendingBoundaries = rootState.pendingSuspenseBoundaries;
1106+
const rootTransitions = rootState.transitions;
1107+
1108+
// If there is a name on the suspense boundary, store that in
1109+
// the pending boundaries.
1110+
let name = null;
1111+
const parent = offscreenFiber.return;
1112+
if (
1113+
parent !== null &&
1114+
parent.tag === SuspenseComponent &&
1115+
parent.memoizedProps.unstable_name
1116+
) {
1117+
name = parent.memoizedProps.unstable_name;
1118+
}
1119+
1120+
if (rootPendingBoundaries !== null) {
1121+
if (previousFiber === null) {
1122+
// Initial mount
1123+
if (isHidden) {
1124+
rootPendingBoundaries.set(offscreenInstance, {
1125+
name,
1126+
});
1127+
}
1128+
} else {
1129+
if (wasHidden && !isHidden) {
1130+
// The suspense boundary went from hidden to visible. Remove
1131+
// the boundary from the pending suspense boundaries set
1132+
// if it's there
1133+
if (rootPendingBoundaries.has(offscreenInstance)) {
1134+
rootPendingBoundaries.delete(offscreenInstance);
1135+
1136+
if (rootPendingBoundaries.size === 0 && rootTransitions !== null) {
1137+
rootTransitions.forEach(transition => {
1138+
addTransitionCompleteCallbackToPendingTransition({
1139+
transitionName: transition.name,
1140+
startTime: transition.startTime,
1141+
});
1142+
});
1143+
}
1144+
}
1145+
} else if (!wasHidden && isHidden) {
1146+
// The suspense boundaries was just hidden. Add the boundary
1147+
// to the pending boundary set if it's there
1148+
rootPendingBoundaries.set(offscreenInstance, {
1149+
name,
1150+
});
1151+
}
1152+
}
1153+
}
1154+
}
1155+
}
1156+
10691157
function hideOrUnhideAllChildren(finishedWork, isHidden) {
10701158
// Only hide or unhide the top-most host nodes.
10711159
let hostSubtreeRoot = null;
@@ -2747,22 +2835,48 @@ function commitPassiveMountOnFiber(
27472835
}
27482836

27492837
if (enableTransitionTracing) {
2838+
// Get the transitions that were initiatized during the render
2839+
// and add a start transition callback for each of them
2840+
const state = finishedWork.memoizedState;
2841+
// TODO Since it's a mutable field, this should live on the FiberRoot
2842+
if (state.transitions === null) {
2843+
state.transitions = new Set([]);
2844+
}
2845+
const pendingTransitions = state.transitions;
2846+
const pendingSuspenseBoundaries = state.pendingSuspenseBoundaries;
2847+
2848+
// Initial render
27502849
if (committedTransitions !== null) {
27512850
committedTransitions.forEach(transition => {
2752-
// TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
27532851
addTransitionStartCallbackToPendingTransition({
27542852
transitionName: transition.name,
27552853
startTime: transition.startTime,
27562854
});
2855+
pendingTransitions.add(transition);
2856+
});
27572857

2758-
addTransitionCompleteCallbackToPendingTransition({
2759-
transitionName: transition.name,
2760-
startTime: transition.startTime,
2858+
if (
2859+
pendingSuspenseBoundaries === null ||
2860+
pendingSuspenseBoundaries.size === 0
2861+
) {
2862+
pendingTransitions.forEach(transition => {
2863+
addTransitionCompleteCallbackToPendingTransition({
2864+
transitionName: transition.name,
2865+
startTime: transition.startTime,
2866+
});
27612867
});
2762-
});
2868+
}
27632869

27642870
clearTransitionsForLanes(finishedRoot, committedLanes);
2765-
finishedWork.memoizedState.transitions = null;
2871+
}
2872+
2873+
// If there are no more pending suspense boundaries we
2874+
// clear the transitions because they are all complete.
2875+
if (
2876+
pendingSuspenseBoundaries === null ||
2877+
pendingSuspenseBoundaries.size === 0
2878+
) {
2879+
state.transitions = null;
27662880
}
27672881
}
27682882
break;
@@ -2800,9 +2914,44 @@ function commitPassiveMountOnFiber(
28002914
}
28012915

28022916
if (enableTransitionTracing) {
2803-
// TODO: Add code to actually process the update queue
2917+
const isFallback = finishedWork.memoizedState;
2918+
const queue = (finishedWork.updateQueue: any);
2919+
const rootMemoizedState = finishedRoot.current.memoizedState;
2920+
2921+
if (queue !== null) {
2922+
// We have one instance of the pendingSuspenseBoundaries map.
2923+
// We only need one because we update it during the commit phase.
2924+
// We instantiate a new Map if we haven't already
2925+
if (rootMemoizedState.pendingSuspenseBoundaries === null) {
2926+
rootMemoizedState.pendingSuspenseBoundaries = new Map();
2927+
}
2928+
2929+
if (isFallback) {
2930+
const transitions = queue.transitions;
2931+
let prevTransitions = finishedWork.memoizedState.transitions;
2932+
// Add all the transitions saved in the update queue during
2933+
// the render phase (ie the transitions associated with this boundary)
2934+
// into the transitions set.
2935+
if (transitions !== null) {
2936+
if (prevTransitions === null) {
2937+
// We only have one instance of the transitions set
2938+
// because we update it only during the commit phase. We
2939+
// will create the set on a as needed basis in the commit phase
2940+
finishedWork.memoizedState.transitions = prevTransitions = new Set();
2941+
}
2942+
2943+
transitions.forEach(transition => {
2944+
prevTransitions.add(transition);
2945+
});
2946+
}
2947+
}
2948+
}
2949+
2950+
commitTransitionProgress(finishedRoot, finishedWork);
2951+
28042952
finishedWork.updateQueue = null;
28052953
}
2954+
28062955
break;
28072956
}
28082957
case CacheComponent: {

0 commit comments

Comments
 (0)