@@ -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 ,
@@ -63,6 +69,7 @@ import {
63
69
OffscreenComponent ,
64
70
LegacyHiddenComponent ,
65
71
CacheComponent ,
72
+ TracingMarkerComponent ,
66
73
} from './ReactWorkTags' ;
67
74
import { detachDeletedInstance } from './ReactFiberHostConfig' ;
68
75
import {
@@ -1002,7 +1009,8 @@ function commitLayoutEffectOnFiber(
1002
1009
case IncompleteClassComponent:
1003
1010
case ScopeComponent:
1004
1011
case OffscreenComponent:
1005
- case LegacyHiddenComponent: {
1012
+ case LegacyHiddenComponent:
1013
+ case TracingMarkerComponent: {
1006
1014
break ;
1007
1015
}
1008
1016
@@ -1067,6 +1075,77 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
1067
1075
}
1068
1076
}
1069
1077
1078
+ function addOrRemovePendingBoundariesOnRoot (
1079
+ finishedRoot : FiberRoot ,
1080
+ finishedWork : Fiber ,
1081
+ ) {
1082
+ // This function adds suspense boundaries to the root
1083
+ // or tracing marker's pendingSuspenseBoundaries map.
1084
+ // When a suspense boundary goes from a resolved to a fallback
1085
+ // state we add the boundary to the map, and when it goes from
1086
+ // a fallback to a resolved state, we remove the boundary from
1087
+ // the map.
1088
+
1089
+ // We use stateNode on the Offscreen component as a stable object
1090
+ // that doesnt change from render to render. This way we can
1091
+ // distinguish between different Offscreen instances (vs. the same
1092
+ // Offscreen instance with different fibers)
1093
+ const offscreenInstance = finishedWork . stateNode ;
1094
+
1095
+ let prevState : SuspenseState | null = null ;
1096
+ if (
1097
+ finishedWork . alternate !== null &&
1098
+ finishedWork . alternate . memoizedState !== null
1099
+ ) {
1100
+ prevState = finishedWork . alternate . memoizedState ;
1101
+ }
1102
+ const nextState : SuspenseState | null = finishedWork . memoizedState ;
1103
+
1104
+ const wasHidden = prevState !== null ;
1105
+ const isHidden = nextState !== null ;
1106
+
1107
+ const rootPendingBoundaries =
1108
+ finishedRoot . current . memoizedState . pendingSuspenseBoundaries ;
1109
+
1110
+ // If there is a name on the suspense boundary, store that in
1111
+ // the pending boundaries.
1112
+ let name = null ;
1113
+ const parent = finishedWork . return ;
1114
+ if (
1115
+ parent !== null &&
1116
+ parent . tag === SuspenseComponent &&
1117
+ parent . memoizedProps . unstable_name
1118
+ ) {
1119
+ name = parent . memoizedProps . unstable_name ;
1120
+ }
1121
+
1122
+ if ( rootPendingBoundaries !== null ) {
1123
+ if ( finishedWork . alternate === null ) {
1124
+ // Initial mount
1125
+ if ( isHidden ) {
1126
+ rootPendingBoundaries . set ( offscreenInstance , {
1127
+ name,
1128
+ } ) ;
1129
+ }
1130
+ } else {
1131
+ if ( wasHidden && ! isHidden ) {
1132
+ // The suspense boundary went from hidden to visible. Remove
1133
+ // the boundary from the pending suspense boundaries set
1134
+ // if it's there
1135
+ if ( rootPendingBoundaries . has ( offscreenInstance ) ) {
1136
+ rootPendingBoundaries . delete ( offscreenInstance ) ;
1137
+ }
1138
+ } else if ( ! wasHidden && isHidden ) {
1139
+ // The suspense boundaries was just hidden. Add the boundary
1140
+ // to the pending boundary set if it's there
1141
+ rootPendingBoundaries . set ( offscreenInstance , {
1142
+ name,
1143
+ } ) ;
1144
+ }
1145
+ }
1146
+ }
1147
+ }
1148
+
1070
1149
function hideOrUnhideAllChildren ( finishedWork , isHidden ) {
1071
1150
// Only hide or unhide the top-most host nodes.
1072
1151
let hostSubtreeRoot = null ;
@@ -2725,22 +2804,54 @@ function commitPassiveMountOnFiber(
2725
2804
}
2726
2805
2727
2806
if ( enableTransitionTracing ) {
2728
- if ( committedTransitions !== null ) {
2807
+ // Get the transitions that were initiatized during the render
2808
+ // and add a start transition callback for each of them
2809
+ const state = finishedWork . memoizedState ;
2810
+ if ( state . transitions === null ) {
2811
+ state . transitions = new Set ( [ ] ) ;
2812
+ }
2813
+ const pendingTransitions = state . transitions ;
2814
+
2815
+ if ( committedTransitions != null ) {
2729
2816
committedTransitions . forEach ( transition => {
2730
- // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2731
2817
addTransitionStartCallbackToPendingTransition ( {
2732
2818
transitionName : transition . name ,
2733
2819
startTime : transition . startTime ,
2734
2820
} ) ;
2735
-
2736
- addTransitionCompleteCallbackToPendingTransition ( {
2737
- transitionName : transition . name ,
2738
- startTime : transition . startTime ,
2739
- } ) ;
2821
+ pendingTransitions . add ( transition ) ;
2740
2822
} ) ;
2741
2823
2742
2824
clearTransitionsForLanes ( finishedRoot , committedLanes ) ;
2743
- finishedWork . memoizedState . transitions = null ;
2825
+ }
2826
+
2827
+ const pendingSuspenseBoundaries = state . pendingSuspenseBoundaries ;
2828
+ const processedTransitions = new Set ( ) ;
2829
+ // process the lazy transitions list by filtering duplicate transitions
2830
+ // and calling the transition complete callback on all transitions
2831
+ // if there are no more pending suspense boundaries
2832
+ pendingTransitions . forEach ( transition => {
2833
+ if ( ! processedTransitions . has ( transition ) ) {
2834
+ if (
2835
+ pendingSuspenseBoundaries === null ||
2836
+ pendingSuspenseBoundaries . size === 0
2837
+ ) {
2838
+ addTransitionCompleteCallbackToPendingTransition ( {
2839
+ transitionName : transition . name ,
2840
+ startTime : transition . startTime ,
2841
+ } ) ;
2842
+ }
2843
+ processedTransitions . add ( transition ) ;
2844
+ }
2845
+ } ) ;
2846
+
2847
+ // If there are no more pending suspense boundaries we
2848
+ // clear the transitions because they are all complete. Otherwise
2849
+ // we store the transitions where we remove all duplicates
2850
+ if (
2851
+ pendingSuspenseBoundaries === null ||
2852
+ pendingSuspenseBoundaries . size === 0
2853
+ ) {
2854
+ state . transitions = null ;
2744
2855
}
2745
2856
}
2746
2857
break ;
@@ -2776,6 +2887,46 @@ function commitPassiveMountOnFiber(
2776
2887
}
2777
2888
}
2778
2889
}
2890
+
2891
+ if ( enableTransitionTracing ) {
2892
+ const isFallback = finishedWork . memoizedState ;
2893
+ const queue = ( finishedWork . updateQueue : any ) ;
2894
+ const rootMemoizedState = finishedRoot . current . memoizedState ;
2895
+
2896
+ if ( queue !== null ) {
2897
+ // We have one instance of the pendingSuspenseBoundaries map.
2898
+ // We only need one because we update it during the commit phase.
2899
+ // We instantiate a new Map if we haven't already
2900
+ if ( rootMemoizedState . pendingSuspenseBoundaries === null ) {
2901
+ rootMemoizedState . pendingSuspenseBoundaries = new Map ( ) ;
2902
+ }
2903
+
2904
+ if ( isFallback ) {
2905
+ const transitions = queue . transitions ;
2906
+ let prevTransitions = finishedWork . memoizedState . transitions ;
2907
+ // Add all the transitions saved in the update queue during
2908
+ // the render phase (ie the transitions associated with this boundary)
2909
+ // into the transitions set.
2910
+ if ( transitions != null ) {
2911
+ if ( prevTransitions === null ) {
2912
+ // We only have one instance of the transitions set
2913
+ // because we update it only during the commit phase. We
2914
+ // will create the set on a as needed basis in the commit phase
2915
+ finishedWork . memoizedState . transitions = prevTransitions = new Set ( ) ;
2916
+ }
2917
+
2918
+ transitions . forEach ( transition => {
2919
+ prevTransitions . add ( transition ) ;
2920
+ } ) ;
2921
+ }
2922
+ }
2923
+ }
2924
+
2925
+ addOrRemovePendingBoundariesOnRoot ( finishedRoot , finishedWork ) ;
2926
+
2927
+ finishedWork . updateQueue = null ;
2928
+ }
2929
+
2779
2930
break ;
2780
2931
}
2781
2932
case CacheComponent : {
0 commit comments