diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 3664803f20438..2997e809a4996 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -24,6 +24,7 @@ import type {Wakeable} from 'shared/ReactTypes'; import type {ReactPriorityLevel} from './ReactInternalTypes'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {HookFlags} from './ReactHookEffectTags'; +import type {Flags} from './ReactFiberFlags'; import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import { @@ -35,6 +36,7 @@ import { enableSuspenseCallback, enableScopeAPI, enableDoubleInvokingEffects, + enableRecursiveCommitTraversal, } from 'shared/ReactFeatureFlags'; import { FunctionComponent, @@ -67,10 +69,21 @@ import { ContentReset, Placement, Snapshot, + Visibility, Update, Callback, - LayoutMask, Ref, + PlacementAndUpdate, + Hydrating, + HydratingAndUpdate, + Passive, + PassiveStatic, + BeforeMutationMask, + MutationMask, + LayoutMask, + PassiveMask, + MountLayoutDev, + MountPassiveDev, } from './ReactFiberFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; @@ -88,13 +101,20 @@ import { recordPassiveEffectDuration, startPassiveEffectTimer, } from './ReactProfilerTimer.new'; -import {ProfileMode} from './ReactTypeOfMode'; +import { + NoMode, + BlockingMode, + ConcurrentMode, + ProfileMode, +} from './ReactTypeOfMode'; import {commitUpdateQueue} from './ReactUpdateQueue.new'; import { getPublicInstance, supportsMutation, supportsPersistence, supportsHydration, + prepareForCommit, + beforeActiveInstanceBlur, commitMount, commitUpdate, resetTextContent, @@ -132,6 +152,9 @@ import { Passive as HookPassive, } from './ReactHookEffectTags'; import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new'; +import {doesFiberContain} from './ReactFiberTreeReflection'; + +let nextEffect: Fiber | null = null; // Used to avoid traversing the return path to find the nearest Profiler ancestor during commit. let nearestProfilerOnStack: Fiber | null = null; @@ -256,98 +279,6 @@ export function safelyCallDestroy( } } -function commitBeforeMutationLifeCycles( - current: Fiber | null, - finishedWork: Fiber, -): void { - switch (finishedWork.tag) { - case FunctionComponent: - case ForwardRef: - case SimpleMemoComponent: - case Block: { - return; - } - case ClassComponent: { - if (finishedWork.flags & Snapshot) { - if (current !== null) { - const prevProps = current.memoizedProps; - const prevState = current.memoizedState; - const instance = finishedWork.stateNode; - // We could update instance props and state here, - // but instead we rely on them being set during last render. - // TODO: revisit this when we implement resuming. - if (__DEV__) { - if ( - finishedWork.type === finishedWork.elementType && - !didWarnAboutReassigningProps - ) { - if (instance.props !== finishedWork.memoizedProps) { - console.error( - 'Expected %s props to match memoized props before ' + - 'getSnapshotBeforeUpdate. ' + - 'This might either be because of a bug in React, or because ' + - 'a component reassigns its own `this.props`. ' + - 'Please file an issue.', - getComponentName(finishedWork.type) || 'instance', - ); - } - if (instance.state !== finishedWork.memoizedState) { - console.error( - 'Expected %s state to match memoized state before ' + - 'getSnapshotBeforeUpdate. ' + - 'This might either be because of a bug in React, or because ' + - 'a component reassigns its own `this.state`. ' + - 'Please file an issue.', - getComponentName(finishedWork.type) || 'instance', - ); - } - } - } - const snapshot = instance.getSnapshotBeforeUpdate( - finishedWork.elementType === finishedWork.type - ? prevProps - : resolveDefaultProps(finishedWork.type, prevProps), - prevState, - ); - if (__DEV__) { - const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set); - if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) { - didWarnSet.add(finishedWork.type); - console.error( - '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + - 'must be returned. You have returned undefined.', - getComponentName(finishedWork.type), - ); - } - } - instance.__reactInternalSnapshotBeforeUpdate = snapshot; - } - } - return; - } - case HostRoot: { - if (supportsMutation) { - if (finishedWork.flags & Snapshot) { - const root = finishedWork.stateNode; - clearContainer(root.containerInfo); - } - } - return; - } - case HostComponent: - case HostText: - case HostPortal: - case IncompleteClassComponent: - // Nothing to do for these component types - return; - } - invariant( - false, - 'This unit of work tag should not have side-effects. This error is ' + - 'likely caused by a bug in React. Please file an issue.', - ); -} - function commitHookEffectListUnmount( flags: HookFlags, finishedWork: Fiber, @@ -462,6 +393,530 @@ function commitProfilerPassiveEffect( } } +let focusedInstanceHandle: null | Fiber = null; +let shouldFireAfterActiveInstanceBlur: boolean = false; + +export function commitBeforeMutationEffects( + root: FiberRoot, + firstChild: Fiber, +) { + focusedInstanceHandle = prepareForCommit(root.containerInfo); + + if (enableRecursiveCommitTraversal) { + recursivelyCommitBeforeMutationEffects(firstChild); + } else { + nextEffect = firstChild; + iterativelyCommitBeforeMutationEffects_begin(); + } + + // We no longer need to track the active instance fiber + const shouldFire = shouldFireAfterActiveInstanceBlur; + shouldFireAfterActiveInstanceBlur = false; + focusedInstanceHandle = null; + + return shouldFire; +} + +function recursivelyCommitBeforeMutationEffects(firstChild: Fiber) { + let fiber = firstChild; + while (fiber !== null) { + // TODO: Should wrap this in flags check, too, as optimization + if (fiber.deletions !== null) { + commitBeforeMutationEffectsDeletions(fiber.deletions); + } + + const child = fiber.child; + if (fiber.subtreeFlags & BeforeMutationMask && child !== null) { + recursivelyCommitBeforeMutationEffects(child); + } + + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitBeforeMutationEffectsOnFiber, + null, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitBeforeMutationEffectsOnFiber(fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + fiber = fiber.sibling; + } +} + +function iterativelyCommitBeforeMutationEffects_begin() { + while (nextEffect !== null) { + const fiber = nextEffect; + + // TODO: Should wrap this in flags check, too, as optimization + const deletions = fiber.deletions; + if (deletions !== null) { + commitBeforeMutationEffectsDeletions(deletions); + } + + const child = fiber.child; + if ( + (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && + child !== null + ) { + child.return = fiber; + nextEffect = child; + } else { + iterativelyCommitBeforeMutationEffects_complete(); + } + } +} + +function iterativelyCommitBeforeMutationEffects_complete() { + while (nextEffect !== null) { + const fiber = nextEffect; + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitBeforeMutationEffectsOnFiber, + null, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitBeforeMutationEffectsOnFiber(fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) { + const current = finishedWork.alternate; + const flags = finishedWork.flags; + + if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { + // Check to see if the focused element was inside of a hidden (Suspense) subtree. + if ( + // TODO: Can optimize this further with separate Hide and Show flags. We + // only care about Hide here. + (flags & Visibility) !== NoFlags && + finishedWork.tag === SuspenseComponent && + isSuspenseBoundaryBeingHidden(current, finishedWork) && + doesFiberContain(finishedWork, focusedInstanceHandle) + ) { + shouldFireAfterActiveInstanceBlur = true; + beforeActiveInstanceBlur(finishedWork); + } + } + + if ((flags & Snapshot) !== NoFlags) { + setCurrentDebugFiberInDEV(finishedWork); + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: + case Block: { + break; + } + case ClassComponent: { + if (finishedWork.flags & Snapshot) { + if (current !== null) { + const prevProps = current.memoizedProps; + const prevState = current.memoizedState; + const instance = finishedWork.stateNode; + // We could update instance props and state here, + // but instead we rely on them being set during last render. + // TODO: revisit this when we implement resuming. + if (__DEV__) { + if ( + finishedWork.type === finishedWork.elementType && + !didWarnAboutReassigningProps + ) { + if (instance.props !== finishedWork.memoizedProps) { + console.error( + 'Expected %s props to match memoized props before ' + + 'getSnapshotBeforeUpdate. ' + + 'This might either be because of a bug in React, or because ' + + 'a component reassigns its own `this.props`. ' + + 'Please file an issue.', + getComponentName(finishedWork.type) || 'instance', + ); + } + if (instance.state !== finishedWork.memoizedState) { + console.error( + 'Expected %s state to match memoized state before ' + + 'getSnapshotBeforeUpdate. ' + + 'This might either be because of a bug in React, or because ' + + 'a component reassigns its own `this.state`. ' + + 'Please file an issue.', + getComponentName(finishedWork.type) || 'instance', + ); + } + } + } + const snapshot = instance.getSnapshotBeforeUpdate( + finishedWork.elementType === finishedWork.type + ? prevProps + : resolveDefaultProps(finishedWork.type, prevProps), + prevState, + ); + if (__DEV__) { + const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set); + if ( + snapshot === undefined && + !didWarnSet.has(finishedWork.type) + ) { + didWarnSet.add(finishedWork.type); + console.error( + '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + + 'must be returned. You have returned undefined.', + getComponentName(finishedWork.type), + ); + } + } + instance.__reactInternalSnapshotBeforeUpdate = snapshot; + } + } + break; + } + case HostRoot: { + if (supportsMutation) { + if (finishedWork.flags & Snapshot) { + const root = finishedWork.stateNode; + clearContainer(root.containerInfo); + } + } + break; + } + case HostComponent: + case HostText: + case HostPortal: + case IncompleteClassComponent: + // Nothing to do for these component types + break; + default: + invariant( + false, + 'This unit of work tag should not have side-effects. This error is ' + + 'likely caused by a bug in React. Please file an issue.', + ); + } + resetCurrentDebugFiberInDEV(); + } +} + +function commitBeforeMutationEffectsDeletions(deletions: Array) { + for (let i = 0; i < deletions.length; i++) { + const fiber = deletions[i]; + + // TODO (effects) It would be nice to avoid calling doesFiberContain() + // Maybe we can repurpose one of the subtreeFlags positions for this instead? + // Use it to store which part of the tree the focused instance is in? + // This assumes we can safely determine that instance during the "render" phase. + + if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) { + shouldFireAfterActiveInstanceBlur = true; + beforeActiveInstanceBlur(fiber); + } + } +} + +export function commitMutationEffects( + firstChild: Fiber, + root: FiberRoot, + renderPriorityLevel: ReactPriorityLevel, +) { + if (enableRecursiveCommitTraversal) { + recursivelyCommitMutationEffects(firstChild, root, renderPriorityLevel); + } else { + nextEffect = firstChild; + iterativelyCommitMutationEffects_begin(root, renderPriorityLevel); + } +} + +function recursivelyCommitMutationEffects( + firstChild: Fiber, + root: FiberRoot, + renderPriorityLevel: ReactPriorityLevel, +) { + let fiber = firstChild; + while (fiber !== null) { + const deletions = fiber.deletions; + if (deletions !== null) { + commitMutationEffectsDeletions( + deletions, + fiber, + root, + renderPriorityLevel, + ); + } + + if (fiber.child !== null) { + const mutationFlags = fiber.subtreeFlags & MutationMask; + if (mutationFlags !== NoFlags) { + recursivelyCommitMutationEffects( + fiber.child, + root, + renderPriorityLevel, + ); + } + } + + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitMutationEffectsOnFiber, + null, + fiber, + root, + renderPriorityLevel, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + fiber = fiber.sibling; + } +} + +function iterativelyCommitMutationEffects_begin( + root: FiberRoot, + renderPriorityLevel: ReactPriorityLevel, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + + // TODO: Should wrap this in flags check, too, as optimization + const deletions = fiber.deletions; + if (deletions !== null) { + commitMutationEffectsDeletions( + deletions, + fiber, + root, + renderPriorityLevel, + ); + } + + const child = fiber.child; + if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) { + child.return = fiber; + nextEffect = child; + } else { + iterativelyCommitMutationEffects_complete(root, renderPriorityLevel); + } + } +} + +function iterativelyCommitMutationEffects_complete( + root: FiberRoot, + renderPriorityLevel: ReactPriorityLevel, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitMutationEffectsOnFiber, + null, + fiber, + root, + renderPriorityLevel, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function commitMutationEffectsOnFiber( + fiber: Fiber, + root: FiberRoot, + renderPriorityLevel, +) { + const flags = fiber.flags; + if (flags & ContentReset) { + commitResetTextContent(fiber); + } + + if (flags & Ref) { + const current = fiber.alternate; + if (current !== null) { + commitDetachRef(current); + } + if (enableScopeAPI) { + // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. + if (fiber.tag === ScopeComponent) { + commitAttachRef(fiber); + } + } + } + + // The following switch statement is only concerned about placement, + // updates, and deletions. To avoid needing to add a case for every possible + // bitmap value, we remove the secondary effects from the effect tag and + // switch on that value. + const primaryFlags = flags & (Placement | Update | Hydrating); + switch (primaryFlags) { + case Placement: { + commitPlacement(fiber); + // Clear the "placement" from effect tag so that we know that this is + // inserted, before any life-cycles like componentDidMount gets called. + // TODO: findDOMNode doesn't rely on this any more but isMounted does + // and isMounted is deprecated anyway so we should be able to kill this. + fiber.flags &= ~Placement; + break; + } + case PlacementAndUpdate: { + // Placement + commitPlacement(fiber); + // Clear the "placement" from effect tag so that we know that this is + // inserted, before any life-cycles like componentDidMount gets called. + fiber.flags &= ~Placement; + + // Update + const current = fiber.alternate; + commitWork(current, fiber); + break; + } + case Hydrating: { + fiber.flags &= ~Hydrating; + break; + } + case HydratingAndUpdate: { + fiber.flags &= ~Hydrating; + + // Update + const current = fiber.alternate; + commitWork(current, fiber); + break; + } + case Update: { + const current = fiber.alternate; + commitWork(current, fiber); + break; + } + } +} + +function commitMutationEffectsDeletions( + deletions: Array, + nearestMountedAncestor: Fiber, + root: FiberRoot, + renderPriorityLevel, +) { + for (let i = 0; i < deletions.length; i++) { + const childToDelete = deletions[i]; + if (__DEV__) { + invokeGuardedCallback( + null, + commitDeletion, + null, + root, + childToDelete, + nearestMountedAncestor, + renderPriorityLevel, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(childToDelete, nearestMountedAncestor, error); + } + } else { + try { + commitDeletion( + root, + childToDelete, + nearestMountedAncestor, + renderPriorityLevel, + ); + } catch (error) { + captureCommitPhaseError(childToDelete, nearestMountedAncestor, error); + } + } + } +} + +export function commitLayoutEffects( + finishedWork: Fiber, + finishedRoot: FiberRoot, +) { + if (enableRecursiveCommitTraversal) { + if (__DEV__) { + setCurrentDebugFiberInDEV(finishedWork); + invokeGuardedCallback( + null, + recursivelyCommitLayoutEffects, + null, + finishedWork, + finishedRoot, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(finishedWork, null, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + recursivelyCommitLayoutEffects(finishedWork, finishedRoot); + } catch (error) { + captureCommitPhaseError(finishedWork, null, error); + } + } + } else { + nextEffect = finishedWork; + iterativelyCommitLayoutEffects_begin(finishedWork, finishedRoot); + } +} + function recursivelyCommitLayoutEffects( finishedWork: Fiber, finishedRoot: FiberRoot, @@ -660,18 +1115,229 @@ function recursivelyCommitLayoutEffects( } } } - - if (enableScopeAPI) { - // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. - if (flags & Ref && tag !== ScopeComponent) { - commitAttachRef(finishedWork); - } - } else { - if (flags & Ref) { - commitAttachRef(finishedWork); - } + + if (enableScopeAPI) { + // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. + if (flags & Ref && tag !== ScopeComponent) { + commitAttachRef(finishedWork); + } + } else { + if (flags & Ref) { + commitAttachRef(finishedWork); + } + } + break; + } + } +} + +function iterativelyCommitLayoutEffects_begin( + subtreeRoot: Fiber, + finishedRoot: FiberRoot, +) { + while (nextEffect !== null) { + const finishedWork: Fiber = nextEffect; + const firstChild = finishedWork.child; + + if ( + (finishedWork.subtreeFlags & LayoutMask) !== NoFlags && + firstChild !== null + ) { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.tag === Profiler + ) { + const prevProfilerOnStack = nearestProfilerOnStack; + nearestProfilerOnStack = finishedWork; + + let child = firstChild; + while (child !== null) { + nextEffect = child; + iterativelyCommitLayoutEffects_begin(child, finishedRoot); + child = child.sibling; + } + nextEffect = finishedWork; + + if ((finishedWork.flags & LayoutMask) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(finishedWork); + invokeGuardedCallback( + null, + commitLayoutEffectsForProfiler, + null, + finishedWork, + finishedRoot, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(finishedWork, finishedWork.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitLayoutEffectsForProfiler(finishedWork, finishedRoot); + } catch (error) { + captureCommitPhaseError(finishedWork, finishedWork.return, error); + } + } + } + + // Propagate layout effect durations to the next nearest Profiler ancestor. + // Do not reset these values until the next render so DevTools has a chance to read them first. + if (prevProfilerOnStack !== null) { + prevProfilerOnStack.stateNode.effectDuration += + finishedWork.stateNode.effectDuration; + } + nearestProfilerOnStack = prevProfilerOnStack; + + if (finishedWork === subtreeRoot) { + nextEffect = null; + return; + } + const sibling = finishedWork.sibling; + if (sibling !== null) { + sibling.return = finishedWork.return; + nextEffect = sibling; + } else { + nextEffect = finishedWork.return; + iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot); + } + } else { + firstChild.return = finishedWork; + nextEffect = firstChild; + } + } else { + iterativelyCommitLayoutEffects_complete(subtreeRoot, finishedRoot); + } + } +} + +function iterativelyCommitLayoutEffects_complete( + subtreeRoot: Fiber, + finishedRoot: FiberRoot, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + + if ((fiber.flags & LayoutMask) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitLayoutEffectsOnFiber, + null, + finishedRoot, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitLayoutEffectsOnFiber(finishedRoot, fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + } + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = nextEffect.return; + } +} + +function commitLayoutEffectsOnFiber( + finishedRoot: FiberRoot, + finishedWork: Fiber, +) { + const tag = finishedWork.tag; + const flags = finishedWork.flags; + if ((flags & (Update | Callback)) !== NoFlags) { + switch (tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: + case Block: { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); + } finally { + recordLayoutEffectDuration(finishedWork); + } + } else { + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); + } + break; + } + case ClassComponent: { + // NOTE: Layout effect durations are measured within this function. + commitLayoutEffectsForClassComponent(finishedWork); + break; } - break; + case HostRoot: { + commitLayoutEffectsForHostRoot(finishedWork); + break; + } + case HostComponent: { + commitLayoutEffectsForHostComponent(finishedWork); + break; + } + case Profiler: { + commitLayoutEffectsForProfiler(finishedWork, finishedRoot); + break; + } + case SuspenseComponent: { + commitSuspenseHydrationCallbacks(finishedRoot, finishedWork); + break; + } + case FundamentalComponent: + case HostPortal: + case HostText: + case IncompleteClassComponent: + case LegacyHiddenComponent: + case OffscreenComponent: + case ScopeComponent: + case SuspenseListComponent: { + // We have no life-cycles associated with these component types. + break; + } + default: { + invariant( + false, + 'This unit of work tag should not have side-effects. This error is ' + + 'likely caused by a bug in React. Please file an issue.', + ); + } + } + } + + if (enableScopeAPI) { + // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. + if (flags & Ref && tag !== ScopeComponent) { + commitAttachRef(finishedWork); + } + } else { + if (flags & Ref) { + commitAttachRef(finishedWork); } } } @@ -972,6 +1638,401 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) { } } +export function commitPassiveMountEffects( + root: FiberRoot, + firstChild: Fiber, +): void { + if (enableRecursiveCommitTraversal) { + recursivelyCommitPassiveMountEffects(root, firstChild); + } else { + nextEffect = firstChild; + iterativelyCommitPassiveMountEffects_begin(firstChild, root); + } +} + +function recursivelyCommitPassiveMountEffects( + root: FiberRoot, + firstChild: Fiber, +): void { + let fiber = firstChild; + while (fiber !== null) { + let prevProfilerOnStack = null; + if (enableProfilerTimer && enableProfilerCommitHooks) { + if (fiber.tag === Profiler) { + prevProfilerOnStack = nearestProfilerOnStack; + nearestProfilerOnStack = fiber; + } + } + + const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask; + + if (fiber.child !== null && primarySubtreeFlags !== NoFlags) { + recursivelyCommitPassiveMountEffects(root, fiber.child); + } + + if ((fiber.flags & Passive) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitPassiveMountOnFiber, + null, + root, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitPassiveMountOnFiber(root, fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + } + + if (enableProfilerTimer && enableProfilerCommitHooks) { + if (fiber.tag === Profiler) { + // Bubble times to the next nearest ancestor Profiler. + // After we process that Profiler, we'll bubble further up. + if (prevProfilerOnStack !== null) { + prevProfilerOnStack.stateNode.passiveEffectDuration += + fiber.stateNode.passiveEffectDuration; + } + + nearestProfilerOnStack = prevProfilerOnStack; + } + } + + fiber = fiber.sibling; + } +} + +function iterativelyCommitPassiveMountEffects_begin( + subtreeRoot: Fiber, + root: FiberRoot, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + const firstChild = fiber.child; + if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.tag === Profiler + ) { + const prevProfilerOnStack = nearestProfilerOnStack; + nearestProfilerOnStack = fiber; + + let child = firstChild; + while (child !== null) { + nextEffect = child; + iterativelyCommitPassiveMountEffects_begin(child, root); + child = child.sibling; + } + nextEffect = fiber; + + if ((fiber.flags & PassiveMask) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitProfilerPassiveEffect, + null, + root, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitProfilerPassiveEffect(root, fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + } + + // Bubble times to the next nearest ancestor Profiler. + // After we process that Profiler, we'll bubble further up. + if (prevProfilerOnStack !== null) { + prevProfilerOnStack.stateNode.passiveEffectDuration += + fiber.stateNode.passiveEffectDuration; + } + + nearestProfilerOnStack = prevProfilerOnStack; + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + } else { + nextEffect = fiber.return; + iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root); + } + } else { + firstChild.return = fiber; + nextEffect = firstChild; + } + } else { + iterativelyCommitPassiveMountEffects_complete(subtreeRoot, root); + } + } +} + +function iterativelyCommitPassiveMountEffects_complete( + subtreeRoot: Fiber, + root: FiberRoot, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + if ((fiber.flags & Passive) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitPassiveMountOnFiber, + null, + root, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, fiber.return, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitPassiveMountOnFiber(root, fiber); + } catch (error) { + captureCommitPhaseError(fiber, fiber.return, error); + } + } + } + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +export function commitPassiveUnmountEffects(firstChild: Fiber): void { + if (enableRecursiveCommitTraversal) { + recursivelyCommitPassiveUnmountEffects(firstChild); + } else { + nextEffect = firstChild; + iterativelyCommitPassiveUnmountEffects_begin(); + } +} + +function recursivelyCommitPassiveUnmountEffects(firstChild: Fiber): void { + let fiber = firstChild; + while (fiber !== null) { + const deletions = fiber.deletions; + if (deletions !== null) { + for (let i = 0; i < deletions.length; i++) { + const fiberToDelete = deletions[i]; + recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree( + fiberToDelete, + fiber, + ); + + // Now that passive effects have been processed, it's safe to detach lingering pointers. + detachFiberAfterEffects(fiberToDelete); + } + } + + const child = fiber.child; + if (child !== null) { + // If any children have passive effects then traverse the subtree. + // Note that this requires checking subtreeFlags of the current Fiber, + // rather than the subtreeFlags/effectsTag of the first child, + // since that would not cover passive effects in siblings. + const passiveFlags = fiber.subtreeFlags & PassiveMask; + if (passiveFlags !== NoFlags) { + recursivelyCommitPassiveUnmountEffects(child); + } + } + + const primaryFlags = fiber.flags & Passive; + if (primaryFlags !== NoFlags) { + setCurrentDebugFiberInDEV(fiber); + commitPassiveUnmountOnFiber(fiber); + resetCurrentDebugFiberInDEV(); + } + + fiber = fiber.sibling; + } +} + +function iterativelyCommitPassiveUnmountEffects_begin() { + while (nextEffect !== null) { + const fiber = nextEffect; + const child = fiber.child; + + // TODO: Should wrap this in flags check, too, as optimization + const deletions = fiber.deletions; + if (deletions !== null) { + for (let i = 0; i < deletions.length; i++) { + const fiberToDelete = deletions[i]; + nextEffect = fiberToDelete; + iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin( + fiberToDelete, + fiber, + ); + + // Now that passive effects have been processed, it's safe to detach lingering pointers. + detachFiberAfterEffects(fiberToDelete); + } + nextEffect = fiber; + } + + if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) { + child.return = fiber; + nextEffect = child; + } else { + iterativelyCommitPassiveUnmountEffects_complete(); + } + } +} + +function iterativelyCommitPassiveUnmountEffects_complete() { + while (nextEffect !== null) { + const fiber = nextEffect; + if ((fiber.flags & Passive) !== NoFlags) { + setCurrentDebugFiberInDEV(fiber); + commitPassiveUnmountOnFiber(fiber); + resetCurrentDebugFiberInDEV(); + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree( + fiberToDelete: Fiber, + nearestMountedAncestor: Fiber, +): void { + if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) { + // If any children have passive effects then traverse the subtree. + // Note that this requires checking subtreeFlags of the current Fiber, + // rather than the subtreeFlags/effectsTag of the first child, + // since that would not cover passive effects in siblings. + let child = fiberToDelete.child; + while (child !== null) { + recursivelyCommitPassiveUnmountEffectsInsideOfDeletedTree( + child, + nearestMountedAncestor, + ); + child = child.sibling; + } + } + + if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) { + setCurrentDebugFiberInDEV(fiberToDelete); + commitPassiveUnmountInsideDeletedTreeOnFiber( + fiberToDelete, + nearestMountedAncestor, + ); + resetCurrentDebugFiberInDEV(); + } +} + +function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_begin( + deletedSubtreeRoot: Fiber, + nearestMountedAncestor: Fiber, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + const child = fiber.child; + if ((fiber.subtreeFlags & PassiveStatic) !== NoFlags && child !== null) { + child.return = fiber; + nextEffect = child; + } else { + iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete( + deletedSubtreeRoot, + nearestMountedAncestor, + ); + } + } +} + +function iterativelyCommitPassiveUnmountEffectsInsideOfDeletedTree_complete( + deletedSubtreeRoot: Fiber, + nearestMountedAncestor: Fiber, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + if ((fiber.flags & PassiveStatic) !== NoFlags) { + setCurrentDebugFiberInDEV(fiber); + commitPassiveUnmountInsideDeletedTreeOnFiber( + fiber, + nearestMountedAncestor, + ); + resetCurrentDebugFiberInDEV(); + } + + if (fiber === deletedSubtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function detachFiberAfterEffects(fiber: Fiber): void { + // Null out fields to improve GC for references that may be lingering (e.g. DevTools). + // Note that we already cleared the return pointer in detachFiberMutation(). + fiber.child = null; + fiber.deletions = null; + fiber.dependencies = null; + fiber.memoizedProps = null; + fiber.memoizedState = null; + fiber.pendingProps = null; + fiber.sibling = null; + fiber.stateNode = null; + fiber.updateQueue = null; + + if (__DEV__) { + fiber._debugOwner = null; + } +} + function commitAttachRef(finishedWork: Fiber) { const ref = finishedWork.ref; if (ref !== null) { @@ -1950,7 +3011,7 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) { // This function detects when a Suspense boundary goes from visible to hidden. // It returns false if the boundary is already hidden. // TODO: Use an effect tag. -export function isSuspenseBoundaryBeingHidden( +function isSuspenseBoundaryBeingHidden( current: Fiber | null, finishedWork: Fiber, ): boolean { @@ -1971,7 +3032,7 @@ function commitResetTextContent(current: Fiber): void { resetTextContent(current.stateNode); } -function commitPassiveUnmount(finishedWork: Fiber): void { +function commitPassiveUnmountOnFiber(finishedWork: Fiber): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: @@ -2001,7 +3062,7 @@ function commitPassiveUnmount(finishedWork: Fiber): void { } } -function commitPassiveUnmountInsideDeletedTree( +function commitPassiveUnmountInsideDeletedTreeOnFiber( current: Fiber, nearestMountedAncestor: Fiber | null, ): void { @@ -2034,7 +3095,7 @@ function commitPassiveUnmountInsideDeletedTree( } } -function commitPassiveMount( +function commitPassiveMountOnFiber( finishedRoot: FiberRoot, finishedWork: Fiber, ): void { @@ -2188,20 +3249,57 @@ function invokePassiveEffectUnmountInDEV(fiber: Fiber): void { } } -export { - commitBeforeMutationLifeCycles, - commitResetTextContent, - commitPlacement, - commitDeletion, - commitWork, - commitAttachRef, - commitDetachRef, - commitPassiveUnmount, - commitPassiveUnmountInsideDeletedTree, - commitPassiveMount, - invokeLayoutEffectMountInDEV, - invokeLayoutEffectUnmountInDEV, - invokePassiveEffectMountInDEV, - invokePassiveEffectUnmountInDEV, - recursivelyCommitLayoutEffects, -}; +// TODO: Convert this to iteration instead of recursion, too. Leaving this for +// a follow up because the flag is off. +export function commitDoubleInvokeEffectsInDEV( + fiber: Fiber, + hasPassiveEffects: boolean, +) { + if (__DEV__ && enableDoubleInvokingEffects) { + // Never double-invoke effects for legacy roots. + if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { + return; + } + + setCurrentDebugFiberInDEV(fiber); + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV); + if (hasPassiveEffects) { + invokeEffectsInDev( + fiber, + MountPassiveDev, + invokePassiveEffectUnmountInDEV, + ); + } + + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV); + if (hasPassiveEffects) { + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV); + } + resetCurrentDebugFiberInDEV(); + } +} + +function invokeEffectsInDev( + firstChild: Fiber, + fiberFlags: Flags, + invokeEffectFn: (fiber: Fiber) => void, +): void { + if (__DEV__ && enableDoubleInvokingEffects) { + // We don't need to re-check for legacy roots here. + // This function will not be called within legacy roots. + let fiber = firstChild; + while (fiber !== null) { + if (fiber.child !== null) { + const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags; + if (primarySubtreeFlag !== NoFlags) { + invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn); + } + } + + if ((fiber.flags & fiberFlags) !== NoFlags) { + invokeEffectFn(fiber); + } + fiber = fiber.sibling; + } + } +} diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 33d8398fad5fe..ee2df3564cc02 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -15,21 +15,18 @@ import type {Interaction} from 'scheduler/src/Tracing'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {StackCursor} from './ReactFiberStack.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; -import type {Flags} from './ReactFiberFlags'; import { warnAboutDeprecatedLifecycles, enableSuspenseServerRenderer, replayFailedUnitOfWorkWithInvokeGuardedCallback, enableProfilerTimer, - enableProfilerCommitHooks, enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, decoupleUpdatePriorityFromScheduler, enableDebugTracing, enableSchedulingProfiler, - enableScopeAPI, skipUnmountedBoundaries, enableDoubleInvokingEffects, } from 'shared/ReactFeatureFlags'; @@ -83,13 +80,11 @@ import * as Scheduler from 'scheduler'; import {__interactionsRef, __subscriberRef} from 'scheduler/tracing'; import { - prepareForCommit, resetAfterCommit, scheduleTimeout, cancelTimeout, noTimeout, warnsIfNotActing, - beforeActiveInstanceBlur, afterActiveInstanceBlur, clearContainer, } from './ReactFiberHostConfig'; @@ -116,31 +111,19 @@ import { MemoComponent, SimpleMemoComponent, Block, - ScopeComponent, - Profiler, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; import { NoFlags, Placement, - Update, - PlacementAndUpdate, - Ref, - ContentReset, - Snapshot, - Passive, PassiveStatic, Incomplete, HostEffectMask, Hydrating, - HydratingAndUpdate, - Visibility, BeforeMutationMask, MutationMask, LayoutMask, PassiveMask, - MountPassiveDev, - MountLayoutDev, } from './ReactFiberFlags'; import { NoLanePriority, @@ -191,22 +174,12 @@ import { createClassErrorUpdate, } from './ReactFiberThrow.new'; import { - commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber, - commitPlacement, - commitWork, - commitDeletion, - commitPassiveUnmount as commitPassiveUnmountOnFiber, - commitPassiveUnmountInsideDeletedTree as commitPassiveUnmountInsideDeletedTreeOnFiber, - commitPassiveMount as commitPassiveMountOnFiber, - commitDetachRef, - commitAttachRef, - commitResetTextContent, - isSuspenseBoundaryBeingHidden, - invokeLayoutEffectMountInDEV, - invokePassiveEffectMountInDEV, - invokeLayoutEffectUnmountInDEV, - invokePassiveEffectUnmountInDEV, - recursivelyCommitLayoutEffects, + commitBeforeMutationEffects, + commitMutationEffects, + commitLayoutEffects, + commitPassiveMountEffects, + commitPassiveUnmountEffects, + commitDoubleInvokeEffectsInDEV, } from './ReactFiberCommitWork.new'; import {enqueueUpdate} from './ReactUpdateQueue.new'; import {resetContextDependencies} from './ReactFiberNewContext.new'; @@ -247,7 +220,6 @@ import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors'; // Used by `act` import enqueueTask from 'shared/enqueueTask'; -import {doesFiberContain} from './ReactFiberTreeReflection'; const ceil = Math.ceil; @@ -327,9 +299,6 @@ let workInProgressRootRenderTargetTime: number = Infinity; // suspense heuristics and opt out of rendering more content. const RENDER_TIMEOUT_MS = 500; -// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit. -let nearestProfilerOnStack: Fiber | null = null; - function resetRenderTimer() { workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS; } @@ -374,9 +343,6 @@ let currentEventPendingLanes: Lanes = NoLanes; // We warn about state updates for unmounted components differently in this case. let isFlushingPassiveEffects = false; -let focusedInstanceHandle: null | Fiber = null; -let shouldFireAfterActiveInstanceBlur: boolean = false; - export function getWorkInProgressRoot(): FiberRoot | null { return workInProgressRoot; } @@ -1916,13 +1882,10 @@ function commitRootImpl(root, renderPriorityLevel) { // The first phase a "before mutation" phase. We use this phase to read the // state of the host tree right before we mutate it. This is where // getSnapshotBeforeUpdate is called. - focusedInstanceHandle = prepareForCommit(root.containerInfo); - shouldFireAfterActiveInstanceBlur = false; - - commitBeforeMutationEffects(finishedWork); - - // We no longer need to track the active instance fiber - focusedInstanceHandle = null; + const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects( + root, + finishedWork, + ); if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this @@ -1957,27 +1920,7 @@ function commitRootImpl(root, renderPriorityLevel) { markLayoutEffectsStarted(lanes); } - if (__DEV__) { - setCurrentDebugFiberInDEV(finishedWork); - invokeGuardedCallback( - null, - recursivelyCommitLayoutEffects, - null, - finishedWork, - root, - ); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - recursivelyCommitLayoutEffects(finishedWork, root); - } catch (error) { - captureCommitPhaseErrorOnRoot(finishedWork, finishedWork, error); - } - } + commitLayoutEffects(finishedWork, root); if (__DEV__) { if (enableDebugTracing) { @@ -2117,238 +2060,6 @@ function commitRootImpl(root, renderPriorityLevel) { return null; } -function commitBeforeMutationEffects(firstChild: Fiber) { - let fiber = firstChild; - while (fiber !== null) { - if (fiber.deletions !== null) { - commitBeforeMutationEffectsDeletions(fiber.deletions); - } - - if (fiber.child !== null) { - const primarySubtreeFlags = fiber.subtreeFlags & BeforeMutationMask; - if (primarySubtreeFlags !== NoFlags) { - commitBeforeMutationEffects(fiber.child); - } - } - - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - invokeGuardedCallback(null, commitBeforeMutationEffectsImpl, null, fiber); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseError(fiber, fiber.return, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - commitBeforeMutationEffectsImpl(fiber); - } catch (error) { - captureCommitPhaseError(fiber, fiber.return, error); - } - } - fiber = fiber.sibling; - } -} - -function commitBeforeMutationEffectsImpl(fiber: Fiber) { - const current = fiber.alternate; - const flags = fiber.flags; - - if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { - // Check to see if the focused element was inside of a hidden (Suspense) subtree. - if ( - // TODO: Can optimize this further with separate Hide and Show flags. We - // only care about Hide here. - (flags & Visibility) !== NoFlags && - fiber.tag === SuspenseComponent && - isSuspenseBoundaryBeingHidden(current, fiber) && - doesFiberContain(fiber, focusedInstanceHandle) - ) { - shouldFireAfterActiveInstanceBlur = true; - beforeActiveInstanceBlur(fiber); - } - } - - if ((flags & Snapshot) !== NoFlags) { - setCurrentDebugFiberInDEV(fiber); - commitBeforeMutationEffectOnFiber(current, fiber); - resetCurrentDebugFiberInDEV(); - } -} - -function commitBeforeMutationEffectsDeletions(deletions: Array) { - for (let i = 0; i < deletions.length; i++) { - const fiber = deletions[i]; - - // TODO (effects) It would be nice to avoid calling doesFiberContain() - // Maybe we can repurpose one of the subtreeFlags positions for this instead? - // Use it to store which part of the tree the focused instance is in? - // This assumes we can safely determine that instance during the "render" phase. - - if (doesFiberContain(fiber, ((focusedInstanceHandle: any): Fiber))) { - shouldFireAfterActiveInstanceBlur = true; - beforeActiveInstanceBlur(fiber); - } - } -} - -function commitMutationEffects( - firstChild: Fiber, - root: FiberRoot, - renderPriorityLevel: ReactPriorityLevel, -) { - let fiber = firstChild; - while (fiber !== null) { - const deletions = fiber.deletions; - if (deletions !== null) { - commitMutationEffectsDeletions( - deletions, - fiber, - root, - renderPriorityLevel, - ); - } - - if (fiber.child !== null) { - const mutationFlags = fiber.subtreeFlags & MutationMask; - if (mutationFlags !== NoFlags) { - commitMutationEffects(fiber.child, root, renderPriorityLevel); - } - } - - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - invokeGuardedCallback( - null, - commitMutationEffectsImpl, - null, - fiber, - root, - renderPriorityLevel, - ); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseError(fiber, fiber.return, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - commitMutationEffectsImpl(fiber, root, renderPriorityLevel); - } catch (error) { - captureCommitPhaseError(fiber, fiber.return, error); - } - } - fiber = fiber.sibling; - } -} - -function commitMutationEffectsImpl( - fiber: Fiber, - root: FiberRoot, - renderPriorityLevel, -) { - const flags = fiber.flags; - if (flags & ContentReset) { - commitResetTextContent(fiber); - } - - if (flags & Ref) { - const current = fiber.alternate; - if (current !== null) { - commitDetachRef(current); - } - if (enableScopeAPI) { - // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. - if (fiber.tag === ScopeComponent) { - commitAttachRef(fiber); - } - } - } - - // The following switch statement is only concerned about placement, - // updates, and deletions. To avoid needing to add a case for every possible - // bitmap value, we remove the secondary effects from the effect tag and - // switch on that value. - const primaryFlags = flags & (Placement | Update | Hydrating); - switch (primaryFlags) { - case Placement: { - commitPlacement(fiber); - // Clear the "placement" from effect tag so that we know that this is - // inserted, before any life-cycles like componentDidMount gets called. - // TODO: findDOMNode doesn't rely on this any more but isMounted does - // and isMounted is deprecated anyway so we should be able to kill this. - fiber.flags &= ~Placement; - break; - } - case PlacementAndUpdate: { - // Placement - commitPlacement(fiber); - // Clear the "placement" from effect tag so that we know that this is - // inserted, before any life-cycles like componentDidMount gets called. - fiber.flags &= ~Placement; - - // Update - const current = fiber.alternate; - commitWork(current, fiber); - break; - } - case Hydrating: { - fiber.flags &= ~Hydrating; - break; - } - case HydratingAndUpdate: { - fiber.flags &= ~Hydrating; - - // Update - const current = fiber.alternate; - commitWork(current, fiber); - break; - } - case Update: { - const current = fiber.alternate; - commitWork(current, fiber); - break; - } - } -} - -function commitMutationEffectsDeletions( - deletions: Array, - nearestMountedAncestor: Fiber, - root: FiberRoot, - renderPriorityLevel, -) { - for (let i = 0; i < deletions.length; i++) { - const childToDelete = deletions[i]; - if (__DEV__) { - invokeGuardedCallback( - null, - commitDeletion, - null, - root, - childToDelete, - nearestMountedAncestor, - renderPriorityLevel, - ); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseError(childToDelete, nearestMountedAncestor, error); - } - } else { - try { - commitDeletion( - root, - childToDelete, - nearestMountedAncestor, - renderPriorityLevel, - ); - } catch (error) { - captureCommitPhaseError(childToDelete, nearestMountedAncestor, error); - } - } - } -} - export function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) { @@ -2374,130 +2085,6 @@ export function flushPassiveEffects(): boolean { return false; } -function flushPassiveMountEffects(root, firstChild: Fiber): void { - let fiber = firstChild; - while (fiber !== null) { - let prevProfilerOnStack = null; - if (enableProfilerTimer && enableProfilerCommitHooks) { - if (fiber.tag === Profiler) { - prevProfilerOnStack = nearestProfilerOnStack; - nearestProfilerOnStack = fiber; - } - } - - const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask; - - if (fiber.child !== null && primarySubtreeFlags !== NoFlags) { - flushPassiveMountEffects(root, fiber.child); - } - - if ((fiber.flags & Passive) !== NoFlags) { - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - invokeGuardedCallback( - null, - commitPassiveMountOnFiber, - null, - root, - fiber, - ); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseError(fiber, fiber.return, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - commitPassiveMountOnFiber(root, fiber); - } catch (error) { - captureCommitPhaseError(fiber, fiber.return, error); - } - } - } - - if (enableProfilerTimer && enableProfilerCommitHooks) { - if (fiber.tag === Profiler) { - // Bubble times to the next nearest ancestor Profiler. - // After we process that Profiler, we'll bubble further up. - if (prevProfilerOnStack !== null) { - prevProfilerOnStack.stateNode.passiveEffectDuration += - fiber.stateNode.passiveEffectDuration; - } - - nearestProfilerOnStack = prevProfilerOnStack; - } - } - - fiber = fiber.sibling; - } -} - -function flushPassiveUnmountEffects(firstChild: Fiber): void { - let fiber = firstChild; - while (fiber !== null) { - const deletions = fiber.deletions; - if (deletions !== null) { - for (let i = 0; i < deletions.length; i++) { - const fiberToDelete = deletions[i]; - flushPassiveUnmountEffectsInsideOfDeletedTree(fiberToDelete, fiber); - - // Now that passive effects have been processed, it's safe to detach lingering pointers. - detachFiberAfterEffects(fiberToDelete); - } - } - - const child = fiber.child; - if (child !== null) { - // If any children have passive effects then traverse the subtree. - // Note that this requires checking subtreeFlags of the current Fiber, - // rather than the subtreeFlags/effectsTag of the first child, - // since that would not cover passive effects in siblings. - const passiveFlags = fiber.subtreeFlags & PassiveMask; - if (passiveFlags !== NoFlags) { - flushPassiveUnmountEffects(child); - } - } - - const primaryFlags = fiber.flags & Passive; - if (primaryFlags !== NoFlags) { - setCurrentDebugFiberInDEV(fiber); - commitPassiveUnmountOnFiber(fiber); - resetCurrentDebugFiberInDEV(); - } - - fiber = fiber.sibling; - } -} - -function flushPassiveUnmountEffectsInsideOfDeletedTree( - fiberToDelete: Fiber, - nearestMountedAncestor: Fiber, -): void { - if ((fiberToDelete.subtreeFlags & PassiveStatic) !== NoFlags) { - // If any children have passive effects then traverse the subtree. - // Note that this requires checking subtreeFlags of the current Fiber, - // rather than the subtreeFlags/effectsTag of the first child, - // since that would not cover passive effects in siblings. - let child = fiberToDelete.child; - while (child !== null) { - flushPassiveUnmountEffectsInsideOfDeletedTree( - child, - nearestMountedAncestor, - ); - child = child.sibling; - } - } - - if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) { - setCurrentDebugFiberInDEV(fiberToDelete); - commitPassiveUnmountInsideDeletedTreeOnFiber( - fiberToDelete, - nearestMountedAncestor, - ); - resetCurrentDebugFiberInDEV(); - } -} - function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; @@ -2537,8 +2124,8 @@ function flushPassiveEffectsImpl() { // e.g. a destroy function in one component may unintentionally override a ref // value set by a create function in another component. // Layout effects have the same constraint. - flushPassiveUnmountEffects(root.current); - flushPassiveMountEffects(root, root.current); + commitPassiveUnmountEffects(root.current); + commitPassiveMountEffects(root, root.current); if (__DEV__) { if (enableDebugTracing) { @@ -2840,59 +2427,6 @@ function flushRenderPhaseStrictModeWarningsInDEV() { } } -function commitDoubleInvokeEffectsInDEV( - fiber: Fiber, - hasPassiveEffects: boolean, -) { - if (__DEV__ && enableDoubleInvokingEffects) { - // Never double-invoke effects for legacy roots. - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { - return; - } - - setCurrentDebugFiberInDEV(fiber); - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV); - if (hasPassiveEffects) { - invokeEffectsInDev( - fiber, - MountPassiveDev, - invokePassiveEffectUnmountInDEV, - ); - } - - invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV); - if (hasPassiveEffects) { - invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV); - } - resetCurrentDebugFiberInDEV(); - } -} - -function invokeEffectsInDev( - firstChild: Fiber, - fiberFlags: Flags, - invokeEffectFn: (fiber: Fiber) => void, -): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. - let fiber = firstChild; - while (fiber !== null) { - if (fiber.child !== null) { - const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags; - if (primarySubtreeFlag !== NoFlags) { - invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn); - } - } - - if ((fiber.flags & fiberFlags) !== NoFlags) { - invokeEffectFn(fiber); - } - fiber = fiber.sibling; - } - } -} - let didWarnStateUpdateForNotYetMountedComponent: Set | null = null; function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) { if (__DEV__) { @@ -3675,21 +3209,3 @@ export function act(callback: () => Thenable): Thenable { }; } } - -function detachFiberAfterEffects(fiber: Fiber): void { - // Null out fields to improve GC for references that may be lingering (e.g. DevTools). - // Note that we already cleared the return pointer in detachFiberMutation(). - fiber.child = null; - fiber.deletions = null; - fiber.dependencies = null; - fiber.memoizedProps = null; - fiber.memoizedState = null; - fiber.pendingProps = null; - fiber.sibling = null; - fiber.stateNode = null; - fiber.updateQueue = null; - - if (__DEV__) { - fiber._debugOwner = null; - } -} diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index dbf9f5bd28533..ba92ea1715399 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -136,3 +136,5 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; + +export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 0df33470e5703..2009366d12f1a 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -53,6 +53,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 77148d9be29b9..aa53af7e58c89 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 7e66eb9e75e5e..eee1cb988618f 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 90c4360c6d469..f2b19e6dc26bb 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 4dbd671409e36..11989ccafdc69 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index c98063376f09d..7bb6c2bd8b948 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 362569dfb01b4..23da5613036e2 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = true; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = false; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index f5861a2e336a5..fec7e2beaa870 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -84,6 +84,8 @@ export const enableDiscreteEventFlushingChange = true; // to the correct value. export const enableNewReconciler = __VARIANT__; +export const enableRecursiveCommitTraversal = false; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null;