3553
resetCurrentDebugFiberInDEV();
3554
}
3555
3556
+function detachAlternateSiblings(parentFiber: Fiber) {
3557
+ if (deletedTreeCleanUpLevel >= 1) {
3558
+ // A fiber was deleted from this parent fiber, but it's still part of the
3559
+ // previous (alternate) parent fiber's list of children. Because children
3560
+ // are a linked list, an earlier sibling that's still alive will be
3561
+ // connected to the deleted fiber via its `alternate`:
3562
+ //
3563
+ // live fiber --alternate--> previous live fiber --sibling--> deleted
3564
+ // fiber
3565
+ //
3566
+ // We can't disconnect `alternate` on nodes that haven't been deleted yet,
3567
+ // but we can disconnect the `sibling` and `child` pointers.
3568
+
3569
+ const previousFiber = parentFiber.alternate;
3570
+ if (previousFiber !== null) {
3571
+ let detachedChild = previousFiber.child;
3572
+ if (detachedChild !== null) {
3573
+ previousFiber.child = null;
3574
+ do {
3575
+ const detachedSibling = detachedChild.sibling;
3576
+ detachedChild.sibling = null;
3577
+ detachedChild = detachedSibling;
3578
+ } while (detachedChild !== null);
3579
+ }
3580
+ }
3581
+ }
3582
+}
3583
+
3584
+function commitHookPassiveUnmountEffects(
3585
+ finishedWork: Fiber,
3586
+ nearestMountedAncestor,
3587
+ hookFlags: HookFlags,
3588
+) {
3589
+ if (
3590
+ enableProfilerTimer &&
3591
+ enableProfilerCommitHooks &&
3592
+ finishedWork.mode & ProfileMode
3593
+ ) {
3594
+ startPassiveEffectTimer();
3595
+ commitHookEffectListUnmount(
3596
+ hookFlags,
3597
+ finishedWork,
3598
+ nearestMountedAncestor,
3599
+ );
3600
+ recordPassiveEffectDuration(finishedWork);
3601
+ } else {
3602
+ commitHookEffectListUnmount(
3603
+ hookFlags,
3604
+ finishedWork,
3605
+ nearestMountedAncestor,
3606
+ );
3607
+ }
3608
+}
3609
+
3610
function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
3611
// Deletions effects can be scheduled on any fiber type. They need to happen
3612
// before the children effects have fired.
3616
if (deletions !== null) {
3617
for (let i = 0; i < deletions.length; i++) {
3618
const childToDelete = deletions[i];
3565
- try {
3566
- // TODO: Convert this to use recursion
3567
- nextEffect = childToDelete;
3568
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
3569
- childToDelete,
3570
- parentFiber,
3571
- );
3572
- } catch (error) {
3573
- captureCommitPhaseError(childToDelete, parentFiber, error);
3574
- }
3575
- }
3576
- }
3577
-
3578
- if (deletedTreeCleanUpLevel >= 1) {
3579
- // A fiber was deleted from this parent fiber, but it's still part of
3580
- // the previous (alternate) parent fiber's list of children. Because
3581
- // children are a linked list, an earlier sibling that's still alive
3582
- // will be connected to the deleted fiber via its `alternate`:
3583
- //
3584
- // live fiber
3585
- // --alternate--> previous live fiber
3586
- // --sibling--> deleted fiber
3587
- //
3588
- // We can't disconnect `alternate` on nodes that haven't been deleted
3589
- // yet, but we can disconnect the `sibling` and `child` pointers.
3590
- const previousFiber = parentFiber.alternate;
3591
- if (previousFiber !== null) {
3592
- let detachedChild = previousFiber.child;
3593
- if (detachedChild !== null) {
3594
- previousFiber.child = null;
3595
- do {
3596
- const detachedSibling = detachedChild.sibling;
3597
- detachedChild.sibling = null;
3598
- detachedChild = detachedSibling;
3599
- } while (detachedChild !== null);
3600
- }
3619
+ // TODO: Convert this to use recursion
3620
+ nextEffect = childToDelete;
3621
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
3622
+ childToDelete,
3623
+ parentFiber,
3624
+ );
3625
}
3626
}
3627
+ detachAlternateSiblings(parentFiber);
3628
}
3629
3630
const prevDebugFiber = getCurrentDebugFiberInDEV();
3647
case SimpleMemoComponent: {
3648
recursivelyTraversePassiveUnmountEffects(finishedWork);
3649
if (finishedWork.flags & Passive) {
3625
- if (
3626
- enableProfilerTimer &&
3627
- enableProfilerCommitHooks &&
3628
- finishedWork.mode & ProfileMode
3629
- ) {
3630
- startPassiveEffectTimer();
3631
- commitHookEffectListUnmount(
3632
- HookPassive | HookHasEffect,
3633
- finishedWork,
3634
- finishedWork.return,
3635
- );
3636
- recordPassiveEffectDuration(finishedWork);
3637
- } else {
3638
- commitHookEffectListUnmount(
3639
- HookPassive | HookHasEffect,
3640
- finishedWork,
3641
- finishedWork.return,
3642
- );
3643
- }
3650
+ commitHookPassiveUnmountEffects(
3651
+ finishedWork,
3652
+ finishedWork.return,
3653
+ HookPassive | HookHasEffect,
3654
+ );
3655
}
3656
break;
3657
}
3647
- // TODO: Disconnect passive effects when a tree is hidden, perhaps after
3648
- // a delay.
3649
- // case OffscreenComponent: {
3650
- // ...
3651
- // }
3658
+ case OffscreenComponent: {
3659
+ const instance: OffscreenInstance = finishedWork.stateNode;
3660
+ const nextState: OffscreenState | null = finishedWork.memoizedState;
3661
+
3662
+ const isHidden = nextState !== null;
3663
+
3664
+ if (
3665
+ isHidden &&
3666
+ instance.visibility & OffscreenPassiveEffectsConnected &&
3667
+ // For backwards compatibility, don't unmount when a tree suspends. In
3668
+ // the future we may change this to unmount after a delay.
3669
+ (finishedWork.return === null ||
3670
+ finishedWork.return.tag !== SuspenseComponent)
3671
+ ) {
3672
+ // The effects are currently connected. Disconnect them.
3673
+ // TODO: Add option or heuristic to delay before disconnecting the
3674
+ // effects. Then if the tree reappears before the delay has elapsed, we
3675
+ // can skip toggling the effects entirely.
3676
+ instance.visibility &= ~OffscreenPassiveEffectsConnected;
3677
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
3678
+ } else {
3679
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
3680
+ }
3681
+
3682
+ break;
3683
+ }
3684
default: {
3685
recursivelyTraversePassiveUnmountEffects(finishedWork);
3686
break;
3688
}
3689
}
3690
3691
+function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
3692
+ // Deletions effects can be scheduled on any fiber type. They need to happen
3693
+ // before the children effects have fired.
3694
+ const deletions = parentFiber.deletions;
3695
+
3696
+ if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
3697
+ if (deletions !== null) {
3698
+ for (let i = 0; i < deletions.length; i++) {
3699
+ const childToDelete = deletions[i];
3700
+ // TODO: Convert this to use recursion
3701
+ nextEffect = childToDelete;
3702
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
3703
+ childToDelete,
3704
+ parentFiber,
3705
+ );
3706
+ }
3707
+ }
3708
+ detachAlternateSiblings(parentFiber);
3709
+ }
3710
+
3711
+ const prevDebugFiber = getCurrentDebugFiberInDEV();
3712
+ // TODO: Check PassiveStatic flag
3713
+ let child = parentFiber.child;
3714
+ while (child !== null) {
3715
+ setCurrentDebugFiberInDEV(child);
3716
+ disconnectPassiveEffect(child);
3717
+ child = child.sibling;
3718
+ }
3719
+ setCurrentDebugFiberInDEV(prevDebugFiber);
3720
+}
3721
+
3722
+function disconnectPassiveEffect(finishedWork: Fiber): void {
3723
+ switch (finishedWork.tag) {
3724
+ case FunctionComponent:
3725
+ case ForwardRef:
3726
+ case SimpleMemoComponent: {
3727
+ // TODO: Check PassiveStatic flag
3728
+ commitHookPassiveUnmountEffects(
3729
+ finishedWork,
3730
+ finishedWork.return,
3731
+ HookPassive,
3732
+ );
3733
+ // When disconnecting passive effects, we fire the effects in the same
3734
+ // order as during a deletiong: parent before child
3735
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
3736
+ break;
3737
+ }
3738
+ case OffscreenComponent: {
3739
+ const instance: OffscreenInstance = finishedWork.stateNode;
3740
+ if (instance.visibility & OffscreenPassiveEffectsConnected) {
3741
+ instance.visibility &= ~OffscreenPassiveEffectsConnected;
3742
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
3743
+ } else {
3744
+ // The effects are already disconnected.
3745
+ }
3746
+ break;
3747
+ }
3748
+ default: {
3749
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
3750
+ break;
3751
+ }
3752
+ }
3753
+}
3754
+
3755
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
3756
deletedSubtreeRoot: Fiber,
3757
nearestMountedAncestor: Fiber | null,
3824
case FunctionComponent:
3825
case ForwardRef:
3826
case SimpleMemoComponent: {
3731
- if (
3732
- enableProfilerTimer &&
3733
- enableProfilerCommitHooks &&
3734
- current.mode & ProfileMode
3735
- ) {
3736
- startPassiveEffectTimer();
3737
- commitHookEffectListUnmount(
3738
- HookPassive,
3739
- current,
3740
- nearestMountedAncestor,
3741
- );
3742
- recordPassiveEffectDuration(current);
3743
- } else {
3744
- commitHookEffectListUnmount(
3745
- HookPassive,
3746
- current,
3747
- nearestMountedAncestor,
3748
- );
3749
- }
3827
+ commitHookPassiveUnmountEffects(
3828
+ current,
3829
+ nearestMountedAncestor,
3830
+ HookPassive,
3831
+ );
3832
break;
3833
}
3834
// TODO: run passive unmount effects when unmounting a root.