From 4e5f77f53e51ed8b5d9ee4db86834e693e2e4168 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 15 Sep 2020 17:12:48 -0400 Subject: [PATCH] Moved resetChildLanes into complete work (#19836) This allows us to inline a few checks that are specific to a certain tag-type. --- .../src/ReactFiberCompleteWork.new.js | 202 +++++++++++++++++- .../src/ReactFiberWorkLoop.new.js | 152 +------------ 2 files changed, 198 insertions(+), 156 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 3cb08b48eea5e..b0e74e84ead40 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,7 +58,12 @@ import { OffscreenComponent, LegacyHiddenComponent, } from './ReactWorkTags'; -import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode'; +import { + NoMode, + BlockingMode, + ConcurrentMode, + ProfileMode, +} from './ReactTypeOfMode'; import { Ref, Update, @@ -66,6 +71,7 @@ import { DidCapture, Snapshot, MutationMask, + StaticMask, } from './ReactFiberFlags'; import invariant from 'shared/invariant'; @@ -137,9 +143,16 @@ import { renderHasNotSuspendedYet, popRenderLanes, getRenderTargetTime, + subtreeRenderLanes, } from './ReactFiberWorkLoop.new'; import {createFundamentalStateInstance} from './ReactFiberFundamental.new'; -import {OffscreenLane, SomeRetryLane} from './ReactFiberLane'; +import { + OffscreenLane, + SomeRetryLane, + NoLanes, + includesSomeLane, + mergeLanes, +} from './ReactFiberLane'; import {resetChildFibers} from './ReactChildFiber.new'; import {createScopeInstance} from './ReactFiberScope.new'; import {transferActualDuration} from './ReactProfilerTimer.new'; @@ -668,6 +681,114 @@ function cutOffTailIfNeeded( } } +function bubbleProperties(completedWork: Fiber) { + const didBailout = + completedWork.alternate !== null && + completedWork.alternate.child === completedWork.child; + + let newChildLanes = NoLanes; + let subtreeFlags = NoFlags; + + 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), + ); + + subtreeFlags |= child.subtreeFlags; + subtreeFlags |= child.flags; + + // 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), + ); + + subtreeFlags |= child.subtreeFlags; + subtreeFlags |= child.flags; + + child = child.sibling; + } + } + + completedWork.subtreeFlags |= subtreeFlags; + } 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), + ); + + // "Static" flags share the lifetime of the fiber/hook they belong to, + // so we should bubble those up even during a bailout. All the other + // flags have a lifetime only of a single render + commit, so we should + // ignore them. + subtreeFlags |= child.subtreeFlags & StaticMask; + subtreeFlags |= child.flags & StaticMask; + + 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), + ); + + // "Static" flags share the lifetime of the fiber/hook they belong to, + // so we should bubble those up even during a bailout. All the other + // flags have a lifetime only of a single render + commit, so we should + // ignore them. + subtreeFlags |= child.subtreeFlags & StaticMask; + subtreeFlags |= child.flags & StaticMask; + + child = child.sibling; + } + } + + completedWork.subtreeFlags |= subtreeFlags; + } + + completedWork.childLanes = newChildLanes; +} + function completeWork( current: Fiber | null, workInProgress: Fiber, @@ -686,12 +807,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: { @@ -720,6 +843,7 @@ function completeWork( } } updateHostContainer(current, workInProgress); + bubbleProperties(workInProgress); return null; } case HostComponent: { @@ -746,6 +870,7 @@ function completeWork( 'caused by a bug in React. Please file an issue.', ); // This can happen when we abort work. + bubbleProperties(workInProgress); return null; } @@ -803,6 +928,7 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } case HostText: { @@ -837,6 +963,7 @@ function completeWork( ); } } + bubbleProperties(workInProgress); return null; } case SuspenseComponent: { @@ -856,6 +983,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. @@ -872,6 +1013,20 @@ function completeWork( // If something suspended, schedule an effect to attach retry listeners. // So we might as well always mark this. workInProgress.flags |= 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; } } @@ -887,6 +1042,7 @@ function completeWork( ) { transferActualDuration(workInProgress); } + // Don't bubble properties in this case. return workInProgress; } @@ -964,6 +1120,19 @@ function completeWork( // Always notify the callback workInProgress.flags |= Update; } + bubbleProperties(workInProgress); + if (enableProfilerTimer) { + if ((workInProgress.mode & ProfileMode) !== NoMode) { + if (nextDidTimeout) { + // 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: @@ -972,10 +1141,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 @@ -984,6 +1155,7 @@ function completeWork( if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } + bubbleProperties(workInProgress); return null; } case SuspenseListComponent: { @@ -995,6 +1167,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; } @@ -1060,6 +1233,7 @@ function completeWork( ForceSuspenseFallback, ), ); + // Don't bubble properties in this case. return workInProgress.child; } row = row.sibling; @@ -1117,6 +1291,7 @@ function completeWork( !getIsHydrating() // We don't cut it if we're hydrating. ) { // We're done. + bubbleProperties(workInProgress); return null; } } else if ( @@ -1188,8 +1363,10 @@ function completeWork( } pushSuspenseContext(workInProgress, suspenseContext); // Do a pass over the next row. + // Don't bubble properties in this case. return next; } + bubbleProperties(workInProgress); return null; } case FundamentalComponent: { @@ -1217,6 +1394,7 @@ function completeWork( ): any): Instance); fundamentalInstance.instance = instance; if (fundamentalImpl.reconcileChildren === false) { + bubbleProperties(workInProgress); return null; } appendAllChildren(instance, workInProgress, false, false); @@ -1239,6 +1417,7 @@ function completeWork( markUpdate(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1261,24 +1440,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' @@ -1286,6 +1468,16 @@ function completeWork( workInProgress.flags |= Update; } } + + // Don't bubble properties for hidden children. + if ( + !nextIsHidden || + includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane)) || + (workInProgress.mode & ConcurrentMode) === NoMode + ) { + bubbleProperties(workInProgress); + } + return null; } } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 0f8766ce4ad43..361824de35e9e 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -115,8 +115,6 @@ import { MemoComponent, SimpleMemoComponent, Block, - OffscreenComponent, - LegacyHiddenComponent, ScopeComponent, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; @@ -139,7 +137,6 @@ import { MutationMask, LayoutMask, PassiveMask, - StaticMask, } from './ReactFiberFlags'; import { NoLanePriority, @@ -151,7 +148,6 @@ import { NoLane, SyncLane, SyncBatchedLane, - OffscreenLane, NoTimestamp, findUpdateLane, findTransitionLane, @@ -290,7 +286,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. @@ -1719,8 +1715,6 @@ function completeUnitOfWork(unitOfWork: Fiber): void { workInProgress = next; return; } - - resetChildLanes(completedWork); } else { // This fiber did not complete because something threw. Pop values off // the stack without entering the complete phase. If this is a boundary, @@ -1782,150 +1776,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 subtreeFlags = NoFlags; - - 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), - ); - - subtreeFlags |= child.subtreeFlags; - subtreeFlags |= child.flags; - - // 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), - ); - - subtreeFlags |= child.subtreeFlags; - subtreeFlags |= child.flags; - - child = child.sibling; - } - } - - completedWork.subtreeFlags |= subtreeFlags; - } 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), - ); - - // "Static" flags share the lifetime of the fiber/hook they belong to, - // so we should bubble those up even during a bailout. All the other - // flags have a lifetime only of a single render + commit, so we should - // ignore them. - subtreeFlags |= child.subtreeFlags & StaticMask; - subtreeFlags |= child.flags & StaticMask; - - 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), - ); - - // "Static" flags share the lifetime of the fiber/hook they belong to, - // so we should bubble those up even during a bailout. All the other - // flags have a lifetime only of a single render + commit, so we should - // ignore them. - subtreeFlags |= child.subtreeFlags & StaticMask; - subtreeFlags |= child.flags & StaticMask; - - child = child.sibling; - } - } - - completedWork.subtreeFlags |= subtreeFlags; - } - - completedWork.childLanes = newChildLanes; -} - function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority(