@@ -17,6 +17,7 @@ import type {
17
17
} from './ReactFiberHostConfig' ;
18
18
import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
19
19
import type { Lanes } from './ReactFiberLane' ;
20
+ import { NoTimestamp , SyncLane } from './ReactFiberLane' ;
20
21
import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
21
22
import type { UpdateQueue } from './ReactFiberClassUpdateQueue' ;
22
23
import type { FunctionComponentUpdateQueue } from './ReactFiberHooks' ;
@@ -152,7 +153,6 @@ import {
152
153
clearSingleton ,
153
154
acquireSingletonInstance ,
154
155
releaseSingletonInstance ,
155
- scheduleMicrotask ,
156
156
} from './ReactFiberHostConfig' ;
157
157
import {
158
158
captureCommitPhaseError ,
@@ -169,7 +169,6 @@ import {
169
169
setIsRunningInsertionEffect ,
170
170
getExecutionContext ,
171
171
CommitContext ,
172
- RenderContext ,
173
172
NoContext ,
174
173
} from './ReactFiberWorkLoop' ;
175
174
import {
@@ -205,6 +204,8 @@ import {
205
204
TransitionRoot ,
206
205
TransitionTracingMarker ,
207
206
} from './ReactFiberTracingMarkerComponent' ;
207
+ import { scheduleUpdateOnFiber } from './ReactFiberWorkLoop' ;
208
+ import { enqueueConcurrentRenderForLane } from './ReactFiberConcurrentUpdates' ;
208
209
209
210
let didWarnAboutUndefinedSnapshotBeforeUpdate : Set < mixed > | null = null ;
210
211
if ( __DEV__ ) {
@@ -2407,24 +2408,44 @@ function getRetryCache(finishedWork) {
2407
2408
}
2408
2409
2409
2410
export function detachOffscreenInstance ( instance : OffscreenInstance ) : void {
2410
- const currentOffscreenFiber = instance . _current ;
2411
- if ( currentOffscreenFiber === null ) {
2411
+ const fiber = instance . _current ;
2412
+ if ( fiber === null ) {
2412
2413
throw new Error (
2413
2414
'Calling Offscreen.detach before instance handle has been set.' ,
2414
2415
) ;
2415
2416
}
2416
2417
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 ) ;
2428
2449
}
2429
2450
}
2430
2451
@@ -2857,12 +2878,19 @@ function commitMutationEffectsOnFiber(
2857
2878
}
2858
2879
2859
2880
commitReconciliationEffects ( finishedWork ) ;
2881
+
2882
+ const offscreenInstance : OffscreenInstance = finishedWork . stateNode ;
2883
+
2860
2884
// TODO: Add explicit effect flag to set _current.
2861
- finishedWork . stateNode . _current = finishedWork ;
2885
+ offscreenInstance . _current = finishedWork ;
2862
2886
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 ;
2865
2892
2893
+ if ( flags & Visibility ) {
2866
2894
// Track the current state on the Offscreen instance so we can
2867
2895
// read it during an event
2868
2896
if ( isHidden ) {
0 commit comments