@@ -22,11 +22,17 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
22
22
import type { UpdateQueue } from './ReactUpdateQueue.new' ;
23
23
import type { FunctionComponentUpdateQueue } from './ReactFiberHooks.new' ;
24
24
import type { Wakeable } from 'shared/ReactTypes' ;
25
- import type { OffscreenState } from './ReactFiberOffscreenComponent' ;
25
+ import type {
26
+ OffscreenState ,
27
+ OffscreenInstance ,
28
+ } from './ReactFiberOffscreenComponent' ;
26
29
import type { HookFlags } from './ReactHookEffectTags' ;
27
30
import type { Cache } from './ReactFiberCacheComponent.new' ;
28
31
import type { RootState } from './ReactFiberRoot.new' ;
29
- import type { Transition } from './ReactFiberTracingMarkerComponent.new' ;
32
+ import type {
33
+ Transition ,
34
+ PendingSuspenseBoundaries ,
35
+ } from './ReactFiberTracingMarkerComponent.new' ;
30
36
31
37
import {
32
38
enableCreateEventHandleAPI ,
@@ -62,6 +68,7 @@ import {
62
68
OffscreenComponent ,
63
69
LegacyHiddenComponent ,
64
70
CacheComponent ,
71
+ TracingMarkerComponent ,
65
72
} from './ReactWorkTags' ;
66
73
import { detachDeletedInstance } from './ReactFiberHostConfig' ;
67
74
import {
@@ -1001,7 +1008,8 @@ function commitLayoutEffectOnFiber(
1001
1008
case IncompleteClassComponent:
1002
1009
case ScopeComponent:
1003
1010
case OffscreenComponent:
1004
- case LegacyHiddenComponent: {
1011
+ case LegacyHiddenComponent:
1012
+ case TracingMarkerComponent: {
1005
1013
break ;
1006
1014
}
1007
1015
@@ -1066,6 +1074,77 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
1066
1074
}
1067
1075
}
1068
1076
1077
+ function commitTransitionProgress (
1078
+ finishedRoot : FiberRoot ,
1079
+ offscreenFiber : Fiber ,
1080
+ ) {
1081
+ // This function adds suspense boundaries to the root
1082
+ // or tracing marker's pendingSuspenseBoundaries map.
1083
+ // When a suspense boundary goes from a resolved to a fallback
1084
+ // state we add the boundary to the map, and when it goes from
1085
+ // a fallback to a resolved state, we remove the boundary from
1086
+ // the map.
1087
+
1088
+ // We use stateNode on the Offscreen component as a stable object
1089
+ // that doesnt change from render to render. This way we can
1090
+ // distinguish between different Offscreen instances (vs. the same
1091
+ // Offscreen instance with different fibers)
1092
+ const offscreenInstance : OffscreenInstance = offscreenFiber . stateNode ;
1093
+
1094
+ let prevState : SuspenseState | null = null ;
1095
+ const previousFiber = offscreenFiber . alternate ;
1096
+ if ( previousFiber !== null && previousFiber . memoizedState !== null ) {
1097
+ prevState = previousFiber . memoizedState ;
1098
+ }
1099
+ const nextState : SuspenseState | null = offscreenFiber . memoizedState ;
1100
+
1101
+ const wasHidden = prevState !== null ;
1102
+ const isHidden = nextState !== null ;
1103
+
1104
+ const rootState : RootState = finishedRoot . current . memoizedState ;
1105
+ // TODO(luna) move pendingSuspenseBoundaries and transitions from
1106
+ // HostRoot fiber to FiberRoot
1107
+ const rootPendingBoundaries = rootState . pendingSuspenseBoundaries ;
1108
+
1109
+ // If there is a name on the suspense boundary, store that in
1110
+ // the pending boundaries.
1111
+ let name = null ;
1112
+ const parent = offscreenFiber . return ;
1113
+ if (
1114
+ parent !== null &&
1115
+ parent . tag === SuspenseComponent &&
1116
+ parent . memoizedProps . unstable_name
1117
+ ) {
1118
+ name = parent . memoizedProps . unstable_name ;
1119
+ }
1120
+
1121
+ if ( rootPendingBoundaries !== null ) {
1122
+ if ( previousFiber === null ) {
1123
+ // Initial mount
1124
+ if ( isHidden ) {
1125
+ rootPendingBoundaries . set ( offscreenInstance , {
1126
+ name,
1127
+ } ) ;
1128
+ }
1129
+ } else {
1130
+ if ( wasHidden && ! isHidden ) {
1131
+ // The suspense boundary went from hidden to visible. Remove
1132
+ // the boundary from the pending suspense boundaries set
1133
+ // if it's there
1134
+ if ( rootPendingBoundaries . has ( offscreenInstance ) ) {
1135
+ rootPendingBoundaries . delete ( offscreenInstance ) ;
1136
+ }
1137
+ } else if ( ! wasHidden && isHidden ) {
1138
+ // The suspense boundaries was just hidden. Add the boundary
1139
+ // to the pending boundary set if it's there
1140
+ rootPendingBoundaries . set ( offscreenInstance , {
1141
+ name,
1142
+ } ) ;
1143
+ }
1144
+ }
1145
+ }
1146
+ }
1147
+
1069
1148
function hideOrUnhideAllChildren ( finishedWork , isHidden ) {
1070
1149
// Only hide or unhide the top-most host nodes.
1071
1150
let hostSubtreeRoot = null ;
@@ -2747,22 +2826,53 @@ function commitPassiveMountOnFiber(
2747
2826
}
2748
2827
2749
2828
if ( enableTransitionTracing ) {
2750
- if ( committedTransitions !== null ) {
2829
+ // Get the transitions that were initiatized during the render
2830
+ // and add a start transition callback for each of them
2831
+ const state = finishedWork . memoizedState ;
2832
+ if ( state . transitions === null ) {
2833
+ state . transitions = new Set ( [ ] ) ;
2834
+ }
2835
+ const pendingTransitions = state . transitions ;
2836
+
2837
+ if ( committedTransitions != null ) {
2751
2838
committedTransitions . forEach ( transition => {
2752
- // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2753
2839
addTransitionStartCallbackToPendingTransition ( {
2754
2840
transitionName : transition . name ,
2755
2841
startTime : transition . startTime ,
2756
2842
} ) ;
2757
-
2758
- addTransitionCompleteCallbackToPendingTransition ( {
2759
- transitionName : transition . name ,
2760
- startTime : transition . startTime ,
2761
- } ) ;
2843
+ pendingTransitions . add ( transition ) ;
2762
2844
} ) ;
2763
2845
2764
2846
clearTransitionsForLanes ( finishedRoot , committedLanes ) ;
2765
- finishedWork . memoizedState . transitions = null ;
2847
+ }
2848
+
2849
+ const pendingSuspenseBoundaries = state . pendingSuspenseBoundaries ;
2850
+ const processedTransitions = new Set ( ) ;
2851
+ // process the lazy transitions list by filtering duplicate transitions
2852
+ // and calling the transition complete callback on all transitions
2853
+ // if there are no more pending suspense boundaries
2854
+ pendingTransitions . forEach ( transition => {
2855
+ if ( ! processedTransitions . has ( transition ) ) {
2856
+ if (
2857
+ pendingSuspenseBoundaries === null ||
2858
+ pendingSuspenseBoundaries . size === 0
2859
+ ) {
2860
+ addTransitionCompleteCallbackToPendingTransition ( {
2861
+ transitionName : transition . name ,
2862
+ startTime : transition . startTime ,
2863
+ } ) ;
2864
+ }
2865
+ processedTransitions . add ( transition ) ;
2866
+ }
2867
+ } ) ;
2868
+
2869
+ // If there are no more pending suspense boundaries we
2870
+ // clear the transitions because they are all complete.
2871
+ if (
2872
+ pendingSuspenseBoundaries === null ||
2873
+ pendingSuspenseBoundaries . size === 0
2874
+ ) {
2875
+ state . transitions = null ;
2766
2876
}
2767
2877
}
2768
2878
break ;
@@ -2800,9 +2910,44 @@ function commitPassiveMountOnFiber(
2800
2910
}
2801
2911
2802
2912
if ( enableTransitionTracing ) {
2803
- // TODO: Add code to actually process the update queue
2913
+ const isFallback = finishedWork . memoizedState ;
2914
+ const queue = ( finishedWork . updateQueue : any ) ;
2915
+ const rootMemoizedState = finishedRoot . current . memoizedState ;
2916
+
2917
+ if ( queue !== null ) {
2918
+ // We have one instance of the pendingSuspenseBoundaries map.
2919
+ // We only need one because we update it during the commit phase.
2920
+ // We instantiate a new Map if we haven't already
2921
+ if ( rootMemoizedState . pendingSuspenseBoundaries === null ) {
2922
+ rootMemoizedState . pendingSuspenseBoundaries = new Map ( ) ;
2923
+ }
2924
+
2925
+ if ( isFallback ) {
2926
+ const transitions = queue . transitions ;
2927
+ let prevTransitions = finishedWork . memoizedState . transitions ;
2928
+ // Add all the transitions saved in the update queue during
2929
+ // the render phase (ie the transitions associated with this boundary)
2930
+ // into the transitions set.
2931
+ if ( transitions != null ) {
2932
+ if ( prevTransitions === null ) {
2933
+ // We only have one instance of the transitions set
2934
+ // because we update it only during the commit phase. We
2935
+ // will create the set on a as needed basis in the commit phase
2936
+ finishedWork . memoizedState . transitions = prevTransitions = new Set ( ) ;
2937
+ }
2938
+
2939
+ transitions . forEach ( transition => {
2940
+ prevTransitions . add ( transition ) ;
2941
+ } ) ;
2942
+ }
2943
+ }
2944
+ }
2945
+
2946
+ commitTransitionProgress ( finishedRoot , finishedWork ) ;
2947
+
2804
2948
finishedWork . updateQueue = null ;
2805
2949
}
2950
+
2806
2951
break ;
2807
2952
}
2808
2953
case CacheComponent : {
0 commit comments