diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 69eccc53d0850..515a7c41443c2 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -8,7 +8,7 @@ */ import type {Fiber} from './ReactInternalTypes'; -import type {Lanes} from './ReactFiberLane'; +import type {Lanes, Lane} from './ReactFiberLane'; import type { ReactFundamentalComponentInstance, ReactScopeInstance, @@ -58,16 +58,30 @@ import { OffscreenComponent, LegacyHiddenComponent, } from './ReactWorkTags'; -import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode'; +import { + NoMode, + BlockingMode, + ProfileMode, + ConcurrentMode, +} from './ReactTypeOfMode'; import { Ref, Update, NoEffect, DidCapture, Snapshot, + BeforeMutationMask, MutationMask, + LayoutMask, + PassiveMask, } from './ReactSideEffectTags'; -import {NoEffect as NoSubtreeTag, Mutation} from './ReactSubtreeTags'; +import { + NoEffect as NoSubtreeTag, + BeforeMutation as BeforeMutationSubtreeTag, + Mutation as MutationSubtreeTag, + Layout as LayoutSubtreeTag, + Passive as PassiveSubtreeTag, +} from './ReactSubtreeTags'; import invariant from 'shared/invariant'; import { @@ -138,9 +152,15 @@ import { renderDidSuspendDelayIfPossible, renderHasNotSuspendedYet, popRenderLanes, + subtreeRenderLanes, } from './ReactFiberWorkLoop.new'; import {createFundamentalStateInstance} from './ReactFiberFundamental.new'; -import {OffscreenLane} from './ReactFiberLane'; +import { + includesSomeLane, + OffscreenLane, + mergeLanes, + NoLanes, +} from './ReactFiberLane'; import {resetChildFibers} from './ReactChildFiber.new'; import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new'; import {createScopeInstance} from './ReactFiberScope.new'; @@ -167,7 +187,7 @@ function hadNoMutationsEffects(current: null | Fiber, completedWork: Fiber) { if ((child.effectTag & MutationMask) !== NoEffect) { return false; } - if ((child.subtreeTag & Mutation) !== NoSubtreeTag) { + if ((child.subtreeTag & MutationSubtreeTag) !== NoSubtreeTag) { return false; } child = child.sibling; @@ -670,6 +690,124 @@ function cutOffTailIfNeeded( } } +function bubbleProperties(completedWork: Fiber): void { + const didBailout = + completedWork.alternate !== null && + completedWork.alternate.child === completedWork.child; + + let newChildLanes = NoLanes; + let subtreeTag = NoSubtreeTag; + + if (!didBailout) { + // Bubble up the earliest expiration time. + if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { + // In profiling mode, resetChildExpirationTime is also used to reset + // profiler durations. + let actualDuration = completedWork.actualDuration; + let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); + + let child = completedWork.child; + while (child !== null) { + newChildLanes = mergeLanes( + newChildLanes, + mergeLanes(child.lanes, child.childLanes), + ); + + subtreeTag |= child.subtreeTag; + + const effectTag = child.effectTag; + if ((effectTag & BeforeMutationMask) !== NoEffect) { + subtreeTag |= BeforeMutationSubtreeTag; + } + if ((effectTag & MutationMask) !== NoEffect) { + subtreeTag |= MutationSubtreeTag; + } + if ((effectTag & LayoutMask) !== NoEffect) { + subtreeTag |= LayoutSubtreeTag; + } + if ((effectTag & PassiveMask) !== NoEffect) { + subtreeTag |= PassiveSubtreeTag; + } + + // When a fiber is cloned, its actualDuration is reset to 0. This value will + // only be updated if work is done on the fiber (i.e. it doesn't bailout). + // When work is done, it should bubble to the parent's actualDuration. If + // the fiber has not been cloned though, (meaning no work was done), then + // this value will reflect the amount of time spent working on a previous + // render. In that case it should not bubble. We determine whether it was + // cloned by comparing the child pointer. + actualDuration += child.actualDuration; + + treeBaseDuration += child.treeBaseDuration; + child = child.sibling; + } + + completedWork.actualDuration = actualDuration; + completedWork.treeBaseDuration = treeBaseDuration; + } else { + let child = completedWork.child; + while (child !== null) { + newChildLanes = mergeLanes( + newChildLanes, + mergeLanes(child.lanes, child.childLanes), + ); + + subtreeTag |= child.subtreeTag; + + const effectTag = child.effectTag; + if ((effectTag & BeforeMutationMask) !== NoEffect) { + subtreeTag |= BeforeMutationSubtreeTag; + } + if ((effectTag & MutationMask) !== NoEffect) { + subtreeTag |= MutationSubtreeTag; + } + if ((effectTag & LayoutMask) !== NoEffect) { + subtreeTag |= LayoutSubtreeTag; + } + if ((effectTag & PassiveMask) !== NoEffect) { + subtreeTag |= PassiveSubtreeTag; + } + + child = child.sibling; + } + } + + completedWork.subtreeTag |= subtreeTag; + } else { + // Bubble up the earliest expiration time. + if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { + // In profiling mode, resetChildExpirationTime is also used to reset + // profiler durations. + let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); + + let child = completedWork.child; + while (child !== null) { + newChildLanes = mergeLanes( + newChildLanes, + mergeLanes(child.lanes, child.childLanes), + ); + + treeBaseDuration += child.treeBaseDuration; + child = child.sibling; + } + + completedWork.treeBaseDuration = treeBaseDuration; + } else { + let child = completedWork.child; + while (child !== null) { + newChildLanes = mergeLanes( + newChildLanes, + mergeLanes(child.lanes, child.childLanes), + ); + + child = child.sibling; + } + } + } + + completedWork.childLanes = newChildLanes; +} + function completeWork( current: Fiber | null, workInProgress: Fiber, @@ -688,12 +826,14 @@ function completeWork( case Profiler: case ContextConsumer: case MemoComponent: + bubbleProperties(workInProgress); return null; case ClassComponent: { const Component = workInProgress.type; if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } + bubbleProperties(workInProgress); return null; } case HostRoot: { @@ -722,6 +862,7 @@ function completeWork( } } updateHostContainer(current, workInProgress); + bubbleProperties(workInProgress); return null; } case HostComponent: { @@ -756,6 +897,7 @@ function completeWork( 'caused by a bug in React. Please file an issue.', ); // This can happen when we abort work. + bubbleProperties(workInProgress); return null; } @@ -835,6 +977,8 @@ function completeWork( markRef(workInProgress); } } + + bubbleProperties(workInProgress); return null; } case HostText: { @@ -869,6 +1013,7 @@ function completeWork( ); } } + bubbleProperties(workInProgress); return null; } case SuspenseComponent: { @@ -888,6 +1033,20 @@ function completeWork( if (enableSchedulerTracing) { markSpawnedWork(OffscreenLane); } + bubbleProperties(workInProgress); + if (enableProfilerTimer) { + if ((workInProgress.mode & ProfileMode) !== NoMode) { + const isTimedOutSuspense = nextState !== null; + if (isTimedOutSuspense) { + // Don't count time spent in a timed out Suspense subtree as part of the base duration. + const primaryChildFragment = workInProgress.child; + if (primaryChildFragment !== null) { + // $FlowFixMe Flow doens't support type casting in combiation with the -= operator + workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number); + } + } + } + } return null; } else { // We should never have been in a hydration state if we didn't have a current. @@ -904,6 +1063,20 @@ function completeWork( // If something suspended, schedule an effect to attach retry listeners. // So we might as well always mark this. workInProgress.effectTag |= Update; + bubbleProperties(workInProgress); + if (enableProfilerTimer) { + if ((workInProgress.mode & ProfileMode) !== NoMode) { + const isTimedOutSuspense = nextState !== null; + if (isTimedOutSuspense) { + // Don't count time spent in a timed out Suspense subtree as part of the base duration. + const primaryChildFragment = workInProgress.child; + if (primaryChildFragment !== null) { + // $FlowFixMe Flow doens't support type casting in combiation with the -= operator + workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number); + } + } + } + } return null; } } @@ -996,6 +1169,20 @@ function completeWork( // Always notify the callback workInProgress.effectTag |= Update; } + bubbleProperties(workInProgress); + if (enableProfilerTimer) { + if ((workInProgress.mode & ProfileMode) !== NoMode) { + const isTimedOutSuspense = nextState !== null; + if (isTimedOutSuspense) { + // Don't count time spent in a timed out Suspense subtree as part of the base duration. + const primaryChildFragment = workInProgress.child; + if (primaryChildFragment !== null) { + // $FlowFixMe Flow doens't support type casting in combiation with the -= operator + workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number); + } + } + } + } return null; } case HostPortal: @@ -1004,10 +1191,12 @@ function completeWork( if (current === null) { preparePortalMount(workInProgress.stateNode.containerInfo); } + bubbleProperties(workInProgress); return null; case ContextProvider: // Pop provider fiber popProvider(workInProgress); + bubbleProperties(workInProgress); return null; case IncompleteClassComponent: { // Same as class component case. I put it down here so that the tags are @@ -1016,6 +1205,7 @@ function completeWork( if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } + bubbleProperties(workInProgress); return null; } case SuspenseListComponent: { @@ -1027,6 +1217,7 @@ function completeWork( if (renderState === null) { // We're running in the default, "independent" mode. // We don't do anything in this mode. + bubbleProperties(workInProgress); return null; } @@ -1146,6 +1337,7 @@ function completeWork( lastEffect.nextEffect = null; } // We're done. + bubbleProperties(workInProgress); return null; } } else if ( @@ -1231,6 +1423,7 @@ function completeWork( // Do a pass over the next row. return next; } + bubbleProperties(workInProgress); return null; } case FundamentalComponent: { @@ -1258,6 +1451,7 @@ function completeWork( ): any): Instance); fundamentalInstance.instance = instance; if (fundamentalImpl.reconcileChildren === false) { + bubbleProperties(workInProgress); return null; } appendAllChildren(instance, workInProgress, false, false); @@ -1280,6 +1474,7 @@ function completeWork( markUpdate(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1325,24 +1520,27 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; } case Block: if (enableBlocksAPI) { + bubbleProperties(workInProgress); return null; } break; case OffscreenComponent: case LegacyHiddenComponent: { popRenderLanes(workInProgress); + const nextState: OffscreenState | null = workInProgress.memoizedState; + const nextIsHidden = nextState !== null; + if (current !== null) { - const nextState: OffscreenState | null = workInProgress.memoizedState; const prevState: OffscreenState | null = current.memoizedState; const prevIsHidden = prevState !== null; - const nextIsHidden = nextState !== null; if ( prevIsHidden !== nextIsHidden && newProps.mode !== 'unstable-defer-without-hiding' @@ -1350,6 +1548,16 @@ function completeWork( workInProgress.effectTag |= Update; } } + + // Don't bubble properties for hidden children. + if ( + !nextIsHidden || + includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) || + (workInProgress.mode & ConcurrentMode) === NoLanes + ) { + bubbleProperties(workInProgress); + } + return null; } } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 7e6845f19a061..136236127ad3a 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -116,8 +116,6 @@ import { MemoComponent, SimpleMemoComponent, Block, - OffscreenComponent, - LegacyHiddenComponent, ScopeComponent, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; @@ -137,9 +135,6 @@ import { HostEffectMask, Hydrating, HydratingAndUpdate, - BeforeMutationMask, - MutationMask, - LayoutMask, PassiveMask, } from './ReactSideEffectTags'; import { @@ -161,7 +156,6 @@ import { NoLane, SyncLane, SyncBatchedLane, - OffscreenLane, NoTimestamp, findUpdateLane, findTransitionLane, @@ -297,7 +291,7 @@ let workInProgressRootRenderLanes: Lanes = NoLanes; // // Most things in the work loop should deal with workInProgressRootRenderLanes. // Most things in begin/complete phases should deal with subtreeRenderLanes. -let subtreeRenderLanes: Lanes = NoLanes; +export let subtreeRenderLanes: Lanes = NoLanes; const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes); // Whether to root completed, errored, suspended, etc. @@ -1745,8 +1739,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void { return; } - resetChildLanes(completedWork); - if ( returnFiber !== null && // Do not append effects to parents if a sibling failed to complete @@ -1847,160 +1839,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void { } } -function resetChildLanes(completedWork: Fiber) { - if ( - // TODO: Move this check out of the hot path by moving `resetChildLanes` - // to switch statement in `completeWork`. - (completedWork.tag === LegacyHiddenComponent || - completedWork.tag === OffscreenComponent) && - completedWork.memoizedState !== null && - !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) && - (completedWork.mode & ConcurrentMode) !== NoLanes - ) { - // The children of this component are hidden. Don't bubble their - // expiration times. - return; - } - - const didBailout = - completedWork.alternate !== null && - completedWork.alternate.child === completedWork.child; - - let newChildLanes = NoLanes; - let subtreeTag = NoSubtreeTag; - - if (!didBailout) { - // Bubble up the earliest expiration time. - if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { - // In profiling mode, resetChildExpirationTime is also used to reset - // profiler durations. - let actualDuration = completedWork.actualDuration; - let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); - - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - - subtreeTag |= child.subtreeTag; - - const effectTag = child.effectTag; - if ((effectTag & BeforeMutationMask) !== NoEffect) { - subtreeTag |= BeforeMutationSubtreeTag; - } - if ((effectTag & MutationMask) !== NoEffect) { - subtreeTag |= MutationSubtreeTag; - } - if ((effectTag & LayoutMask) !== NoEffect) { - subtreeTag |= LayoutSubtreeTag; - } - if ((effectTag & PassiveMask) !== NoEffect) { - subtreeTag |= PassiveSubtreeTag; - } - - // When a fiber is cloned, its actualDuration is reset to 0. This value will - // only be updated if work is done on the fiber (i.e. it doesn't bailout). - // When work is done, it should bubble to the parent's actualDuration. If - // the fiber has not been cloned though, (meaning no work was done), then - // this value will reflect the amount of time spent working on a previous - // render. In that case it should not bubble. We determine whether it was - // cloned by comparing the child pointer. - actualDuration += child.actualDuration; - - treeBaseDuration += child.treeBaseDuration; - child = child.sibling; - } - - const isTimedOutSuspense = - completedWork.tag === SuspenseComponent && - completedWork.memoizedState !== null; - if (isTimedOutSuspense) { - // Don't count time spent in a timed out Suspense subtree as part of the base duration. - const primaryChildFragment = completedWork.child; - if (primaryChildFragment !== null) { - treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number); - } - } - - completedWork.actualDuration = actualDuration; - completedWork.treeBaseDuration = treeBaseDuration; - } else { - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - - subtreeTag |= child.subtreeTag; - - const effectTag = child.effectTag; - if ((effectTag & BeforeMutationMask) !== NoEffect) { - subtreeTag |= BeforeMutationSubtreeTag; - } - if ((effectTag & MutationMask) !== NoEffect) { - subtreeTag |= MutationSubtreeTag; - } - if ((effectTag & LayoutMask) !== NoEffect) { - subtreeTag |= LayoutSubtreeTag; - } - if ((effectTag & PassiveMask) !== NoEffect) { - subtreeTag |= PassiveSubtreeTag; - } - - child = child.sibling; - } - } - - completedWork.subtreeTag |= subtreeTag; - } else { - // Bubble up the earliest expiration time. - if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) { - // In profiling mode, resetChildExpirationTime is also used to reset - // profiler durations. - let treeBaseDuration = ((completedWork.selfBaseDuration: any): number); - - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - - treeBaseDuration += child.treeBaseDuration; - child = child.sibling; - } - - const isTimedOutSuspense = - completedWork.tag === SuspenseComponent && - completedWork.memoizedState !== null; - if (isTimedOutSuspense) { - // Don't count time spent in a timed out Suspense subtree as part of the base duration. - const primaryChildFragment = completedWork.child; - if (primaryChildFragment !== null) { - treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number); - } - } - - completedWork.treeBaseDuration = treeBaseDuration; - } else { - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - - child = child.sibling; - } - } - } - - completedWork.childLanes = newChildLanes; -} - function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority(