@@ -174,6 +174,8 @@ import {
174
174
setShallowSuspenseListContext ,
175
175
pushPrimaryTreeSuspenseHandler ,
176
176
pushFallbackTreeSuspenseHandler ,
177
+ pushOffscreenSuspenseHandler ,
178
+ reuseSuspenseHandlerOnStack ,
177
179
popSuspenseHandler ,
178
180
} from './ReactFiberSuspenseContext.new' ;
179
181
import {
@@ -678,6 +680,52 @@ function updateOffscreenComponent(
678
680
( enableLegacyHidden && nextProps . mode === 'unstable-defer-without-hiding' )
679
681
) {
680
682
// Rendering a hidden tree.
683
+
684
+ const didSuspend = ( workInProgress . flags & DidCapture ) !== NoFlags ;
685
+ if ( didSuspend ) {
686
+ // Something suspended inside a hidden tree
687
+
688
+ // Include the base lanes from the last render
689
+ const nextBaseLanes =
690
+ prevState !== null
691
+ ? mergeLanes ( prevState . baseLanes , renderLanes )
692
+ : renderLanes ;
693
+
694
+ if ( current !== null ) {
695
+ // Reset to the current children
696
+ let currentChild = ( workInProgress . child = current . child ) ;
697
+
698
+ // The current render suspended, but there may be other lanes with
699
+ // pending work. We can't read `childLanes` from the current Offscreen
700
+ // fiber because we reset it when it was deferred; however, we can read
701
+ // the pending lanes from the child fibers.
702
+ let currentChildLanes = NoLanes ;
703
+ while ( currentChild !== null ) {
704
+ currentChildLanes = mergeLanes (
705
+ mergeLanes ( currentChildLanes , currentChild . lanes ) ,
706
+ currentChild . childLanes ,
707
+ ) ;
708
+ currentChild = currentChild . sibling ;
709
+ }
710
+ const lanesWeJustAttempted = nextBaseLanes ;
711
+ const remainingChildLanes = removeLanes (
712
+ currentChildLanes ,
713
+ lanesWeJustAttempted ,
714
+ ) ;
715
+ workInProgress . childLanes = remainingChildLanes ;
716
+ } else {
717
+ workInProgress . childLanes = NoLanes ;
718
+ workInProgress . child = null ;
719
+ }
720
+
721
+ return deferHiddenOffscreenComponent (
722
+ current ,
723
+ workInProgress ,
724
+ nextBaseLanes ,
725
+ renderLanes ,
726
+ ) ;
727
+ }
728
+
681
729
if ( ( workInProgress . mode & ConcurrentMode ) === NoMode ) {
682
730
// In legacy sync mode, don't defer the subtree. Render it now.
683
731
// TODO: Consider how Offscreen should work with transitions in the future
@@ -694,50 +742,28 @@ function updateOffscreenComponent(
694
742
}
695
743
}
696
744
reuseHiddenContextOnStack ( workInProgress ) ;
745
+ pushOffscreenSuspenseHandler ( workInProgress ) ;
697
746
} else if ( ! includesSomeLane ( renderLanes , ( OffscreenLane : Lane ) ) ) {
698
747
// We're hidden, and we're not rendering at Offscreen. We will bail out
699
748
// and resume this tree later.
700
- let nextBaseLanes = renderLanes ;
701
- if ( prevState !== null ) {
702
- // Include the base lanes from the last render
703
- nextBaseLanes = mergeLanes ( nextBaseLanes , prevState . baseLanes ) ;
704
- }
705
749
706
- // Schedule this fiber to re-render at offscreen priority. Then bailout.
750
+ // Schedule this fiber to re-render at Offscreen priority
707
751
workInProgress . lanes = workInProgress . childLanes = laneToLanes (
708
752
OffscreenLane ,
709
753
) ;
710
- const nextState : OffscreenState = {
711
- baseLanes : nextBaseLanes ,
712
- // Save the cache pool so we can resume later.
713
- cachePool : enableCache ? getOffscreenDeferredCache ( ) : null ,
714
- } ;
715
- workInProgress . memoizedState = nextState ;
716
- workInProgress . updateQueue = null ;
717
- if ( enableCache ) {
718
- // push the cache pool even though we're going to bail out
719
- // because otherwise there'd be a context mismatch
720
- if ( current !== null ) {
721
- pushTransition ( workInProgress , null , null ) ;
722
- }
723
- }
724
-
725
- // We're about to bail out, but we need to push this to the stack anyway
726
- // to avoid a push/pop misalignment.
727
- reuseHiddenContextOnStack ( workInProgress ) ;
728
754
729
- if ( enableLazyContextPropagation && current !== null ) {
730
- // Since this tree will resume rendering in a separate render, we need
731
- // to propagate parent contexts now so we don't lose track of which
732
- // ones changed.
733
- propagateParentContextChangesToDeferredTree (
734
- current ,
735
- workInProgress ,
736
- renderLanes ,
737
- ) ;
738
- }
755
+ // Include the base lanes from the last render
756
+ const nextBaseLanes =
757
+ prevState !== null
758
+ ? mergeLanes ( prevState . baseLanes , renderLanes )
759
+ : renderLanes ;
739
760
740
- return null ;
761
+ return deferHiddenOffscreenComponent (
762
+ current ,
763
+ workInProgress ,
764
+ nextBaseLanes ,
765
+ renderLanes ,
766
+ ) ;
741
767
} else {
742
768
// This is the second render. The surrounding visible content has already
743
769
// committed. Now we resume rendering the hidden tree.
@@ -764,6 +790,7 @@ function updateOffscreenComponent(
764
790
} else {
765
791
reuseHiddenContextOnStack ( workInProgress ) ;
766
792
}
793
+ pushOffscreenSuspenseHandler ( workInProgress ) ;
767
794
}
768
795
} else {
769
796
// Rendering a visible tree.
@@ -791,6 +818,7 @@ function updateOffscreenComponent(
791
818
792
819
// Push the lanes that were skipped when we bailed out.
793
820
pushHiddenContext ( workInProgress , prevState ) ;
821
+ reuseSuspenseHandlerOnStack ( workInProgress ) ;
794
822
795
823
// Since we're not hidden anymore, reset the state
796
824
workInProgress . memoizedState = null ;
@@ -811,13 +839,54 @@ function updateOffscreenComponent(
811
839
// We're about to bail out, but we need to push this to the stack anyway
812
840
// to avoid a push/pop misalignment.
813
841
reuseHiddenContextOnStack ( workInProgress ) ;
842
+ reuseSuspenseHandlerOnStack ( workInProgress ) ;
814
843
}
815
844
}
816
845
817
846
reconcileChildren ( current , workInProgress , nextChildren , renderLanes ) ;
818
847
return workInProgress . child ;
819
848
}
820
849
850
+ function deferHiddenOffscreenComponent (
851
+ current : Fiber | null ,
852
+ workInProgress : Fiber ,
853
+ nextBaseLanes : Lanes ,
854
+ renderLanes : Lanes ,
855
+ ) {
856
+ const nextState : OffscreenState = {
857
+ baseLanes : nextBaseLanes ,
858
+ // Save the cache pool so we can resume later.
859
+ cachePool : enableCache ? getOffscreenDeferredCache ( ) : null ,
860
+ } ;
861
+ workInProgress . memoizedState = nextState ;
862
+ if ( enableCache ) {
863
+ // push the cache pool even though we're going to bail out
864
+ // because otherwise there'd be a context mismatch
865
+ if ( current !== null ) {
866
+ pushTransition ( workInProgress , null , null ) ;
867
+ }
868
+ }
869
+
870
+ // We're about to bail out, but we need to push this to the stack anyway
871
+ // to avoid a push/pop misalignment.
872
+ reuseHiddenContextOnStack ( workInProgress ) ;
873
+
874
+ pushOffscreenSuspenseHandler ( workInProgress ) ;
875
+
876
+ if ( enableLazyContextPropagation && current !== null ) {
877
+ // Since this tree will resume rendering in a separate render, we need
878
+ // to propagate parent contexts now so we don't lose track of which
879
+ // ones changed.
880
+ propagateParentContextChangesToDeferredTree (
881
+ current ,
882
+ workInProgress ,
883
+ renderLanes ,
884
+ ) ;
885
+ }
886
+
887
+ return null ;
888
+ }
889
+
821
890
// Note: These happen to have identical begin phases, for now. We shouldn't hold
822
891
// ourselves to this constraint, though. If the behavior diverges, we should
823
892
// fork the function.
@@ -2109,13 +2178,19 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2109
2178
if ( enableTransitionTracing ) {
2110
2179
const currentTransitions = getPendingTransitions ( ) ;
2111
2180
if ( currentTransitions !== null ) {
2112
- // If there are no transitions, we don't need to keep track of tracing markers
2113
2181
const parentMarkerInstances = getMarkerInstances ( ) ;
2114
- const primaryChildUpdateQueue : OffscreenQueue = {
2115
- transitions : currentTransitions ,
2116
- markerInstances : parentMarkerInstances ,
2117
- } ;
2118
- primaryChildFragment . updateQueue = primaryChildUpdateQueue ;
2182
+ const offscreenQueue : OffscreenQueue | null = ( primaryChildFragment . updateQueue : any ) ;
2183
+ if ( offscreenQueue === null ) {
2184
+ const newOffscreenQueue : OffscreenQueue = {
2185
+ transitions : currentTransitions ,
2186
+ markerInstances : parentMarkerInstances ,
2187
+ wakeables : null ,
2188
+ } ;
2189
+ primaryChildFragment . updateQueue = newOffscreenQueue ;
2190
+ } else {
2191
+ offscreenQueue . transitions = currentTransitions ;
2192
+ offscreenQueue . markerInstances = parentMarkerInstances ;
2193
+ }
2119
2194
}
2120
2195
}
2121
2196
@@ -2140,6 +2215,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2140
2215
) ;
2141
2216
workInProgress . memoizedState = SUSPENDED_MARKER ;
2142
2217
2218
+ // TODO: Transition Tracing is not yet implemented for CPU Suspense.
2219
+
2143
2220
// Since nothing actually suspended, there will nothing to ping this to
2144
2221
// get it started back up to attempt the next item. While in terms of
2145
2222
// priority this work has the same priority as this current render, it's
@@ -2201,11 +2278,31 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2201
2278
const currentTransitions = getPendingTransitions ( ) ;
2202
2279
if ( currentTransitions !== null ) {
2203
2280
const parentMarkerInstances = getMarkerInstances ( ) ;
2204
- const primaryChildUpdateQueue : OffscreenQueue = {
2205
- transitions : currentTransitions ,
2206
- markerInstances : parentMarkerInstances ,
2207
- } ;
2208
- primaryChildFragment . updateQueue = primaryChildUpdateQueue ;
2281
+ const offscreenQueue : OffscreenQueue | null = ( primaryChildFragment . updateQueue : any ) ;
2282
+ const currentOffscreenQueue : OffscreenQueue | null = ( current . updateQueue : any ) ;
2283
+ if ( offscreenQueue === null ) {
2284
+ const newOffscreenQueue : OffscreenQueue = {
2285
+ transitions : currentTransitions ,
2286
+ markerInstances : parentMarkerInstances ,
2287
+ wakeables : null ,
2288
+ } ;
2289
+ primaryChildFragment . updateQueue = newOffscreenQueue ;
2290
+ } else if ( offscreenQueue === currentOffscreenQueue ) {
2291
+ // If the work-in-progress queue is the same object as current, we
2292
+ // can't modify it without cloning it first.
2293
+ const newOffscreenQueue : OffscreenQueue = {
2294
+ transitions : currentTransitions ,
2295
+ markerInstances : parentMarkerInstances ,
2296
+ wakeables :
2297
+ currentOffscreenQueue !== null
2298
+ ? currentOffscreenQueue . wakeables
2299
+ : null ,
2300
+ } ;
2301
+ primaryChildFragment . updateQueue = newOffscreenQueue ;
2302
+ } else {
2303
+ offscreenQueue . transitions = currentTransitions ;
2304
+ offscreenQueue . markerInstances = parentMarkerInstances ;
2305
+ }
2209
2306
}
2210
2307
}
2211
2308
primaryChildFragment . childLanes = getRemainingWorkInPrimaryTree (
0 commit comments