Skip to content

Commit 806f024

Browse files
committed
Offscreen add attach (#25603)
`Offscreen.attach` is imperative API to signal to Offscreen that its updates should be high priority and effects should be mounted. Coupled with `Offscreen.detach` it gives ability to manually control Offscreen. Unlike with mode `visible` and `hidden`, it is developers job to make sure contents of Offscreen are not visible to users. `Offscreen.attach` only works if mode is `manual`. Example uses: ```jsx let offscreenRef = useRef(null); <Offscreen mode={'manual'} ref={offscreenRef)}> <Child /> </Offscreen> // ------ // Offscreen is attached by default. // For example user scrolls away and Offscreen subtree is not visible anymore. offscreenRef.current.detach(); // User scrolls back and Offscreen subtree is visible again. offscreenRef.current.attach(); ``` Co-authored-by: Andrew Clark <[email protected]> DiffTrain build for [996e4c0](996e4c0) [View git log for this commit](https://github.com/facebook/react/commits/996e4c0d56dabab382ca932cd5b8517e63020999)
1 parent a434b3e commit 806f024

34 files changed

+1880
-1370
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b14d7fa4b88dad5f0017d084e462952c700aa2ad
1+
996e4c0d56dabab382ca932cd5b8517e63020999
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b14d7fa4b88dad5f0017d084e462952c700aa2ad
1+
996e4c0d56dabab382ca932cd5b8517e63020999

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-classic-b14d7fa4b-20221209";
30+
var ReactVersion = "18.3.0-www-classic-996e4c0d5-20221212";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-b14d7fa4b-20221209";
30+
var ReactVersion = "18.3.0-www-modern-996e4c0d5-20221212";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,4 +643,4 @@ exports.useSyncExternalStore = function(
643643
);
644644
};
645645
exports.useTransition = useTransition;
646-
exports.version = "18.3.0-www-classic-b14d7fa4b-20221209";
646+
exports.version = "18.3.0-www-classic-996e4c0d5-20221212";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,4 @@ exports.useSyncExternalStore = function(
635635
);
636636
};
637637
exports.useTransition = useTransition;
638-
exports.version = "18.3.0-www-modern-b14d7fa4b-20221209";
638+
exports.version = "18.3.0-www-modern-996e4c0d5-20221212";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ exports.useSyncExternalStore = function(
654654
);
655655
};
656656
exports.useTransition = useTransition;
657-
exports.version = "18.3.0-www-classic-b14d7fa4b-20221209";
657+
exports.version = "18.3.0-www-classic-996e4c0d5-20221212";
658658

659659
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
660660
if (

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ exports.useSyncExternalStore = function(
646646
);
647647
};
648648
exports.useTransition = useTransition;
649-
exports.version = "18.3.0-www-modern-b14d7fa4b-20221209";
649+
exports.version = "18.3.0-www-modern-996e4c0d5-20221212";
650650

651651
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
652652
if (

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-b14d7fa4b-20221209";
72+
var ReactVersion = "18.3.0-www-classic-996e4c0d5-20221212";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -2530,17 +2530,6 @@ function shim$1() {
25302530
var prepareScopeUpdate = shim$1;
25312531
var getInstanceFromScope = shim$1;
25322532

2533-
// Renderers that don't support microtasks
2534-
// can re-export everything from this module.
2535-
function shim$2() {
2536-
throw new Error(
2537-
"The current renderer does not support microtasks. " +
2538-
"This error is likely caused by a bug in React. " +
2539-
"Please file an issue."
2540-
);
2541-
} // Test selectors (when unsupported)
2542-
var scheduleMicrotask = shim$2;
2543-
25442533
var pooledTransform = new Transform();
25452534
var NO_CONTEXT = {};
25462535
var UPDATE_SIGNAL = {};
@@ -13106,13 +13095,15 @@ function updateSimpleMemoComponent(
1310613095
function updateOffscreenComponent(current, workInProgress, renderLanes) {
1310713096
var nextProps = workInProgress.pendingProps;
1310813097
var nextChildren = nextProps.children;
13098+
var nextIsDetached =
13099+
(workInProgress.stateNode._pendingVisibility & OffscreenDetached) !== 0;
1310913100
var prevState = current !== null ? current.memoizedState : null;
1311013101
markRef(current, workInProgress);
1311113102

1311213103
if (
1311313104
nextProps.mode === "hidden" ||
13114-
nextProps.mode === "unstable-defer-without-hiding" || // TODO: remove read from stateNode.
13115-
workInProgress.stateNode._visibility & OffscreenDetached
13105+
nextProps.mode === "unstable-defer-without-hiding" ||
13106+
nextIsDetached
1311613107
) {
1311713108
// Rendering a hidden tree.
1311813109
var didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
@@ -20800,22 +20791,46 @@ function getRetryCache(finishedWork) {
2080020791
}
2080120792

2080220793
function detachOffscreenInstance(instance) {
20803-
var currentOffscreenFiber = instance._current;
20794+
var fiber = instance._current;
2080420795

20805-
if (currentOffscreenFiber === null) {
20796+
if (fiber === null) {
2080620797
throw new Error(
2080720798
"Calling Offscreen.detach before instance handle has been set."
2080820799
);
2080920800
}
2081020801

20811-
var executionContext = getExecutionContext();
20802+
if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
20803+
// The instance is already detached, this is a noop.
20804+
return;
20805+
} // TODO: There is an opportunity to optimise this by not entering commit phase
20806+
// and unmounting effects directly.
2081220807

20813-
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
20814-
scheduleMicrotask();
20815-
} else {
20816-
instance._visibility |= OffscreenDetached;
20817-
disappearLayoutEffects(currentOffscreenFiber);
20818-
disconnectPassiveEffect(currentOffscreenFiber);
20808+
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
20809+
20810+
if (root !== null) {
20811+
instance._pendingVisibility |= OffscreenDetached;
20812+
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
20813+
}
20814+
}
20815+
function attachOffscreenInstance(instance) {
20816+
var fiber = instance._current;
20817+
20818+
if (fiber === null) {
20819+
throw new Error(
20820+
"Calling Offscreen.detach before instance handle has been set."
20821+
);
20822+
}
20823+
20824+
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
20825+
// The instance is already attached, this is a noop.
20826+
return;
20827+
}
20828+
20829+
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
20830+
20831+
if (root !== null) {
20832+
instance._pendingVisibility &= ~OffscreenDetached;
20833+
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
2081920834
}
2082020835
}
2082120836

@@ -21170,14 +21185,19 @@ function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
2117021185
recursivelyTraverseMutationEffects(root, finishedWork);
2117121186
}
2117221187

21173-
commitReconciliationEffects(finishedWork); // TODO: Add explicit effect flag to set _current.
21188+
commitReconciliationEffects(finishedWork);
21189+
var offscreenInstance = finishedWork.stateNode; // TODO: Add explicit effect flag to set _current.
21190+
21191+
offscreenInstance._current = finishedWork; // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
21192+
// to support batching of `attach` and `detach` calls.
2117421193

21175-
finishedWork.stateNode._current = finishedWork;
21194+
offscreenInstance._visibility &= ~OffscreenDetached;
21195+
offscreenInstance._visibility |=
21196+
offscreenInstance._pendingVisibility & OffscreenDetached;
2117621197

2117721198
if (flags & Visibility) {
21178-
var offscreenInstance = finishedWork.stateNode; // Track the current state on the Offscreen instance so we can
21199+
// Track the current state on the Offscreen instance so we can
2117921200
// read it during an event
21180-
2118121201
if (_isHidden) {
2118221202
offscreenInstance._visibility &= ~OffscreenVisible;
2118321203
} else {
@@ -27211,12 +27231,16 @@ function createFiberFromOffscreen(pendingProps, mode, lanes, key) {
2721127231
fiber.lanes = lanes;
2721227232
var primaryChildInstance = {
2721327233
_visibility: OffscreenVisible,
27234+
_pendingVisibility: OffscreenVisible,
2721427235
_pendingMarkers: null,
2721527236
_retryCache: null,
2721627237
_transitions: null,
2721727238
_current: null,
2721827239
detach: function() {
2721927240
return detachOffscreenInstance(primaryChildInstance);
27241+
},
27242+
attach: function() {
27243+
return attachOffscreenInstance(primaryChildInstance);
2722027244
}
2722127245
};
2722227246
fiber.stateNode = primaryChildInstance;
@@ -27230,12 +27254,16 @@ function createFiberFromLegacyHidden(pendingProps, mode, lanes, key) {
2723027254

2723127255
var instance = {
2723227256
_visibility: OffscreenVisible,
27257+
_pendingVisibility: OffscreenVisible,
2723327258
_pendingMarkers: null,
2723427259
_transitions: null,
2723527260
_retryCache: null,
2723627261
_current: null,
2723727262
detach: function() {
2723827263
return detachOffscreenInstance(instance);
27264+
},
27265+
attach: function() {
27266+
return attachOffscreenInstance(instance);
2723927267
}
2724027268
};
2724127269
fiber.stateNode = instance;

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-modern-b14d7fa4b-20221209";
72+
var ReactVersion = "18.3.0-www-modern-996e4c0d5-20221212";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -2527,17 +2527,6 @@ function shim$1() {
25272527
var prepareScopeUpdate = shim$1;
25282528
var getInstanceFromScope = shim$1;
25292529

2530-
// Renderers that don't support microtasks
2531-
// can re-export everything from this module.
2532-
function shim$2() {
2533-
throw new Error(
2534-
"The current renderer does not support microtasks. " +
2535-
"This error is likely caused by a bug in React. " +
2536-
"Please file an issue."
2537-
);
2538-
} // Test selectors (when unsupported)
2539-
var scheduleMicrotask = shim$2;
2540-
25412530
var pooledTransform = new Transform();
25422531
var NO_CONTEXT = {};
25432532
var UPDATE_SIGNAL = {};
@@ -12834,13 +12823,15 @@ function updateSimpleMemoComponent(
1283412823
function updateOffscreenComponent(current, workInProgress, renderLanes) {
1283512824
var nextProps = workInProgress.pendingProps;
1283612825
var nextChildren = nextProps.children;
12826+
var nextIsDetached =
12827+
(workInProgress.stateNode._pendingVisibility & OffscreenDetached) !== 0;
1283712828
var prevState = current !== null ? current.memoizedState : null;
1283812829
markRef(current, workInProgress);
1283912830

1284012831
if (
1284112832
nextProps.mode === "hidden" ||
12842-
nextProps.mode === "unstable-defer-without-hiding" || // TODO: remove read from stateNode.
12843-
workInProgress.stateNode._visibility & OffscreenDetached
12833+
nextProps.mode === "unstable-defer-without-hiding" ||
12834+
nextIsDetached
1284412835
) {
1284512836
// Rendering a hidden tree.
1284612837
var didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
@@ -20489,22 +20480,46 @@ function getRetryCache(finishedWork) {
2048920480
}
2049020481

2049120482
function detachOffscreenInstance(instance) {
20492-
var currentOffscreenFiber = instance._current;
20483+
var fiber = instance._current;
2049320484

20494-
if (currentOffscreenFiber === null) {
20485+
if (fiber === null) {
2049520486
throw new Error(
2049620487
"Calling Offscreen.detach before instance handle has been set."
2049720488
);
2049820489
}
2049920490

20500-
var executionContext = getExecutionContext();
20491+
if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags) {
20492+
// The instance is already detached, this is a noop.
20493+
return;
20494+
} // TODO: There is an opportunity to optimise this by not entering commit phase
20495+
// and unmounting effects directly.
2050120496

20502-
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
20503-
scheduleMicrotask();
20504-
} else {
20505-
instance._visibility |= OffscreenDetached;
20506-
disappearLayoutEffects(currentOffscreenFiber);
20507-
disconnectPassiveEffect(currentOffscreenFiber);
20497+
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
20498+
20499+
if (root !== null) {
20500+
instance._pendingVisibility |= OffscreenDetached;
20501+
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
20502+
}
20503+
}
20504+
function attachOffscreenInstance(instance) {
20505+
var fiber = instance._current;
20506+
20507+
if (fiber === null) {
20508+
throw new Error(
20509+
"Calling Offscreen.detach before instance handle has been set."
20510+
);
20511+
}
20512+
20513+
if ((instance._pendingVisibility & OffscreenDetached) === NoFlags) {
20514+
// The instance is already attached, this is a noop.
20515+
return;
20516+
}
20517+
20518+
var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
20519+
20520+
if (root !== null) {
20521+
instance._pendingVisibility &= ~OffscreenDetached;
20522+
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
2050820523
}
2050920524
}
2051020525

@@ -20859,14 +20874,19 @@ function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
2085920874
recursivelyTraverseMutationEffects(root, finishedWork);
2086020875
}
2086120876

20862-
commitReconciliationEffects(finishedWork); // TODO: Add explicit effect flag to set _current.
20877+
commitReconciliationEffects(finishedWork);
20878+
var offscreenInstance = finishedWork.stateNode; // TODO: Add explicit effect flag to set _current.
20879+
20880+
offscreenInstance._current = finishedWork; // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
20881+
// to support batching of `attach` and `detach` calls.
2086320882

20864-
finishedWork.stateNode._current = finishedWork;
20883+
offscreenInstance._visibility &= ~OffscreenDetached;
20884+
offscreenInstance._visibility |=
20885+
offscreenInstance._pendingVisibility & OffscreenDetached;
2086520886

2086620887
if (flags & Visibility) {
20867-
var offscreenInstance = finishedWork.stateNode; // Track the current state on the Offscreen instance so we can
20888+
// Track the current state on the Offscreen instance so we can
2086820889
// read it during an event
20869-
2087020890
if (_isHidden) {
2087120891
offscreenInstance._visibility &= ~OffscreenVisible;
2087220892
} else {
@@ -26900,12 +26920,16 @@ function createFiberFromOffscreen(pendingProps, mode, lanes, key) {
2690026920
fiber.lanes = lanes;
2690126921
var primaryChildInstance = {
2690226922
_visibility: OffscreenVisible,
26923+
_pendingVisibility: OffscreenVisible,
2690326924
_pendingMarkers: null,
2690426925
_retryCache: null,
2690526926
_transitions: null,
2690626927
_current: null,
2690726928
detach: function() {
2690826929
return detachOffscreenInstance(primaryChildInstance);
26930+
},
26931+
attach: function() {
26932+
return attachOffscreenInstance(primaryChildInstance);
2690926933
}
2691026934
};
2691126935
fiber.stateNode = primaryChildInstance;
@@ -26919,12 +26943,16 @@ function createFiberFromLegacyHidden(pendingProps, mode, lanes, key) {
2691926943

2692026944
var instance = {
2692126945
_visibility: OffscreenVisible,
26946+
_pendingVisibility: OffscreenVisible,
2692226947
_pendingMarkers: null,
2692326948
_transitions: null,
2692426949
_retryCache: null,
2692526950
_current: null,
2692626951
detach: function() {
2692726952
return detachOffscreenInstance(instance);
26953+
},
26954+
attach: function() {
26955+
return attachOffscreenInstance(instance);
2692826956
}
2692926957
};
2693026958
fiber.stateNode = instance;

0 commit comments

Comments
 (0)