Skip to content

Commit 051ac55

Browse files
committed
[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.
1 parent a7b192e commit 051ac55

6 files changed

Lines changed: 118 additions & 66 deletions

packages/react-reconciler/src/ReactFiberBeginWork.new.js

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ import {
175175
addSubtreeSuspenseContext,
176176
setShallowSuspenseContext,
177177
} from './ReactFiberSuspenseContext.new';
178+
import {
179+
pushHiddenContext,
180+
reuseHiddenContextOnStack,
181+
} from './ReactFiberHiddenContext.new';
178182
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
179183
import {
180184
pushProvider,
@@ -232,7 +236,6 @@ import {
232236
renderDidSuspendDelayIfPossible,
233237
markSkippedUpdateLanes,
234238
getWorkInProgressRoot,
235-
pushRenderLanes,
236239
} from './ReactFiberWorkLoop.new';
237240
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
238241
import {setWorkInProgressVersion} from './ReactMutableSource.new';
@@ -688,21 +691,14 @@ function updateOffscreenComponent(
688691
pushTransition(workInProgress, null, null);
689692
}
690693
}
691-
pushRenderLanes(workInProgress, renderLanes);
694+
reuseHiddenContextOnStack(workInProgress);
692695
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
693-
let spawnedCachePool: SpawnedCachePool | null = null;
694696
// We're hidden, and we're not rendering at Offscreen. We will bail out
695697
// and resume this tree later.
696-
let nextBaseLanes;
698+
let nextBaseLanes = renderLanes;
697699
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);
706702
}
707703

708704
// Schedule this fiber to re-render at offscreen priority. Then bailout.
@@ -711,7 +707,8 @@ function updateOffscreenComponent(
711707
);
712708
const nextState: OffscreenState = {
713709
baseLanes: nextBaseLanes,
714-
cachePool: spawnedCachePool,
710+
// Save the cache pool so we can resume later.
711+
cachePool: enableCache ? getOffscreenDeferredCache() : null,
715712
};
716713
workInProgress.memoizedState = nextState;
717714
workInProgress.updateQueue = null;
@@ -725,7 +722,7 @@ function updateOffscreenComponent(
725722

726723
// We're about to bail out, but we need to push this to the stack anyway
727724
// to avoid a push/pop misalignment.
728-
pushRenderLanes(workInProgress, nextBaseLanes);
725+
reuseHiddenContextOnStack(workInProgress);
729726

730727
if (enableLazyContextPropagation && current !== null) {
731728
// Since this tree will resume rendering in a separate render, we need
@@ -749,9 +746,6 @@ function updateOffscreenComponent(
749746
cachePool: null,
750747
};
751748
workInProgress.memoizedState = nextState;
752-
// Push the lanes that were skipped when we bailed out.
753-
const subtreeRenderLanes =
754-
prevState !== null ? prevState.baseLanes : renderLanes;
755749
if (enableCache && current !== null) {
756750
// If the render that spawned this one accessed the cache pool, resume
757751
// using the same cache. Unless the parent changed, since that means
@@ -762,16 +756,17 @@ function updateOffscreenComponent(
762756
pushTransition(workInProgress, prevCachePool, null);
763757
}
764758

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+
}
766765
}
767766
} else {
768767
// Rendering a visible tree.
769-
let subtreeRenderLanes;
770768
if (prevState !== null) {
771769
// We're going from hidden -> visible.
772-
773-
subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes);
774-
775770
let prevCachePool = null;
776771
if (enableCache) {
777772
// If the render that spawned this one accessed the cache pool, resume
@@ -789,13 +784,15 @@ function updateOffscreenComponent(
789784

790785
pushTransition(workInProgress, prevCachePool, transitions);
791786

787+
// Push the lanes that were skipped when we bailed out.
788+
pushHiddenContext(workInProgress, prevState);
789+
792790
// Since we're not hidden anymore, reset the state
793791
workInProgress.memoizedState = null;
794792
} else {
795793
// We weren't previously hidden, and we still aren't, so there's nothing
796794
// special to do. Need to push to the stack regardless, though, to avoid
797795
// a push/pop misalignment.
798-
subtreeRenderLanes = renderLanes;
799796

800797
if (enableCache) {
801798
// If the render that spawned this one accessed the cache pool, resume
@@ -805,8 +802,11 @@ function updateOffscreenComponent(
805802
pushTransition(workInProgress, null, null);
806803
}
807804
}
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);
808809
}
809-
pushRenderLanes(workInProgress, subtreeRenderLanes);
810810
}
811811

812812
reconcileChildren(current, workInProgress, nextChildren, renderLanes);

packages/react-reconciler/src/ReactFiberCompleteWork.new.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ import {
117117
ForceSuspenseFallback,
118118
setDefaultShallowSuspenseContext,
119119
} from './ReactFiberSuspenseContext.new';
120+
import {popHiddenContext} from './ReactFiberHiddenContext.new';
120121
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
121122
import {
122123
isContextProvider as isLegacyContextProvider,
@@ -146,9 +147,7 @@ import {
146147
renderDidSuspend,
147148
renderDidSuspendDelayIfPossible,
148149
renderHasNotSuspendedYet,
149-
popRenderLanes,
150150
getRenderTargetTime,
151-
subtreeRenderLanes,
152151
getWorkInProgressTransitions,
153152
} from './ReactFiberWorkLoop.new';
154153
import {
@@ -1499,7 +1498,7 @@ function completeWork(
14991498
}
15001499
case OffscreenComponent:
15011500
case LegacyHiddenComponent: {
1502-
popRenderLanes(workInProgress);
1501+
popHiddenContext(workInProgress);
15031502
const nextState: OffscreenState | null = workInProgress.memoizedState;
15041503
const nextIsHidden = nextState !== null;
15051504

@@ -1520,7 +1519,7 @@ function completeWork(
15201519
} else {
15211520
// Don't bubble properties for hidden children unless we're rendering
15221521
// at offscreen priority.
1523-
if (includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))) {
1522+
if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
15241523
bubbleProperties(workInProgress);
15251524
// Check if there was an insertion or update in the hidden subtree.
15261525
// If so, we need to hide those nodes in the commit phase, so
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Intentionally blank. File only exists in new reconciler fork.

packages/react-reconciler/src/ReactFiberUnwindWork.new.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ import {
3737

3838
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
3939
import {popSuspenseContext} from './ReactFiberSuspenseContext.new';
40+
import {popHiddenContext} from './ReactFiberHiddenContext.new';
4041
import {resetHydrationState} from './ReactFiberHydrationContext.new';
4142
import {
4243
isContextProvider as isLegacyContextProvider,
4344
popContext as popLegacyContext,
4445
popTopLevelContextObject as popTopLevelLegacyContextObject,
4546
} from './ReactFiberContext.new';
4647
import {popProvider} from './ReactFiberNewContext.new';
47-
import {popRenderLanes} from './ReactFiberWorkLoop.new';
4848
import {popCacheProvider} from './ReactFiberCacheComponent.new';
4949
import {transferActualDuration} from './ReactProfilerTimer.new';
5050
import {popTreeContext} from './ReactFiberTreeContext.new';
@@ -151,7 +151,7 @@ function unwindWork(
151151
return null;
152152
case OffscreenComponent:
153153
case LegacyHiddenComponent:
154-
popRenderLanes(workInProgress);
154+
popHiddenContext(workInProgress);
155155
popTransition(workInProgress, current);
156156
return null;
157157
case CacheComponent:
@@ -219,7 +219,7 @@ function unwindInterruptedWork(
219219
break;
220220
case OffscreenComponent:
221221
case LegacyHiddenComponent:
222-
popRenderLanes(interruptedWork);
222+
popHiddenContext(interruptedWork);
223223
popTransition(interruptedWork, current);
224224
break;
225225
case CacheComponent:

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type {Wakeable} from 'shared/ReactTypes';
1111
import type {Fiber, FiberRoot} from './ReactInternalTypes';
1212
import type {Lanes, Lane} from './ReactFiberLane.new';
1313
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14-
import type {StackCursor} from './ReactFiberStack.new';
1514
import type {Flags} from './ReactFiberFlags';
1615
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
1716
import type {EventPriority} from './ReactEventPriorities.new';
@@ -192,11 +191,6 @@ import {
192191
createCapturedValueAtFiber,
193192
type CapturedValue,
194193
} from './ReactCapturedValue';
195-
import {
196-
push as pushToStack,
197-
pop as popFromStack,
198-
createCursor,
199-
} from './ReactFiberStack.new';
200194
import {
201195
enqueueConcurrentRenderForLane,
202196
finishQueueingConcurrentUpdates,
@@ -285,26 +279,20 @@ let workInProgress: Fiber | null = null;
285279
// The lanes we're rendering
286280
let workInProgressRootRenderLanes: Lanes = NoLanes;
287281

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.
293287
//
294288
// 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;
298291

299292
// Whether to root completed, errored, suspended, etc.
300293
let workInProgressRootExitStatus: RootExitStatus = RootInProgress;
301294
// A fatal error, if one is thrown
302295
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;
308296
// The work left over by components that were visited during this render. Only
309297
// includes unprocessed updates, not work in bailed out children.
310298
let workInProgressRootSkippedLanes: Lanes = NoLanes;
@@ -1455,18 +1443,16 @@ export function flushControlled(fn: () => mixed): void {
14551443
}
14561444
}
14571445

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;
14651452
}
14661453

1467-
export function popRenderLanes(fiber: Fiber) {
1468-
subtreeRenderLanes = subtreeRenderLanesCursor.current;
1469-
popFromStack(subtreeRenderLanesCursor, fiber);
1454+
export function getRenderLanes(): Lanes {
1455+
return renderLanes;
14701456
}
14711457

14721458
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
@@ -1497,7 +1483,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
14971483
workInProgressRoot = root;
14981484
const rootWorkInProgress = createWorkInProgress(root.current, null);
14991485
workInProgress = rootWorkInProgress;
1500-
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1486+
workInProgressRootRenderLanes = renderLanes = lanes;
15011487
workInProgressRootExitStatus = RootInProgress;
15021488
workInProgressRootFatalError = null;
15031489
workInProgressRootSkippedLanes = NoLanes;
@@ -1864,10 +1850,10 @@ function performUnitOfWork(unitOfWork: Fiber): void {
18641850
let next;
18651851
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
18661852
startProfilerTimer(unitOfWork);
1867-
next = beginWork(current, unitOfWork, subtreeRenderLanes);
1853+
next = beginWork(current, unitOfWork, renderLanes);
18681854
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
18691855
} else {
1870-
next = beginWork(current, unitOfWork, subtreeRenderLanes);
1856+
next = beginWork(current, unitOfWork, renderLanes);
18711857
}
18721858

18731859
resetCurrentDebugFiberInDEV();
@@ -1901,10 +1887,10 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
19011887
!enableProfilerTimer ||
19021888
(completedWork.mode & ProfileMode) === NoMode
19031889
) {
1904-
next = completeWork(current, completedWork, subtreeRenderLanes);
1890+
next = completeWork(current, completedWork, renderLanes);
19051891
} else {
19061892
startProfilerTimer(completedWork);
1907-
next = completeWork(current, completedWork, subtreeRenderLanes);
1893+
next = completeWork(current, completedWork, renderLanes);
19081894
// Update render duration assuming we didn't error.
19091895
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
19101896
}
@@ -1919,7 +1905,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
19191905
// This fiber did not complete because something threw. Pop values off
19201906
// the stack without entering the complete phase. If this is a boundary,
19211907
// capture values if possible.
1922-
const next = unwindWork(current, completedWork, subtreeRenderLanes);
1908+
const next = unwindWork(current, completedWork, renderLanes);
19231909

19241910
// Because this fiber did not complete, don't reset its lanes.
19251911

0 commit comments

Comments
 (0)