From b66ae09b6e9feba21aaea248da0e0c870193b597 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 17 Nov 2020 13:51:57 -0600 Subject: [PATCH] Track subtreeFlags et al with bubbleProperties Original PR: #19836 --- .../react-reconciler/src/ReactFiber.new.js | 5 +- .../react-reconciler/src/ReactFiber.old.js | 5 +- .../src/ReactFiberBeginWork.new.js | 7 + .../src/ReactFiberBeginWork.old.js | 7 + .../src/ReactFiberCompleteWork.new.js | 223 +++++++++++++++++- .../src/ReactFiberCompleteWork.old.js | 223 +++++++++++++++++- .../src/ReactFiberWorkLoop.new.js | 83 +------ .../src/ReactFiberWorkLoop.old.js | 83 +------ 8 files changed, 458 insertions(+), 178 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index edeae5caa6feb..2581831470001 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -28,7 +28,7 @@ import { enableFundamentalAPI, enableScopeAPI, } from 'shared/ReactFeatureFlags'; -import {NoFlags, Placement} from './ReactFiberFlags'; +import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot, BlockingRoot} from './ReactRootTags'; import { IndeterminateComponent, @@ -299,6 +299,9 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { } } + // Reset all effects except static ones. + // Static effects are not specific to a render. + workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 8e3df44ac676f..cd5a3a3649dfb 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -28,7 +28,7 @@ import { enableFundamentalAPI, enableScopeAPI, } from 'shared/ReactFeatureFlags'; -import {NoFlags, Placement} from './ReactFiberFlags'; +import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot, BlockingRoot} from './ReactRootTags'; import { IndeterminateComponent, @@ -299,6 +299,9 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { } } + // Reset all effects except static ones. + // Static effects are not specific to a render. + workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 3ae9945a5707f..3ae2541bf061a 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -63,6 +63,7 @@ import { Deletion, ChildDeletion, ForceUpdateForLegacySuspense, + StaticMask, } from './ReactFiberFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { @@ -2092,6 +2093,12 @@ function updateSuspenseFallbackChildren( currentPrimaryChildFragment, primaryChildProps, ); + + // Since we're reusing a current tree, we need to reuse the flags, too. + // (We don't do this in legacy mode, because in legacy mode we don't re-use + // the current tree; see previous branch.) + primaryChildFragment.subtreeFlags = + currentPrimaryChildFragment.subtreeFlags & StaticMask; } let fallbackChildFragment; if (currentFallbackChildFragment !== null) { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 7cd86c488c044..ff68570676ed7 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -63,6 +63,7 @@ import { Deletion, ChildDeletion, ForceUpdateForLegacySuspense, + StaticMask, } from './ReactFiberFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { @@ -2092,6 +2093,12 @@ function updateSuspenseFallbackChildren( currentPrimaryChildFragment, primaryChildProps, ); + + // Since we're reusing a current tree, we need to reuse the flags, too. + // (We don't do this in legacy mode, because in legacy mode we don't re-use + // the current tree; see previous branch.) + primaryChildFragment.subtreeFlags = + currentPrimaryChildFragment.subtreeFlags & StaticMask; } let fallbackChildFragment; if (currentFallbackChildFragment !== null) { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 9d91de82073c6..53a328121051f 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.new'; +import type {Lanes, Lane} from './ReactFiberLane.new'; import type { ReactFundamentalComponentInstance, ReactScopeInstance, @@ -57,8 +57,20 @@ import { OffscreenComponent, LegacyHiddenComponent, } from './ReactWorkTags'; -import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode'; -import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags'; +import { + NoMode, + BlockingMode, + ConcurrentMode, + ProfileMode, +} from './ReactTypeOfMode'; +import { + Ref, + Update, + NoFlags, + DidCapture, + Snapshot, + StaticMask, +} from './ReactFiberFlags'; import invariant from 'shared/invariant'; import { @@ -128,9 +140,16 @@ import { renderHasNotSuspendedYet, popRenderLanes, getRenderTargetTime, + subtreeRenderLanes, } from './ReactFiberWorkLoop.new'; import {createFundamentalStateInstance} from './ReactFiberFundamental.new'; -import {OffscreenLane, SomeRetryLane} from './ReactFiberLane.new'; +import { + OffscreenLane, + SomeRetryLane, + NoLanes, + includesSomeLane, + mergeLanes, +} from './ReactFiberLane.new'; import {resetChildFibers} from './ReactChildFiber.new'; import {createScopeInstance} from './ReactFiberScope.new'; import {transferActualDuration} from './ReactProfilerTimer.new'; @@ -640,6 +659,126 @@ 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; + + // Update the return pointer so the tree is consistent. This is a code + // smell because it assumes the commit phase is never concurrent with + // the render phase. Will address during refactor to alternate model. + child.return = completedWork; + + 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; + + // Update the return pointer so the tree is consistent. This is a code + // smell because it assumes the commit phase is never concurrent with + // the render phase. Will address during refactor to alternate model. + child.return = completedWork; + + child = child.sibling; + } + } + + completedWork.subtreeFlags |= subtreeFlags; + } + + completedWork.childLanes = newChildLanes; + + return didBailout; +} + function completeWork( current: Fiber | null, workInProgress: Fiber, @@ -658,12 +797,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: { @@ -692,6 +833,7 @@ function completeWork( } } updateHostContainer(workInProgress); + bubbleProperties(workInProgress); return null; } case HostComponent: { @@ -718,6 +860,7 @@ function completeWork( 'caused by a bug in React. Please file an issue.', ); // This can happen when we abort work. + bubbleProperties(workInProgress); return null; } @@ -775,6 +918,7 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } case HostText: { @@ -809,6 +953,7 @@ function completeWork( ); } } + bubbleProperties(workInProgress); return null; } case SuspenseComponent: { @@ -828,6 +973,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. @@ -844,6 +1003,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; } } @@ -859,6 +1032,7 @@ function completeWork( ) { transferActualDuration(workInProgress); } + // Don't bubble properties in this case. return workInProgress; } @@ -936,6 +1110,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: @@ -944,10 +1131,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 @@ -956,6 +1145,7 @@ function completeWork( if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } + bubbleProperties(workInProgress); return null; } case SuspenseListComponent: { @@ -967,6 +1157,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; } @@ -1025,6 +1216,7 @@ function completeWork( } workInProgress.lastEffect = renderState.lastEffect; // Reset the child fibers to their original state. + workInProgress.subtreeFlags = NoFlags; resetChildFibers(workInProgress, renderLanes); // Set up the Suspense Context to force suspense and immediately @@ -1036,6 +1228,7 @@ function completeWork( ForceSuspenseFallback, ), ); + // Don't bubble properties in this case. return workInProgress.child; } row = row.sibling; @@ -1102,6 +1295,7 @@ function completeWork( lastEffect.nextEffect = null; } // We're done. + bubbleProperties(workInProgress); return null; } } else if ( @@ -1177,8 +1371,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: { @@ -1206,6 +1402,7 @@ function completeWork( ): any): Instance); fundamentalInstance.instance = instance; if (fundamentalImpl.reconcileChildren === false) { + bubbleProperties(workInProgress); return null; } appendAllChildren(instance, workInProgress, false, false); @@ -1228,6 +1425,7 @@ function completeWork( markUpdate(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1250,6 +1448,7 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1257,12 +1456,12 @@ function completeWork( 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' @@ -1270,6 +1469,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/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 6f1942f7b90bb..8f0e68353d90c 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -8,7 +8,7 @@ */ import type {Fiber} from './ReactInternalTypes'; -import type {Lanes} from './ReactFiberLane.old'; +import type {Lanes, Lane} from './ReactFiberLane.old'; import type { ReactFundamentalComponentInstance, ReactScopeInstance, @@ -57,8 +57,20 @@ import { OffscreenComponent, LegacyHiddenComponent, } from './ReactWorkTags'; -import {NoMode, BlockingMode, ProfileMode} from './ReactTypeOfMode'; -import {Ref, Update, NoFlags, DidCapture, Snapshot} from './ReactFiberFlags'; +import { + NoMode, + BlockingMode, + ConcurrentMode, + ProfileMode, +} from './ReactTypeOfMode'; +import { + Ref, + Update, + NoFlags, + DidCapture, + Snapshot, + StaticMask, +} from './ReactFiberFlags'; import invariant from 'shared/invariant'; import { @@ -128,9 +140,16 @@ import { renderHasNotSuspendedYet, popRenderLanes, getRenderTargetTime, + subtreeRenderLanes, } from './ReactFiberWorkLoop.old'; import {createFundamentalStateInstance} from './ReactFiberFundamental.old'; -import {OffscreenLane, SomeRetryLane} from './ReactFiberLane.old'; +import { + OffscreenLane, + SomeRetryLane, + NoLanes, + includesSomeLane, + mergeLanes, +} from './ReactFiberLane.old'; import {resetChildFibers} from './ReactChildFiber.old'; import {createScopeInstance} from './ReactFiberScope.old'; import {transferActualDuration} from './ReactProfilerTimer.old'; @@ -640,6 +659,126 @@ 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; + + // Update the return pointer so the tree is consistent. This is a code + // smell because it assumes the commit phase is never concurrent with + // the render phase. Will address during refactor to alternate model. + child.return = completedWork; + + 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; + + // Update the return pointer so the tree is consistent. This is a code + // smell because it assumes the commit phase is never concurrent with + // the render phase. Will address during refactor to alternate model. + child.return = completedWork; + + child = child.sibling; + } + } + + completedWork.subtreeFlags |= subtreeFlags; + } + + completedWork.childLanes = newChildLanes; + + return didBailout; +} + function completeWork( current: Fiber | null, workInProgress: Fiber, @@ -658,12 +797,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: { @@ -692,6 +833,7 @@ function completeWork( } } updateHostContainer(workInProgress); + bubbleProperties(workInProgress); return null; } case HostComponent: { @@ -718,6 +860,7 @@ function completeWork( 'caused by a bug in React. Please file an issue.', ); // This can happen when we abort work. + bubbleProperties(workInProgress); return null; } @@ -775,6 +918,7 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } case HostText: { @@ -809,6 +953,7 @@ function completeWork( ); } } + bubbleProperties(workInProgress); return null; } case SuspenseComponent: { @@ -828,6 +973,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. @@ -844,6 +1003,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; } } @@ -859,6 +1032,7 @@ function completeWork( ) { transferActualDuration(workInProgress); } + // Don't bubble properties in this case. return workInProgress; } @@ -936,6 +1110,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: @@ -944,10 +1131,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 @@ -956,6 +1145,7 @@ function completeWork( if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } + bubbleProperties(workInProgress); return null; } case SuspenseListComponent: { @@ -967,6 +1157,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; } @@ -1025,6 +1216,7 @@ function completeWork( } workInProgress.lastEffect = renderState.lastEffect; // Reset the child fibers to their original state. + workInProgress.subtreeFlags = NoFlags; resetChildFibers(workInProgress, renderLanes); // Set up the Suspense Context to force suspense and immediately @@ -1036,6 +1228,7 @@ function completeWork( ForceSuspenseFallback, ), ); + // Don't bubble properties in this case. return workInProgress.child; } row = row.sibling; @@ -1102,6 +1295,7 @@ function completeWork( lastEffect.nextEffect = null; } // We're done. + bubbleProperties(workInProgress); return null; } } else if ( @@ -1177,8 +1371,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: { @@ -1206,6 +1402,7 @@ function completeWork( ): any): Instance); fundamentalInstance.instance = instance; if (fundamentalImpl.reconcileChildren === false) { + bubbleProperties(workInProgress); return null; } appendAllChildren(instance, workInProgress, false, false); @@ -1228,6 +1425,7 @@ function completeWork( markUpdate(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1250,6 +1448,7 @@ function completeWork( markRef(workInProgress); } } + bubbleProperties(workInProgress); return null; } break; @@ -1257,12 +1456,12 @@ function completeWork( 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' @@ -1270,6 +1469,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 2db6d5e7554cf..52631ef726632 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -110,8 +110,6 @@ import { ForwardRef, MemoComponent, SimpleMemoComponent, - OffscreenComponent, - LegacyHiddenComponent, ScopeComponent, Profiler, } from './ReactWorkTags'; @@ -144,7 +142,6 @@ import { NoLane, SyncLane, SyncBatchedLane, - OffscreenLane, NoTimestamp, findUpdateLane, findTransitionLane, @@ -285,7 +282,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. @@ -1742,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 @@ -1821,6 +1816,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void { // Mark the parent fiber as incomplete and clear its effect list. returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.flags |= Incomplete; + returnFiber.subtreeFlags = NoFlags; returnFiber.deletions = null; } } @@ -1843,81 +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; - } - - let newChildLanes = NoLanes; - - // 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); - - // 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. - const shouldBubbleActualDurations = - completedWork.alternate === null || - completedWork.child !== completedWork.alternate.child; - - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - if (shouldBubbleActualDurations) { - 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), - ); - child = child.sibling; - } - } - - completedWork.childLanes = newChildLanes; -} - function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority( diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 72c580dcf48d2..6e97ab4a33514 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -110,8 +110,6 @@ import { ForwardRef, MemoComponent, SimpleMemoComponent, - OffscreenComponent, - LegacyHiddenComponent, ScopeComponent, Profiler, } from './ReactWorkTags'; @@ -144,7 +142,6 @@ import { NoLane, SyncLane, SyncBatchedLane, - OffscreenLane, NoTimestamp, findUpdateLane, findTransitionLane, @@ -285,7 +282,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. @@ -1742,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 @@ -1821,6 +1816,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void { // Mark the parent fiber as incomplete and clear its effect list. returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.flags |= Incomplete; + returnFiber.subtreeFlags = NoFlags; returnFiber.deletions = null; } } @@ -1843,81 +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; - } - - let newChildLanes = NoLanes; - - // 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); - - // 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. - const shouldBubbleActualDurations = - completedWork.alternate === null || - completedWork.child !== completedWork.alternate.child; - - let child = completedWork.child; - while (child !== null) { - newChildLanes = mergeLanes( - newChildLanes, - mergeLanes(child.lanes, child.childLanes), - ); - if (shouldBubbleActualDurations) { - 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), - ); - child = child.sibling; - } - } - - completedWork.childLanes = newChildLanes; -} - function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority(