diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 2db03f39a2cb1..ad1820e121e95 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -226,7 +226,7 @@ import { markSkippedUpdateLanes, getWorkInProgressRoot, pushRenderLanes, - getWorkInProgressTransitions, + generateNewSuspenseOffscreenID, } from './ReactFiberWorkLoop.old'; import {setWorkInProgressVersion} from './ReactMutableSource.old'; import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent.old'; @@ -246,7 +246,9 @@ import { getSuspendedCache, pushTransition, getOffscreenDeferredCache, + getSuspendedTransitions, } from './ReactFiberTransition.old'; +import {pushRootPendingSuspenseBoundaries} from './ReactFiberTracingMarkerComponent.old'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; @@ -645,13 +647,14 @@ function updateOffscreenComponent( const nextState: OffscreenState = { baseLanes: NoLanes, cachePool: null, + transitions: null, }; workInProgress.memoizedState = nextState; if (enableCache) { // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } pushRenderLanes(workInProgress, renderLanes); @@ -678,6 +681,7 @@ function updateOffscreenComponent( const nextState: OffscreenState = { baseLanes: nextBaseLanes, cachePool: spawnedCachePool, + transitions: null, }; workInProgress.memoizedState = nextState; workInProgress.updateQueue = null; @@ -685,7 +689,7 @@ function updateOffscreenComponent( // push the cache pool even though we're going to bail out // because otherwise there'd be a context mismatch if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } @@ -713,6 +717,7 @@ function updateOffscreenComponent( const nextState: OffscreenState = { baseLanes: NoLanes, cachePool: null, + transitions: null, }; workInProgress.memoizedState = nextState; // Push the lanes that were skipped when we bailed out. @@ -723,7 +728,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState !== null ? prevState.cachePool : null; - pushTransition(workInProgress, prevCachePool); + pushTransition(workInProgress, prevCachePool, null); } pushRenderLanes(workInProgress, subtreeRenderLanes); @@ -736,14 +741,14 @@ function updateOffscreenComponent( subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes); - if (enableCache) { + if (enableCache || enableTransitionTracing) { // If the render that spawned this one accessed the cache pool, resume // using the same cache. Unless the parent changed, since that means // there was a refresh. const prevCachePool = prevState.cachePool; - pushTransition(workInProgress, prevCachePool); + const transitions = prevState.transitions; + pushTransition(workInProgress, prevCachePool, transitions); } - // Since we're not hidden anymore, reset the state workInProgress.memoizedState = null; } else { @@ -757,7 +762,7 @@ function updateOffscreenComponent( // using the same cache. Unless the parent changed, since that means // there was a refresh. if (current !== null) { - pushTransition(workInProgress, null); + pushTransition(workInProgress, null, null); } } } @@ -1326,9 +1331,12 @@ function updateHostRoot(current, workInProgress, renderLanes) { const root: FiberRoot = workInProgress.stateNode; + if (enableCache || enableTransitionTracing) { + pushRootTransition(root, renderLanes); + } + if (enableCache) { const nextCache: Cache = nextState.cache; - pushRootTransition(root); pushCacheProvider(workInProgress, nextCache); if (nextCache !== prevState.cache) { // The root cache refreshed. @@ -1337,7 +1345,28 @@ function updateHostRoot(current, workInProgress, renderLanes) { } if (enableTransitionTracing) { - workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); + const transitions = getSuspendedTransitions(); + const nextTransitions = []; + if (transitions !== null) { + transitions.forEach(transition => { + nextTransitions.push(transition); + }); + } + const rootTransitions = prevState.transitions; + if (rootTransitions != null) { + rootTransitions.forEach(transition => { + nextTransitions.push(transition); + }); + } + + let pendingSuspenseBoundaries = prevState.pendingSuspenseBoundaries; + if (pendingSuspenseBoundaries === null) { + pendingSuspenseBoundaries = new Map(); + } + // probably have to actually copy this + workInProgress.memoizedState.transitions = nextTransitions; + workInProgress.memoizedState.pendingSuspenseBoundaries = pendingSuspenseBoundaries; + pushRootPendingSuspenseBoundaries(workInProgress); } // Caution: React DevTools currently depends on this property @@ -1910,6 +1939,7 @@ function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState { return { baseLanes: renderLanes, cachePool: getSuspendedCache(), + transitions: getSuspendedTransitions(), }; } @@ -1941,9 +1971,29 @@ function updateSuspenseOffscreenState( cachePool = getSuspendedCache(); } } + + const transitions = []; + if (enableTransitionTracing) { + const prevTransitions = prevOffscreenState.transitions; + const newTransitions = getSuspendedTransitions(); + if (prevTransitions !== null) { + prevTransitions.forEach(transition => { + transitions.push(transition); + }); + } + if (newTransitions !== null) { + newTransitions.forEach(transition => { + if (!transitions.includes(transition)) { + transitions.push(transition); + } + }); + } + } + return { baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes), cachePool, + transitions: transitions.length > 0 ? transitions : null, }; } @@ -2274,9 +2324,17 @@ function mountSuspensePrimaryChildren( renderLanes, ) { const mode = workInProgress.mode; + const props = workInProgress.memoizedProps; + let name = null; + if (props !== null) { + name = props.name; + } + const primaryChildProps: OffscreenProps = { mode: 'visible', children: primaryChildren, + name, + id: generateNewSuspenseOffscreenID(), }; const primaryChildFragment = mountWorkInProgressOffscreenFiber( primaryChildProps, @@ -2296,10 +2354,16 @@ function mountSuspenseFallbackChildren( ) { const mode = workInProgress.mode; const progressedPrimaryFragment: Fiber | null = workInProgress.child; - + const props = workInProgress.memoizedProps; + let name = null; + if (props !== null) { + name = props.name; + } const primaryChildProps: OffscreenProps = { mode: 'hidden', children: primaryChildren, + name, + id: generateNewSuspenseOffscreenID(), }; let primaryChildFragment; @@ -2377,15 +2441,22 @@ function updateSuspensePrimaryChildren( primaryChildren, renderLanes, ) { + const name = workInProgress.pendingProps.name; const currentPrimaryChildFragment: Fiber = (current.child: any); const currentFallbackChildFragment: Fiber | null = currentPrimaryChildFragment.sibling; + const props = + currentPrimaryChildFragment.memoizedProps !== null + ? currentPrimaryChildFragment.memoizedProps + : currentPrimaryChildFragment.pendingProps; const primaryChildFragment = updateWorkInProgressOffscreenFiber( currentPrimaryChildFragment, { mode: 'visible', children: primaryChildren, + name, + id: props.id, }, ); if ((workInProgress.mode & ConcurrentMode) === NoMode) { @@ -2415,14 +2486,21 @@ function updateSuspenseFallbackChildren( fallbackChildren, renderLanes, ) { + const name = workInProgress.pendingProps.name; const mode = workInProgress.mode; const currentPrimaryChildFragment: Fiber = (current.child: any); const currentFallbackChildFragment: Fiber | null = currentPrimaryChildFragment.sibling; + const props = + currentPrimaryChildFragment.memoizedProps !== null + ? currentPrimaryChildFragment.memoizedProps + : currentPrimaryChildFragment.pendingProps; const primaryChildProps: OffscreenProps = { mode: 'hidden', children: primaryChildren, + name, + id: props.id, }; let primaryChildFragment; @@ -2581,10 +2659,13 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating( fallbackChildren, renderLanes, ) { + const name = workInProgress.pendingProps.name; const fiberMode = workInProgress.mode; const primaryChildProps: OffscreenProps = { mode: 'visible', children: primaryChildren, + name, + id: generateNewSuspenseOffscreenID(), }; const primaryChildFragment = mountWorkInProgressOffscreenFiber( primaryChildProps, @@ -3500,13 +3581,41 @@ function attemptEarlyBailoutIfNoScheduledUpdate( case HostRoot: pushHostRootContext(workInProgress); const root: FiberRoot = workInProgress.stateNode; + if (enableCache || enableTransitionTracing) { + pushRootTransition(root, renderLanes); + } + if (enableCache) { const cache: Cache = current.memoizedState.cache; pushCacheProvider(workInProgress, cache); - pushRootTransition(root); } if (enableTransitionTracing) { - workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); + const prevState = current.memoizedState; + + const nextTransitions = []; + const transitions = getSuspendedTransitions(); + if (transitions !== null) { + transitions.forEach(transition => { + nextTransitions.push(transition); + }); + } + const rootTransitions = prevState.transitions; + if (rootTransitions != null) { + rootTransitions.forEach(transition => { + if (!nextTransitions.includes(transition)) + nextTransitions.push(transition); + }); + } + + let pendingSuspenseBoundaries = prevState.pendingSuspenseBoundaries; + if (pendingSuspenseBoundaries == null) { + pendingSuspenseBoundaries = new Map(); + } + // probably have to actually copy this + workInProgress.memoizedState.transitions = nextTransitions; + workInProgress.memoizedState.pendingSuspenseBoundaries = pendingSuspenseBoundaries; + + pushRootPendingSuspenseBoundaries(workInProgress); } resetHydrationState(); break; diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js index a34de4142e4ce..dac0e843c156b 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js @@ -14,18 +14,16 @@ import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import {pushProvider, popProvider} from './ReactFiberNewContext.old'; import * as Scheduler from 'scheduler'; - +export type CacheComponentState = {| + +parent: Cache, + +cache: Cache, +|}; export type Cache = {| controller: AbortController, data: Map<() => mixed, mixed>, refCount: number, |}; -export type CacheComponentState = {| - +parent: Cache, - +cache: Cache, -|}; - export type SpawnedCachePool = {| +parent: Cache, +pool: Cache, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 23e9d6070c9a2..a728c285e4d9d 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -61,6 +61,7 @@ import { OffscreenComponent, LegacyHiddenComponent, CacheComponent, + TracingMarkerComponent, } from './ReactWorkTags'; import {detachDeletedInstance} from './ReactFiberHostConfig'; import { @@ -80,6 +81,7 @@ import { LayoutMask, PassiveMask, Visibility, + SuspenseToggle, } from './ReactFiberFlags'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; import { @@ -135,6 +137,7 @@ import { restorePendingUpdaters, addTransitionStartCallbackToPendingTransition, addTransitionCompleteCallbackToPendingTransition, + getWorkInProgressTransitions, } from './ReactFiberWorkLoop.old'; import { NoFlags as NoHookEffect, @@ -160,6 +163,7 @@ import { } from './ReactFiberDevToolsHook.old'; import {releaseCache, retainCache} from './ReactFiberCacheComponent.old'; import {clearTransitionsForLanes} from './ReactFiberLane.old'; +import {getRootPendingSuspenseBoundaries} from './ReactFiberTracingMarkerComponent.old'; let didWarnAboutUndefinedSnapshotBeforeUpdate: Set | null = null; if (__DEV__) { @@ -987,7 +991,8 @@ function commitLayoutEffectOnFiber( case IncompleteClassComponent: case ScopeComponent: case OffscreenComponent: - case LegacyHiddenComponent: { + case LegacyHiddenComponent: + case TracingMarkerComponent: { break; } @@ -1052,6 +1057,52 @@ function reappearLayoutEffectsOnFiber(node: Fiber) { } } +function addOrRemovePendingBoundariesOnRoot( + finishedWork: Fiber, + name: string | null, + id: number, +) { + let prevState: SuspenseState | null = null; + if ( + finishedWork.alternate !== null && + finishedWork.alternate.memoizedState !== null + ) { + prevState = finishedWork.alternate.memoizedState; + } + const nextState: SuspenseState | null = finishedWork.memoizedState; + + const wasHidden = prevState !== null; + const isHidden = nextState !== null; + + const rootPendingBoundaries = getRootPendingSuspenseBoundaries(); + + if (rootPendingBoundaries !== null) { + if (finishedWork.alternate === null) { + // Initial mount + if (isHidden) { + rootPendingBoundaries.set(id, { + name, + }); + } + } else { + if (wasHidden && !isHidden) { + // The suspense boundary went from hidden to visible. Remove + // the boundary from the pending suspense boundaries set + // if it's there + if (rootPendingBoundaries.has(id)) { + rootPendingBoundaries.delete(id); + } + } else if (!wasHidden && isHidden) { + // The suspense boundaries was just hidden. Add the boundary + // to the pending boundary set if it's there + rootPendingBoundaries.set(id, { + name, + }); + } + } + } +} + function hideOrUnhideAllChildren(finishedWork, isHidden) { // Only hide or unhide the top-most host nodes. let hostSubtreeRoot = null; @@ -2212,26 +2263,61 @@ function commitMutationEffectsOnFiber( const flags = finishedWork.flags; if (enableTransitionTracing) { - switch (finishedWork.tag) { - case HostRoot: { - const state = finishedWork.memoizedState; - const transitions = state.transitions; - if (transitions !== null) { - transitions.forEach(transition => { - // TODO(luna) Do we want to log TransitionStart in the startTransition callback instead? - addTransitionStartCallbackToPendingTransition({ - transitionName: transition.name, - startTime: transition.startTime, + if (flags & SuspenseToggle) { + switch (finishedWork.tag) { + case HostRoot: { + const currentTransitions = getWorkInProgressTransitions(); + if (currentTransitions != null) { + currentTransitions.forEach(transition => { + addTransitionStartCallbackToPendingTransition({ + transitionName: transition.name, + startTime: transition.startTime, + }); }); + clearTransitionsForLanes(root, lanes); + } - addTransitionCompleteCallbackToPendingTransition({ - transitionName: transition.name, - startTime: transition.startTime, + const state = finishedWork.memoizedState; + const pendingSuspenseBoundaries = state.pendingSuspenseBoundaries; + const transitions = state.transitions; + + if (pendingSuspenseBoundaries.size === 0 && transitions != null) { + transitions.forEach(transition => { + addTransitionCompleteCallbackToPendingTransition({ + transitionName: transition.name, + startTime: transition.startTime, + }); }); - }); - clearTransitionsForLanes(root, lanes); - state.transitions = null; + state.transitions = null; + } + break; + } + case OffscreenComponent: { + if (enableTransitionTracing) { + const isFallback = finishedWork.memoizedState; + let props; + if (isFallback) { + props = finishedWork.pendingProps; + } else { + props = finishedWork.memoizedProps; + } + + if (props !== null) { + if (__DEV__) { + if (typeof props.id !== 'number') { + console.error('props id needs to be a number'); + } + } + + addOrRemovePendingBoundariesOnRoot( + finishedWork, + props.name || null, + props.id, + ); + } + } + break; } } } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index bea755f7b4b6d..a109e8ec6f6af 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -81,6 +81,7 @@ import { Incomplete, ShouldCapture, ForceClientRender, + SuspenseToggle, } from './ReactFiberFlags'; import { @@ -164,7 +165,11 @@ import {createScopeInstance} from './ReactFiberScope.old'; import {transferActualDuration} from './ReactProfilerTimer.old'; import {popCacheProvider} from './ReactFiberCacheComponent.old'; import {popTreeContext} from './ReactFiberTreeContext.old'; -import {popRootTransition, popTransition} from './ReactFiberTransition.old'; +import { + popRootTransition, + popTransition, + getSuspendedTransitions, +} from './ReactFiberTransition.old'; function markUpdate(workInProgress: Fiber) { // Tag the fiber with an update effect. This turns a Placement into @@ -861,8 +866,6 @@ function completeWork( case HostRoot: { const fiberRoot = (workInProgress.stateNode: FiberRoot); if (enableCache) { - popRootTransition(fiberRoot, renderLanes); - let previousCache: Cache | null = null; if (current !== null) { previousCache = current.memoizedState.cache; @@ -899,6 +902,20 @@ function completeWork( } updateHostContainer(current, workInProgress); bubbleProperties(workInProgress); + if (enableTransitionTracing) { + const transitions = getSuspendedTransitions(); + if ( + transitions !== null || + (workInProgress.subtreeFlags & SuspenseToggle) !== NoFlags + ) { + workInProgress.flags |= SuspenseToggle; + } + } + + if (enableCache || enableTransitionTracing) { + popRootTransition(fiberRoot, renderLanes); + } + return null; } case HostComponent: { @@ -1153,6 +1170,16 @@ function completeWork( } } + if (enableTransitionTracing) { + if ( + (current === null && nextDidTimeout) || + nextDidTimeout !== prevDidTimeout + ) { + const offscreenFiber: Fiber = (workInProgress.child: any); + offscreenFiber.flags |= SuspenseToggle; + } + } + // If the suspended state of the boundary changes, we need to schedule // an effect to toggle the subtree's visibility. When we switch from // fallback -> primary, the inner Offscreen fiber schedules this effect @@ -1549,9 +1576,10 @@ function completeWork( // Run passive effects to retain/release the cache. workInProgress.flags |= Passive; } - if (current !== null) { - popTransition(workInProgress); - } + } + + if (enableCache || enableTransitionTracing) { + popTransition(workInProgress, current); } return null; diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js index 9f366c9ade886..116dd04c8792a 100644 --- a/packages/react-reconciler/src/ReactFiberLane.old.js +++ b/packages/react-reconciler/src/ReactFiberLane.old.js @@ -831,6 +831,7 @@ export function getTransitionsForLanes( const transitionsForLanes = []; while (lanes > 0) { const index = laneToIndex(lanes); + const lane = 1 << index; const transitions = root.transitionLanes[index]; if (transitions !== null) { @@ -861,13 +862,6 @@ export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) { const transitions = root.transitionLanes[index]; if (transitions !== null) { root.transitionLanes[index] = null; - } else { - if (__DEV__) { - console.error( - 'React Bug: transition lanes accessed out of bounds index: %s', - index.toString(), - ); - } } lanes &= ~lane; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 1e561e49facb3..1d3bc1ddb6f68 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -14,7 +14,10 @@ import type { } from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import type {Cache} from './ReactFiberCacheComponent.old'; -import type {Transitions} from './ReactFiberTracingMarkerComponent.old'; +import type { + Transitions, + PendingSuspenseBoundaries, +} from './ReactFiberTracingMarkerComponent.old'; import {noTimeout, supportsHydration} from './ReactFiberHostConfig'; import {createHostRootFiber} from './ReactFiber.old'; @@ -40,6 +43,7 @@ import {createCache, retainCache} from './ReactFiberCacheComponent.old'; export type RootState = { element: any, cache: Cache | null, + pendingSuspenseBoundaries: PendingSuspenseBoundaries | null, transitions: Transitions | null, }; @@ -181,6 +185,7 @@ export function createFiberRoot( element: null, cache: initialCache, transitions: null, + pendingSuspenseBoundaries: null, }; uninitializedFiber.memoizedState = initialState; } else { @@ -188,6 +193,7 @@ export function createFiberRoot( element: null, cache: null, transitions: null, + pendingSuspenseBoundaries: null, }; uninitializedFiber.memoizedState = initialState; } diff --git a/packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js b/packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js index 95fb1b8c8ab42..e5e2ff3047012 100644 --- a/packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js @@ -29,6 +29,7 @@ export type SuspenseProps = {| suspenseCallback?: (Set | null) => mixed, unstable_expectedLoadTime?: number, + name?: string, |}; // A null SuspenseState represents an unsuspended normal Suspense boundary. diff --git a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js index aad0c912c5318..8b007f05de72a 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js @@ -8,11 +8,10 @@ */ import type {TransitionTracingCallbacks} from './ReactInternalTypes'; -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type {Fiber} from './ReactInternalTypes'; import {enableTransitionTracing} from 'shared/ReactFeatureFlags'; export type SuspenseInfo = {name: string | null}; - export type TransitionObject = { transitionName: string, startTime: number, @@ -28,18 +27,23 @@ export type Transition = { startTime: number, }; +export type Transitions = Array | null; export type BatchConfigTransition = { name?: string, startTime?: number, _updatedFibers?: Set, }; -export type Transitions = Array | null; - -export type TransitionCallback = 0 | 1; +export type PendingSuspenseBoundaries = Map; +let rootPendingSuspenseBoundaries: PendingSuspenseBoundaries | null = null; +export function pushRootPendingSuspenseBoundaries(rootFiber: Fiber) { + rootPendingSuspenseBoundaries = + rootFiber.memoizedState.pendingSuspenseBoundaries; +} -export const TransitionStart = 0; -export const TransitionComplete = 1; +export function getRootPendingSuspenseBoundaries(): PendingSuspenseBoundaries | null { + return rootPendingSuspenseBoundaries; +} export function processTransitionCallbacks( pendingTransitions: PendingTransitionCallbacks, diff --git a/packages/react-reconciler/src/ReactFiberTransition.old.js b/packages/react-reconciler/src/ReactFiberTransition.old.js index 666e41d5184cc..f7b576e8fb76b 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.old.js +++ b/packages/react-reconciler/src/ReactFiberTransition.old.js @@ -10,11 +10,13 @@ import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.old'; import type {StackCursor} from './ReactFiberStack.old'; import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old'; +import type {Transitions} from './ReactFiberTracingMarkerComponent.old'; -import {enableCache} from 'shared/ReactFeatureFlags'; +import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags'; import {isPrimaryRenderer} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack.old'; import {getWorkInProgressRoot} from './ReactFiberWorkLoop.old'; +import {getWorkInProgressTransitions} from './ReactFiberWorkLoop.old'; import { createCache, retainCache, @@ -25,6 +27,9 @@ import { // used during the previous render by placing it here, on the stack. const resumedCache: StackCursor = createCursor(null); +let currentTransitions: Transitions | null = null; +const transitionStack: StackCursor = createCursor(null); + function peekCacheFromPool(): Cache | null { if (!enableCache) { return (null: any); @@ -75,25 +80,33 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache { return freshCache; } -export function pushRootTransition(root: FiberRoot) { - if (enableCache) { - return; +export function pushRootTransition(root: FiberRoot, renderLanes: Lanes) { + if (enableTransitionTracing) { + // Assuming that retries will always + // happen in the retry lane and there will never + // be transitions in the retry lane, so therefore + // this will always be an empty array + const rootTransitions = getWorkInProgressTransitions(); + if (rootTransitions != null && rootTransitions.length > 0) { + currentTransitions = rootTransitions; + } else { + currentTransitions = null; + } + + return currentTransitions; } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } export function popRootTransition(root: FiberRoot, renderLanes: Lanes) { - if (enableCache) { - return; + if (enableTransitionTracing) { + currentTransitions = null; } - // Note: This function currently does nothing but I'll leave it here for - // code organization purposes in case that changes. } export function pushTransition( offscreenWorkInProgress: Fiber, prevCachePool: SpawnedCachePool | null, + transitions: Transitions | null, ): void { if (enableCache) { if (prevCachePool === null) { @@ -102,12 +115,63 @@ export function pushTransition( push(resumedCache, prevCachePool.pool, offscreenWorkInProgress); } } + + if (enableTransitionTracing) { + // This works becuase we only make the transition object + // when the transition first starts + if (transitions !== null) { + const newTransitions = []; + if (currentTransitions !== null) { + currentTransitions.forEach(transition => { + newTransitions.push(transition); + }); + } + if (transitions !== null) { + transitions.forEach(transition => { + if (!newTransitions.includes(transition)) { + newTransitions.push(transition); + } + }); + } + + push(transitionStack, currentTransitions, offscreenWorkInProgress); + currentTransitions = newTransitions; + } + } } -export function popTransition(workInProgress: Fiber) { +export function popTransition(workInProgress: Fiber, current: Fiber | null) { if (enableCache) { pop(resumedCache, workInProgress); } + + if (enableTransitionTracing) { + let prevTransitions: Transitions | null = null; + if ( + current !== null && + current.memoizedState !== null && + current.memoizedState.transitions !== null + ) { + prevTransitions = current.memoizedState.transitions; + } + + if (prevTransitions !== null) { + currentTransitions = transitionStack.current; + pop(transitionStack, workInProgress); + } + } +} + +export function getSuspendedTransitions(): Transitions | null { + if (!enableTransitionTracing) { + return null; + } + + if (currentTransitions === null) { + return null; + } + + return currentTransitions; } export function getSuspendedCache(): SpawnedCachePool | null { diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.old.js b/packages/react-reconciler/src/ReactFiberUnwindWork.old.js index 4578134d58d05..45fb5d8ec0fb6 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.old.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.old.js @@ -32,6 +32,7 @@ import { enableSuspenseServerRenderer, enableProfilerTimer, enableCache, + enableTransitionTracing, } from 'shared/ReactFeatureFlags'; import {popHostContainer, popHostContext} from './ReactFiberHostContext.old'; @@ -79,13 +80,14 @@ function unwindWork( return null; } case HostRoot: { - if (enableCache) { + if (enableCache || enableTransitionTracing) { const root: FiberRoot = workInProgress.stateNode; popRootTransition(root, renderLanes); const cache: Cache = workInProgress.memoizedState.cache; popCacheProvider(workInProgress, cache); } + popHostContainer(workInProgress); popTopLevelLegacyContextObject(workInProgress); resetMutableSourceWorkInProgressVersions(); @@ -153,11 +155,10 @@ function unwindWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(workInProgress); - if (enableCache) { - if (current !== null) { - popTransition(workInProgress); - } + if (enableTransitionTracing || enableCache) { + popTransition(workInProgress, current); } + return null; case CacheComponent: if (enableCache) { @@ -189,7 +190,7 @@ function unwindInterruptedWork( break; } case HostRoot: { - if (enableCache) { + if (enableCache || enableTransitionTracing) { const root: FiberRoot = interruptedWork.stateNode; popRootTransition(root, renderLanes); @@ -221,12 +222,9 @@ function unwindInterruptedWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(interruptedWork); - if (enableCache) { - if (current !== null) { - popTransition(interruptedWork); - } + if (enableCache || enableTransitionTracing) { + popTransition(interruptedWork, current); } - break; case CacheComponent: if (enableCache) { diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index d8bb6b16e29fb..87d99e570ae5a 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -368,6 +368,12 @@ export function addTransitionCompleteCallbackToPendingTransition( } } +let suspenseOffscreenID: number = 0; + +export function generateNewSuspenseOffscreenID(): number { + return suspenseOffscreenID++; +} + function resetRenderTimer() { workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS; }