@samitouri / QOS-React / commits / 34308b5ada

Tidy up early bailout logic at start of begin phase (#21852)

* Extract early bailout to separate function This block is getting hard to read so I moved it to a separate function. I'm about to refactor the logic that wraps around this path. Ideally this early bailout path would happen before the begin phase phase. Perhaps during reconcilation of the parent fiber's children. * Extract state and context check to separate function The only reason we pass `updateLanes` to some begin functions is to check if we can perform an early bail out. But this is also available as `current.lanes`, so we can read it from there instead. I think the only reason we didn't do it this way originally is because components that have two phases — error and Suspense boundaries — use `workInProgress.lanes` to prevent a bail out, since during the initial render there is no `current`. But we can check the `DidCapture` flag instead, which we use elsewhere to detect the second phase.

Andrew Clark committed Jul 29, 2021 at 09:59 UTC 34308b5adaffaf27501ba7a934b95478ba0aa327
4 files changed +520 -490
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+258 -245
@@ -414,7 +414,6 @@ function updateMemoComponent(
414 workInProgress: Fiber,
415 Component: any,
416 nextProps: any,
417 - updateLanes: Lanes,
417 renderLanes: Lanes,
418 ): null | Fiber {
419 if (current === null) {
@@ -442,7 +441,6 @@ function updateMemoComponent(
441 workInProgress,
442 resolvedType,
443 nextProps,
445 - updateLanes,
444 renderLanes,
445 );
446 }
@@ -487,7 +485,11 @@ function updateMemoComponent(
485 }
486 }
487 const currentChild = ((current.child: any): Fiber); // This is always exactly one child
490 - if (!includesSomeLane(updateLanes, renderLanes)) {
488 + const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
489 + current,
490 + renderLanes,
491 + );
492 + if (!hasScheduledUpdateOrContext) {
493 // This will be the props with resolved defaultProps,
494 // unlike current.memoizedProps which will be the unresolved ones.
495 const prevProps = currentChild.memoizedProps;
@@ -512,7 +514,6 @@ function updateSimpleMemoComponent(
514 workInProgress: Fiber,
515 Component: any,
516 nextProps: any,
515 - updateLanes: Lanes,
517 renderLanes: Lanes,
518 ): null | Fiber {
519 // TODO: current can be non-null here even if the component
@@ -558,7 +559,7 @@ function updateSimpleMemoComponent(
559 (__DEV__ ? workInProgress.type === current.type : true)
560 ) {
561 didReceiveUpdate = false;
561 - if (!includesSomeLane(renderLanes, updateLanes)) {
562 + if (!checkScheduledUpdateOrContext(current, renderLanes)) {
563 // The pending lanes were cleared at the beginning of beginWork. We're
564 // about to bail out, but there might be other lanes that weren't
565 // included in the current render. Usually, the priority level of the
@@ -806,7 +807,6 @@ const updateLegacyHiddenComponent = updateOffscreenComponent;
807 function updateCacheComponent(
808 current: Fiber | null,
809 workInProgress: Fiber,
809 - updateLanes: Lanes,
810 renderLanes: Lanes,
811 ) {
812 if (!enableCache) {
@@ -828,7 +828,7 @@ function updateCacheComponent(
828 pushCacheProvider(workInProgress, freshCache);
829 } else {
830 // Check for updates
831 - if (includesSomeLane(renderLanes, updateLanes)) {
831 + if (includesSomeLane(current.lanes, renderLanes)) {
832 cloneUpdateQueue(current, workInProgress);
833 processUpdateQueue(workInProgress, null, null, renderLanes);
834 }
@@ -1372,7 +1372,6 @@ function mountLazyComponent(
1372 _current,
1373 workInProgress,
1374 elementType,
1375 - updateLanes,
1375 renderLanes,
1376 ) {
1377 if (_current !== null) {
@@ -1462,7 +1461,6 @@ function mountLazyComponent(
1461 workInProgress,
1462 Component,
1463 resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
1465 - updateLanes,
1464 renderLanes,
1465 );
1466 return child;
@@ -3347,13 +3345,241 @@ function remountFiber(
3345 }
3346 }
3347
3348 +function checkScheduledUpdateOrContext(
3349 + current: Fiber,
3350 + renderLanes: Lanes,
3351 +): boolean {
3352 + // Before performing an early bailout, we must check if there are pending
3353 + // updates or context.
3354 + const updateLanes = current.lanes;
3355 + if (includesSomeLane(updateLanes, renderLanes)) {
3356 + return true;
3357 + }
3358 + // No pending update, but because context is propagated lazily, we need
3359 + // to check for a context change before we bail out.
3360 + if (enableLazyContextPropagation) {
3361 + const dependencies = current.dependencies;
3362 + if (dependencies !== null && checkIfContextChanged(dependencies)) {
3363 + return true;
3364 + }
3365 + }
3366 + return false;
3367 +}
3368 +
3369 +function attemptEarlyBailoutIfNoScheduledUpdate(
3370 + current: Fiber,
3371 + workInProgress: Fiber,
3372 + renderLanes: Lanes,
3373 +) {
3374 + // This fiber does not have any pending work. Bailout without entering
3375 + // the begin phase. There's still some bookkeeping we that needs to be done
3376 + // in this optimized path, mostly pushing stuff onto the stack.
3377 + switch (workInProgress.tag) {
3378 + case HostRoot:
3379 + pushHostRootContext(workInProgress);
3380 + if (enableCache) {
3381 + const root: FiberRoot = workInProgress.stateNode;
3382 + const cache: Cache = current.memoizedState.cache;
3383 + pushCacheProvider(workInProgress, cache);
3384 + pushRootCachePool(root);
3385 + }
3386 + resetHydrationState();
3387 + break;
3388 + case HostComponent:
3389 + pushHostContext(workInProgress);
3390 + break;
3391 + case ClassComponent: {
3392 + const Component = workInProgress.type;
3393 + if (isLegacyContextProvider(Component)) {
3394 + pushLegacyContextProvider(workInProgress);
3395 + }
3396 + break;
3397 + }
3398 + case HostPortal:
3399 + pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
3400 + break;
3401 + case ContextProvider: {
3402 + const newValue = workInProgress.memoizedProps.value;
3403 + const context: ReactContext<any> = workInProgress.type._context;
3404 + pushProvider(workInProgress, context, newValue);
3405 + break;
3406 + }
3407 + case Profiler:
3408 + if (enableProfilerTimer) {
3409 + // Profiler should only call onRender when one of its descendants actually rendered.
3410 + const hasChildWork = includesSomeLane(
3411 + renderLanes,
3412 + workInProgress.childLanes,
3413 + );
3414 + if (hasChildWork) {
3415 + workInProgress.flags |= Update;
3416 + }
3417 +
3418 + if (enableProfilerCommitHooks) {
3419 + // Reset effect durations for the next eventual effect phase.
3420 + // These are reset during render to allow the DevTools commit hook a chance to read them,
3421 + const stateNode = workInProgress.stateNode;
3422 + stateNode.effectDuration = 0;
3423 + stateNode.passiveEffectDuration = 0;
3424 + }
3425 + }
3426 + break;
3427 + case SuspenseComponent: {
3428 + const state: SuspenseState | null = workInProgress.memoizedState;
3429 + if (state !== null) {
3430 + if (enableSuspenseServerRenderer) {
3431 + if (state.dehydrated !== null) {
3432 + pushSuspenseContext(
3433 + workInProgress,
3434 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3435 + );
3436 + // We know that this component will suspend again because if it has
3437 + // been unsuspended it has committed as a resolved Suspense component.
3438 + // If it needs to be retried, it should have work scheduled on it.
3439 + workInProgress.flags |= DidCapture;
3440 + // We should never render the children of a dehydrated boundary until we
3441 + // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3442 + return null;
3443 + }
3444 + }
3445 +
3446 + // If this boundary is currently timed out, we need to decide
3447 + // whether to retry the primary children, or to skip over it and
3448 + // go straight to the fallback. Check the priority of the primary
3449 + // child fragment.
3450 + const primaryChildFragment: Fiber = (workInProgress.child: any);
3451 + const primaryChildLanes = primaryChildFragment.childLanes;
3452 + if (includesSomeLane(renderLanes, primaryChildLanes)) {
3453 + // The primary children have pending work. Use the normal path
3454 + // to attempt to render the primary children again.
3455 + return updateSuspenseComponent(current, workInProgress, renderLanes);
3456 + } else {
3457 + // The primary child fragment does not have pending work marked
3458 + // on it
3459 + pushSuspenseContext(
3460 + workInProgress,
3461 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3462 + );
3463 + // The primary children do not have pending work with sufficient
3464 + // priority. Bailout.
3465 + const child = bailoutOnAlreadyFinishedWork(
3466 + current,
3467 + workInProgress,
3468 + renderLanes,
3469 + );
3470 + if (child !== null) {
3471 + // The fallback children have pending work. Skip over the
3472 + // primary children and work on the fallback.
3473 + return child.sibling;
3474 + } else {
3475 + // Note: We can return `null` here because we already checked
3476 + // whether there were nested context consumers, via the call to
3477 + // `bailoutOnAlreadyFinishedWork` above.
3478 + return null;
3479 + }
3480 + }
3481 + } else {
3482 + pushSuspenseContext(
3483 + workInProgress,
3484 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3485 + );
3486 + }
3487 + break;
3488 + }
3489 + case SuspenseListComponent: {
3490 + const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3491 +
3492 + let hasChildWork = includesSomeLane(
3493 + renderLanes,
3494 + workInProgress.childLanes,
3495 + );
3496 +
3497 + if (enableLazyContextPropagation && !hasChildWork) {
3498 + // Context changes may not have been propagated yet. We need to do
3499 + // that now, before we can decide whether to bail out.
3500 + // TODO: We use `childLanes` as a heuristic for whether there is
3501 + // remaining work in a few places, including
3502 + // `bailoutOnAlreadyFinishedWork` and
3503 + // `updateDehydratedSuspenseComponent`. We should maybe extract this
3504 + // into a dedicated function.
3505 + lazilyPropagateParentContextChanges(
3506 + current,
3507 + workInProgress,
3508 + renderLanes,
3509 + );
3510 + hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
3511 + }
3512 +
3513 + if (didSuspendBefore) {
3514 + if (hasChildWork) {
3515 + // If something was in fallback state last time, and we have all the
3516 + // same children then we're still in progressive loading state.
3517 + // Something might get unblocked by state updates or retries in the
3518 + // tree which will affect the tail. So we need to use the normal
3519 + // path to compute the correct tail.
3520 + return updateSuspenseListComponent(
3521 + current,
3522 + workInProgress,
3523 + renderLanes,
3524 + );
3525 + }
3526 + // If none of the children had any work, that means that none of
3527 + // them got retried so they'll still be blocked in the same way
3528 + // as before. We can fast bail out.
3529 + workInProgress.flags |= DidCapture;
3530 + }
3531 +
3532 + // If nothing suspended before and we're rendering the same children,
3533 + // then the tail doesn't matter. Anything new that suspends will work
3534 + // in the "together" mode, so we can continue from the state we had.
3535 + const renderState = workInProgress.memoizedState;
3536 + if (renderState !== null) {
3537 + // Reset to the "together" mode in case we've started a different
3538 + // update in the past but didn't complete it.
3539 + renderState.rendering = null;
3540 + renderState.tail = null;
3541 + renderState.lastEffect = null;
3542 + }
3543 + pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3544 +
3545 + if (hasChildWork) {
3546 + break;
3547 + } else {
3548 + // If none of the children had any work, that means that none of
3549 + // them got retried so they'll still be blocked in the same way
3550 + // as before. We can fast bail out.
3551 + return null;
3552 + }
3553 + }
3554 + case OffscreenComponent:
3555 + case LegacyHiddenComponent: {
3556 + // Need to check if the tree still needs to be deferred. This is
3557 + // almost identical to the logic used in the normal update path,
3558 + // so we'll just enter that. The only difference is we'll bail out
3559 + // at the next level instead of this one, because the child props
3560 + // have not changed. Which is fine.
3561 + // TODO: Probably should refactor `beginWork` to split the bailout
3562 + // path from the normal path. I'm tempted to do a labeled break here
3563 + // but I won't :)
3564 + workInProgress.lanes = NoLanes;
3565 + return updateOffscreenComponent(current, workInProgress, renderLanes);
3566 + }
3567 + case CacheComponent: {
3568 + if (enableCache) {
3569 + const cache: Cache = current.memoizedState.cache;
3570 + pushCacheProvider(workInProgress, cache);
3571 + }
3572 + break;
3573 + }
3574 + }
3575 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3576 +}
3577 +
3578 function beginWork(
3579 current: Fiber | null,
3580 workInProgress: Fiber,
3581 renderLanes: Lanes,
3582 ): Fiber | null {
3355 - let updateLanes = workInProgress.lanes;
3356 -
3583 if (__DEV__) {
3584 if (workInProgress._debugNeedsRemount && current !== null) {
3585 // This will restart the begin phase with a new fiber.
@@ -3373,17 +3599,6 @@ function beginWork(
3599 }
3600
3601 if (current !== null) {
3376 - // TODO: The factoring of this block is weird.
3377 - if (
3378 - enableLazyContextPropagation &&
3379 - !includesSomeLane(renderLanes, updateLanes)
3380 - ) {
3381 - const dependencies = current.dependencies;
3382 - if (dependencies !== null && checkIfContextChanged(dependencies)) {
3383 - updateLanes = mergeLanes(updateLanes, renderLanes);
3384 - }
3385 - }
3386 -
3602 const oldProps = current.memoizedProps;
3603 const newProps = workInProgress.pendingProps;
3604
@@ -3396,221 +3611,27 @@ function beginWork(
3611 // If props or context changed, mark the fiber as having performed work.
3612 // This may be unset if the props are determined to be equal later (memo).
3613 didReceiveUpdate = true;
3399 - } else if (!includesSomeLane(renderLanes, updateLanes)) {
3400 - didReceiveUpdate = false;
3401 - // This fiber does not have any pending work. Bailout without entering
3402 - // the begin phase. There's still some bookkeeping we that needs to be done
3403 - // in this optimized path, mostly pushing stuff onto the stack.
3404 - switch (workInProgress.tag) {
3405 - case HostRoot:
3406 - pushHostRootContext(workInProgress);
3407 - if (enableCache) {
3408 - const root: FiberRoot = workInProgress.stateNode;
3409 - const cache: Cache = current.memoizedState.cache;
3410 - pushCacheProvider(workInProgress, cache);
3411 - pushRootCachePool(root);
3412 - }
3413 - resetHydrationState();
3414 - break;
3415 - case HostComponent:
3416 - pushHostContext(workInProgress);
3417 - break;
3418 - case ClassComponent: {
3419 - const Component = workInProgress.type;
3420 - if (isLegacyContextProvider(Component)) {
3421 - pushLegacyContextProvider(workInProgress);
3422 - }
3423 - break;
3424 - }
3425 - case HostPortal:
3426 - pushHostContainer(
3427 - workInProgress,
3428 - workInProgress.stateNode.containerInfo,
3429 - );
3430 - break;
3431 - case ContextProvider: {
3432 - const newValue = workInProgress.memoizedProps.value;
3433 - const context: ReactContext<any> = workInProgress.type._context;
3434 - pushProvider(workInProgress, context, newValue);
3435 - break;
3436 - }
3437 - case Profiler:
3438 - if (enableProfilerTimer) {
3439 - // Profiler should only call onRender when one of its descendants actually rendered.
3440 - const hasChildWork = includesSomeLane(
3441 - renderLanes,
3442 - workInProgress.childLanes,
3443 - );
3444 - if (hasChildWork) {
3445 - workInProgress.flags |= Update;
3446 - }
3447 -
3448 - if (enableProfilerCommitHooks) {
3449 - // Reset effect durations for the next eventual effect phase.
3450 - // These are reset during render to allow the DevTools commit hook a chance to read them,
3451 - const stateNode = workInProgress.stateNode;
3452 - stateNode.effectDuration = 0;
3453 - stateNode.passiveEffectDuration = 0;
3454 - }
3455 - }
3456 - break;
3457 - case SuspenseComponent: {
3458 - const state: SuspenseState | null = workInProgress.memoizedState;
3459 - if (state !== null) {
3460 - if (enableSuspenseServerRenderer) {
3461 - if (state.dehydrated !== null) {
3462 - pushSuspenseContext(
3463 - workInProgress,
3464 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3465 - );
3466 - // We know that this component will suspend again because if it has
3467 - // been unsuspended it has committed as a resolved Suspense component.
3468 - // If it needs to be retried, it should have work scheduled on it.
3469 - workInProgress.flags |= DidCapture;
3470 - // We should never render the children of a dehydrated boundary until we
3471 - // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3472 - return null;
3473 - }
3474 - }
3475 -
3476 - // If this boundary is currently timed out, we need to decide
3477 - // whether to retry the primary children, or to skip over it and
3478 - // go straight to the fallback. Check the priority of the primary
3479 - // child fragment.
3480 - const primaryChildFragment: Fiber = (workInProgress.child: any);
3481 - const primaryChildLanes = primaryChildFragment.childLanes;
3482 - if (includesSomeLane(renderLanes, primaryChildLanes)) {
3483 - // The primary children have pending work. Use the normal path
3484 - // to attempt to render the primary children again.
3485 - return updateSuspenseComponent(
3486 - current,
3487 - workInProgress,
3488 - renderLanes,
3489 - );
3490 - } else {
3491 - // The primary child fragment does not have pending work marked
3492 - // on it
3493 - pushSuspenseContext(
3494 - workInProgress,
3495 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3496 - );
3497 - // The primary children do not have pending work with sufficient
3498 - // priority. Bailout.
3499 - const child = bailoutOnAlreadyFinishedWork(
3500 - current,
3501 - workInProgress,
3502 - renderLanes,
3503 - );
3504 - if (child !== null) {
3505 - // The fallback children have pending work. Skip over the
3506 - // primary children and work on the fallback.
3507 - return child.sibling;
3508 - } else {
3509 - // Note: We can return `null` here because we already checked
3510 - // whether there were nested context consumers, via the call to
3511 - // `bailoutOnAlreadyFinishedWork` above.
3512 - return null;
3513 - }
3514 - }
3515 - } else {
3516 - pushSuspenseContext(
3517 - workInProgress,
3518 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3519 - );
3520 - }
3521 - break;
3522 - }
3523 - case SuspenseListComponent: {
3524 - const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3525 -
3526 - let hasChildWork = includesSomeLane(
3527 - renderLanes,
3528 - workInProgress.childLanes,
3529 - );
3530 -
3531 - if (enableLazyContextPropagation && !hasChildWork) {
3532 - // Context changes may not have been propagated yet. We need to do
3533 - // that now, before we can decide whether to bail out.
3534 - // TODO: We use `childLanes` as a heuristic for whether there is
3535 - // remaining work in a few places, including
3536 - // `bailoutOnAlreadyFinishedWork` and
3537 - // `updateDehydratedSuspenseComponent`. We should maybe extract this
3538 - // into a dedicated function.
3539 - lazilyPropagateParentContextChanges(
3540 - current,
3541 - workInProgress,
3542 - renderLanes,
3543 - );
3544 - hasChildWork = includesSomeLane(
3545 - renderLanes,
3546 - workInProgress.childLanes,
3547 - );
3548 - }
3549 -
3550 - if (didSuspendBefore) {
3551 - if (hasChildWork) {
3552 - // If something was in fallback state last time, and we have all the
3553 - // same children then we're still in progressive loading state.
3554 - // Something might get unblocked by state updates or retries in the
3555 - // tree which will affect the tail. So we need to use the normal
3556 - // path to compute the correct tail.
3557 - return updateSuspenseListComponent(
3558 - current,
3559 - workInProgress,
3560 - renderLanes,
3561 - );
3562 - }
3563 - // If none of the children had any work, that means that none of
3564 - // them got retried so they'll still be blocked in the same way
3565 - // as before. We can fast bail out.
3566 - workInProgress.flags |= DidCapture;
3567 - }
3568 -
3569 - // If nothing suspended before and we're rendering the same children,
3570 - // then the tail doesn't matter. Anything new that suspends will work
3571 - // in the "together" mode, so we can continue from the state we had.
3572 - const renderState = workInProgress.memoizedState;
3573 - if (renderState !== null) {
3574 - // Reset to the "together" mode in case we've started a different
3575 - // update in the past but didn't complete it.
3576 - renderState.rendering = null;
3577 - renderState.tail = null;
3578 - renderState.lastEffect = null;
3579 - }
3580 - pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3581 -
3582 - if (hasChildWork) {
3583 - break;
3584 - } else {
3585 - // If none of the children had any work, that means that none of
3586 - // them got retried so they'll still be blocked in the same way
3587 - // as before. We can fast bail out.
3588 - return null;
3589 - }
3590 - }
3591 - case OffscreenComponent:
3592 - case LegacyHiddenComponent: {
3593 - // Need to check if the tree still needs to be deferred. This is
3594 - // almost identical to the logic used in the normal update path,
3595 - // so we'll just enter that. The only difference is we'll bail out
3596 - // at the next level instead of this one, because the child props
3597 - // have not changed. Which is fine.
3598 - // TODO: Probably should refactor `beginWork` to split the bailout
3599 - // path from the normal path. I'm tempted to do a labeled break here
3600 - // but I won't :)
3601 - workInProgress.lanes = NoLanes;
3602 - return updateOffscreenComponent(current, workInProgress, renderLanes);
3603 - }
3604 - case CacheComponent: {
3605 - if (enableCache) {
3606 - const cache: Cache = current.memoizedState.cache;
3607 - pushCacheProvider(workInProgress, cache);
3608 - }
3609 - break;
3610 - }
3611 - }
3612 - return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3614 } else {
3615 + // Neither props nor legacy context changes. Check if there's a pending
3616 + // update or context change.
3617 + const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
3618 + current,
3619 + renderLanes,
3620 + );
3621 + if (
3622 + !hasScheduledUpdateOrContext &&
3623 + // If this is the second pass of an error or suspense boundary, there
3624 + // may not be work scheduled on `current`, so we check for this flag.
3625 + (workInProgress.flags & DidCapture) === NoFlags
3626 + ) {
3627 + // No pending updates or context. Bail out now.
3628 + didReceiveUpdate = false;
3629 + return attemptEarlyBailoutIfNoScheduledUpdate(
3630 + current,
3631 + workInProgress,
3632 + renderLanes,
3633 + );
3634 + }
3635 if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
3636 // This is a special case that only exists for legacy mode.
3637 // See https://github.com/facebook/react/pull/19216.
@@ -3649,7 +3670,6 @@ function beginWork(
3670 current,
3671 workInProgress,
3672 elementType,
3652 - updateLanes,
3673 renderLanes,
3674 );
3675 }
@@ -3742,7 +3762,6 @@ function beginWork(
3762 workInProgress,
3763 type,
3764 resolvedProps,
3745 - updateLanes,
3765 renderLanes,
3766 );
3767 }
@@ -3752,7 +3771,6 @@ function beginWork(
3771 workInProgress,
3772 workInProgress.type,
3773 workInProgress.pendingProps,
3755 - updateLanes,
3774 renderLanes,
3775 );
3776 }
@@ -3788,12 +3806,7 @@ function beginWork(
3806 }
3807 case CacheComponent: {
3808 if (enableCache) {
3791 - return updateCacheComponent(
3792 - current,
3793 - workInProgress,
3794 - updateLanes,
3795 - renderLanes,
3796 - );
3809 + return updateCacheComponent(current, workInProgress, renderLanes);
3810 }
3811 break;
3812 }
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+258 -245
@@ -414,7 +414,6 @@ function updateMemoComponent(
414 workInProgress: Fiber,
415 Component: any,
416 nextProps: any,
417 - updateLanes: Lanes,
417 renderLanes: Lanes,
418 ): null | Fiber {
419 if (current === null) {
@@ -442,7 +441,6 @@ function updateMemoComponent(
441 workInProgress,
442 resolvedType,
443 nextProps,
445 - updateLanes,
444 renderLanes,
445 );
446 }
@@ -487,7 +485,11 @@ function updateMemoComponent(
485 }
486 }
487 const currentChild = ((current.child: any): Fiber); // This is always exactly one child
490 - if (!includesSomeLane(updateLanes, renderLanes)) {
488 + const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
489 + current,
490 + renderLanes,
491 + );
492 + if (!hasScheduledUpdateOrContext) {
493 // This will be the props with resolved defaultProps,
494 // unlike current.memoizedProps which will be the unresolved ones.
495 const prevProps = currentChild.memoizedProps;
@@ -512,7 +514,6 @@ function updateSimpleMemoComponent(
514 workInProgress: Fiber,
515 Component: any,
516 nextProps: any,
515 - updateLanes: Lanes,
517 renderLanes: Lanes,
518 ): null | Fiber {
519 // TODO: current can be non-null here even if the component
@@ -558,7 +559,7 @@ function updateSimpleMemoComponent(
559 (__DEV__ ? workInProgress.type === current.type : true)
560 ) {
561 didReceiveUpdate = false;
561 - if (!includesSomeLane(renderLanes, updateLanes)) {
562 + if (!checkScheduledUpdateOrContext(current, renderLanes)) {
563 // The pending lanes were cleared at the beginning of beginWork. We're
564 // about to bail out, but there might be other lanes that weren't
565 // included in the current render. Usually, the priority level of the
@@ -806,7 +807,6 @@ const updateLegacyHiddenComponent = updateOffscreenComponent;
807 function updateCacheComponent(
808 current: Fiber | null,
809 workInProgress: Fiber,
809 - updateLanes: Lanes,
810 renderLanes: Lanes,
811 ) {
812 if (!enableCache) {
@@ -828,7 +828,7 @@ function updateCacheComponent(
828 pushCacheProvider(workInProgress, freshCache);
829 } else {
830 // Check for updates
831 - if (includesSomeLane(renderLanes, updateLanes)) {
831 + if (includesSomeLane(current.lanes, renderLanes)) {
832 cloneUpdateQueue(current, workInProgress);
833 processUpdateQueue(workInProgress, null, null, renderLanes);
834 }
@@ -1372,7 +1372,6 @@ function mountLazyComponent(
1372 _current,
1373 workInProgress,
1374 elementType,
1375 - updateLanes,
1375 renderLanes,
1376 ) {
1377 if (_current !== null) {
@@ -1462,7 +1461,6 @@ function mountLazyComponent(
1461 workInProgress,
1462 Component,
1463 resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
1465 - updateLanes,
1464 renderLanes,
1465 );
1466 return child;
@@ -3347,13 +3345,241 @@ function remountFiber(
3345 }
3346 }
3347
3348 +function checkScheduledUpdateOrContext(
3349 + current: Fiber,
3350 + renderLanes: Lanes,
3351 +): boolean {
3352 + // Before performing an early bailout, we must check if there are pending
3353 + // updates or context.
3354 + const updateLanes = current.lanes;
3355 + if (includesSomeLane(updateLanes, renderLanes)) {
3356 + return true;
3357 + }
3358 + // No pending update, but because context is propagated lazily, we need
3359 + // to check for a context change before we bail out.
3360 + if (enableLazyContextPropagation) {
3361 + const dependencies = current.dependencies;
3362 + if (dependencies !== null && checkIfContextChanged(dependencies)) {
3363 + return true;
3364 + }
3365 + }
3366 + return false;
3367 +}
3368 +
3369 +function attemptEarlyBailoutIfNoScheduledUpdate(
3370 + current: Fiber,
3371 + workInProgress: Fiber,
3372 + renderLanes: Lanes,
3373 +) {
3374 + // This fiber does not have any pending work. Bailout without entering
3375 + // the begin phase. There's still some bookkeeping we that needs to be done
3376 + // in this optimized path, mostly pushing stuff onto the stack.
3377 + switch (workInProgress.tag) {
3378 + case HostRoot:
3379 + pushHostRootContext(workInProgress);
3380 + if (enableCache) {
3381 + const root: FiberRoot = workInProgress.stateNode;
3382 + const cache: Cache = current.memoizedState.cache;
3383 + pushCacheProvider(workInProgress, cache);
3384 + pushRootCachePool(root);
3385 + }
3386 + resetHydrationState();
3387 + break;
3388 + case HostComponent:
3389 + pushHostContext(workInProgress);
3390 + break;
3391 + case ClassComponent: {
3392 + const Component = workInProgress.type;
3393 + if (isLegacyContextProvider(Component)) {
3394 + pushLegacyContextProvider(workInProgress);
3395 + }
3396 + break;
3397 + }
3398 + case HostPortal:
3399 + pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
3400 + break;
3401 + case ContextProvider: {
3402 + const newValue = workInProgress.memoizedProps.value;
3403 + const context: ReactContext<any> = workInProgress.type._context;
3404 + pushProvider(workInProgress, context, newValue);
3405 + break;
3406 + }
3407 + case Profiler:
3408 + if (enableProfilerTimer) {
3409 + // Profiler should only call onRender when one of its descendants actually rendered.
3410 + const hasChildWork = includesSomeLane(
3411 + renderLanes,
3412 + workInProgress.childLanes,
3413 + );
3414 + if (hasChildWork) {
3415 + workInProgress.flags |= Update;
3416 + }
3417 +
3418 + if (enableProfilerCommitHooks) {
3419 + // Reset effect durations for the next eventual effect phase.
3420 + // These are reset during render to allow the DevTools commit hook a chance to read them,
3421 + const stateNode = workInProgress.stateNode;
3422 + stateNode.effectDuration = 0;
3423 + stateNode.passiveEffectDuration = 0;
3424 + }
3425 + }
3426 + break;
3427 + case SuspenseComponent: {
3428 + const state: SuspenseState | null = workInProgress.memoizedState;
3429 + if (state !== null) {
3430 + if (enableSuspenseServerRenderer) {
3431 + if (state.dehydrated !== null) {
3432 + pushSuspenseContext(
3433 + workInProgress,
3434 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3435 + );
3436 + // We know that this component will suspend again because if it has
3437 + // been unsuspended it has committed as a resolved Suspense component.
3438 + // If it needs to be retried, it should have work scheduled on it.
3439 + workInProgress.flags |= DidCapture;
3440 + // We should never render the children of a dehydrated boundary until we
3441 + // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3442 + return null;
3443 + }
3444 + }
3445 +
3446 + // If this boundary is currently timed out, we need to decide
3447 + // whether to retry the primary children, or to skip over it and
3448 + // go straight to the fallback. Check the priority of the primary
3449 + // child fragment.
3450 + const primaryChildFragment: Fiber = (workInProgress.child: any);
3451 + const primaryChildLanes = primaryChildFragment.childLanes;
3452 + if (includesSomeLane(renderLanes, primaryChildLanes)) {
3453 + // The primary children have pending work. Use the normal path
3454 + // to attempt to render the primary children again.
3455 + return updateSuspenseComponent(current, workInProgress, renderLanes);
3456 + } else {
3457 + // The primary child fragment does not have pending work marked
3458 + // on it
3459 + pushSuspenseContext(
3460 + workInProgress,
3461 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3462 + );
3463 + // The primary children do not have pending work with sufficient
3464 + // priority. Bailout.
3465 + const child = bailoutOnAlreadyFinishedWork(
3466 + current,
3467 + workInProgress,
3468 + renderLanes,
3469 + );
3470 + if (child !== null) {
3471 + // The fallback children have pending work. Skip over the
3472 + // primary children and work on the fallback.
3473 + return child.sibling;
3474 + } else {
3475 + // Note: We can return `null` here because we already checked
3476 + // whether there were nested context consumers, via the call to
3477 + // `bailoutOnAlreadyFinishedWork` above.
3478 + return null;
3479 + }
3480 + }
3481 + } else {
3482 + pushSuspenseContext(
3483 + workInProgress,
3484 + setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3485 + );
3486 + }
3487 + break;
3488 + }
3489 + case SuspenseListComponent: {
3490 + const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3491 +
3492 + let hasChildWork = includesSomeLane(
3493 + renderLanes,
3494 + workInProgress.childLanes,
3495 + );
3496 +
3497 + if (enableLazyContextPropagation && !hasChildWork) {
3498 + // Context changes may not have been propagated yet. We need to do
3499 + // that now, before we can decide whether to bail out.
3500 + // TODO: We use `childLanes` as a heuristic for whether there is
3501 + // remaining work in a few places, including
3502 + // `bailoutOnAlreadyFinishedWork` and
3503 + // `updateDehydratedSuspenseComponent`. We should maybe extract this
3504 + // into a dedicated function.
3505 + lazilyPropagateParentContextChanges(
3506 + current,
3507 + workInProgress,
3508 + renderLanes,
3509 + );
3510 + hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
3511 + }
3512 +
3513 + if (didSuspendBefore) {
3514 + if (hasChildWork) {
3515 + // If something was in fallback state last time, and we have all the
3516 + // same children then we're still in progressive loading state.
3517 + // Something might get unblocked by state updates or retries in the
3518 + // tree which will affect the tail. So we need to use the normal
3519 + // path to compute the correct tail.
3520 + return updateSuspenseListComponent(
3521 + current,
3522 + workInProgress,
3523 + renderLanes,
3524 + );
3525 + }
3526 + // If none of the children had any work, that means that none of
3527 + // them got retried so they'll still be blocked in the same way
3528 + // as before. We can fast bail out.
3529 + workInProgress.flags |= DidCapture;
3530 + }
3531 +
3532 + // If nothing suspended before and we're rendering the same children,
3533 + // then the tail doesn't matter. Anything new that suspends will work
3534 + // in the "together" mode, so we can continue from the state we had.
3535 + const renderState = workInProgress.memoizedState;
3536 + if (renderState !== null) {
3537 + // Reset to the "together" mode in case we've started a different
3538 + // update in the past but didn't complete it.
3539 + renderState.rendering = null;
3540 + renderState.tail = null;
3541 + renderState.lastEffect = null;
3542 + }
3543 + pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3544 +
3545 + if (hasChildWork) {
3546 + break;
3547 + } else {
3548 + // If none of the children had any work, that means that none of
3549 + // them got retried so they'll still be blocked in the same way
3550 + // as before. We can fast bail out.
3551 + return null;
3552 + }
3553 + }
3554 + case OffscreenComponent:
3555 + case LegacyHiddenComponent: {
3556 + // Need to check if the tree still needs to be deferred. This is
3557 + // almost identical to the logic used in the normal update path,
3558 + // so we'll just enter that. The only difference is we'll bail out
3559 + // at the next level instead of this one, because the child props
3560 + // have not changed. Which is fine.
3561 + // TODO: Probably should refactor `beginWork` to split the bailout
3562 + // path from the normal path. I'm tempted to do a labeled break here
3563 + // but I won't :)
3564 + workInProgress.lanes = NoLanes;
3565 + return updateOffscreenComponent(current, workInProgress, renderLanes);
3566 + }
3567 + case CacheComponent: {
3568 + if (enableCache) {
3569 + const cache: Cache = current.memoizedState.cache;
3570 + pushCacheProvider(workInProgress, cache);
3571 + }
3572 + break;
3573 + }
3574 + }
3575 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3576 +}
3577 +
3578 function beginWork(
3579 current: Fiber | null,
3580 workInProgress: Fiber,
3581 renderLanes: Lanes,
3582 ): Fiber | null {
3355 - let updateLanes = workInProgress.lanes;
3356 -
3583 if (__DEV__) {
3584 if (workInProgress._debugNeedsRemount && current !== null) {
3585 // This will restart the begin phase with a new fiber.
@@ -3373,17 +3599,6 @@ function beginWork(
3599 }
3600
3601 if (current !== null) {
3376 - // TODO: The factoring of this block is weird.
3377 - if (
3378 - enableLazyContextPropagation &&
3379 - !includesSomeLane(renderLanes, updateLanes)
3380 - ) {
3381 - const dependencies = current.dependencies;
3382 - if (dependencies !== null && checkIfContextChanged(dependencies)) {
3383 - updateLanes = mergeLanes(updateLanes, renderLanes);
3384 - }
3385 - }
3386 -
3602 const oldProps = current.memoizedProps;
3603 const newProps = workInProgress.pendingProps;
3604
@@ -3396,221 +3611,27 @@ function beginWork(
3611 // If props or context changed, mark the fiber as having performed work.
3612 // This may be unset if the props are determined to be equal later (memo).
3613 didReceiveUpdate = true;
3399 - } else if (!includesSomeLane(renderLanes, updateLanes)) {
3400 - didReceiveUpdate = false;
3401 - // This fiber does not have any pending work. Bailout without entering
3402 - // the begin phase. There's still some bookkeeping we that needs to be done
3403 - // in this optimized path, mostly pushing stuff onto the stack.
3404 - switch (workInProgress.tag) {
3405 - case HostRoot:
3406 - pushHostRootContext(workInProgress);
3407 - if (enableCache) {
3408 - const root: FiberRoot = workInProgress.stateNode;
3409 - const cache: Cache = current.memoizedState.cache;
3410 - pushCacheProvider(workInProgress, cache);
3411 - pushRootCachePool(root);
3412 - }
3413 - resetHydrationState();
3414 - break;
3415 - case HostComponent:
3416 - pushHostContext(workInProgress);
3417 - break;
3418 - case ClassComponent: {
3419 - const Component = workInProgress.type;
3420 - if (isLegacyContextProvider(Component)) {
3421 - pushLegacyContextProvider(workInProgress);
3422 - }
3423 - break;
3424 - }
3425 - case HostPortal:
3426 - pushHostContainer(
3427 - workInProgress,
3428 - workInProgress.stateNode.containerInfo,
3429 - );
3430 - break;
3431 - case ContextProvider: {
3432 - const newValue = workInProgress.memoizedProps.value;
3433 - const context: ReactContext<any> = workInProgress.type._context;
3434 - pushProvider(workInProgress, context, newValue);
3435 - break;
3436 - }
3437 - case Profiler:
3438 - if (enableProfilerTimer) {
3439 - // Profiler should only call onRender when one of its descendants actually rendered.
3440 - const hasChildWork = includesSomeLane(
3441 - renderLanes,
3442 - workInProgress.childLanes,
3443 - );
3444 - if (hasChildWork) {
3445 - workInProgress.flags |= Update;
3446 - }
3447 -
3448 - if (enableProfilerCommitHooks) {
3449 - // Reset effect durations for the next eventual effect phase.
3450 - // These are reset during render to allow the DevTools commit hook a chance to read them,
3451 - const stateNode = workInProgress.stateNode;
3452 - stateNode.effectDuration = 0;
3453 - stateNode.passiveEffectDuration = 0;
3454 - }
3455 - }
3456 - break;
3457 - case SuspenseComponent: {
3458 - const state: SuspenseState | null = workInProgress.memoizedState;
3459 - if (state !== null) {
3460 - if (enableSuspenseServerRenderer) {
3461 - if (state.dehydrated !== null) {
3462 - pushSuspenseContext(
3463 - workInProgress,
3464 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3465 - );
3466 - // We know that this component will suspend again because if it has
3467 - // been unsuspended it has committed as a resolved Suspense component.
3468 - // If it needs to be retried, it should have work scheduled on it.
3469 - workInProgress.flags |= DidCapture;
3470 - // We should never render the children of a dehydrated boundary until we
3471 - // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3472 - return null;
3473 - }
3474 - }
3475 -
3476 - // If this boundary is currently timed out, we need to decide
3477 - // whether to retry the primary children, or to skip over it and
3478 - // go straight to the fallback. Check the priority of the primary
3479 - // child fragment.
3480 - const primaryChildFragment: Fiber = (workInProgress.child: any);
3481 - const primaryChildLanes = primaryChildFragment.childLanes;
3482 - if (includesSomeLane(renderLanes, primaryChildLanes)) {
3483 - // The primary children have pending work. Use the normal path
3484 - // to attempt to render the primary children again.
3485 - return updateSuspenseComponent(
3486 - current,
3487 - workInProgress,
3488 - renderLanes,
3489 - );
3490 - } else {
3491 - // The primary child fragment does not have pending work marked
3492 - // on it
3493 - pushSuspenseContext(
3494 - workInProgress,
3495 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3496 - );
3497 - // The primary children do not have pending work with sufficient
3498 - // priority. Bailout.
3499 - const child = bailoutOnAlreadyFinishedWork(
3500 - current,
3501 - workInProgress,
3502 - renderLanes,
3503 - );
3504 - if (child !== null) {
3505 - // The fallback children have pending work. Skip over the
3506 - // primary children and work on the fallback.
3507 - return child.sibling;
3508 - } else {
3509 - // Note: We can return `null` here because we already checked
3510 - // whether there were nested context consumers, via the call to
3511 - // `bailoutOnAlreadyFinishedWork` above.
3512 - return null;
3513 - }
3514 - }
3515 - } else {
3516 - pushSuspenseContext(
3517 - workInProgress,
3518 - setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3519 - );
3520 - }
3521 - break;
3522 - }
3523 - case SuspenseListComponent: {
3524 - const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
3525 -
3526 - let hasChildWork = includesSomeLane(
3527 - renderLanes,
3528 - workInProgress.childLanes,
3529 - );
3530 -
3531 - if (enableLazyContextPropagation && !hasChildWork) {
3532 - // Context changes may not have been propagated yet. We need to do
3533 - // that now, before we can decide whether to bail out.
3534 - // TODO: We use `childLanes` as a heuristic for whether there is
3535 - // remaining work in a few places, including
3536 - // `bailoutOnAlreadyFinishedWork` and
3537 - // `updateDehydratedSuspenseComponent`. We should maybe extract this
3538 - // into a dedicated function.
3539 - lazilyPropagateParentContextChanges(
3540 - current,
3541 - workInProgress,
3542 - renderLanes,
3543 - );
3544 - hasChildWork = includesSomeLane(
3545 - renderLanes,
3546 - workInProgress.childLanes,
3547 - );
3548 - }
3549 -
3550 - if (didSuspendBefore) {
3551 - if (hasChildWork) {
3552 - // If something was in fallback state last time, and we have all the
3553 - // same children then we're still in progressive loading state.
3554 - // Something might get unblocked by state updates or retries in the
3555 - // tree which will affect the tail. So we need to use the normal
3556 - // path to compute the correct tail.
3557 - return updateSuspenseListComponent(
3558 - current,
3559 - workInProgress,
3560 - renderLanes,
3561 - );
3562 - }
3563 - // If none of the children had any work, that means that none of
3564 - // them got retried so they'll still be blocked in the same way
3565 - // as before. We can fast bail out.
3566 - workInProgress.flags |= DidCapture;
3567 - }
3568 -
3569 - // If nothing suspended before and we're rendering the same children,
3570 - // then the tail doesn't matter. Anything new that suspends will work
3571 - // in the "together" mode, so we can continue from the state we had.
3572 - const renderState = workInProgress.memoizedState;
3573 - if (renderState !== null) {
3574 - // Reset to the "together" mode in case we've started a different
3575 - // update in the past but didn't complete it.
3576 - renderState.rendering = null;
3577 - renderState.tail = null;
3578 - renderState.lastEffect = null;
3579 - }
3580 - pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3581 -
3582 - if (hasChildWork) {
3583 - break;
3584 - } else {
3585 - // If none of the children had any work, that means that none of
3586 - // them got retried so they'll still be blocked in the same way
3587 - // as before. We can fast bail out.
3588 - return null;
3589 - }
3590 - }
3591 - case OffscreenComponent:
3592 - case LegacyHiddenComponent: {
3593 - // Need to check if the tree still needs to be deferred. This is
3594 - // almost identical to the logic used in the normal update path,
3595 - // so we'll just enter that. The only difference is we'll bail out
3596 - // at the next level instead of this one, because the child props
3597 - // have not changed. Which is fine.
3598 - // TODO: Probably should refactor `beginWork` to split the bailout
3599 - // path from the normal path. I'm tempted to do a labeled break here
3600 - // but I won't :)
3601 - workInProgress.lanes = NoLanes;
3602 - return updateOffscreenComponent(current, workInProgress, renderLanes);
3603 - }
3604 - case CacheComponent: {
3605 - if (enableCache) {
3606 - const cache: Cache = current.memoizedState.cache;
3607 - pushCacheProvider(workInProgress, cache);
3608 - }
3609 - break;
3610 - }
3611 - }
3612 - return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
3614 } else {
3615 + // Neither props nor legacy context changes. Check if there's a pending
3616 + // update or context change.
3617 + const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
3618 + current,
3619 + renderLanes,
3620 + );
3621 + if (
3622 + !hasScheduledUpdateOrContext &&
3623 + // If this is the second pass of an error or suspense boundary, there
3624 + // may not be work scheduled on `current`, so we check for this flag.
3625 + (workInProgress.flags & DidCapture) === NoFlags
3626 + ) {
3627 + // No pending updates or context. Bail out now.
3628 + didReceiveUpdate = false;
3629 + return attemptEarlyBailoutIfNoScheduledUpdate(
3630 + current,
3631 + workInProgress,
3632 + renderLanes,
3633 + );
3634 + }
3635 if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
3636 // This is a special case that only exists for legacy mode.
3637 // See https://github.com/facebook/react/pull/19216.
@@ -3649,7 +3670,6 @@ function beginWork(
3670 current,
3671 workInProgress,
3672 elementType,
3652 - updateLanes,
3673 renderLanes,
3674 );
3675 }
@@ -3742,7 +3762,6 @@ function beginWork(
3762 workInProgress,
3763 type,
3764 resolvedProps,
3745 - updateLanes,
3765 renderLanes,
3766 );
3767 }
@@ -3752,7 +3771,6 @@ function beginWork(
3771 workInProgress,
3772 workInProgress.type,
3773 workInProgress.pendingProps,
3755 - updateLanes,
3774 renderLanes,
3775 );
3776 }
@@ -3788,12 +3806,7 @@ function beginWork(
3806 }
3807 case CacheComponent: {
3808 if (enableCache) {
3791 - return updateCacheComponent(
3792 - current,
3793 - workInProgress,
3794 - updateLanes,
3795 - renderLanes,
3796 - );
3809 + return updateCacheComponent(current, workInProgress, renderLanes);
3810 }
3811 break;
3812 }
packages/react-reconciler/src/ReactFiberThrow.new.js
+2
@@ -383,6 +383,8 @@ function throwException(
383 attachPingListener(root, wakeable, rootRenderLanes);
384
385 workInProgress.flags |= ShouldCapture;
386 + // TODO: I think we can remove this, since we now use `DidCapture` in
387 + // the begin phase to prevent an early bailout.
388 workInProgress.lanes = rootRenderLanes;
389
390 return;
packages/react-reconciler/src/ReactFiberThrow.old.js
+2
@@ -383,6 +383,8 @@ function throwException(
383 attachPingListener(root, wakeable, rootRenderLanes);
384
385 workInProgress.flags |= ShouldCapture;
386 + // TODO: I think we can remove this, since we now use `DidCapture` in
387 + // the begin phase to prevent an early bailout.
388 workInProgress.lanes = rootRenderLanes;
389
390 return;