Suspensily committing a prerendered tree (#26434)

Prerendering a tree (i.e. with Offscreen) should not suspend the commit
phase, because the content is not yet visible. However, when revealing a
prerendered tree, we should suspend the commit phase if resources in the
prerendered tree haven't finished loading yet.

To do this properly, we need to visit all the visible nodes in the tree
that might possibly suspend. This includes nodes in the current tree,
because even though they were already "mounted", the resources might not
have loaded yet, because we didn't suspend when it was prerendered.

We will need to add this capability to the Offscreen component's
"manual" mode, too. Something like a `ready()` method that returns a
promise that resolves when the tree has fully loaded.

Also includes some fixes to #26450. See PR for details.

DiffTrain build for [768f965de2](https://github.com/facebook/react/commit/768f965de2d4c6be7f688562ef02382478c82e5b)
This commit is contained in:
acdlite
2023-03-27 03:53:29 +00:00
parent a8b6d3f91e
commit 49e6dee1af
18 changed files with 3319 additions and 2367 deletions
+1 -1
View File
@@ -1 +1 @@
d12bdcda69afd219f4d91cbd60d6fae2a375d35b
768f965de2d4c6be7f688562ef02382478c82e5b
+1 -1
View File
@@ -27,7 +27,7 @@ if (
}
"use strict";
var ReactVersion = "18.3.0-www-modern-770d70a2";
var ReactVersion = "18.3.0-www-modern-4cb7601b";
// ATTENTION
// When adding new symbols to this file,
+80 -61
View File
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}
var ReactVersion = "18.3.0-www-classic-dfeb256e";
var ReactVersion = "18.3.0-www-classic-e4add0e3";
var LegacyRoot = 0;
var ConcurrentRoot = 1;
@@ -543,11 +543,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -587,8 +588,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -624,7 +625,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -2934,9 +2935,6 @@ function unhideTextInstance(textInstance, text) {
function getInstanceFromNode(node) {
throw new Error("Not implemented.");
}
function maySuspendCommit(type, props) {
return false;
}
function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
@@ -5590,13 +5588,6 @@ function trackUsedThenable(thenableState, thenable, index) {
}
}
}
function suspendCommit() {
// This extra indirection only exists so it can handle passing
// noopSuspenseyCommitThenable through to throwException.
// TODO: Factor the thenable check out of throwException
suspendedThenable = noopSuspenseyCommitThenable;
throw SuspenseyCommitException;
} // This is used to track the actual thenable that suspended so it can be
// passed to the rest of the Suspense implementation — which, for historical
// reasons, expects to receive a thenable.
@@ -17697,7 +17688,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -17708,28 +17703,16 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
if (!includesOnlyNonUrgentLanes(renderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
suspendCommit();
}
}
}
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
function scheduleRetryEffect(workInProgress, retryQueue) {
@@ -18160,12 +18143,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -18201,7 +18182,7 @@ function completeWork(current, workInProgress, renderLanes) {
} else {
getRootHostContainer();
var _instance3 = createInstance(_type, newProps);
var _instance3 = createInstance(_type2, newProps);
appendAllChildren(_instance3, workInProgress);
workInProgress.stateNode = _instance3; // Certain renderers require commit-time effects for initial mount.
@@ -18218,17 +18199,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -22603,13 +22574,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -22624,7 +22606,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource();
}
@@ -22640,8 +22622,36 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
}
case HostRoot:
case HostPortal:
// eslint-disable-next-line-no-fallthrough
case HostPortal: {
{
recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -24707,15 +24717,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
+80 -61
View File
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}
var ReactVersion = "18.3.0-www-modern-fc6afcc9";
var ReactVersion = "18.3.0-www-modern-7a6ee7b0";
var LegacyRoot = 0;
var ConcurrentRoot = 1;
@@ -543,11 +543,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -587,8 +588,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -624,7 +625,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -2931,9 +2932,6 @@ function unhideTextInstance(textInstance, text) {
function getInstanceFromNode(node) {
throw new Error("Not implemented.");
}
function maySuspendCommit(type, props) {
return false;
}
function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
@@ -5346,13 +5344,6 @@ function trackUsedThenable(thenableState, thenable, index) {
}
}
}
function suspendCommit() {
// This extra indirection only exists so it can handle passing
// noopSuspenseyCommitThenable through to throwException.
// TODO: Factor the thenable check out of throwException
suspendedThenable = noopSuspenseyCommitThenable;
throw SuspenseyCommitException;
} // This is used to track the actual thenable that suspended so it can be
// passed to the rest of the Suspense implementation — which, for historical
// reasons, expects to receive a thenable.
@@ -17386,7 +17377,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -17397,28 +17392,16 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
if (!includesOnlyNonUrgentLanes(renderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
suspendCommit();
}
}
}
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
function scheduleRetryEffect(workInProgress, retryQueue) {
@@ -17842,12 +17825,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -17883,7 +17864,7 @@ function completeWork(current, workInProgress, renderLanes) {
} else {
getRootHostContainer();
var _instance3 = createInstance(_type, newProps);
var _instance3 = createInstance(_type2, newProps);
appendAllChildren(_instance3, workInProgress);
workInProgress.stateNode = _instance3; // Certain renderers require commit-time effects for initial mount.
@@ -17900,17 +17881,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -22263,13 +22234,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -22284,7 +22266,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource();
}
@@ -22300,8 +22282,36 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
}
case HostRoot:
case HostPortal:
// eslint-disable-next-line-no-fallthrough
case HostPortal: {
{
recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -24367,15 +24377,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
+39 -22
View File
@@ -7473,8 +7473,9 @@ function recursivelyTraverseAtomicPassiveEffects(
parentFiber = parentFiber.sibling;
}
}
var suspenseyCommitFlag = 8192;
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & 16777216)
if (parentFiber.subtreeFlags & suspenseyCommitFlag)
for (parentFiber = parentFiber.child; null !== parentFiber; )
accumulateSuspenseyCommitOnFiber(parentFiber),
(parentFiber = parentFiber.sibling);
@@ -7483,12 +7484,27 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
switch (fiber.tag) {
case 26:
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & 16777216 && null !== fiber.memoizedState)
if (fiber.flags & suspenseyCommitFlag && null !== fiber.memoizedState)
throw Error(formatProdErrorMessage(442));
break;
case 5:
recursivelyAccumulateSuspenseyCommit(fiber);
break;
case 3:
case 4:
recursivelyAccumulateSuspenseyCommit(fiber);
break;
case 22:
if (null === fiber.memoizedState) {
var current = fiber.alternate;
null !== current && null !== current.memoizedState
? ((current = suspenseyCommitFlag),
(suspenseyCommitFlag = 16777216),
recursivelyAccumulateSuspenseyCommit(fiber),
(suspenseyCommitFlag = current))
: recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
default:
recursivelyAccumulateSuspenseyCommit(fiber);
}
@@ -8302,11 +8318,12 @@ function handleThrow(root, thrownValue) {
? (root = null === shellBoundary ? !0 : !1)
: ((root = suspenseHandlerStackCursor.current),
(root =
null !== root &&
(workInProgressRootRenderLanes & 125829120) ===
workInProgressRootRenderLanes
? root === shellBoundary
: !1)),
null === root ||
((workInProgressRootRenderLanes & 125829120) !==
workInProgressRootRenderLanes &&
0 === (workInProgressRootRenderLanes & 1073741824))
? !1
: root === shellBoundary)),
(workInProgressSuspendedReason =
root &&
0 === (workInProgressRootSkippedLanes & 268435455) &&
@@ -10016,19 +10033,19 @@ var slice = Array.prototype.slice,
};
return Text;
})(React.Component),
devToolsConfig$jscomp$inline_1141 = {
devToolsConfig$jscomp$inline_1146 = {
findFiberByHostInstance: function () {
return null;
},
bundleType: 0,
version: "18.3.0-www-classic-2dcecc61",
version: "18.3.0-www-classic-9b739764",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1319 = {
bundleType: devToolsConfig$jscomp$inline_1141.bundleType,
version: devToolsConfig$jscomp$inline_1141.version,
rendererPackageName: devToolsConfig$jscomp$inline_1141.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1141.rendererConfig,
var internals$jscomp$inline_1324 = {
bundleType: devToolsConfig$jscomp$inline_1146.bundleType,
version: devToolsConfig$jscomp$inline_1146.version,
rendererPackageName: devToolsConfig$jscomp$inline_1146.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1146.rendererConfig,
overrideHookState: null,
overrideHookStateDeletePath: null,
overrideHookStateRenamePath: null,
@@ -10045,26 +10062,26 @@ var internals$jscomp$inline_1319 = {
return null === fiber ? null : fiber.stateNode;
},
findFiberByHostInstance:
devToolsConfig$jscomp$inline_1141.findFiberByHostInstance ||
devToolsConfig$jscomp$inline_1146.findFiberByHostInstance ||
emptyFindFiberByHostInstance,
findHostInstancesForRefresh: null,
scheduleRefresh: null,
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-www-classic-2dcecc61"
reconcilerVersion: "18.3.0-www-classic-9b739764"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1320 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_1325 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_1320.isDisabled &&
hook$jscomp$inline_1320.supportsFiber
!hook$jscomp$inline_1325.isDisabled &&
hook$jscomp$inline_1325.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_1320.inject(
internals$jscomp$inline_1319
(rendererID = hook$jscomp$inline_1325.inject(
internals$jscomp$inline_1324
)),
(injectedHook = hook$jscomp$inline_1320);
(injectedHook = hook$jscomp$inline_1325);
} catch (err) {}
}
var Path = Mode$1.Path;
+39 -22
View File
@@ -7205,8 +7205,9 @@ function recursivelyTraverseAtomicPassiveEffects(
parentFiber = parentFiber.sibling;
}
}
var suspenseyCommitFlag = 8192;
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & 16777216)
if (parentFiber.subtreeFlags & suspenseyCommitFlag)
for (parentFiber = parentFiber.child; null !== parentFiber; )
accumulateSuspenseyCommitOnFiber(parentFiber),
(parentFiber = parentFiber.sibling);
@@ -7215,12 +7216,27 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
switch (fiber.tag) {
case 26:
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & 16777216 && null !== fiber.memoizedState)
if (fiber.flags & suspenseyCommitFlag && null !== fiber.memoizedState)
throw Error(formatProdErrorMessage(442));
break;
case 5:
recursivelyAccumulateSuspenseyCommit(fiber);
break;
case 3:
case 4:
recursivelyAccumulateSuspenseyCommit(fiber);
break;
case 22:
if (null === fiber.memoizedState) {
var current = fiber.alternate;
null !== current && null !== current.memoizedState
? ((current = suspenseyCommitFlag),
(suspenseyCommitFlag = 16777216),
recursivelyAccumulateSuspenseyCommit(fiber),
(suspenseyCommitFlag = current))
: recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
default:
recursivelyAccumulateSuspenseyCommit(fiber);
}
@@ -8034,11 +8050,12 @@ function handleThrow(root, thrownValue) {
? (root = null === shellBoundary ? !0 : !1)
: ((root = suspenseHandlerStackCursor.current),
(root =
null !== root &&
(workInProgressRootRenderLanes & 125829120) ===
workInProgressRootRenderLanes
? root === shellBoundary
: !1)),
null === root ||
((workInProgressRootRenderLanes & 125829120) !==
workInProgressRootRenderLanes &&
0 === (workInProgressRootRenderLanes & 1073741824))
? !1
: root === shellBoundary)),
(workInProgressSuspendedReason =
root &&
0 === (workInProgressRootSkippedLanes & 268435455) &&
@@ -9681,19 +9698,19 @@ var slice = Array.prototype.slice,
};
return Text;
})(React.Component),
devToolsConfig$jscomp$inline_1121 = {
devToolsConfig$jscomp$inline_1126 = {
findFiberByHostInstance: function () {
return null;
},
bundleType: 0,
version: "18.3.0-www-modern-ef21bdef",
version: "18.3.0-www-modern-68e129ab",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1299 = {
bundleType: devToolsConfig$jscomp$inline_1121.bundleType,
version: devToolsConfig$jscomp$inline_1121.version,
rendererPackageName: devToolsConfig$jscomp$inline_1121.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1121.rendererConfig,
var internals$jscomp$inline_1304 = {
bundleType: devToolsConfig$jscomp$inline_1126.bundleType,
version: devToolsConfig$jscomp$inline_1126.version,
rendererPackageName: devToolsConfig$jscomp$inline_1126.rendererPackageName,
rendererConfig: devToolsConfig$jscomp$inline_1126.rendererConfig,
overrideHookState: null,
overrideHookStateDeletePath: null,
overrideHookStateRenamePath: null,
@@ -9710,26 +9727,26 @@ var internals$jscomp$inline_1299 = {
return null === fiber ? null : fiber.stateNode;
},
findFiberByHostInstance:
devToolsConfig$jscomp$inline_1121.findFiberByHostInstance ||
devToolsConfig$jscomp$inline_1126.findFiberByHostInstance ||
emptyFindFiberByHostInstance,
findHostInstancesForRefresh: null,
scheduleRefresh: null,
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-www-modern-ef21bdef"
reconcilerVersion: "18.3.0-www-modern-68e129ab"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1300 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_1305 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_1300.isDisabled &&
hook$jscomp$inline_1300.supportsFiber
!hook$jscomp$inline_1305.isDisabled &&
hook$jscomp$inline_1305.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_1300.inject(
internals$jscomp$inline_1299
(rendererID = hook$jscomp$inline_1305.inject(
internals$jscomp$inline_1304
)),
(injectedHook = hook$jscomp$inline_1300);
(injectedHook = hook$jscomp$inline_1305);
} catch (err) {}
}
var Path = Mode$1.Path;
+203 -97
View File
@@ -513,11 +513,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -557,8 +558,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -594,7 +595,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -11464,6 +11465,7 @@ function preinit$1(href, options) {
link.addEventListener("error", function () {
state.loading |= Errored;
});
state.loading |= Inserted;
insertStylesheet(_instance, precedence, resourceRoot);
} // Construct a Resource and cache it
@@ -11795,7 +11797,11 @@ function acquireResource(hoistableRoot, resource, props) {
var ownerDocument = getDocumentFromRoot(hoistableRoot);
instance = ownerDocument.createElement("style");
markNodeAsHoistable(instance);
setInitialProperties(instance, "style", styleProps);
setInitialProperties(instance, "style", styleProps); // TODO: `style` does not have loading state for tracking insertions. I
// guess because these aren't suspensey? Not sure whether this is a
// factoring smell.
// resource.state.loading |= Inserted;
insertStylesheet(instance, qualifiedProps.precedence, hoistableRoot);
resource.instance = instance;
return instance;
@@ -11836,6 +11842,7 @@ function acquireResource(hoistableRoot, resource, props) {
linkInstance.onerror = reject;
});
setInitialProperties(_instance3, "link", stylesheetProps);
resource.state.loading |= Inserted;
insertStylesheet(_instance3, _qualifiedProps.precedence, hoistableRoot);
resource.instance = _instance3;
return _instance3;
@@ -11892,6 +11899,28 @@ function acquireResource(hoistableRoot, resource, props) {
);
}
}
} else {
// In the case of stylesheets, they might have already been assigned an
// instance during `suspendResource`. But that doesn't mean they were
// inserted, because the commit might have been interrupted. So we need to
// check now.
//
// The other resource types are unaffected because they are not
// yet suspensey.
//
// TODO: This is a bit of a code smell. Consider refactoring how
// `suspendResource` and `acquireResource` work together. The idea is that
// `suspendResource` does all the same stuff as `acquireResource` except
// for the insertion.
if (
resource.type === "stylesheet" &&
(resource.state.loading & Inserted) === NotLoaded
) {
var _qualifiedProps2 = props;
var _instance5 = resource.instance;
resource.state.loading |= Inserted;
insertStylesheet(_instance5, _qualifiedProps2.precedence, hoistableRoot);
}
}
return resource.instance;
@@ -12339,9 +12368,6 @@ function isHostHoistableType(type, props, hostContext) {
return false;
}
function maySuspendCommit(type, props) {
return false;
}
function mayResourceSuspendCommit(resource) {
return (
resource.type === "stylesheet" &&
@@ -12352,6 +12378,17 @@ function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
}
function preloadResource(resource) {
if (
resource.type === "stylesheet" &&
(resource.state.loading & Settled) === NotLoaded
) {
// we have not finished loading the underlying stylesheet yet.
return false;
} // Return true to indicate it's already loaded
return true;
}
var suspendedState = null; // We use a noop function when we begin suspending because if possible we want the
// waitfor step to finish synchronously. If it doesn't we'll return a function to
// provide the actual unsuspend function and that will get completed when the count
@@ -28454,7 +28491,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -28465,24 +28506,42 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
if (!includesOnlyNonUrgentLanes(renderLanes));
function preloadResourceAndSuspendIfNeeded(
workInProgress,
resource,
type,
props,
renderLanes
) {
// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
if (!mayResourceSuspendCommit(resource)) {
workInProgress.flags &= ~MaySuspendCommit;
return;
}
workInProgress.flags |= MaySuspendCommit;
var rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesOnlyNonUrgentLanes(rootRenderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
var isReady = preloadResource(resource);
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
}
@@ -28937,58 +28996,74 @@ function completeWork(current, workInProgress, renderLanes) {
case HostHoistable: {
{
var currentRef = current ? current.ref : null;
// The branching here is more complicated than you might expect because
// a HostHoistable sometimes corresponds to a Resource and sometimes
// corresponds to an Instance. It can also switch during an update.
var type = workInProgress.type;
var nextResource = workInProgress.memoizedState;
if (currentRef !== workInProgress.ref) {
markRef(workInProgress);
}
var maySuspend = false; // @TODO refactor this block to create the instance here in complete phase if we
// are not hydrating.
if (
if (current === null) {
// We are mounting and must Update this Hoistable in this commit
current === null || // We are transitioning to, from, or between Hoistable Resources
// and require an update
current.memoizedState !== workInProgress.memoizedState
) {
if (workInProgress.memoizedState !== null) {
maySuspend = mayResourceSuspendCommit(workInProgress.memoizedState);
} else {
maySuspend = maySuspendCommit();
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
markUpdate(workInProgress);
} else if (workInProgress.memoizedState === null) {
maySuspend = maySuspendCommit(); // We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables
updateHostComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps
);
}
bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
return null;
} else {
// This is a Hoistable Instance
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
// We are updating.
var currentResource = current.memoizedState;
return null;
if (nextResource !== currentResource) {
// We are transitioning to, from, or between Hoistable Resources
// and require an update
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
if (nextResource === currentResource) {
workInProgress.flags &= ~MaySuspendCommit;
} else {
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
}
return null;
} else {
// This is a Hoistable Instance
//
// We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables.
updateHostComponent(current, workInProgress, type, newProps); // This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
}
}
}
// eslint-disable-next-line-no-fallthrough
@@ -28997,10 +29072,10 @@ function completeWork(current, workInProgress, renderLanes) {
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
var _type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
updateHostComponent(current, workInProgress, _type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -29032,7 +29107,7 @@ function completeWork(current, workInProgress, renderLanes) {
instance = workInProgress.stateNode;
} else {
instance = resolveSingletonInstance(
type,
_type,
newProps,
rootContainerInstance,
currentHostContext,
@@ -29056,12 +29131,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -29100,7 +29173,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -29112,7 +29185,7 @@ function completeWork(current, workInProgress, renderLanes) {
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(_instance3, _type, newProps)) {
if (finalizeInitialChildren(_instance3, _type2, newProps)) {
markUpdate(workInProgress);
}
}
@@ -29128,17 +29201,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -33878,13 +33941,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -33899,7 +33973,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
// This should always be set by visiting HostRoot first
@@ -33927,10 +34001,33 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(fiber);
currentHoistableRoot = previousHoistableRoot;
break;
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
// eslint-disable-next-line-no-fallthrough
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -36098,15 +36195,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -39564,7 +39670,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-classic-30e8fd15";
var ReactVersion = "18.3.0-www-classic-f9e1fdbb";
function createPortal$1(
children,
+203 -97
View File
@@ -330,11 +330,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -374,8 +375,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -411,7 +412,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
// This module only exists as an ESM wrapper around the external CommonJS
var scheduleCallback$2 = Scheduler.unstable_scheduleCallback;
@@ -22558,7 +22559,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -22569,24 +22574,42 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
if (!includesOnlyNonUrgentLanes(renderLanes));
function preloadResourceAndSuspendIfNeeded(
workInProgress,
resource,
type,
props,
renderLanes
) {
// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
if (!mayResourceSuspendCommit(resource)) {
workInProgress.flags &= ~MaySuspendCommit;
return;
}
workInProgress.flags |= MaySuspendCommit;
var rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesOnlyNonUrgentLanes(rootRenderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
var isReady = preloadResource(resource);
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
}
@@ -23034,58 +23057,74 @@ function completeWork(current, workInProgress, renderLanes) {
case HostHoistable: {
{
var currentRef = current ? current.ref : null;
// The branching here is more complicated than you might expect because
// a HostHoistable sometimes corresponds to a Resource and sometimes
// corresponds to an Instance. It can also switch during an update.
var type = workInProgress.type;
var nextResource = workInProgress.memoizedState;
if (currentRef !== workInProgress.ref) {
markRef(workInProgress);
}
var maySuspend = false; // @TODO refactor this block to create the instance here in complete phase if we
// are not hydrating.
if (
if (current === null) {
// We are mounting and must Update this Hoistable in this commit
current === null || // We are transitioning to, from, or between Hoistable Resources
// and require an update
current.memoizedState !== workInProgress.memoizedState
) {
if (workInProgress.memoizedState !== null) {
maySuspend = mayResourceSuspendCommit(workInProgress.memoizedState);
} else {
maySuspend = maySuspendCommit();
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
markUpdate(workInProgress);
} else if (workInProgress.memoizedState === null) {
maySuspend = maySuspendCommit(); // We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables
updateHostComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps
);
}
bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
return null;
} else {
// This is a Hoistable Instance
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
// We are updating.
var currentResource = current.memoizedState;
return null;
if (nextResource !== currentResource) {
// We are transitioning to, from, or between Hoistable Resources
// and require an update
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
if (nextResource === currentResource) {
workInProgress.flags &= ~MaySuspendCommit;
} else {
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
}
return null;
} else {
// This is a Hoistable Instance
//
// We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables.
updateHostComponent(current, workInProgress, type, newProps); // This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
}
}
}
// eslint-disable-next-line-no-fallthrough
@@ -23094,10 +23133,10 @@ function completeWork(current, workInProgress, renderLanes) {
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
var _type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
updateHostComponent(current, workInProgress, _type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -23129,7 +23168,7 @@ function completeWork(current, workInProgress, renderLanes) {
instance = workInProgress.stateNode;
} else {
instance = resolveSingletonInstance(
type,
_type,
newProps,
rootContainerInstance,
currentHostContext,
@@ -23153,12 +23192,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -23197,7 +23234,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -23209,7 +23246,7 @@ function completeWork(current, workInProgress, renderLanes) {
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(_instance3, _type, newProps)) {
if (finalizeInitialChildren(_instance3, _type2, newProps)) {
markUpdate(workInProgress);
}
}
@@ -23225,17 +23262,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -27953,13 +27980,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -27974,7 +28012,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
// This should always be set by visiting HostRoot first
@@ -28002,10 +28040,33 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(fiber);
currentHoistableRoot = previousHoistableRoot;
break;
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
// eslint-disable-next-line-no-fallthrough
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -30173,15 +30234,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -33639,7 +33709,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-modern-ef21bdef";
var ReactVersion = "18.3.0-www-modern-68e129ab";
function createPortal$1(
children,
@@ -42813,6 +42883,7 @@ function preinit$1(href, options) {
link.addEventListener("error", function () {
state.loading |= Errored;
});
state.loading |= Inserted;
insertStylesheet(_instance, precedence, resourceRoot);
} // Construct a Resource and cache it
@@ -43144,7 +43215,11 @@ function acquireResource(hoistableRoot, resource, props) {
var ownerDocument = getDocumentFromRoot(hoistableRoot);
instance = ownerDocument.createElement("style");
markNodeAsHoistable(instance);
setInitialProperties(instance, "style", styleProps);
setInitialProperties(instance, "style", styleProps); // TODO: `style` does not have loading state for tracking insertions. I
// guess because these aren't suspensey? Not sure whether this is a
// factoring smell.
// resource.state.loading |= Inserted;
insertStylesheet(instance, qualifiedProps.precedence, hoistableRoot);
resource.instance = instance;
return instance;
@@ -43185,6 +43260,7 @@ function acquireResource(hoistableRoot, resource, props) {
linkInstance.onerror = reject;
});
setInitialProperties(_instance3, "link", stylesheetProps);
resource.state.loading |= Inserted;
insertStylesheet(_instance3, _qualifiedProps.precedence, hoistableRoot);
resource.instance = _instance3;
return _instance3;
@@ -43241,6 +43317,28 @@ function acquireResource(hoistableRoot, resource, props) {
);
}
}
} else {
// In the case of stylesheets, they might have already been assigned an
// instance during `suspendResource`. But that doesn't mean they were
// inserted, because the commit might have been interrupted. So we need to
// check now.
//
// The other resource types are unaffected because they are not
// yet suspensey.
//
// TODO: This is a bit of a code smell. Consider refactoring how
// `suspendResource` and `acquireResource` work together. The idea is that
// `suspendResource` does all the same stuff as `acquireResource` except
// for the insertion.
if (
resource.type === "stylesheet" &&
(resource.state.loading & Inserted) === NotLoaded
) {
var _qualifiedProps2 = props;
var _instance5 = resource.instance;
resource.state.loading |= Inserted;
insertStylesheet(_instance5, _qualifiedProps2.precedence, hoistableRoot);
}
}
return resource.instance;
@@ -43688,9 +43786,6 @@ function isHostHoistableType(type, props, hostContext) {
return false;
}
function maySuspendCommit(type, props) {
return false;
}
function mayResourceSuspendCommit(resource) {
return (
resource.type === "stylesheet" &&
@@ -43701,6 +43796,17 @@ function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
}
function preloadResource(resource) {
if (
resource.type === "stylesheet" &&
(resource.state.loading & Settled) === NotLoaded
) {
// we have not finished loading the underlying stylesheet yet.
return false;
} // Return true to indicate it's already loaded
return true;
}
var suspendedState = null; // We use a noop function when we begin suspending because if possible we want the
// waitfor step to finish synchronously. If it doesn't we'll return a function to
// provide the actual unsuspend function and that will get completed when the count
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -502,11 +502,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -546,8 +547,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -583,7 +584,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -18858,7 +18859,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -18869,24 +18874,42 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
if (!includesOnlyNonUrgentLanes(renderLanes));
function preloadResourceAndSuspendIfNeeded(
workInProgress,
resource,
type,
props,
renderLanes
) {
// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
if (!mayResourceSuspendCommit(resource)) {
workInProgress.flags &= ~MaySuspendCommit;
return;
}
workInProgress.flags |= MaySuspendCommit;
var rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesOnlyNonUrgentLanes(rootRenderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
var isReady = preloadResource(resource);
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
}
@@ -19341,58 +19364,74 @@ function completeWork(current, workInProgress, renderLanes) {
case HostHoistable: {
{
var currentRef = current ? current.ref : null;
// The branching here is more complicated than you might expect because
// a HostHoistable sometimes corresponds to a Resource and sometimes
// corresponds to an Instance. It can also switch during an update.
var type = workInProgress.type;
var nextResource = workInProgress.memoizedState;
if (currentRef !== workInProgress.ref) {
markRef(workInProgress);
}
var maySuspend = false; // @TODO refactor this block to create the instance here in complete phase if we
// are not hydrating.
if (
if (current === null) {
// We are mounting and must Update this Hoistable in this commit
current === null || // We are transitioning to, from, or between Hoistable Resources
// and require an update
current.memoizedState !== workInProgress.memoizedState
) {
if (workInProgress.memoizedState !== null) {
maySuspend = mayResourceSuspendCommit(workInProgress.memoizedState);
} else {
maySuspend = maySuspendCommit();
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
markUpdate(workInProgress);
} else if (workInProgress.memoizedState === null) {
maySuspend = maySuspendCommit(); // We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables
updateHostComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps
);
}
bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
return null;
} else {
// This is a Hoistable Instance
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
// We are updating.
var currentResource = current.memoizedState;
return null;
if (nextResource !== currentResource) {
// We are transitioning to, from, or between Hoistable Resources
// and require an update
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
if (nextResource === currentResource) {
workInProgress.flags &= ~MaySuspendCommit;
} else {
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
}
return null;
} else {
// This is a Hoistable Instance
//
// We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables.
updateHostComponent(current, workInProgress, type, newProps); // This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
}
}
}
// eslint-disable-next-line-no-fallthrough
@@ -19401,10 +19440,10 @@ function completeWork(current, workInProgress, renderLanes) {
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
var _type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
updateHostComponent(current, workInProgress, _type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -19436,7 +19475,7 @@ function completeWork(current, workInProgress, renderLanes) {
instance = workInProgress.stateNode;
} else {
instance = resolveSingletonInstance(
type,
_type,
newProps,
rootContainerInstance,
currentHostContext,
@@ -19460,12 +19499,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -19504,7 +19541,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -19516,7 +19553,7 @@ function completeWork(current, workInProgress, renderLanes) {
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(_instance3, _type, newProps)) {
if (finalizeInitialChildren(_instance3, _type2, newProps)) {
markUpdate(workInProgress);
}
}
@@ -19532,17 +19569,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -24282,13 +24309,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -24303,7 +24341,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
// This should always be set by visiting HostRoot first
@@ -24331,10 +24369,33 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(fiber);
currentHoistableRoot = previousHoistableRoot;
break;
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
// eslint-disable-next-line-no-fallthrough
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -26985,15 +27046,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -30451,7 +30521,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-classic-173231f6";
var ReactVersion = "18.3.0-www-classic-a0e3bd11";
function createPortal$1(
children,
@@ -40589,6 +40659,7 @@ function preinit(href, options) {
link.addEventListener("error", function () {
state.loading |= Errored;
});
state.loading |= Inserted;
insertStylesheet(_instance, precedence, resourceRoot);
} // Construct a Resource and cache it
@@ -40920,7 +40991,11 @@ function acquireResource(hoistableRoot, resource, props) {
var ownerDocument = getDocumentFromRoot(hoistableRoot);
instance = ownerDocument.createElement("style");
markNodeAsHoistable(instance);
setInitialProperties(instance, "style", styleProps);
setInitialProperties(instance, "style", styleProps); // TODO: `style` does not have loading state for tracking insertions. I
// guess because these aren't suspensey? Not sure whether this is a
// factoring smell.
// resource.state.loading |= Inserted;
insertStylesheet(instance, qualifiedProps.precedence, hoistableRoot);
resource.instance = instance;
return instance;
@@ -40961,6 +41036,7 @@ function acquireResource(hoistableRoot, resource, props) {
linkInstance.onerror = reject;
});
setInitialProperties(_instance3, "link", stylesheetProps);
resource.state.loading |= Inserted;
insertStylesheet(_instance3, _qualifiedProps.precedence, hoistableRoot);
resource.instance = _instance3;
return _instance3;
@@ -41017,6 +41093,28 @@ function acquireResource(hoistableRoot, resource, props) {
);
}
}
} else {
// In the case of stylesheets, they might have already been assigned an
// instance during `suspendResource`. But that doesn't mean they were
// inserted, because the commit might have been interrupted. So we need to
// check now.
//
// The other resource types are unaffected because they are not
// yet suspensey.
//
// TODO: This is a bit of a code smell. Consider refactoring how
// `suspendResource` and `acquireResource` work together. The idea is that
// `suspendResource` does all the same stuff as `acquireResource` except
// for the insertion.
if (
resource.type === "stylesheet" &&
(resource.state.loading & Inserted) === NotLoaded
) {
var _qualifiedProps2 = props;
var _instance5 = resource.instance;
resource.state.loading |= Inserted;
insertStylesheet(_instance5, _qualifiedProps2.precedence, hoistableRoot);
}
}
return resource.instance;
@@ -41464,9 +41562,6 @@ function isHostHoistableType(type, props, hostContext) {
return false;
}
function maySuspendCommit(type, props) {
return false;
}
function mayResourceSuspendCommit(resource) {
return (
resource.type === "stylesheet" &&
@@ -41477,6 +41572,17 @@ function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
}
function preloadResource(resource) {
if (
resource.type === "stylesheet" &&
(resource.state.loading & Settled) === NotLoaded
) {
// we have not finished loading the underlying stylesheet yet.
return false;
} // Return true to indicate it's already loaded
return true;
}
var suspendedState = null; // We use a noop function when we begin suspending because if possible we want the
// waitfor step to finish synchronously. If it doesn't we'll return a function to
// provide the actual unsuspend function and that will get completed when the count
@@ -319,11 +319,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -363,8 +364,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -400,7 +401,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
// This module only exists as an ESM wrapper around the external CommonJS
var scheduleCallback$2 = Scheduler.unstable_scheduleCallback;
@@ -22694,7 +22695,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -22705,24 +22710,42 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
if (!includesOnlyNonUrgentLanes(renderLanes));
function preloadResourceAndSuspendIfNeeded(
workInProgress,
resource,
type,
props,
renderLanes
) {
// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
if (!mayResourceSuspendCommit(resource)) {
workInProgress.flags &= ~MaySuspendCommit;
return;
}
workInProgress.flags |= MaySuspendCommit;
var rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesOnlyNonUrgentLanes(rootRenderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
var isReady = preloadResource(resource);
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
}
@@ -23170,58 +23193,74 @@ function completeWork(current, workInProgress, renderLanes) {
case HostHoistable: {
{
var currentRef = current ? current.ref : null;
// The branching here is more complicated than you might expect because
// a HostHoistable sometimes corresponds to a Resource and sometimes
// corresponds to an Instance. It can also switch during an update.
var type = workInProgress.type;
var nextResource = workInProgress.memoizedState;
if (currentRef !== workInProgress.ref) {
markRef(workInProgress);
}
var maySuspend = false; // @TODO refactor this block to create the instance here in complete phase if we
// are not hydrating.
if (
if (current === null) {
// We are mounting and must Update this Hoistable in this commit
current === null || // We are transitioning to, from, or between Hoistable Resources
// and require an update
current.memoizedState !== workInProgress.memoizedState
) {
if (workInProgress.memoizedState !== null) {
maySuspend = mayResourceSuspendCommit(workInProgress.memoizedState);
} else {
maySuspend = maySuspendCommit();
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
markUpdate(workInProgress);
} else if (workInProgress.memoizedState === null) {
maySuspend = maySuspendCommit(); // We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables
updateHostComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps
);
}
bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
return null;
} else {
// This is a Hoistable Instance
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
// We are updating.
var currentResource = current.memoizedState;
return null;
if (nextResource !== currentResource) {
// We are transitioning to, from, or between Hoistable Resources
// and require an update
markUpdate(workInProgress);
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
if (nextResource === currentResource) {
workInProgress.flags &= ~MaySuspendCommit;
} else {
preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
}
return null;
} else {
// This is a Hoistable Instance
//
// We may have props to update on the Hoistable instance. We use the
// updateHostComponent path becuase it produces the update queue
// we need for Hoistables.
updateHostComponent(current, workInProgress, type, newProps); // This must come at the very end of the complete phase.
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
}
}
}
// eslint-disable-next-line-no-fallthrough
@@ -23230,10 +23269,10 @@ function completeWork(current, workInProgress, renderLanes) {
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
var _type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps);
updateHostComponent(current, workInProgress, _type, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -23265,7 +23304,7 @@ function completeWork(current, workInProgress, renderLanes) {
instance = workInProgress.stateNode;
} else {
instance = resolveSingletonInstance(
type,
_type,
newProps,
rootContainerInstance,
currentHostContext,
@@ -23289,12 +23328,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -23333,7 +23370,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -23345,7 +23382,7 @@ function completeWork(current, workInProgress, renderLanes) {
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(_instance3, _type, newProps)) {
if (finalizeInitialChildren(_instance3, _type2, newProps)) {
markUpdate(workInProgress);
}
}
@@ -23361,17 +23398,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -28089,13 +28116,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -28110,7 +28148,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
// This should always be set by visiting HostRoot first
@@ -28138,10 +28176,33 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(fiber);
currentHoistableRoot = previousHoistableRoot;
break;
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
// eslint-disable-next-line-no-fallthrough
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -30792,15 +30853,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -34258,7 +34328,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-modern-2789db5d";
var ReactVersion = "18.3.0-www-modern-c961e1cf";
function createPortal$1(
children,
@@ -43564,6 +43634,7 @@ function preinit$1(href, options) {
link.addEventListener("error", function () {
state.loading |= Errored;
});
state.loading |= Inserted;
insertStylesheet(_instance, precedence, resourceRoot);
} // Construct a Resource and cache it
@@ -43895,7 +43966,11 @@ function acquireResource(hoistableRoot, resource, props) {
var ownerDocument = getDocumentFromRoot(hoistableRoot);
instance = ownerDocument.createElement("style");
markNodeAsHoistable(instance);
setInitialProperties(instance, "style", styleProps);
setInitialProperties(instance, "style", styleProps); // TODO: `style` does not have loading state for tracking insertions. I
// guess because these aren't suspensey? Not sure whether this is a
// factoring smell.
// resource.state.loading |= Inserted;
insertStylesheet(instance, qualifiedProps.precedence, hoistableRoot);
resource.instance = instance;
return instance;
@@ -43936,6 +44011,7 @@ function acquireResource(hoistableRoot, resource, props) {
linkInstance.onerror = reject;
});
setInitialProperties(_instance3, "link", stylesheetProps);
resource.state.loading |= Inserted;
insertStylesheet(_instance3, _qualifiedProps.precedence, hoistableRoot);
resource.instance = _instance3;
return _instance3;
@@ -43992,6 +44068,28 @@ function acquireResource(hoistableRoot, resource, props) {
);
}
}
} else {
// In the case of stylesheets, they might have already been assigned an
// instance during `suspendResource`. But that doesn't mean they were
// inserted, because the commit might have been interrupted. So we need to
// check now.
//
// The other resource types are unaffected because they are not
// yet suspensey.
//
// TODO: This is a bit of a code smell. Consider refactoring how
// `suspendResource` and `acquireResource` work together. The idea is that
// `suspendResource` does all the same stuff as `acquireResource` except
// for the insertion.
if (
resource.type === "stylesheet" &&
(resource.state.loading & Inserted) === NotLoaded
) {
var _qualifiedProps2 = props;
var _instance5 = resource.instance;
resource.state.loading |= Inserted;
insertStylesheet(_instance5, _qualifiedProps2.precedence, hoistableRoot);
}
}
return resource.instance;
@@ -44439,9 +44537,6 @@ function isHostHoistableType(type, props, hostContext) {
return false;
}
function maySuspendCommit(type, props) {
return false;
}
function mayResourceSuspendCommit(resource) {
return (
resource.type === "stylesheet" &&
@@ -44452,6 +44547,17 @@ function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
}
function preloadResource(resource) {
if (
resource.type === "stylesheet" &&
(resource.state.loading & Settled) === NotLoaded
) {
// we have not finished loading the underlying stylesheet yet.
return false;
} // Return true to indicate it's already loaded
return true;
}
var suspendedState = null; // We use a noop function when we begin suspending because if possible we want the
// waitfor step to finish synchronously. If it doesn't we'll return a function to
// provide the actual unsuspend function and that will get completed when the count
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -480,11 +480,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -518,8 +519,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -550,7 +551,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -2143,9 +2144,6 @@ function prepareScopeUpdate(scopeInstance, inst) {
function getInstanceFromScope(scopeInstance) {
return nodeToInstanceMap.get(scopeInstance) || null;
}
function maySuspendCommit(type, props) {
return false;
}
function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
@@ -4557,13 +4555,6 @@ function trackUsedThenable(thenableState, thenable, index) {
}
}
}
function suspendCommit() {
// This extra indirection only exists so it can handle passing
// noopSuspenseyCommitThenable through to throwException.
// TODO: Factor the thenable check out of throwException
suspendedThenable = noopSuspenseyCommitThenable;
throw SuspenseyCommitException;
} // This is used to track the actual thenable that suspended so it can be
// passed to the rest of the Suspense implementation — which, for historical
// reasons, expects to receive a thenable.
@@ -15302,7 +15293,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -15313,28 +15308,16 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
if (!includesOnlyNonUrgentLanes(renderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
suspendCommit();
}
}
}
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
function scheduleRetryEffect(workInProgress, retryQueue) {
@@ -15740,12 +15723,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -15782,7 +15763,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -15804,17 +15785,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -19565,13 +19536,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -19586,7 +19568,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource();
}
@@ -19602,8 +19584,36 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
}
case HostRoot:
case HostPortal:
// eslint-disable-next-line-no-fallthrough
case HostPortal: {
{
recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -21384,15 +21394,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -24353,7 +24372,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-classic-5df7639d";
var ReactVersion = "18.3.0-www-classic-d0bd2b40";
// Might add PROFILE later.
@@ -480,11 +480,12 @@ var Visibility =
8192;
var StoreConsistency =
/* */
16384; // It's OK to reuse this bit because these flags are mutually exclusive for
16384; // It's OK to reuse these bits because these flags are mutually exclusive for
// different fiber types. We should really be doing this for as many flags as
// possible, because we're about to run out of bits.
var ScheduleRetry = StoreConsistency;
var ShouldSuspendCommit = Visibility;
var LifecycleEffectMask =
Passive$1 | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)
@@ -518,8 +519,8 @@ var LayoutStatic =
var PassiveStatic =
/* */
8388608;
var SuspenseyCommit =
/* */
var MaySuspendCommit =
/* */
16777216; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`.
var PlacementDEV =
@@ -550,7 +551,7 @@ var PassiveMask = Passive$1 | Visibility | ChildDeletion; // Union of tags that
// This allows certain concepts to persist without recalculating them,
// e.g. whether a subtree contains passive effects or portals.
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | SuspenseyCommit;
var StaticMask = LayoutStatic | PassiveStatic | RefStatic | MaySuspendCommit;
var ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner;
function getNearestMountedFiber(fiber) {
@@ -2143,9 +2144,6 @@ function prepareScopeUpdate(scopeInstance, inst) {
function getInstanceFromScope(scopeInstance) {
return nodeToInstanceMap.get(scopeInstance) || null;
}
function maySuspendCommit(type, props) {
return false;
}
function preloadInstance(type, props) {
// Return true to indicate it's already loaded
return true;
@@ -4557,13 +4555,6 @@ function trackUsedThenable(thenableState, thenable, index) {
}
}
}
function suspendCommit() {
// This extra indirection only exists so it can handle passing
// noopSuspenseyCommitThenable through to throwException.
// TODO: Factor the thenable check out of throwException
suspendedThenable = noopSuspenseyCommitThenable;
throw SuspenseyCommitException;
} // This is used to track the actual thenable that suspended so it can be
// passed to the rest of the Suspense implementation — which, for historical
// reasons, expects to receive a thenable.
@@ -15302,7 +15293,11 @@ function updateHostComponent(
markUpdate(workInProgress);
}
}
} // TODO: This should ideally move to begin phase, but currently the instance is
} // This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
@@ -15313,28 +15308,16 @@ function preloadInstanceAndSuspendIfNeeded(
props,
renderLanes
) {
workInProgress.flags |= SuspenseyCommit; // Check if we're rendering at a "non-urgent" priority. This is the same
// check that `useDeferredValue` does to determine whether it needs to
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
// suspending until you opt in with startTransition or Suspense) but it
// also happens to be the desired behavior for the concrete use cases we've
// thought of so far, like CSS loading, fonts, images, etc.
// TODO: We may decide to expose a way to force a fallback even during a
// sync update.
if (!includesOnlyNonUrgentLanes(renderLanes));
else {
// Preload the instance
var isReady = preloadInstance();
if (!isReady) {
if (shouldRemainOnPreviousScreen());
else {
// Trigger a fallback rather than block the render.
suspendCommit();
}
}
}
{
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
workInProgress.flags &= ~MaySuspendCommit;
return;
} // Mark this fiber with a flag. This gets set on all host instances
}
function scheduleRetryEffect(workInProgress, retryQueue) {
@@ -15740,12 +15723,10 @@ function completeWork(current, workInProgress, renderLanes) {
case HostComponent: {
popHostContext(workInProgress);
var _type = workInProgress.type;
var _maySuspend = maySuspendCommit();
var _type2 = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, _type, newProps);
updateHostComponent(current, workInProgress, _type2, newProps);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
@@ -15782,7 +15763,7 @@ function completeWork(current, workInProgress, renderLanes) {
var _rootContainerInstance = getRootHostContainer();
var _instance3 = createInstance(
_type,
_type2,
newProps,
_rootContainerInstance,
_currentHostContext2,
@@ -15804,17 +15785,7 @@ function completeWork(current, workInProgress, renderLanes) {
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
if (_maySuspend) {
preloadInstanceAndSuspendIfNeeded(
workInProgress,
_type,
newProps,
renderLanes
);
} else {
workInProgress.flags &= ~SuspenseyCommit;
}
preloadInstanceAndSuspendIfNeeded(workInProgress);
return null;
}
@@ -19565,13 +19536,24 @@ function commitPassiveUnmountEffects(finishedWork) {
setCurrentFiber(finishedWork);
commitPassiveUnmountOnFiber(finishedWork);
resetCurrentFiber();
}
} // If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
var suspenseyCommitFlag = ShouldSuspendCommit;
function accumulateSuspenseyCommit(finishedWork) {
accumulateSuspenseyCommitOnFiber(finishedWork);
}
function recursivelyAccumulateSuspenseyCommit(parentFiber) {
if (parentFiber.subtreeFlags & SuspenseyCommit) {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
var child = parentFiber.child;
while (child !== null) {
@@ -19586,7 +19568,7 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(fiber);
if (fiber.flags & SuspenseyCommit) {
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource();
}
@@ -19602,8 +19584,36 @@ function accumulateSuspenseyCommitOnFiber(fiber) {
}
case HostRoot:
case HostPortal:
// eslint-disable-next-line-no-fallthrough
case HostPortal: {
{
recursivelyAccumulateSuspenseyCommit(fiber);
}
break;
}
case OffscreenComponent: {
var isHidden = fiber.memoizedState !== null;
if (isHidden);
else {
var current = fiber.alternate;
var wasHidden = current !== null && current.memoizedState !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
var prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(fiber);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(fiber);
}
}
break;
}
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
@@ -21384,15 +21394,24 @@ function shouldRemainOnPreviousScreen() {
if (handler === null);
else {
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
if (
includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
return handler === getShellBoundary();
}
} // For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// TODO: We should wait during Offscreen prerendering, too.
return false;
}
@@ -24353,7 +24372,7 @@ function createFiberRoot(
return root;
}
var ReactVersion = "18.3.0-www-modern-770d70a2";
var ReactVersion = "18.3.0-www-modern-4cb7601b";
// Might add PROFILE later.