@@ -22,7 +22,10 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
2222import type {UpdateQueue} from './ReactUpdateQueue.new';
2323import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
2424import type {Wakeable} from 'shared/ReactTypes';
25- import type {OffscreenState} from './ReactFiberOffscreenComponent';
25+ import type {
26+ OffscreenState,
27+ OffscreenInstance,
28+ } from './ReactFiberOffscreenComponent';
2629import type {HookFlags} from './ReactHookEffectTags';
2730import type {Cache} from './ReactFiberCacheComponent.new';
2831import type {RootState} from './ReactFiberRoot.new';
@@ -62,6 +65,7 @@ import {
6265 OffscreenComponent,
6366 LegacyHiddenComponent,
6467 CacheComponent,
68+ TracingMarkerComponent,
6569} from './ReactWorkTags';
6670import {detachDeletedInstance} from './ReactFiberHostConfig';
6771import {
@@ -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+
10691157function 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