@@ -17,6 +17,7 @@ import type {
1717} from './ReactFiberHostConfig' ;
1818import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
1919import type { Lanes } from './ReactFiberLane' ;
20+ import { NoTimestamp , SyncLane } from './ReactFiberLane' ;
2021import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
2122import type { UpdateQueue } from './ReactFiberClassUpdateQueue' ;
2223import type { FunctionComponentUpdateQueue } from './ReactFiberHooks' ;
@@ -152,7 +153,6 @@ import {
152153 clearSingleton ,
153154 acquireSingletonInstance ,
154155 releaseSingletonInstance ,
155- scheduleMicrotask ,
156156} from './ReactFiberHostConfig' ;
157157import {
158158 captureCommitPhaseError ,
@@ -169,7 +169,6 @@ import {
169169 setIsRunningInsertionEffect ,
170170 getExecutionContext ,
171171 CommitContext ,
172- RenderContext ,
173172 NoContext ,
174173} from './ReactFiberWorkLoop' ;
175174import {
@@ -205,6 +204,8 @@ import {
205204 TransitionRoot ,
206205 TransitionTracingMarker ,
207206} from './ReactFiberTracingMarkerComponent' ;
207+ import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop' ;
208+ import { enqueueConcurrentRenderForLane } from './ReactFiberConcurrentUpdates' ;
208209
209210let didWarnAboutUndefinedSnapshotBeforeUpdate : Set < mixed > | null = null ;
210211if ( __DEV__ ) {
@@ -2407,24 +2408,44 @@ function getRetryCache(finishedWork) {
24072408}
24082409
24092410export function detachOffscreenInstance ( instance : OffscreenInstance ) : void {
2410- const currentOffscreenFiber = instance . _current ;
2411- if ( currentOffscreenFiber === null ) {
2411+ const fiber = instance . _current ;
2412+ if ( fiber === null ) {
24122413 throw new Error (
24132414 'Calling Offscreen.detach before instance handle has been set.' ,
24142415 ) ;
24152416 }
24162417
2417- const executionContext = getExecutionContext ( ) ;
2418- if ( ( executionContext & ( RenderContext | CommitContext ) ) !== NoContext ) {
2419- scheduleMicrotask ( ( ) => {
2420- instance . _visibility |= OffscreenDetached ;
2421- disappearLayoutEffects ( currentOffscreenFiber ) ;
2422- disconnectPassiveEffect ( currentOffscreenFiber ) ;
2423- } ) ;
2424- } else {
2425- instance . _visibility |= OffscreenDetached ;
2426- disappearLayoutEffects ( currentOffscreenFiber ) ;
2427- disconnectPassiveEffect ( currentOffscreenFiber ) ;
2418+ if ( ( instance . _pendingVisibility & OffscreenDetached ) !== NoFlags ) {
2419+ // The instance is already detached, this is a noop.
2420+ return;
2421+ }
2422+
2423+ // TODO: There is an opportunity to optimise this by not entering commit phase
2424+ // and unmounting effects directly.
2425+ const root = enqueueConcurrentRenderForLane ( fiber , SyncLane ) ;
2426+ if ( root !== null ) {
2427+ instance . _pendingVisibility |= OffscreenDetached ;
2428+ scheduleUpdateOnFiber ( root , fiber , SyncLane , NoTimestamp ) ;
2429+ }
2430+ }
2431+
2432+ export function attachOffscreenInstance ( instance : OffscreenInstance ) : void {
2433+ const fiber = instance . _current ;
2434+ if ( fiber === null ) {
2435+ throw new Error (
2436+ 'Calling Offscreen.detach before instance handle has been set.' ,
2437+ ) ;
2438+ }
2439+
2440+ if ( ( instance . _pendingVisibility & OffscreenDetached ) === NoFlags ) {
2441+ // The instance is already attached, this is a noop.
2442+ return ;
2443+ }
2444+
2445+ const root = enqueueConcurrentRenderForLane ( fiber , SyncLane ) ;
2446+ if ( root !== null ) {
2447+ instance . _pendingVisibility &= ~ OffscreenDetached ;
2448+ scheduleUpdateOnFiber ( root , fiber , SyncLane , NoTimestamp ) ;
24282449 }
24292450}
24302451
@@ -2857,12 +2878,19 @@ function commitMutationEffectsOnFiber(
28572878 }
28582879
28592880 commitReconciliationEffects ( finishedWork ) ;
2881+
2882+ const offscreenInstance : OffscreenInstance = finishedWork . stateNode ;
2883+
28602884 // TODO: Add explicit effect flag to set _current.
2861- finishedWork . stateNode . _current = finishedWork ;
2885+ offscreenInstance . _current = finishedWork ;
28622886
2863- if ( flags & Visibility ) {
2864- const offscreenInstance : OffscreenInstance = finishedWork . stateNode ;
2887+ // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
2888+ // to support batching of `attach` and `detach` calls.
2889+ offscreenInstance . _visibility &= ~ OffscreenDetached ;
2890+ offscreenInstance . _visibility |=
2891+ offscreenInstance . _pendingVisibility & OffscreenDetached ;
28652892
2893+ if ( flags & Visibility ) {
28662894 // Track the current state on the Offscreen instance so we can
28672895 // read it during an event
28682896 if ( isHidden ) {
0 commit comments