diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 6d47fac3f4661..d8843eb0ca0d4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -17,7 +17,6 @@ import type { } from './ReactFiberHostConfig'; import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; -import type {Lanes} from './ReactFiberLane'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {UpdateQueue} from './ReactUpdateQueue.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; @@ -70,11 +69,17 @@ import { Snapshot, Update, Callback, + LayoutMask, PassiveMask, + Ref, } from './ReactFiberFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; - +import { + current as currentDebugFiberInDEV, + resetCurrentFiber as resetCurrentDebugFiberInDEV, + setCurrentFiber as setCurrentDebugFiberInDEV, +} from './ReactCurrentFiber'; import {onCommitUnmount} from './ReactFiberDevToolsHook.new'; import {resolveDefaultProps} from './ReactFiberLazyComponent.new'; import { @@ -130,6 +135,9 @@ import { } from './ReactHookEffectTags'; import {didWarnAboutReassigningProps} from './ReactFiberBeginWork.new'; +// Used to avoid traversing the return path to find the nearest Profiler ancestor during commit. +let nearestProfilerOnStack: Fiber | null = null; + let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null; if (__DEV__) { didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); @@ -424,19 +432,6 @@ function commitProfilerPassiveEffect( ); } } - - // Bubble times to the next nearest ancestor Profiler. - // After we process that Profiler, we'll bubble further up. - // TODO: Use JS Stack instead - let parentFiber = finishedWork.return; - while (parentFiber !== null) { - if (parentFiber.tag === Profiler) { - const parentStateNode = parentFiber.stateNode; - parentStateNode.passiveEffectDuration += passiveEffectDuration; - break; - } - parentFiber = parentFiber.return; - } break; } default: @@ -445,328 +440,470 @@ function commitProfilerPassiveEffect( } } -function commitLifeCycles( - finishedRoot: FiberRoot, - current: Fiber | null, +function recursivelyCommitLayoutEffects( finishedWork: Fiber, - committedLanes: Lanes, -): void { - switch (finishedWork.tag) { - case FunctionComponent: - case ForwardRef: - case SimpleMemoComponent: - case Block: { - // At this point layout effects have already been destroyed (during mutation phase). - // This is done to prevent sibling component effects from interfering with each other, - // e.g. a destroy function in one component should never override a ref set - // by a create function in another component during the same commit. - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - finishedWork.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); - } finally { - recordLayoutEffectDuration(finishedWork); - } - } else { - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); + finishedRoot: FiberRoot, +) { + const {flags, tag} = finishedWork; + switch (tag) { + case Profiler: { + let prevProfilerOnStack = null; + if (enableProfilerTimer && enableProfilerCommitHooks) { + prevProfilerOnStack = nearestProfilerOnStack; + nearestProfilerOnStack = finishedWork; } - if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags) { - schedulePassiveEffectCallback(); - } - return; - } - case ClassComponent: { - const instance = finishedWork.stateNode; - if (finishedWork.flags & Update) { - if (current === null) { - // 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. + let child = finishedWork.child; + while (child !== null) { + const primarySubtreeFlags = finishedWork.subtreeFlags & LayoutMask; + if (primarySubtreeFlags !== NoFlags) { if (__DEV__) { - if ( - finishedWork.type === finishedWork.elementType && - !didWarnAboutReassigningProps - ) { - if (instance.props !== finishedWork.memoizedProps) { - console.error( - 'Expected %s props to match memoized props before ' + - 'componentDidMount. ' + - '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 ' + - 'componentDidMount. ' + - '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 prevCurrentFiberInDEV = currentDebugFiberInDEV; + setCurrentDebugFiberInDEV(child); + invokeGuardedCallback( + null, + recursivelyCommitLayoutEffects, + null, + child, + finishedRoot, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(child, finishedWork, error); } - } - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - finishedWork.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - instance.componentDidMount(); - } finally { - recordLayoutEffectDuration(finishedWork); + if (prevCurrentFiberInDEV !== null) { + setCurrentDebugFiberInDEV(prevCurrentFiberInDEV); + } else { + resetCurrentDebugFiberInDEV(); } } else { - instance.componentDidMount(); + try { + recursivelyCommitLayoutEffects(child, finishedRoot); + } catch (error) { + captureCommitPhaseError(child, finishedWork, error); + } } - } else { - const prevProps = - finishedWork.elementType === finishedWork.type - ? current.memoizedProps - : resolveDefaultProps(finishedWork.type, current.memoizedProps); - const prevState = current.memoizedState; - // 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. + } + child = child.sibling; + } + + const primaryFlags = flags & (Update | Callback); + if (primaryFlags !== NoFlags) { + if (enableProfilerTimer) { if (__DEV__) { - if ( - finishedWork.type === finishedWork.elementType && - !didWarnAboutReassigningProps - ) { - if (instance.props !== finishedWork.memoizedProps) { - console.error( - 'Expected %s props to match memoized props before ' + - 'componentDidUpdate. ' + - '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 ' + - 'componentDidUpdate. ' + - '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 prevCurrentFiberInDEV = currentDebugFiberInDEV; + setCurrentDebugFiberInDEV(finishedWork); + invokeGuardedCallback( + null, + commitLayoutEffectsForProfiler, + null, + finishedWork, + finishedRoot, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(finishedWork, finishedWork.return, error); } - } - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - finishedWork.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - instance.componentDidUpdate( - prevProps, - prevState, - instance.__reactInternalSnapshotBeforeUpdate, - ); - } finally { - recordLayoutEffectDuration(finishedWork); + if (prevCurrentFiberInDEV !== null) { + setCurrentDebugFiberInDEV(prevCurrentFiberInDEV); + } else { + resetCurrentDebugFiberInDEV(); } } else { - instance.componentDidUpdate( - prevProps, - prevState, - instance.__reactInternalSnapshotBeforeUpdate, + try { + commitLayoutEffectsForProfiler(finishedWork, finishedRoot); + } catch (error) { + captureCommitPhaseError(finishedWork, finishedWork.return, error); + } + } + } + } + + if (enableProfilerTimer && enableProfilerCommitHooks) { + // 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; + } + break; + } + + // case Offscreen: { + // TODO: Fast path to invoke all nested layout effects when Offscren goes from hidden to visible. + // break; + // } + + default: { + let child = finishedWork.child; + while (child !== null) { + const primarySubtreeFlags = finishedWork.subtreeFlags & LayoutMask; + if (primarySubtreeFlags !== NoFlags) { + if (__DEV__) { + const prevCurrentFiberInDEV = currentDebugFiberInDEV; + setCurrentDebugFiberInDEV(child); + invokeGuardedCallback( + null, + recursivelyCommitLayoutEffects, + null, + child, + finishedRoot, ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(child, finishedWork, error); + } + if (prevCurrentFiberInDEV !== null) { + setCurrentDebugFiberInDEV(prevCurrentFiberInDEV); + } else { + resetCurrentDebugFiberInDEV(); + } + } else { + try { + recursivelyCommitLayoutEffects(child, finishedRoot); + } catch (error) { + captureCommitPhaseError(child, finishedWork, error); + } } } + child = child.sibling; } - // TODO: I think this is now always non-null by the time it reaches the - // commit phase. Consider removing the type check. - const updateQueue: UpdateQueue< - *, - > | null = (finishedWork.updateQueue: any); - if (updateQueue !== null) { - if (__DEV__) { - if ( - finishedWork.type === finishedWork.elementType && - !didWarnAboutReassigningProps - ) { - if (instance.props !== finishedWork.memoizedProps) { - console.error( - 'Expected %s props to match memoized props before ' + - 'processing the update queue. ' + - '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', + const primaryFlags = flags & (Update | Callback); + if (primaryFlags !== 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, ); } - if (instance.state !== finishedWork.memoizedState) { - console.error( - 'Expected %s state to match memoized state before ' + - 'processing the update queue. ' + - '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', - ); + + if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags) { + schedulePassiveEffectCallback(); } + break; + } + case ClassComponent: { + // NOTE: Layout effect durations are measured within this function. + commitLayoutEffectsForClassComponent(finishedWork); + break; + } + case HostRoot: { + commitLayoutEffectsForHostRoot(finishedWork); + break; + } + case HostComponent: { + commitLayoutEffectsForHostComponent(finishedWork); + 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.', + ); } } - // 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. - commitUpdateQueue(finishedWork, updateQueue, instance); } - return; - } - case HostRoot: { - // TODO: I think this is now always non-null by the time it reaches the - // commit phase. Consider removing the type check. - const updateQueue: UpdateQueue< - *, - > | null = (finishedWork.updateQueue: any); - if (updateQueue !== null) { - let instance = null; - if (finishedWork.child !== null) { - switch (finishedWork.child.tag) { - case HostComponent: - instance = getPublicInstance(finishedWork.child.stateNode); - break; - case ClassComponent: - instance = finishedWork.child.stateNode; - break; - } + + 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); } - commitUpdateQueue(finishedWork, updateQueue, instance); } - return; + break; } - case HostComponent: { - const instance: Instance = finishedWork.stateNode; + } +} - // Renderers may schedule work to be done after host components are mounted - // (eg DOM renderer may schedule auto-focus for inputs and form controls). - // These effects should only be committed when components are first mounted, - // aka when there is no current/alternate. - if (current === null && finishedWork.flags & Update) { - const type = finishedWork.type; - const props = finishedWork.memoizedProps; - commitMount(instance, type, props, finishedWork); - } +function commitLayoutEffectsForProfiler( + finishedWork: Fiber, + finishedRoot: FiberRoot, +) { + if (enableProfilerTimer) { + const flags = finishedWork.flags; + const current = finishedWork.alternate; - return; - } - case HostText: { - // We have no life-cycles associated with text. - return; - } - case HostPortal: { - // We have no life-cycles associated with portals. - return; - } - case Profiler: { - if (enableProfilerTimer) { - const {onCommit, onRender} = finishedWork.memoizedProps; - const {effectDuration} = finishedWork.stateNode; - const flags = finishedWork.flags; + const {onCommit, onRender} = finishedWork.memoizedProps; + const {effectDuration} = finishedWork.stateNode; - const commitTime = getCommitTime(); + const commitTime = getCommitTime(); + + const OnRenderFlag = Update; + const OnCommitFlag = Callback; - const OnRenderFlag = Update; - const OnCommitFlag = Callback; + if ((flags & OnRenderFlag) !== NoFlags && typeof onRender === 'function') { + if (enableSchedulerTracing) { + onRender( + finishedWork.memoizedProps.id, + current === null ? 'mount' : 'update', + finishedWork.actualDuration, + finishedWork.treeBaseDuration, + finishedWork.actualStartTime, + commitTime, + finishedRoot.memoizedInteractions, + ); + } else { + onRender( + finishedWork.memoizedProps.id, + current === null ? 'mount' : 'update', + finishedWork.actualDuration, + finishedWork.treeBaseDuration, + finishedWork.actualStartTime, + commitTime, + ); + } + } + if (enableProfilerCommitHooks) { + if ( + (flags & OnCommitFlag) !== NoFlags && + typeof onCommit === 'function' + ) { + if (enableSchedulerTracing) { + onCommit( + finishedWork.memoizedProps.id, + current === null ? 'mount' : 'update', + effectDuration, + commitTime, + finishedRoot.memoizedInteractions, + ); + } else { + onCommit( + finishedWork.memoizedProps.id, + current === null ? 'mount' : 'update', + effectDuration, + commitTime, + ); + } + } + } + } +} + +function commitLayoutEffectsForClassComponent(finishedWork: Fiber) { + const instance = finishedWork.stateNode; + const current = finishedWork.alternate; + if (finishedWork.flags & Update) { + if (current === null) { + // 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 ( - (flags & OnRenderFlag) !== NoFlags && - typeof onRender === 'function' + finishedWork.type === finishedWork.elementType && + !didWarnAboutReassigningProps ) { - if (enableSchedulerTracing) { - onRender( - finishedWork.memoizedProps.id, - current === null ? 'mount' : 'update', - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, - finishedRoot.memoizedInteractions, + if (instance.props !== finishedWork.memoizedProps) { + console.error( + 'Expected %s props to match memoized props before ' + + 'componentDidMount. ' + + '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', ); - } else { - onRender( - finishedWork.memoizedProps.id, - current === null ? 'mount' : 'update', - finishedWork.actualDuration, - finishedWork.treeBaseDuration, - finishedWork.actualStartTime, - commitTime, + } + if (instance.state !== finishedWork.memoizedState) { + console.error( + 'Expected %s state to match memoized state before ' + + 'componentDidMount. ' + + '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', ); } } - - if (enableProfilerCommitHooks) { - if ( - (flags & OnCommitFlag) !== NoFlags && - typeof onCommit === 'function' - ) { - if (enableSchedulerTracing) { - onCommit( - finishedWork.memoizedProps.id, - current === null ? 'mount' : 'update', - effectDuration, - commitTime, - finishedRoot.memoizedInteractions, - ); - } else { - onCommit( - finishedWork.memoizedProps.id, - current === null ? 'mount' : 'update', - effectDuration, - commitTime, - ); - } + } + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + instance.componentDidMount(); + } finally { + recordLayoutEffectDuration(finishedWork); + } + } else { + instance.componentDidMount(); + } + } else { + const prevProps = + finishedWork.elementType === finishedWork.type + ? current.memoizedProps + : resolveDefaultProps(finishedWork.type, current.memoizedProps); + const prevState = current.memoizedState; + // 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 ' + + 'componentDidUpdate. ' + + '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', + ); } - - // 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. - // TODO: Use JS Stack instead - let parentFiber = finishedWork.return; - while (parentFiber !== null) { - if (parentFiber.tag === Profiler) { - const parentStateNode = parentFiber.stateNode; - parentStateNode.effectDuration += effectDuration; - break; - } - parentFiber = parentFiber.return; + if (instance.state !== finishedWork.memoizedState) { + console.error( + 'Expected %s state to match memoized state before ' + + 'componentDidUpdate. ' + + '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', + ); } } } - return; + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + instance.componentDidUpdate( + prevProps, + prevState, + instance.__reactInternalSnapshotBeforeUpdate, + ); + } finally { + recordLayoutEffectDuration(finishedWork); + } + } else { + instance.componentDidUpdate( + prevProps, + prevState, + instance.__reactInternalSnapshotBeforeUpdate, + ); + } } - case SuspenseComponent: { - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork); - return; + } + + // TODO: I think this is now always non-null by the time it reaches the + // commit phase. Consider removing the type check. + const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any); + if (updateQueue !== null) { + if (__DEV__) { + if ( + finishedWork.type === finishedWork.elementType && + !didWarnAboutReassigningProps + ) { + if (instance.props !== finishedWork.memoizedProps) { + console.error( + 'Expected %s props to match memoized props before ' + + 'processing the update queue. ' + + '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 ' + + 'processing the update queue. ' + + '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', + ); + } + } } - case SuspenseListComponent: - case IncompleteClassComponent: - case FundamentalComponent: - case ScopeComponent: - case OffscreenComponent: - case LegacyHiddenComponent: - return; + // 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. + commitUpdateQueue(finishedWork, updateQueue, instance); + } +} + +function commitLayoutEffectsForHostRoot(finishedWork: Fiber) { + // TODO: I think this is now always non-null by the time it reaches the + // commit phase. Consider removing the type check. + const updateQueue: UpdateQueue<*> | null = (finishedWork.updateQueue: any); + if (updateQueue !== null) { + let instance = null; + if (finishedWork.child !== null) { + switch (finishedWork.child.tag) { + case HostComponent: + instance = getPublicInstance(finishedWork.child.stateNode); + break; + case ClassComponent: + instance = finishedWork.child.stateNode; + break; + } + } + commitUpdateQueue(finishedWork, updateQueue, instance); + } +} + +function commitLayoutEffectsForHostComponent(finishedWork: Fiber) { + const instance: Instance = finishedWork.stateNode; + const current = finishedWork.alternate; + + // Renderers may schedule work to be done after host components are mounted + // (eg DOM renderer may schedule auto-focus for inputs and form controls). + // These effects should only be committed when components are first mounted, + // aka when there is no current/alternate. + if (current === null && finishedWork.flags & Update) { + const type = finishedWork.type; + const props = finishedWork.memoizedProps; + commitMount(instance, type, props, finishedWork); } - 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 hideOrUnhideAllChildren(finishedWork, isHidden) { @@ -1790,7 +1927,7 @@ function commitResetTextContent(current: Fiber): void { resetTextContent(current.stateNode); } -function commitPassiveUnmountInsideDeletedTree(finishedWork: Fiber): void { +function commitPassiveUnmount(finishedWork: Fiber): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: @@ -1820,7 +1957,7 @@ function commitPassiveUnmountInsideDeletedTree(finishedWork: Fiber): void { } } -function commitPassiveUnmount( +function commitPassiveUnmountInsideDeletedTree( current: Fiber, nearestMountedAncestor: Fiber | null, ): void { @@ -2016,7 +2153,6 @@ export { commitPlacement, commitDeletion, commitWork, - commitLifeCycles, commitAttachRef, commitDetachRef, commitPassiveUnmount, @@ -2026,4 +2162,5 @@ export { invokeLayoutEffectUnmountInDEV, invokePassiveEffectMountInDEV, invokePassiveEffectUnmountInDEV, + recursivelyCommitLayoutEffects, }; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index bd8ef8ce5e3c6..4f40bde182310 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -22,6 +22,7 @@ import { enableSuspenseServerRenderer, replayFailedUnitOfWorkWithInvokeGuardedCallback, enableProfilerTimer, + enableProfilerCommitHooks, enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, @@ -116,6 +117,7 @@ import { SimpleMemoComponent, Block, ScopeComponent, + Profiler, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; import { @@ -126,7 +128,6 @@ import { Ref, ContentReset, Snapshot, - Callback, Passive, PassiveStatic, Incomplete, @@ -190,7 +191,6 @@ import { } from './ReactFiberThrow.new'; import { commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber, - commitLifeCycles as commitLayoutEffectOnFiber, commitPlacement, commitWork, commitDeletion, @@ -205,6 +205,7 @@ import { invokePassiveEffectMountInDEV, invokeLayoutEffectUnmountInDEV, invokePassiveEffectUnmountInDEV, + recursivelyCommitLayoutEffects, } from './ReactFiberCommitWork.new'; import {enqueueUpdate} from './ReactUpdateQueue.new'; import {resetContextDependencies} from './ReactFiberNewContext.new'; @@ -325,6 +326,9 @@ 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; } @@ -1937,7 +1941,27 @@ function commitRootImpl(root, renderPriorityLevel) { markLayoutEffectsStarted(lanes); } - commitLayoutEffects(finishedWork, root, 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); + } + } if (__DEV__) { if (enableDebugTracing) { @@ -2253,8 +2277,7 @@ function commitMutationEffectsImpl( commitDetachRef(current); } if (enableScopeAPI) { - // TODO: This is a temporary solution that allowed us to transition away - // from React Flare on www. + // TODO: This is a temporary solution that allowed us to transition away from React Flare on www. if (fiber.tag === ScopeComponent) { commitAttachRef(fiber); } @@ -2355,75 +2378,6 @@ export function schedulePassiveEffectCallback() { } } -function commitLayoutEffects( - firstChild: Fiber, - root: FiberRoot, - committedLanes: Lanes, -) { - let fiber = firstChild; - while (fiber !== null) { - if (fiber.child !== null) { - const primarySubtreeFlags = fiber.subtreeFlags & LayoutMask; - if (primarySubtreeFlags !== NoFlags) { - commitLayoutEffects(fiber.child, root, committedLanes); - } - } - - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - invokeGuardedCallback( - null, - commitLayoutEffectsImpl, - null, - fiber, - root, - committedLanes, - ); - if (hasCaughtError()) { - const error = clearCaughtError(); - captureCommitPhaseError(fiber, fiber.return, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - commitLayoutEffectsImpl(fiber, root, committedLanes); - } catch (error) { - captureCommitPhaseError(fiber, fiber.return, error); - } - } - fiber = fiber.sibling; - } -} - -function commitLayoutEffectsImpl( - fiber: Fiber, - root: FiberRoot, - committedLanes: Lanes, -) { - const flags = fiber.flags; - - setCurrentDebugFiberInDEV(fiber); - - if (flags & (Update | Callback)) { - const current = fiber.alternate; - commitLayoutEffectOnFiber(root, current, fiber, committedLanes); - } - - if (enableScopeAPI) { - // TODO: This is a temporary solution that allowed us to transition away - // from React Flare on www. - if (flags & Ref && fiber.tag !== ScopeComponent) { - commitAttachRef(fiber); - } - } else { - if (flags & Ref) { - commitAttachRef(fiber); - } - } - - resetCurrentDebugFiberInDEV(); -} - export function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) { @@ -2452,6 +2406,14 @@ export function flushPassiveEffects(): boolean { 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) { @@ -2482,6 +2444,19 @@ function flushPassiveMountEffects(root, firstChild: Fiber): void { } } + 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; } } @@ -2515,7 +2490,7 @@ function flushPassiveUnmountEffects(firstChild: Fiber): void { const primaryFlags = fiber.flags & Passive; if (primaryFlags !== NoFlags) { setCurrentDebugFiberInDEV(fiber); - commitPassiveUnmountInsideDeletedTreeOnFiber(fiber); + commitPassiveUnmountOnFiber(fiber); resetCurrentDebugFiberInDEV(); } @@ -2544,7 +2519,10 @@ function flushPassiveUnmountEffectsInsideOfDeletedTree( if ((fiberToDelete.flags & PassiveStatic) !== NoFlags) { setCurrentDebugFiberInDEV(fiberToDelete); - commitPassiveUnmountOnFiber(fiberToDelete, nearestMountedAncestor); + commitPassiveUnmountInsideDeletedTreeOnFiber( + fiberToDelete, + nearestMountedAncestor, + ); resetCurrentDebugFiberInDEV(); } }