[Fiber] Reset remaining child lanes after propagating context inside Offscreen (#34658)

Otherwise, when a context is propagated into an Activity (or Suspense)
this will leave work behind on the Offscreen component itself. Which
will cause an extra unnecessary render and commit pass just to figure
out that we're still defering it to idle.

This is because lazy context propagation, when calling to schedule some
work walks back up the tree all the way to the root. This is usually
fine for other nodes since they'll recompute their remaining child lanes
on the way up. However, for the Offscreen component we'll have already
computed it. We need to set it after propagation to ensure it gets
reset.
This commit is contained in:
Sebastian Markbåge 2025-09-30 14:51:48 -04:00 committed by GitHub
parent 0d8ff4d8c7
commit d8a15c49a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -649,6 +649,7 @@ function updateOffscreenComponent(
? mergeLanes(prevState.baseLanes, renderLanes)
: renderLanes;
let remainingChildLanes;
if (current !== null) {
// Reset to the current children
let currentChild = (workInProgress.child = current.child);
@ -666,13 +667,12 @@ function updateOffscreenComponent(
currentChild = currentChild.sibling;
}
const lanesWeJustAttempted = nextBaseLanes;
const remainingChildLanes = removeLanes(
remainingChildLanes = removeLanes(
currentChildLanes,
lanesWeJustAttempted,
);
workInProgress.childLanes = remainingChildLanes;
} else {
workInProgress.childLanes = NoLanes;
remainingChildLanes = NoLanes;
workInProgress.child = null;
}
@ -681,6 +681,7 @@ function updateOffscreenComponent(
workInProgress,
nextBaseLanes,
renderLanes,
remainingChildLanes,
);
}
@ -707,8 +708,9 @@ function updateOffscreenComponent(
// and resume this tree later.
// Schedule this fiber to re-render at Offscreen priority
workInProgress.lanes = workInProgress.childLanes =
laneToLanes(OffscreenLane);
const remainingChildLanes = (workInProgress.lanes =
laneToLanes(OffscreenLane));
// Include the base lanes from the last render
const nextBaseLanes =
@ -721,6 +723,7 @@ function updateOffscreenComponent(
workInProgress,
nextBaseLanes,
renderLanes,
remainingChildLanes,
);
} else {
// This is the second render. The surrounding visible content has already
@ -826,6 +829,7 @@ function deferHiddenOffscreenComponent(
workInProgress: Fiber,
nextBaseLanes: Lanes,
renderLanes: Lanes,
remainingChildLanes: Lanes,
) {
const nextState: OffscreenState = {
baseLanes: nextBaseLanes,
@ -856,6 +860,13 @@ function deferHiddenOffscreenComponent(
);
}
// We override the remaining child lanes to be the subset that we computed
// on the outside. We need to do this after propagating the context
// because propagateParentContextChangesToDeferredTree may schedule
// work which bubbles all the way up to the root and updates our child lanes.
// We want to dismiss that since we're not going to work on it yet.
workInProgress.childLanes = remainingChildLanes;
return null;
}