@@ -3553,6 +3553,60 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
35533553 resetCurrentDebugFiberInDEV ( ) ;
35543554}
35553555
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+
35563610function recursivelyTraversePassiveUnmountEffects ( parentFiber : Fiber ) : void {
35573611 // Deletions effects can be scheduled on any fiber type. They need to happen
35583612 // before the children effects have fired.
@@ -3562,44 +3616,15 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
35623616 if ( deletions !== null ) {
35633617 for ( let i = 0 ; i < deletions . length ; i ++ ) {
35643618 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+ ) ;
36013625 }
36023626 }
3627+ detachAlternateSiblings ( parentFiber ) ;
36033628 }
36043629
36053630 const prevDebugFiber = getCurrentDebugFiberInDEV ( ) ;
@@ -3622,40 +3647,111 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
36223647 case SimpleMemoComponent : {
36233648 recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
36243649 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+ ) ;
36443655 }
36453656 break ;
36463657 }
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+ }
36523684 default: {
36533685 recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
36543686 break ;
36553687 }
36563688 }
36573689}
36583690
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+
36593755function commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
36603756 deletedSubtreeRoot : Fiber ,
36613757 nearestMountedAncestor : Fiber | null ,
@@ -3728,25 +3824,11 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
37283824 case FunctionComponent :
37293825 case ForwardRef :
37303826 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+ ) ;
37503832 break ;
37513833 }
37523834 // TODO: run passive unmount effects when unmounting a root.
0 commit comments