@@ -174,6 +174,8 @@ import {
174174 setShallowSuspenseListContext ,
175175 pushPrimaryTreeSuspenseHandler ,
176176 pushFallbackTreeSuspenseHandler ,
177+ pushOffscreenSuspenseHandler ,
178+ reuseSuspenseHandlerOnStack ,
177179 popSuspenseHandler ,
178180} from './ReactFiberSuspenseContext.new' ;
179181import {
@@ -678,6 +680,52 @@ function updateOffscreenComponent(
678680 ( enableLegacyHidden && nextProps . mode === 'unstable-defer-without-hiding' )
679681 ) {
680682 // 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+
681729 if ( ( workInProgress . mode & ConcurrentMode ) === NoMode ) {
682730 // In legacy sync mode, don't defer the subtree. Render it now.
683731 // TODO: Consider how Offscreen should work with transitions in the future
@@ -694,50 +742,28 @@ function updateOffscreenComponent(
694742 }
695743 }
696744 reuseHiddenContextOnStack ( workInProgress ) ;
745+ pushOffscreenSuspenseHandler ( workInProgress ) ;
697746 } else if ( ! includesSomeLane ( renderLanes , ( OffscreenLane : Lane ) ) ) {
698747 // We're hidden, and we're not rendering at Offscreen. We will bail out
699748 // 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- }
705749
706- // Schedule this fiber to re-render at offscreen priority. Then bailout.
750+ // Schedule this fiber to re-render at Offscreen priority
707751 workInProgress . lanes = workInProgress . childLanes = laneToLanes (
708752 OffscreenLane ,
709753 ) ;
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 ) ;
728754
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 ;
739760
740- return null ;
761+ return deferHiddenOffscreenComponent (
762+ current ,
763+ workInProgress ,
764+ nextBaseLanes ,
765+ renderLanes ,
766+ ) ;
741767 } else {
742768 // This is the second render. The surrounding visible content has already
743769 // committed. Now we resume rendering the hidden tree.
@@ -764,6 +790,7 @@ function updateOffscreenComponent(
764790 } else {
765791 reuseHiddenContextOnStack ( workInProgress ) ;
766792 }
793+ pushOffscreenSuspenseHandler ( workInProgress ) ;
767794 }
768795 } else {
769796 // Rendering a visible tree.
@@ -791,6 +818,7 @@ function updateOffscreenComponent(
791818
792819 // Push the lanes that were skipped when we bailed out.
793820 pushHiddenContext ( workInProgress , prevState ) ;
821+ reuseSuspenseHandlerOnStack ( workInProgress ) ;
794822
795823 // Since we're not hidden anymore, reset the state
796824 workInProgress . memoizedState = null ;
@@ -811,13 +839,54 @@ function updateOffscreenComponent(
811839 // We're about to bail out, but we need to push this to the stack anyway
812840 // to avoid a push/pop misalignment.
813841 reuseHiddenContextOnStack ( workInProgress ) ;
842+ reuseSuspenseHandlerOnStack ( workInProgress ) ;
814843 }
815844 }
816845
817846 reconcileChildren ( current , workInProgress , nextChildren , renderLanes ) ;
818847 return workInProgress . child ;
819848}
820849
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+
821890// Note: These happen to have identical begin phases, for now. We shouldn't hold
822891// ourselves to this constraint, though. If the behavior diverges, we should
823892// fork the function.
@@ -2109,13 +2178,19 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
21092178 if ( enableTransitionTracing ) {
21102179 const currentTransitions = getPendingTransitions ( ) ;
21112180 if ( currentTransitions !== null ) {
2112- // If there are no transitions, we don't need to keep track of tracing markers
21132181 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+ }
21192194 }
21202195 }
21212196
@@ -2140,6 +2215,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
21402215 ) ;
21412216 workInProgress . memoizedState = SUSPENDED_MARKER ;
21422217
2218+ // TODO: Transition Tracing is not yet implemented for CPU Suspense.
2219+
21432220 // Since nothing actually suspended, there will nothing to ping this to
21442221 // get it started back up to attempt the next item. While in terms of
21452222 // priority this work has the same priority as this current render, it's
@@ -2201,11 +2278,31 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
22012278 const currentTransitions = getPendingTransitions ( ) ;
22022279 if ( currentTransitions !== null ) {
22032280 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+ }
22092306 }
22102307 }
22112308 primaryChildFragment . childLanes = getRemainingWorkInPrimaryTree (
0 commit comments