@samitouri / QOS-React-2 / commits / 1859329021

Track nearest Suspense handler on stack (#24585)

* [FORKED] Add HiddenContext to track if subtree is hidden This adds a new stack cursor for tracking whether we're rendering inside a subtree that's currently hidden. This corresponds to the same place where we're already tracking the "base lanes" needed to reveal a hidden subtree — that is, when going from hidden -> visible, the base lanes are the ones that we skipped over when we deferred the subtree. We must includes all the base lanes and their updates in order to avoid an inconsistency with the surrounding content that already committed. I consolidated the base lanes logic and the hidden logic into the same set of push/pop calls. This is intended to replace the InvisibleParentContext that is currently part of SuspenseContext, but I haven't done that part yet. * Add previous commit to forked revisions * [FORKED] Track nearest Suspense handler on stack Instead of traversing the return path whenever something suspends to find the nearest Suspense boundary, we can push the Suspense boundary onto the stack before entering its subtree. This doesn't affect the overall algorithm that much, but because we already do all the same logic in the begin phase, we can save some redundant work by tracking that information on the stack instead of recomputing it every time. * Add previous commit to forked revisions

Andrew Clark committed Jun 30, 2022 at 10:03 UTC 1859329021892484051339a76c2250fa4ca5f77e
10 files changed +316 -265
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+86 -94
@@ -37,7 +37,6 @@ import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
37 import type {RootState} from './ReactFiberRoot.new';
38 import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
39 import {
40 - enableSuspenseAvoidThisFallback,
40 enableCPUSuspense,
41 enableUseMutableSource,
42 } from 'shared/ReactFeatureFlags';
@@ -167,14 +166,19 @@ import {shouldError, shouldSuspend} from './ReactFiberReconciler';
166 import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
167 import {
168 suspenseStackCursor,
170 - pushSuspenseContext,
171 - InvisibleParentSuspenseContext,
169 + pushSuspenseListContext,
170 ForceSuspenseFallback,
173 - hasSuspenseContext,
174 - setDefaultShallowSuspenseContext,
175 - addSubtreeSuspenseContext,
176 - setShallowSuspenseContext,
171 + hasSuspenseListContext,
172 + setDefaultShallowSuspenseListContext,
173 + setShallowSuspenseListContext,
174 + pushPrimaryTreeSuspenseHandler,
175 + pushFallbackTreeSuspenseHandler,
176 + popSuspenseHandler,
177 } from './ReactFiberSuspenseContext.new';
178 +import {
179 + pushHiddenContext,
180 + reuseHiddenContextOnStack,
181 +} from './ReactFiberHiddenContext.new';
182 import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
183 import {
184 pushProvider,
@@ -232,7 +236,6 @@ import {
236 renderDidSuspendDelayIfPossible,
237 markSkippedUpdateLanes,
238 getWorkInProgressRoot,
235 - pushRenderLanes,
239 } from './ReactFiberWorkLoop.new';
240 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
241 import {setWorkInProgressVersion} from './ReactMutableSource.new';
@@ -688,21 +691,14 @@ function updateOffscreenComponent(
691 pushTransition(workInProgress, null, null);
692 }
693 }
691 - pushRenderLanes(workInProgress, renderLanes);
694 + reuseHiddenContextOnStack(workInProgress);
695 } else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
693 - let spawnedCachePool: SpawnedCachePool | null = null;
696 // We're hidden, and we're not rendering at Offscreen. We will bail out
697 // and resume this tree later.
696 - let nextBaseLanes;
698 + let nextBaseLanes = renderLanes;
699 if (prevState !== null) {
698 - const prevBaseLanes = prevState.baseLanes;
699 - nextBaseLanes = mergeLanes(prevBaseLanes, renderLanes);
700 - if (enableCache) {
701 - // Save the cache pool so we can resume later.
702 - spawnedCachePool = getOffscreenDeferredCache();
703 - }
704 - } else {
705 - nextBaseLanes = renderLanes;
700 + // Include the base lanes from the last render
701 + nextBaseLanes = mergeLanes(nextBaseLanes, prevState.baseLanes);
702 }
703
704 // Schedule this fiber to re-render at offscreen priority. Then bailout.
@@ -711,7 +707,8 @@ function updateOffscreenComponent(
707 );
708 const nextState: OffscreenState = {
709 baseLanes: nextBaseLanes,
714 - cachePool: spawnedCachePool,
710 + // Save the cache pool so we can resume later.
711 + cachePool: enableCache ? getOffscreenDeferredCache() : null,
712 };
713 workInProgress.memoizedState = nextState;
714 workInProgress.updateQueue = null;
@@ -725,7 +722,7 @@ function updateOffscreenComponent(
722
723 // We're about to bail out, but we need to push this to the stack anyway
724 // to avoid a push/pop misalignment.
728 - pushRenderLanes(workInProgress, nextBaseLanes);
725 + reuseHiddenContextOnStack(workInProgress);
726
727 if (enableLazyContextPropagation && current !== null) {
728 // Since this tree will resume rendering in a separate render, we need
@@ -749,9 +746,6 @@ function updateOffscreenComponent(
746 cachePool: null,
747 };
748 workInProgress.memoizedState = nextState;
752 - // Push the lanes that were skipped when we bailed out.
753 - const subtreeRenderLanes =
754 - prevState !== null ? prevState.baseLanes : renderLanes;
749 if (enableCache && current !== null) {
750 // If the render that spawned this one accessed the cache pool, resume
751 // using the same cache. Unless the parent changed, since that means
@@ -762,16 +756,17 @@ function updateOffscreenComponent(
756 pushTransition(workInProgress, prevCachePool, null);
757 }
758
765 - pushRenderLanes(workInProgress, subtreeRenderLanes);
759 + // Push the lanes that were skipped when we bailed out.
760 + if (prevState !== null) {
761 + pushHiddenContext(workInProgress, prevState);
762 + } else {
763 + reuseHiddenContextOnStack(workInProgress);
764 + }
765 }
766 } else {
767 // Rendering a visible tree.
769 - let subtreeRenderLanes;
768 if (prevState !== null) {
769 // We're going from hidden -> visible.
772 -
773 - subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes);
774 -
770 let prevCachePool = null;
771 if (enableCache) {
772 // If the render that spawned this one accessed the cache pool, resume
@@ -789,13 +784,15 @@ function updateOffscreenComponent(
784
785 pushTransition(workInProgress, prevCachePool, transitions);
786
787 + // Push the lanes that were skipped when we bailed out.
788 + pushHiddenContext(workInProgress, prevState);
789 +
790 // Since we're not hidden anymore, reset the state
791 workInProgress.memoizedState = null;
792 } else {
793 // We weren't previously hidden, and we still aren't, so there's nothing
794 // special to do. Need to push to the stack regardless, though, to avoid
795 // a push/pop misalignment.
798 - subtreeRenderLanes = renderLanes;
796
797 if (enableCache) {
798 // If the render that spawned this one accessed the cache pool, resume
@@ -805,8 +802,11 @@ function updateOffscreenComponent(
802 pushTransition(workInProgress, null, null);
803 }
804 }
805 +
806 + // We're about to bail out, but we need to push this to the stack anyway
807 + // to avoid a push/pop misalignment.
808 + reuseHiddenContextOnStack(workInProgress);
809 }
809 - pushRenderLanes(workInProgress, subtreeRenderLanes);
810 }
811
812 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
@@ -1969,7 +1969,6 @@ function updateSuspenseOffscreenState(
1969
1970 // TODO: Probably should inline this back
1971 function shouldRemainOnFallback(
1972 - suspenseContext: SuspenseContext,
1972 current: null | Fiber,
1973 workInProgress: Fiber,
1974 renderLanes: Lanes,
@@ -1989,7 +1988,8 @@ function shouldRemainOnFallback(
1988 }
1989
1990 // Not currently showing content. Consult the Suspense context.
1992 - return hasSuspenseContext(
1991 + const suspenseContext: SuspenseContext = suspenseStackCursor.current;
1992 + return hasSuspenseListContext(
1993 suspenseContext,
1994 (ForceSuspenseFallback: SuspenseContext),
1995 );
@@ -2010,50 +2010,18 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2010 }
2011 }
2012
2013 - let suspenseContext: SuspenseContext = suspenseStackCursor.current;
2014 -
2013 let showFallback = false;
2014 const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
2017 -
2015 if (
2016 didSuspend ||
2020 - shouldRemainOnFallback(
2021 - suspenseContext,
2022 - current,
2023 - workInProgress,
2024 - renderLanes,
2025 - )
2017 + shouldRemainOnFallback(current, workInProgress, renderLanes)
2018 ) {
2019 // Something in this boundary's subtree already suspended. Switch to
2020 // rendering the fallback children.
2021 showFallback = true;
2022 workInProgress.flags &= ~DidCapture;
2031 - } else {
2032 - // Attempting the main content
2033 - if (
2034 - current === null ||
2035 - (current.memoizedState: null | SuspenseState) !== null
2036 - ) {
2037 - // This is a new mount or this boundary is already showing a fallback state.
2038 - // Mark this subtree context as having at least one invisible parent that could
2039 - // handle the fallback state.
2040 - // Avoided boundaries are not considered since they cannot handle preferred fallback states.
2041 - if (
2042 - !enableSuspenseAvoidThisFallback ||
2043 - nextProps.unstable_avoidThisFallback !== true
2044 - ) {
2045 - suspenseContext = addSubtreeSuspenseContext(
2046 - suspenseContext,
2047 - InvisibleParentSuspenseContext,
2048 - );
2049 - }
2050 - }
2023 }
2024
2053 - suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
2054 -
2055 - pushSuspenseContext(workInProgress, suspenseContext);
2056 -
2025 // OK, the next part is confusing. We're about to reconcile the Suspense
2026 // boundary's children. This involves some custom reconciliation logic. Two
2027 // main reasons this is so complicated.
@@ -2081,24 +2049,40 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2049
2050 // Special path for hydration
2051 // If we're currently hydrating, try to hydrate this boundary.
2084 - tryToClaimNextHydratableInstance(workInProgress);
2085 - // This could've been a dehydrated suspense component.
2086 - const suspenseState: null | SuspenseState = workInProgress.memoizedState;
2087 - if (suspenseState !== null) {
2088 - const dehydrated = suspenseState.dehydrated;
2089 - if (dehydrated !== null) {
2090 - return mountDehydratedSuspenseComponent(
2091 - workInProgress,
2092 - dehydrated,
2093 - renderLanes,
2094 - );
2052 + if (getIsHydrating()) {
2053 + // We must push the suspense handler context *before* attempting to
2054 + // hydrate, to avoid a mismatch in case it errors.
2055 + if (showFallback) {
2056 + pushPrimaryTreeSuspenseHandler(workInProgress);
2057 + } else {
2058 + pushFallbackTreeSuspenseHandler(workInProgress);
2059 + }
2060 + tryToClaimNextHydratableInstance(workInProgress);
2061 + // This could've been a dehydrated suspense component.
2062 + const suspenseState: null | SuspenseState = workInProgress.memoizedState;
2063 + if (suspenseState !== null) {
2064 + const dehydrated = suspenseState.dehydrated;
2065 + if (dehydrated !== null) {
2066 + return mountDehydratedSuspenseComponent(
2067 + workInProgress,
2068 + dehydrated,
2069 + renderLanes,
2070 + );
2071 + }
2072 }
2073 + // If hydration didn't succeed, fall through to the normal Suspense path.
2074 + // To avoid a stack mismatch we need to pop the Suspense handler that we
2075 + // pushed above. This will become less awkward when move the hydration
2076 + // logic to its own fiber.
2077 + popSuspenseHandler(workInProgress);
2078 }
2079
2080 const nextPrimaryChildren = nextProps.children;
2081 const nextFallbackChildren = nextProps.fallback;
2082
2083 if (showFallback) {
2084 + pushFallbackTreeSuspenseHandler(workInProgress);
2085 +
2086 const fallbackFragment = mountSuspenseFallbackChildren(
2087 workInProgress,
2088 nextPrimaryChildren,
@@ -2131,6 +2115,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2115 // This is a CPU-bound tree. Skip this tree and show a placeholder to
2116 // unblock the surrounding content. Then immediately retry after the
2117 // initial commit.
2118 + pushFallbackTreeSuspenseHandler(workInProgress);
2119 const fallbackFragment = mountSuspenseFallbackChildren(
2120 workInProgress,
2121 nextPrimaryChildren,
@@ -2154,6 +2139,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2139 workInProgress.lanes = SomeRetryLane;
2140 return fallbackFragment;
2141 } else {
2142 + pushPrimaryTreeSuspenseHandler(workInProgress);
2143 return mountSuspensePrimaryChildren(
2144 workInProgress,
2145 nextPrimaryChildren,
@@ -2181,6 +2167,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2167 }
2168
2169 if (showFallback) {
2170 + pushFallbackTreeSuspenseHandler(workInProgress);
2171 +
2172 const nextFallbackChildren = nextProps.fallback;
2173 const nextPrimaryChildren = nextProps.children;
2174 const fallbackChildFragment = updateSuspenseFallbackChildren(
@@ -2215,6 +2203,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2203 workInProgress.memoizedState = SUSPENDED_MARKER;
2204 return fallbackChildFragment;
2205 } else {
2206 + pushPrimaryTreeSuspenseHandler(workInProgress);
2207 +
2208 const nextPrimaryChildren = nextProps.children;
2209 const primaryChildFragment = updateSuspensePrimaryChildren(
2210 current,
@@ -2585,6 +2575,7 @@ function updateDehydratedSuspenseComponent(
2575 ): null | Fiber {
2576 if (!didSuspend) {
2577 // This is the first render pass. Attempt to hydrate.
2578 + pushPrimaryTreeSuspenseHandler(workInProgress);
2579
2580 // We should never be hydrating at this point because it is the first pass,
2581 // but after we've already committed once.
@@ -2751,6 +2742,8 @@ function updateDehydratedSuspenseComponent(
2742
2743 if (workInProgress.flags & ForceClientRender) {
2744 // Something errored during hydration. Try again without hydrating.
2745 + pushPrimaryTreeSuspenseHandler(workInProgress);
2746 +
2747 workInProgress.flags &= ~ForceClientRender;
2748 const capturedValue = createCapturedValue(
2749 new Error(
@@ -2767,6 +2760,10 @@ function updateDehydratedSuspenseComponent(
2760 } else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
2761 // Something suspended and we should still be in dehydrated mode.
2762 // Leave the existing child in place.
2763 +
2764 + // Push to avoid a mismatch
2765 + pushFallbackTreeSuspenseHandler(workInProgress);
2766 +
2767 workInProgress.child = current.child;
2768 // The dehydrated completion pass expects this flag to be there
2769 // but the normal suspense pass doesn't.
@@ -2775,6 +2772,8 @@ function updateDehydratedSuspenseComponent(
2772 } else {
2773 // Suspended but we should no longer be in dehydrated mode.
2774 // Therefore we now have to render the fallback.
2775 + pushFallbackTreeSuspenseHandler(workInProgress);
2776 +
2777 const nextPrimaryChildren = nextProps.children;
2778 const nextFallbackChildren = nextProps.fallback;
2779 const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
@@ -3070,12 +3069,12 @@ function updateSuspenseListComponent(
3069
3070 let suspenseContext: SuspenseContext = suspenseStackCursor.current;
3071
3073 - const shouldForceFallback = hasSuspenseContext(
3072 + const shouldForceFallback = hasSuspenseListContext(
3073 suspenseContext,
3074 (ForceSuspenseFallback: SuspenseContext),
3075 );
3076 if (shouldForceFallback) {
3078 - suspenseContext = setShallowSuspenseContext(
3077 + suspenseContext = setShallowSuspenseListContext(
3078 suspenseContext,
3079 ForceSuspenseFallback,
3080 );
@@ -3093,9 +3092,9 @@ function updateSuspenseListComponent(
3092 renderLanes,
3093 );
3094 }
3096 - suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
3095 + suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
3096 }
3098 - pushSuspenseContext(workInProgress, suspenseContext);
3097 + pushSuspenseListContext(workInProgress, suspenseContext);
3098
3099 if ((workInProgress.mode & ConcurrentMode) === NoMode) {
3100 // In legacy mode, SuspenseList doesn't work so we just
@@ -3559,10 +3558,9 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3558 const state: SuspenseState | null = workInProgress.memoizedState;
3559 if (state !== null) {
3560 if (state.dehydrated !== null) {
3562 - pushSuspenseContext(
3563 - workInProgress,
3564 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3565 - );
3561 + // We're not going to render the children, so this is just to maintain
3562 + // push/pop symmetry
3563 + pushPrimaryTreeSuspenseHandler(workInProgress);
3564 // We know that this component will suspend again because if it has
3565 // been unsuspended it has committed as a resolved Suspense component.
3566 // If it needs to be retried, it should have work scheduled on it.
@@ -3585,10 +3583,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3583 } else {
3584 // The primary child fragment does not have pending work marked
3585 // on it
3588 - pushSuspenseContext(
3589 - workInProgress,
3590 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3591 - );
3586 + pushPrimaryTreeSuspenseHandler(workInProgress);
3587 // The primary children do not have pending work with sufficient
3588 // priority. Bailout.
3589 const child = bailoutOnAlreadyFinishedWork(
@@ -3608,10 +3603,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3603 }
3604 }
3605 } else {
3611 - pushSuspenseContext(
3612 - workInProgress,
3613 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3614 - );
3606 + pushPrimaryTreeSuspenseHandler(workInProgress);
3607 }
3608 break;
3609 }
@@ -3669,7 +3661,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3661 renderState.tail = null;
3662 renderState.lastEffect = null;
3663 }
3672 - pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3664 + pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
3665
3666 if (hasChildWork) {
3667 break;
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+38 -38
@@ -27,7 +27,6 @@ import type {
27 SuspenseState,
28 SuspenseListRenderState,
29 } from './ReactFiberSuspenseComponent.new';
30 -import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
30 import type {OffscreenState} from './ReactFiberOffscreenComponent';
31 import type {Cache} from './ReactFiberCacheComponent.new';
32 import {
@@ -109,14 +108,17 @@ import {
108 } from './ReactFiberHostContext.new';
109 import {
110 suspenseStackCursor,
112 - InvisibleParentSuspenseContext,
113 - hasSuspenseContext,
114 - popSuspenseContext,
115 - pushSuspenseContext,
116 - setShallowSuspenseContext,
111 + popSuspenseListContext,
112 + popSuspenseHandler,
113 + pushSuspenseListContext,
114 + setShallowSuspenseListContext,
115 ForceSuspenseFallback,
118 - setDefaultShallowSuspenseContext,
116 + setDefaultShallowSuspenseListContext,
117 } from './ReactFiberSuspenseContext.new';
118 +import {
119 + popHiddenContext,
120 + isCurrentTreeHidden,
121 +} from './ReactFiberHiddenContext.new';
122 import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
123 import {
124 isContextProvider as isLegacyContextProvider,
@@ -146,9 +148,7 @@ import {
148 renderDidSuspend,
149 renderDidSuspendDelayIfPossible,
150 renderHasNotSuspendedYet,
149 - popRenderLanes,
151 getRenderTargetTime,
151 - subtreeRenderLanes,
152 getWorkInProgressTransitions,
153 } from './ReactFiberWorkLoop.new';
154 import {
@@ -1077,7 +1077,7 @@ function completeWork(
1077 return null;
1078 }
1079 case SuspenseComponent: {
1080 - popSuspenseContext(workInProgress);
1080 + popSuspenseHandler(workInProgress);
1081 const nextState: null | SuspenseState = workInProgress.memoizedState;
1082
1083 // Special path for dehydrated boundaries. We may eventually move this
@@ -1186,25 +1186,23 @@ function completeWork(
1186 // If this render already had a ping or lower pri updates,
1187 // and this is the first time we know we're going to suspend we
1188 // should be able to immediately restart from within throwException.
1189 - const hasInvisibleChildContext =
1190 - current === null &&
1191 - (workInProgress.memoizedProps.unstable_avoidThisFallback !==
1192 - true ||
1193 - !enableSuspenseAvoidThisFallback);
1194 - if (
1195 - hasInvisibleChildContext ||
1196 - hasSuspenseContext(
1197 - suspenseStackCursor.current,
1198 - (InvisibleParentSuspenseContext: SuspenseContext),
1199 - )
1200 - ) {
1201 - // If this was in an invisible tree or a new render, then showing
1202 - // this boundary is ok.
1203 - renderDidSuspend();
1204 - } else {
1205 - // Otherwise, we're going to have to hide content so we should
1206 - // suspend for longer if possible.
1189 +
1190 + // Check if this is a "bad" fallback state or a good one. A bad
1191 + // fallback state is one that we only show as a last resort; if this
1192 + // is a transition, we'll block it from displaying, and wait for
1193 + // more data to arrive.
1194 + const isBadFallback =
1195 + // It's bad to switch to a fallback if content is already visible
1196 + (current !== null && !prevDidTimeout && !isCurrentTreeHidden()) ||
1197 + // Experimental: Some fallbacks are always bad
1198 + (enableSuspenseAvoidThisFallback &&
1199 + workInProgress.memoizedProps.unstable_avoidThisFallback ===
1200 + true);
1201 +
1202 + if (isBadFallback) {
1203 renderDidSuspendDelayIfPossible();
1204 + } else {
1205 + renderDidSuspend();
1206 }
1207 }
1208 }
@@ -1266,7 +1264,7 @@ function completeWork(
1264 return null;
1265 }
1266 case SuspenseListComponent: {
1269 - popSuspenseContext(workInProgress);
1267 + popSuspenseListContext(workInProgress);
1268
1269 const renderState: null | SuspenseListRenderState =
1270 workInProgress.memoizedState;
@@ -1332,11 +1330,11 @@ function completeWork(
1330 workInProgress.subtreeFlags = NoFlags;
1331 resetChildFibers(workInProgress, renderLanes);
1332
1335 - // Set up the Suspense Context to force suspense and immediately
1336 - // rerender the children.
1337 - pushSuspenseContext(
1333 + // Set up the Suspense List Context to force suspense and
1334 + // immediately rerender the children.
1335 + pushSuspenseListContext(
1336 workInProgress,
1339 - setShallowSuspenseContext(
1337 + setShallowSuspenseListContext(
1338 suspenseStackCursor.current,
1339 ForceSuspenseFallback,
1340 ),
@@ -1459,14 +1457,16 @@ function completeWork(
1457 // setting it the first time we go from not suspended to suspended.
1458 let suspenseContext = suspenseStackCursor.current;
1459 if (didSuspendAlready) {
1462 - suspenseContext = setShallowSuspenseContext(
1460 + suspenseContext = setShallowSuspenseListContext(
1461 suspenseContext,
1462 ForceSuspenseFallback,
1463 );
1464 } else {
1467 - suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
1465 + suspenseContext = setDefaultShallowSuspenseListContext(
1466 + suspenseContext,
1467 + );
1468 }
1469 - pushSuspenseContext(workInProgress, suspenseContext);
1469 + pushSuspenseListContext(workInProgress, suspenseContext);
1470 // Do a pass over the next row.
1471 // Don't bubble properties in this case.
1472 return next;
@@ -1499,7 +1499,7 @@ function completeWork(
1499 }
1500 case OffscreenComponent:
1501 case LegacyHiddenComponent: {
1502 - popRenderLanes(workInProgress);
1502 + popHiddenContext(workInProgress);
1503 const nextState: OffscreenState | null = workInProgress.memoizedState;
1504 const nextIsHidden = nextState !== null;
1505
@@ -1520,7 +1520,7 @@ function completeWork(
1520 } else {
1521 // Don't bubble properties for hidden children unless we're rendering
1522 // at offscreen priority.
1523 - if (includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))) {
1523 + if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
1524 bubbleProperties(workInProgress);
1525 // Check if there was an insertion or update in the hidden subtree.
1526 // If so, we need to hide those nodes in the commit phase, so
packages/react-reconciler/src/ReactFiberHiddenContext.new.js new
+70
@@ -0,0 +1,70 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Fiber} from './ReactInternalTypes';
11 +import type {StackCursor} from './ReactFiberStack.new';
12 +import type {Lanes} from './ReactFiberLane.new';
13 +
14 +import {createCursor, push, pop} from './ReactFiberStack.new';
15 +
16 +import {getRenderLanes, setRenderLanes} from './ReactFiberWorkLoop.new';
17 +import {NoLanes, mergeLanes} from './ReactFiberLane.new';
18 +
19 +// TODO: Remove `renderLanes` context in favor of hidden context
20 +type HiddenContext = {
21 + // Represents the lanes that must be included when processing updates in
22 + // order to reveal the hidden content.
23 + // TODO: Remove `subtreeLanes` context from work loop in favor of this one.
24 + baseLanes: number,
25 +};
26 +
27 +// TODO: This isn't being used yet, but it's intended to replace the
28 +// InvisibleParentContext that is currently managed by SuspenseContext.
29 +export const currentTreeHiddenStackCursor: StackCursor<HiddenContext | null> = createCursor(
30 + null,
31 +);
32 +export const prevRenderLanesStackCursor: StackCursor<Lanes> = createCursor(
33 + NoLanes,
34 +);
35 +
36 +export function pushHiddenContext(fiber: Fiber, context: HiddenContext): void {
37 + const prevRenderLanes = getRenderLanes();
38 + push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
39 + push(currentTreeHiddenStackCursor, context, fiber);
40 +
41 + // When rendering a subtree that's currently hidden, we must include all
42 + // lanes that would have rendered if the hidden subtree hadn't been deferred.
43 + // That is, in order to reveal content from hidden -> visible, we must commit
44 + // all the updates that we skipped when we originally hid the tree.
45 + setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
46 +}
47 +
48 +export function reuseHiddenContextOnStack(fiber: Fiber): void {
49 + // This subtree is not currently hidden, so we don't need to add any lanes
50 + // to the render lanes. But we still need to push something to avoid a
51 + // context mismatch. Reuse the existing context on the stack.
52 + push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
53 + push(
54 + currentTreeHiddenStackCursor,
55 + currentTreeHiddenStackCursor.current,
56 + fiber,
57 + );
58 +}
59 +
60 +export function popHiddenContext(fiber: Fiber): void {
61 + // Restore the previous render lanes from the stack
62 + setRenderLanes(prevRenderLanesStackCursor.current);
63 +
64 + pop(currentTreeHiddenStackCursor, fiber);
65 + pop(prevRenderLanesStackCursor, fiber);
66 +}
67 +
68 +export function isCurrentTreeHidden() {
69 + return currentTreeHiddenStackCursor.current !== null;
70 +}
packages/react-reconciler/src/ReactFiberHiddenContext.old.js new
+1
@@ -0,0 +1 @@
1 +// Intentionally blank. File only exists in new reconciler fork.
packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
-32
@@ -13,7 +13,6 @@ import type {SuspenseInstance} from './ReactFiberHostConfig';
13 import type {Lane} from './ReactFiberLane.new';
14 import type {TreeContext} from './ReactFiberTreeContext.new';
15
16 -import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
16 import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
17 import {NoFlags, DidCapture} from './ReactFiberFlags';
18 import {
@@ -67,37 +66,6 @@ export type SuspenseListRenderState = {|
66 tailMode: SuspenseListTailMode,
67 |};
68
70 -export function shouldCaptureSuspense(
71 - workInProgress: Fiber,
72 - hasInvisibleParent: boolean,
73 -): boolean {
74 - // If it was the primary children that just suspended, capture and render the
75 - // fallback. Otherwise, don't capture and bubble to the next boundary.
76 - const nextState: SuspenseState | null = workInProgress.memoizedState;
77 - if (nextState !== null) {
78 - if (nextState.dehydrated !== null) {
79 - // A dehydrated boundary always captures.
80 - return true;
81 - }
82 - return false;
83 - }
84 - const props = workInProgress.memoizedProps;
85 - // Regular boundaries always capture.
86 - if (
87 - !enableSuspenseAvoidThisFallback ||
88 - props.unstable_avoidThisFallback !== true
89 - ) {
90 - return true;
91 - }
92 - // If it's a boundary we should avoid, then we prefer to bubble up to the
93 - // parent boundary if it is currently invisible.
94 - if (hasInvisibleParent) {
95 - return false;
96 - }
97 - // If the parent is not able to handle it, we must handle it.
98 - return true;
99 -}
100 -
69 export function findFirstSuspended(row: Fiber): null | Fiber {
70 let node = row;
71 while (node !== null) {
packages/react-reconciler/src/ReactFiberSuspenseContext.new.js
+84 -28
@@ -9,33 +9,96 @@
9
10 import type {Fiber} from './ReactInternalTypes';
11 import type {StackCursor} from './ReactFiberStack.new';
12 +import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
13
14 +import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
15 import {createCursor, push, pop} from './ReactFiberStack.new';
16 +import {isCurrentTreeHidden} from './ReactFiberHiddenContext.new';
17 +import {SuspenseComponent} from './ReactWorkTags';
18
19 +// The Suspense handler is the boundary that should capture if something
20 +// suspends, i.e. it's the nearest `catch` block on the stack.
21 +const suspenseHandlerStackCursor: StackCursor<Fiber | null> = createCursor(
22 + null,
23 +);
24 +
25 +function shouldAvoidedBoundaryCapture(
26 + workInProgress: Fiber,
27 + handlerOnStack: Fiber,
28 + props: any,
29 +): boolean {
30 + if (enableSuspenseAvoidThisFallback) {
31 + // If the parent is already showing content, and we're not inside a hidden
32 + // tree, then we should show the avoided fallback.
33 + if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) {
34 + return true;
35 + }
36 +
37 + // If the handler on the stack is also an avoided boundary, then we should
38 + // favor this inner one.
39 + if (
40 + handlerOnStack.tag === SuspenseComponent &&
41 + handlerOnStack.memoizedProps.unstable_avoidThisFallback === true
42 + ) {
43 + return true;
44 + }
45 +
46 + // If this avoided boundary is dehydrated, then it should capture.
47 + const suspenseState: SuspenseState | null = workInProgress.memoizedState;
48 + if (suspenseState !== null && suspenseState.dehydrated !== null) {
49 + return true;
50 + }
51 + }
52 +
53 + // If none of those cases apply, then we should avoid this fallback and show
54 + // the outer one instead.
55 + return false;
56 +}
57 +
58 +export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
59 + const props = handler.pendingProps;
60 + const handlerOnStack = suspenseHandlerStackCursor.current;
61 + if (
62 + enableSuspenseAvoidThisFallback &&
63 + props.unstable_avoidThisFallback === true &&
64 + handlerOnStack !== null &&
65 + !shouldAvoidedBoundaryCapture(handler, handlerOnStack, props)
66 + ) {
67 + // This boundary should not capture if something suspends. Reuse the
68 + // existing handler on the stack.
69 + push(suspenseHandlerStackCursor, handlerOnStack, handler);
70 + } else {
71 + // Push this handler onto the stack.
72 + push(suspenseHandlerStackCursor, handler, handler);
73 + }
74 +}
75 +
76 +export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
77 + // We're about to render the fallback. If something in the fallback suspends,
78 + // it's akin to throwing inside of a `catch` block. This boundary should not
79 + // capture. Reuse the existing handler on the stack.
80 + push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
81 +}
82 +
83 +export function getSuspenseHandler(): Fiber | null {
84 + return suspenseHandlerStackCursor.current;
85 +}
86 +
87 +export function popSuspenseHandler(fiber: Fiber): void {
88 + pop(suspenseHandlerStackCursor, fiber);
89 +}
90 +
91 +// SuspenseList context
92 +// TODO: Move to a separate module? We may change the SuspenseList
93 +// implementation to hide/show in the commit phase, anyway.
94 export opaque type SuspenseContext = number;
95 export opaque type SubtreeSuspenseContext: SuspenseContext = number;
96 export opaque type ShallowSuspenseContext: SuspenseContext = number;
97
98 const DefaultSuspenseContext: SuspenseContext = 0b00;
99
21 -// The Suspense Context is split into two parts. The lower bits is
22 -// inherited deeply down the subtree. The upper bits only affect
23 -// this immediate suspense boundary and gets reset each new
24 -// boundary or suspense list.
100 const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
101
27 -// Subtree Flags:
28 -
29 -// InvisibleParentSuspenseContext indicates that one of our parent Suspense
30 -// boundaries is not currently showing visible main content.
31 -// Either because it is already showing a fallback or is not mounted at all.
32 -// We can use this to determine if it is desirable to trigger a fallback at
33 -// the parent. If not, then we might need to trigger undesirable boundaries
34 -// and/or suspend the commit to avoid hiding the parent content.
35 -export const InvisibleParentSuspenseContext: SubtreeSuspenseContext = 0b01;
36 -
37 -// Shallow Flags:
38 -
102 // ForceSuspenseFallback can be used by SuspenseList to force newly added
103 // items into their fallback state during one of the render passes.
104 export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
@@ -44,40 +107,33 @@ export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
107 DefaultSuspenseContext,
108 );
109
47 -export function hasSuspenseContext(
110 +export function hasSuspenseListContext(
111 parentContext: SuspenseContext,
112 flag: SuspenseContext,
113 ): boolean {
114 return (parentContext & flag) !== 0;
115 }
116
54 -export function setDefaultShallowSuspenseContext(
117 +export function setDefaultShallowSuspenseListContext(
118 parentContext: SuspenseContext,
119 ): SuspenseContext {
120 return parentContext & SubtreeSuspenseContextMask;
121 }
122
60 -export function setShallowSuspenseContext(
123 +export function setShallowSuspenseListContext(
124 parentContext: SuspenseContext,
125 shallowContext: ShallowSuspenseContext,
126 ): SuspenseContext {
127 return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
128 }
129
67 -export function addSubtreeSuspenseContext(
68 - parentContext: SuspenseContext,
69 - subtreeContext: SubtreeSuspenseContext,
70 -): SuspenseContext {
71 - return parentContext | subtreeContext;
72 -}
73 -
74 -export function pushSuspenseContext(
130 +export function pushSuspenseListContext(
131 fiber: Fiber,
132 newContext: SuspenseContext,
133 ): void {
134 push(suspenseStackCursor, newContext, fiber);
135 }
136
81 -export function popSuspenseContext(fiber: Fiber): void {
137 +export function popSuspenseListContext(fiber: Fiber): void {
138 pop(suspenseStackCursor, fiber);
139 }
packages/react-reconciler/src/ReactFiberThrow.new.js
+3 -30
@@ -13,13 +13,11 @@ import type {Lane, Lanes} from './ReactFiberLane.new';
13 import type {CapturedValue} from './ReactCapturedValue';
14 import type {Update} from './ReactFiberClassUpdateQueue.new';
15 import type {Wakeable} from 'shared/ReactTypes';
16 -import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
16
17 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
18 import {
19 ClassComponent,
20 HostRoot,
22 - SuspenseComponent,
21 IncompleteClassComponent,
22 FunctionComponent,
23 ForwardRef,
@@ -34,7 +32,6 @@ import {
32 ForceUpdateForLegacySuspense,
33 ForceClientRender,
34 } from './ReactFiberFlags';
37 -import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
35 import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
36 import {
37 enableDebugTracing,
@@ -50,11 +47,7 @@ import {
47 enqueueUpdate,
48 } from './ReactFiberClassUpdateQueue.new';
49 import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
53 -import {
54 - suspenseStackCursor,
55 - InvisibleParentSuspenseContext,
56 - hasSuspenseContext,
57 -} from './ReactFiberSuspenseContext.new';
50 +import {getSuspenseHandler} from './ReactFiberSuspenseContext.new';
51 import {
52 renderDidError,
53 renderDidSuspendDelayIfPossible,
@@ -269,26 +262,6 @@ function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
262 }
263 }
264
272 -function getNearestSuspenseBoundaryToCapture(returnFiber: Fiber) {
273 - let node = returnFiber;
274 - const hasInvisibleParentBoundary = hasSuspenseContext(
275 - suspenseStackCursor.current,
276 - (InvisibleParentSuspenseContext: SuspenseContext),
277 - );
278 - do {
279 - if (
280 - node.tag === SuspenseComponent &&
281 - shouldCaptureSuspense(node, hasInvisibleParentBoundary)
282 - ) {
283 - return node;
284 - }
285 - // This boundary already captured during this render. Continue to the next
286 - // boundary.
287 - node = node.return;
288 - } while (node !== null);
289 - return null;
290 -}
291 -
265 function markSuspenseBoundaryShouldCapture(
266 suspenseBoundary: Fiber,
267 returnFiber: Fiber,
@@ -444,7 +417,7 @@ function throwException(
417 }
418
419 // Schedule the nearest Suspense to re-render the timed out view.
447 - const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
420 + const suspenseBoundary = getSuspenseHandler();
421 if (suspenseBoundary !== null) {
422 suspenseBoundary.flags &= ~ForceClientRender;
423 markSuspenseBoundaryShouldCapture(
@@ -496,7 +469,7 @@ function throwException(
469 // This is a regular error, not a Suspense wakeable.
470 if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
471 markDidThrowWhileHydratingDEV();
499 - const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
472 + const suspenseBoundary = getSuspenseHandler();
473 // If the error was thrown during hydration, we may be able to recover by
474 // discarding the dehydrated content and switching to a client render.
475 // Instead of surfacing the error, find the nearest Suspense boundary
packages/react-reconciler/src/ReactFiberUnwindWork.new.js
+11 -8
@@ -36,7 +36,11 @@ import {
36 } from 'shared/ReactFeatureFlags';
37
38 import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
39 -import {popSuspenseContext} from './ReactFiberSuspenseContext.new';
39 +import {
40 + popSuspenseListContext,
41 + popSuspenseHandler,
42 +} from './ReactFiberSuspenseContext.new';
43 +import {popHiddenContext} from './ReactFiberHiddenContext.new';
44 import {resetHydrationState} from './ReactFiberHydrationContext.new';
45 import {
46 isContextProvider as isLegacyContextProvider,
@@ -44,7 +48,6 @@ import {
48 popTopLevelContextObject as popTopLevelLegacyContextObject,
49 } from './ReactFiberContext.new';
50 import {popProvider} from './ReactFiberNewContext.new';
47 -import {popRenderLanes} from './ReactFiberWorkLoop.new';
51 import {popCacheProvider} from './ReactFiberCacheComponent.new';
52 import {transferActualDuration} from './ReactProfilerTimer.new';
53 import {popTreeContext} from './ReactFiberTreeContext.new';
@@ -109,7 +112,7 @@ function unwindWork(
112 return null;
113 }
114 case SuspenseComponent: {
112 - popSuspenseContext(workInProgress);
115 + popSuspenseHandler(workInProgress);
116 const suspenseState: null | SuspenseState = workInProgress.memoizedState;
117 if (suspenseState !== null && suspenseState.dehydrated !== null) {
118 if (workInProgress.alternate === null) {
@@ -137,7 +140,7 @@ function unwindWork(
140 return null;
141 }
142 case SuspenseListComponent: {
140 - popSuspenseContext(workInProgress);
143 + popSuspenseListContext(workInProgress);
144 // SuspenseList doesn't actually catch anything. It should've been
145 // caught by a nested boundary. If not, it should bubble through.
146 return null;
@@ -151,7 +154,7 @@ function unwindWork(
154 return null;
155 case OffscreenComponent:
156 case LegacyHiddenComponent:
154 - popRenderLanes(workInProgress);
157 + popHiddenContext(workInProgress);
158 popTransition(workInProgress, current);
159 return null;
160 case CacheComponent:
@@ -208,10 +211,10 @@ function unwindInterruptedWork(
211 popHostContainer(interruptedWork);
212 break;
213 case SuspenseComponent:
211 - popSuspenseContext(interruptedWork);
214 + popSuspenseHandler(interruptedWork);
215 break;
216 case SuspenseListComponent:
214 - popSuspenseContext(interruptedWork);
217 + popSuspenseListContext(interruptedWork);
218 break;
219 case ContextProvider:
220 const context: ReactContext<any> = interruptedWork.type._context;
@@ -219,7 +222,7 @@ function unwindInterruptedWork(
222 break;
223 case OffscreenComponent:
224 case LegacyHiddenComponent:
222 - popRenderLanes(interruptedWork);
225 + popHiddenContext(interruptedWork);
226 popTransition(interruptedWork, current);
227 break;
228 case CacheComponent:
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+21 -35
@@ -11,7 +11,6 @@ import type {Wakeable} from 'shared/ReactTypes';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
12 import type {Lanes, Lane} from './ReactFiberLane.new';
13 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14 -import type {StackCursor} from './ReactFiberStack.new';
14 import type {Flags} from './ReactFiberFlags';
15 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
16 import type {EventPriority} from './ReactEventPriorities.new';
@@ -192,11 +191,6 @@ import {
191 createCapturedValueAtFiber,
192 type CapturedValue,
193 } from './ReactCapturedValue';
195 -import {
196 - push as pushToStack,
197 - pop as popFromStack,
198 - createCursor,
199 -} from './ReactFiberStack.new';
194 import {
195 enqueueConcurrentRenderForLane,
196 finishQueueingConcurrentUpdates,
@@ -285,26 +279,20 @@ let workInProgress: Fiber | null = null;
279 // The lanes we're rendering
280 let workInProgressRootRenderLanes: Lanes = NoLanes;
281
288 -// Stack that allows components to change the render lanes for its subtree
289 -// This is a superset of the lanes we started working on at the root. The only
290 -// case where it's different from `workInProgressRootRenderLanes` is when we
291 -// enter a subtree that is hidden and needs to be unhidden: Suspense and
292 -// Offscreen component.
282 +// A contextual version of workInProgressRootRenderLanes. It is a superset of
283 +// the lanes that we started working on at the root. When we enter a subtree
284 +// that is currently hidden, we add the lanes that would have committed if
285 +// the hidden tree hadn't been deferred. This is modified by the
286 +// HiddenContext module.
287 //
288 // Most things in the work loop should deal with workInProgressRootRenderLanes.
295 -// Most things in begin/complete phases should deal with subtreeRenderLanes.
296 -export let subtreeRenderLanes: Lanes = NoLanes;
297 -const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
289 +// Most things in begin/complete phases should deal with renderLanes.
290 +export let renderLanes: Lanes = NoLanes;
291
292 // Whether to root completed, errored, suspended, etc.
293 let workInProgressRootExitStatus: RootExitStatus = RootInProgress;
294 // A fatal error, if one is thrown
295 let workInProgressRootFatalError: mixed = null;
303 -// "Included" lanes refer to lanes that were worked on during this render. It's
304 -// slightly different than `renderLanes` because `renderLanes` can change as you
305 -// enter and exit an Offscreen tree. This value is the combination of all render
306 -// lanes for the entire render phase.
307 -let workInProgressRootIncludedLanes: Lanes = NoLanes;
296 // The work left over by components that were visited during this render. Only
297 // includes unprocessed updates, not work in bailed out children.
298 let workInProgressRootSkippedLanes: Lanes = NoLanes;
@@ -1455,18 +1443,16 @@ export function flushControlled(fn: () => mixed): void {
1443 }
1444 }
1445
1458 -export function pushRenderLanes(fiber: Fiber, lanes: Lanes) {
1459 - pushToStack(subtreeRenderLanesCursor, subtreeRenderLanes, fiber);
1460 - subtreeRenderLanes = mergeLanes(subtreeRenderLanes, lanes);
1461 - workInProgressRootIncludedLanes = mergeLanes(
1462 - workInProgressRootIncludedLanes,
1463 - lanes,
1464 - );
1446 +// This is called by the HiddenContext module when we enter or leave a
1447 +// hidden subtree. The stack logic is managed there because that's the only
1448 +// place that ever modifies it. Which module it lives in doesn't matter for
1449 +// performance because this function will get inlined regardless
1450 +export function setRenderLanes(subtreeRenderLanes: Lanes) {
1451 + renderLanes = subtreeRenderLanes;
1452 }
1453
1467 -export function popRenderLanes(fiber: Fiber) {
1468 - subtreeRenderLanes = subtreeRenderLanesCursor.current;
1469 - popFromStack(subtreeRenderLanesCursor, fiber);
1454 +export function getRenderLanes(): Lanes {
1455 + return renderLanes;
1456 }
1457
1458 function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
@@ -1497,7 +1483,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1483 workInProgressRoot = root;
1484 const rootWorkInProgress = createWorkInProgress(root.current, null);
1485 workInProgress = rootWorkInProgress;
1500 - workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1486 + workInProgressRootRenderLanes = renderLanes = lanes;
1487 workInProgressRootExitStatus = RootInProgress;
1488 workInProgressRootFatalError = null;
1489 workInProgressRootSkippedLanes = NoLanes;
@@ -1864,10 +1850,10 @@ function performUnitOfWork(unitOfWork: Fiber): void {
1850 let next;
1851 if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
1852 startProfilerTimer(unitOfWork);
1867 - next = beginWork(current, unitOfWork, subtreeRenderLanes);
1853 + next = beginWork(current, unitOfWork, renderLanes);
1854 stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
1855 } else {
1870 - next = beginWork(current, unitOfWork, subtreeRenderLanes);
1856 + next = beginWork(current, unitOfWork, renderLanes);
1857 }
1858
1859 resetCurrentDebugFiberInDEV();
@@ -1901,10 +1887,10 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1887 !enableProfilerTimer ||
1888 (completedWork.mode & ProfileMode) === NoMode
1889 ) {
1904 - next = completeWork(current, completedWork, subtreeRenderLanes);
1890 + next = completeWork(current, completedWork, renderLanes);
1891 } else {
1892 startProfilerTimer(completedWork);
1907 - next = completeWork(current, completedWork, subtreeRenderLanes);
1893 + next = completeWork(current, completedWork, renderLanes);
1894 // Update render duration assuming we didn't error.
1895 stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1896 }
@@ -1919,7 +1905,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1905 // This fiber did not complete because something threw. Pop values off
1906 // the stack without entering the complete phase. If this is a boundary,
1907 // capture values if possible.
1922 - const next = unwindWork(current, completedWork, subtreeRenderLanes);
1908 + const next = unwindWork(current, completedWork, renderLanes);
1909
1910 // Because this fiber did not complete, don't reset its lanes.
1911
scripts/merge-fork/forked-revisions
+2
@@ -0,0 +1,2 @@
1 +6ab05ee2e9c5b1f4c8dc1f7ae8906bf613788ba7 [FORKED] Track nearest Suspense handler on stack
2 +051ac55cb75f426b81f8f75b143f34255476b9bc [FORKED] Add HiddenContext to track if subtree is hidden
\ No newline at end of file