From f5605145245a11da0b3812e13981531c14ce8674 Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 5 Apr 2022 14:47:21 -0500 Subject: [PATCH] old --- .../src/ReactFiberBeginWork.old.js | 84 ++++-- .../src/ReactFiberCacheComponent.old.js | 2 +- .../src/ReactFiberCommitWork.old.js | 250 +++++++++++++++--- .../src/ReactFiberCompleteWork.old.js | 38 ++- .../src/ReactFiberHotReloading.old.js | 4 +- .../src/ReactFiberLane.old.js | 8 +- .../src/ReactFiberRoot.old.js | 10 +- .../src/ReactFiberSuspenseComponent.old.js | 1 + .../ReactFiberTracingMarkerComponent.old.js | 10 +- .../src/ReactFiberTransition.old.js | 82 ++++-- .../src/ReactFiberUnwindWork.old.js | 29 +- .../src/ReactFiberWorkLoop.old.js | 121 ++++++--- 12 files changed, 493 insertions(+), 146 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 57f595da16bbf..dc960b81207de 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -237,7 +237,6 @@ import { markSkippedUpdateLanes, getWorkInProgressRoot, pushRenderLanes, - getWorkInProgressTransitions, } from './ReactFiberWorkLoop.old'; import {setWorkInProgressVersion} from './ReactMutableSource.old'; import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent.old'; @@ -257,6 +256,7 @@ import { getSuspendedCache, pushTransition, getOffscreenDeferredCache, + getSuspendedTransitions, } from './ReactFiberTransition.old'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; @@ -653,16 +653,18 @@ function updateOffscreenComponent( // Rendering a hidden tree. if ((workInProgress.mode & ConcurrentMode) === NoMode) { // In legacy sync mode, don't defer the subtree. Render it now. + // TODO: Consider how Offscreen should work with transitions in the future 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); @@ -689,6 +691,7 @@ function updateOffscreenComponent( const nextState: OffscreenState = { baseLanes: nextBaseLanes, cachePool: spawnedCachePool, + transitions: null, }; workInProgress.memoizedState = nextState; workInProgress.updateQueue = null; @@ -696,7 +699,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); } } @@ -724,6 +727,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. @@ -734,7 +738,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); @@ -747,14 +751,21 @@ function updateOffscreenComponent( subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes); + let prevCachePool = null; if (enableCache) { // 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); + prevCachePool = prevState.cachePool; } + let transitions = null; + if (enableTransitionTracing) { + transitions = prevState.transitions; + } + + pushTransition(workInProgress, prevCachePool, transitions); + // Since we're not hidden anymore, reset the state workInProgress.memoizedState = null; } else { @@ -768,7 +779,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,15 +1337,17 @@ function updateHostRoot(current, workInProgress, renderLanes) { const nextProps = workInProgress.pendingProps; const prevState = workInProgress.memoizedState; const prevChildren = prevState.element; + cloneUpdateQueue(current, workInProgress); processUpdateQueue(workInProgress, nextProps, null, renderLanes); const nextState: RootState = workInProgress.memoizedState; const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); + if (enableCache) { - const nextCache: Cache = nextState.cache; - pushRootTransition(root); + const nextCache: Cache = workInProgress.memoizedState.cache; pushCacheProvider(workInProgress, nextCache); if (nextCache !== prevState.cache) { // The root cache refreshed. @@ -1342,12 +1355,7 @@ function updateHostRoot(current, workInProgress, renderLanes) { } } - if (enableTransitionTracing) { - // FIXME: Slipped past code review. This is not a safe mutation: - // workInProgress.memoizedState is a shared object. Need to fix before - // rolling out the Transition Tracing experiment. - workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); - } + const pendingSuspenseBoundaries = nextState.pendingSuspenseBoundaries || null; // Caution: React DevTools currently depends on this property // being called "element". @@ -1362,6 +1370,7 @@ function updateHostRoot(current, workInProgress, renderLanes) { element: nextChildren, isDehydrated: false, cache: nextState.cache, + pendingSuspenseBoundaries, transitions: nextState.transitions, }; const updateQueue: UpdateQueue = (workInProgress.updateQueue: any); @@ -1435,6 +1444,20 @@ function updateHostRoot(current, workInProgress, renderLanes) { } } } else { + if (enableTransitionTracing) { + if (pendingSuspenseBoundaries !== nextState.pendingSuspenseBoundaries) { + const overrideState: RootState = { + element: nextChildren, + isDehydrated: nextState.isDehydrated, + cache: nextState.cache, + pendingSuspenseBoundaries, + transitions: nextState.transitions, + }; + + workInProgress.memoizedState = overrideState; + } + } + // Root is not dehydrated. Either this is a client-only root, or it // already hydrated. resetHydrationState(); @@ -1979,6 +2002,7 @@ function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState { return { baseLanes: renderLanes, cachePool: getSuspendedCache(), + transitions: getSuspendedTransitions(), }; } @@ -2010,9 +2034,11 @@ function updateSuspenseOffscreenState( cachePool = getSuspendedCache(); } } + return { baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes), cachePool, + transitions: prevOffscreenState.transitions, }; } @@ -2103,6 +2129,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { pushSuspenseContext(workInProgress, suspenseContext); + const root = getWorkInProgressRoot(); + // OK, the next part is confusing. We're about to reconcile the Suspense // boundary's children. This involves some custom reconciliation logic. Two // main reasons this is so complicated. @@ -2143,7 +2171,6 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { } } } - const nextPrimaryChildren = nextProps.children; const nextFallbackChildren = nextProps.fallback; @@ -2159,6 +2186,18 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { renderLanes, ); workInProgress.memoizedState = SUSPENDED_MARKER; + if (enableTransitionTracing) { + const currentTransitions = getSuspendedTransitions(); + if (currentTransitions !== null) { + const primaryChildUpdateQueue: OffscreenQueue = { + transitions: currentTransitions, + rootMemoizedState: + root === null ? null : root.current.memoizedState, + }; + primaryChildFragment.updateQueue = primaryChildUpdateQueue; + } + } + return fallbackFragment; } else if ( enableCPUSuspense && @@ -2285,6 +2324,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { if (currentTransitions !== null) { const primaryChildUpdateQueue: OffscreenQueue = { transitions: currentTransitions, + rootMemoizedState: + root === null ? null : root.current.memoizedState, }; primaryChildFragment.updateQueue = primaryChildUpdateQueue; } @@ -2336,6 +2377,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { if (currentTransitions !== null) { const primaryChildUpdateQueue: OffscreenQueue = { transitions: currentTransitions, + rootMemoizedState: + root === null ? null : root.current.memoizedState, }; primaryChildFragment.updateQueue = primaryChildUpdateQueue; } @@ -2368,6 +2411,7 @@ function mountSuspensePrimaryChildren( renderLanes, ) { const mode = workInProgress.mode; + const primaryChildProps: OffscreenProps = { mode: 'visible', children: primaryChildren, @@ -2390,7 +2434,6 @@ function mountSuspenseFallbackChildren( ) { const mode = workInProgress.mode; const progressedPrimaryFragment: Fiber | null = workInProgress.child; - const primaryChildProps: OffscreenProps = { mode: 'hidden', children: primaryChildren, @@ -3594,14 +3637,13 @@ function attemptEarlyBailoutIfNoScheduledUpdate( case HostRoot: pushHostRootContext(workInProgress); const root: FiberRoot = workInProgress.stateNode; + pushRootTransition(workInProgress, root, renderLanes); + if (enableCache) { const cache: Cache = current.memoizedState.cache; pushCacheProvider(workInProgress, cache); - pushRootTransition(root); - } - if (enableTransitionTracing) { - workInProgress.memoizedState.transitions = getWorkInProgressTransitions(); } + resetHydrationState(); break; case HostComponent: diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js index e530619d41664..118ce6306b53c 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js @@ -13,8 +13,8 @@ import {enableCache} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import {pushProvider, popProvider} from './ReactFiberNewContext.old'; -import * as Scheduler from 'scheduler'; +import * as Scheduler from 'scheduler'; export type Cache = {| controller: AbortController, data: Map<() => mixed, mixed>, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index e962039d9ca9c..e9b01bbb3c8ad 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -22,10 +22,17 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.old'; import type {UpdateQueue} from './ReactUpdateQueue.old'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old'; import type {Wakeable} from 'shared/ReactTypes'; -import type {OffscreenState} from './ReactFiberOffscreenComponent'; +import type { + OffscreenState, + OffscreenInstance, +} from './ReactFiberOffscreenComponent'; import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.old'; import type {RootState} from './ReactFiberRoot.old'; +import type { + Transition, + PendingSuspenseBoundaries, +} from './ReactFiberTracingMarkerComponent.old'; import { enableCreateEventHandleAPI, @@ -62,6 +69,7 @@ import { OffscreenComponent, LegacyHiddenComponent, CacheComponent, + TracingMarkerComponent, } from './ReactWorkTags'; import {detachDeletedInstance} from './ReactFiberHostConfig'; import { @@ -81,6 +89,7 @@ import { LayoutMask, PassiveMask, Visibility, + SuspenseToggle, } from './ReactFiberFlags'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; import { @@ -988,7 +997,8 @@ function commitLayoutEffectOnFiber( case IncompleteClassComponent: case ScopeComponent: case OffscreenComponent: - case LegacyHiddenComponent: { + case LegacyHiddenComponent: + case TracingMarkerComponent: { break; } @@ -1053,6 +1063,62 @@ function reappearLayoutEffectsOnFiber(node: Fiber) { } } +function addOrRemovePendingBoundariesOnRoot( + finishedWork: Fiber, + name: string | null, + offscreenInstance: OffscreenInstance, + rootPendingBoundaries: PendingSuspenseBoundaries | null, +) { + // This function adds suspense boundaries to the root + // or tracing marker that are a part of their subtrees + // when a suspense boundary goes from a resolved to a fallback + // state we add the boundary, and when it goes from a fallback + // to a resolved state, we remove the fallback. + + // We use the state node as an ID because we need a + // unique identifier so that we can add and remove the correct + // boundaries, and we can't use the fiber itself because + // there is the current and workInProgressFibers + + 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; + + if (rootPendingBoundaries !== null) { + if (finishedWork.alternate === null) { + // Initial mount + if (isHidden) { + rootPendingBoundaries.set(offscreenInstance, { + 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(offscreenInstance)) { + rootPendingBoundaries.delete(offscreenInstance); + } + } else if (!wasHidden && isHidden) { + // The suspense boundaries was just hidden. Add the boundary + // to the pending boundary set if it's there + rootPendingBoundaries.set(offscreenInstance, { + name, + }); + } + } + } +} + function hideOrUnhideAllChildren(finishedWork, isHidden) { // Only hide or unhide the top-most host nodes. let hostSubtreeRoot = null; @@ -2206,32 +2272,6 @@ function commitMutationEffectsOnFiber( // because of the shared reconciliation logic below. 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, - }); - - addTransitionCompleteCallbackToPendingTransition({ - transitionName: transition.name, - startTime: transition.startTime, - }); - }); - - clearTransitionsForLanes(root, lanes); - state.transitions = null; - } - } - } - } - if (flags & ContentReset) { commitResetTextContent(finishedWork); } @@ -2618,12 +2658,24 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) { export function commitPassiveMountEffects( root: FiberRoot, finishedWork: Fiber, + committedLanes: Lanes, + transitions: Array | null, ): void { nextEffect = finishedWork; - commitPassiveMountEffects_begin(finishedWork, root); + commitPassiveMountEffects_begin( + finishedWork, + root, + committedLanes, + transitions, + ); } -function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { +function commitPassiveMountEffects_begin( + subtreeRoot: Fiber, + root: FiberRoot, + committedLanes: Lanes, + currentTransitions: Array | null, +) { while (nextEffect !== null) { const fiber = nextEffect; const firstChild = fiber.child; @@ -2631,7 +2683,12 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { ensureCorrectReturnPointer(firstChild, fiber); nextEffect = firstChild; } else { - commitPassiveMountEffects_complete(subtreeRoot, root); + commitPassiveMountEffects_complete( + subtreeRoot, + root, + committedLanes, + currentTransitions, + ); } } } @@ -2639,9 +2696,140 @@ function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { function commitPassiveMountEffects_complete( subtreeRoot: Fiber, root: FiberRoot, + committedLanes: Lanes, + currentTransitions: Array | null, ) { while (nextEffect !== null) { const fiber = nextEffect; + + if (enableTransitionTracing) { + if (fiber.flags & SuspenseToggle) { + switch (fiber.tag) { + case HostRoot: { + // Get the transitions that were initiatized during the render + // and add a start transition callback for each of them + if (currentTransitions != null) { + if (fiber.memoizedState.transitions === null) { + fiber.memoizedState.transitions = []; + } + const newTransitions = []; + for (let i = 0; i < currentTransitions.length; i++) { + const transition = currentTransitions[i]; + addTransitionStartCallbackToPendingTransition({ + transitionName: transition.name, + startTime: transition.startTime, + }); + + newTransitions.push(transition); + } + fiber.memoizedState.transitions.push(newTransitions); + + clearTransitionsForLanes(root, committedLanes); + } + + const state = fiber.memoizedState; + const pendingSuspenseBoundaries = state.pendingSuspenseBoundaries; + const lazyTransitions = state.transitions; + + const filteredTransitions = new Set(); + // process the lazy transitions list by filtering duplicate transitions + // and calling the transition complete callback on all transitions + // if there are no more pending suspense boundaries + if (lazyTransitions !== null) { + for (let i = 0; i < lazyTransitions.length; i++) { + const transitions = lazyTransitions[i]; + if (transitions !== null) { + for (let j = 0; j < transitions.length; j++) { + const transition = transitions[j]; + if ( + transition !== null && + !filteredTransitions.has(transition) + ) { + if ( + pendingSuspenseBoundaries === null || + pendingSuspenseBoundaries.size === 0 + ) { + addTransitionCompleteCallbackToPendingTransition({ + transitionName: transition.name, + startTime: transition.startTime, + }); + } + filteredTransitions.add(transition); + } + } + } + } + + // If there are no more pending suspense boundaries we + // clear the transitions because they are all complete. Otherwise + // we store the transitions where we remove all duplicates + if ( + pendingSuspenseBoundaries === null || + pendingSuspenseBoundaries.size === 0 + ) { + state.transitions = null; + } else { + state.transitions = [Array.from(filteredTransitions)]; + } + } + break; + } + case OffscreenComponent: { + if (enableTransitionTracing) { + const isFallback = fiber.memoizedState; + const queue = (fiber.updateQueue: any); + let pendingSuspenseBoundaries = null; + if (queue !== null) { + const rootMemoizedState = queue.rootMemoizedState; + if (rootMemoizedState.pendingSuspenseBoundaries === null) { + rootMemoizedState.pendingSuspenseBoundaries = new Map(); + } + pendingSuspenseBoundaries = + rootMemoizedState.pendingSuspenseBoundaries; + + if (isFallback) { + const transitions = queue.transitions; + const prevTransitions = fiber.memoizedState.transitions; + if (transitions != null) { + if (prevTransitions === null) { + fiber.memoizedState.transitions = transitions; + } else { + fiber.memoizedState.transitions = transitions.concat( + prevTransitions, + ); + } + } + } + } + + let name = null; + const parent = fiber.return; + if ( + parent !== null && + parent.tag === SuspenseComponent && + parent.memoizedProps.name + ) { + name = parent.memoizedProps.name; + } + // We use stateNode on the Offscreen component as a stable object + // that doesnt change from render to render. This way we can + // distinguish between different Offscreen instances (vs. the same + // Offscreen instance with different fibers) + const stateNode = fiber.stateNode; + + addOrRemovePendingBoundariesOnRoot( + fiber, + name, + stateNode, + pendingSuspenseBoundaries, + ); + } + break; + } + } + } + } + if ((fiber.flags & Passive) !== NoFlags) { setCurrentDebugFiberInDEV(fiber); try { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index ef3d4f7979f29..fdce4c0e3bbca 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -83,6 +83,7 @@ import { Incomplete, ShouldCapture, ForceClientRender, + SuspenseToggle, } from './ReactFiberFlags'; import { @@ -166,7 +167,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 @@ -862,9 +867,16 @@ function completeWork( } case HostRoot: { const fiberRoot = (workInProgress.stateNode: FiberRoot); - if (enableCache) { - popRootTransition(fiberRoot, renderLanes); + if (enableTransitionTracing) { + const transitions = getSuspendedTransitions(); + if (transitions !== null) { + workInProgress.flags |= SuspenseToggle; + } + } + popRootTransition(workInProgress, fiberRoot, renderLanes); + + if (enableCache) { let previousCache: Cache | null = null; if (current !== null) { previousCache = current.memoizedState.cache; @@ -918,6 +930,11 @@ function completeWork( } updateHostContainer(current, workInProgress); bubbleProperties(workInProgress); + if (enableTransitionTracing) { + if ((workInProgress.subtreeFlags & SuspenseToggle) !== NoFlags) { + workInProgress.flags |= SuspenseToggle; + } + } return null; } case HostComponent: { @@ -1172,6 +1189,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 @@ -1567,11 +1594,10 @@ function completeWork( // Run passive effects to retain/release the cache. workInProgress.flags |= Passive; } - if (current !== null) { - popTransition(workInProgress); - } } + popTransition(workInProgress, current); + return null; } case CacheComponent: { diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.old.js b/packages/react-reconciler/src/ReactFiberHotReloading.old.js index 4a5f53d3be731..5fe0869c66baf 100644 --- a/packages/react-reconciler/src/ReactFiberHotReloading.old.js +++ b/packages/react-reconciler/src/ReactFiberHotReloading.old.js @@ -237,7 +237,7 @@ export const scheduleRefresh: ScheduleRefresh = ( return; } const {staleFamilies, updatedFamilies} = update; - flushPassiveEffects(); + flushPassiveEffects(null); flushSync(() => { scheduleFibersWithFamiliesRecursively( root.current, @@ -259,7 +259,7 @@ export const scheduleRoot: ScheduleRoot = ( // Just ignore. We'll delete this with _renderSubtree code path later. return; } - flushPassiveEffects(); + flushPassiveEffects(null); flushSync(() => { updateContainer(element, root, null, null); }); diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js index 37fb44cc10867..908bcd5045467 100644 --- a/packages/react-reconciler/src/ReactFiberLane.old.js +++ b/packages/react-reconciler/src/ReactFiberLane.old.js @@ -829,6 +829,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) { @@ -859,13 +860,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 1d5edd410cfef..3b5b96740e8ac 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -15,7 +15,10 @@ import type { } from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import type {Cache} from './ReactFiberCacheComponent.old'; -import type {Transition} from './ReactFiberTracingMarkerComponent.old'; +import type { + PendingSuspenseBoundaries, + Transition, +} from './ReactFiberTracingMarkerComponent.old'; import {noTimeout, supportsHydration} from './ReactFiberHostConfig'; import {createHostRootFiber} from './ReactFiber.old'; @@ -42,7 +45,8 @@ export type RootState = { element: any, isDehydrated: boolean, cache: Cache, - transitions: Array | null, + pendingSuspenseBoundaries: PendingSuspenseBoundaries | null, + transitions: Array | null> | null, }; function FiberRootNode( @@ -184,6 +188,7 @@ export function createFiberRoot( isDehydrated: hydrate, cache: initialCache, transitions: null, + pendingSuspenseBoundaries: null, }; uninitializedFiber.memoizedState = initialState; } else { @@ -192,6 +197,7 @@ export function createFiberRoot( isDehydrated: hydrate, cache: (null: any), // not enabled yet 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 cc3834f5cc6c3..29018efc96f9f 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js @@ -7,8 +7,9 @@ * @flow */ -import type {TransitionTracingCallbacks} from './ReactInternalTypes'; -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type {TransitionTracingCallbacks, Fiber} from './ReactInternalTypes'; +import type {OffscreenInstance} from './ReactFiberOffscreenComponent'; + import {enableTransitionTracing} from 'shared/ReactFeatureFlags'; export type SuspenseInfo = {name: string | null}; @@ -34,10 +35,7 @@ export type BatchConfigTransition = { _updatedFibers?: Set, }; -export type TransitionCallback = 0 | 1; - -export const TransitionStart = 0; -export const TransitionComplete = 1; +export type PendingSuspenseBoundaries = Map; 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..727d7c02a368d 100644 --- a/packages/react-reconciler/src/ReactFiberTransition.old.js +++ b/packages/react-reconciler/src/ReactFiberTransition.old.js @@ -10,11 +10,15 @@ 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 {Transition} 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 { + getWorkInProgressRoot, + getWorkInProgressTransitions, +} from './ReactFiberWorkLoop.old'; import { createCache, retainCache, @@ -25,6 +29,15 @@ import { // used during the previous render by placing it here, on the stack. const resumedCache: StackCursor = createCursor(null); +// During the render/synchronous commit phase, we don't actually process the +// transitions. Therefore, we want to lazily combine transitions. Instead of +// comparing the arrays of transitions when we combine them and storing them +// and filtering out the duplicates, we will instead store the unprocessed transitions +// in an array of arrays and actually filter them in the passive phase. +const transitionStack: StackCursor | null> | null> = createCursor( + null, +); + function peekCacheFromPool(): Cache | null { if (!enableCache) { return (null: any); @@ -75,25 +88,32 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache { return freshCache; } -export function pushRootTransition(root: FiberRoot) { - if (enableCache) { - return; +export function pushRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + const rootTransitions = getWorkInProgressTransitions(); + const newTransitions = rootTransitions === null ? null : [rootTransitions]; + push(transitionStack, newTransitions, workInProgress); } - // 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; +export function popRootTransition( + workInProgress: Fiber, + root: FiberRoot, + renderLanes: Lanes, +) { + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); } - // 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, + newTransitions: Array | null> | null, ): void { if (enableCache) { if (prevCachePool === null) { @@ -102,12 +122,44 @@ export function pushTransition( push(resumedCache, prevCachePool.pool, offscreenWorkInProgress); } } + + if (enableTransitionTracing) { + if (transitionStack.current === null) { + push(transitionStack, newTransitions, offscreenWorkInProgress); + } else { + push( + transitionStack, + transitionStack.current.concat(newTransitions), + offscreenWorkInProgress, + ); + } + } } -export function popTransition(workInProgress: Fiber) { - if (enableCache) { - pop(resumedCache, workInProgress); +export function popTransition(workInProgress: Fiber, current: Fiber | null) { + if (current !== null) { + if (enableCache) { + pop(resumedCache, workInProgress); + } + + if (enableTransitionTracing) { + pop(transitionStack, workInProgress); + } + } +} + +export function getSuspendedTransitions(): Array | null> | null { + if (!enableTransitionTracing) { + return null; } + + const currentTransitions = transitionStack.current; + + 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..33cbe507fa487 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) { - const root: FiberRoot = workInProgress.stateNode; - popRootTransition(root, renderLanes); + const root: FiberRoot = workInProgress.stateNode; + popRootTransition(workInProgress, root, renderLanes); + if (enableCache) { 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,13 +190,14 @@ function unwindInterruptedWork( break; } case HostRoot: { - if (enableCache) { - const root: FiberRoot = interruptedWork.stateNode; - popRootTransition(root, renderLanes); + const root: FiberRoot = interruptedWork.stateNode; + popRootTransition(interruptedWork, root, renderLanes); + if (enableCache) { const cache: Cache = interruptedWork.memoizedState.cache; popCacheProvider(interruptedWork, cache); } + popHostContainer(interruptedWork); popTopLevelLegacyContextObject(interruptedWork); resetMutableSourceWorkInProgressVersions(); @@ -221,12 +223,7 @@ function unwindInterruptedWork( case OffscreenComponent: case LegacyHiddenComponent: popRenderLanes(interruptedWork); - if (enableCache) { - if (current !== null) { - popTransition(interruptedWork); - } - } - + 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 2c940a71001db..017607ba676a8 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -867,7 +867,8 @@ function performConcurrentWorkOnRoot(root, didTimeout) { // Flush any pending passive effects before deciding which lanes to work on, // in case they schedule additional work. const originalCallbackNode = root.callbackNode; - const didFlushPassiveEffects = flushPassiveEffects(); + // workInProgressTransitions should be what it in the previous render + const didFlushPassiveEffects = flushPassiveEffects(workInProgressTransitions); if (didFlushPassiveEffects) { // Something in the passive effect phase may have canceled the current task. // Check if the task node for this root was changed. @@ -1057,7 +1058,11 @@ function finishConcurrentRender(root, exitStatus, lanes) { case RootErrored: { // We should have already attempted to retry this tree. If we reached // this point, it errored again. Commit it. - commitRoot(root, workInProgressRootRecoverableErrors); + commitRoot( + root, + workInProgressRootRecoverableErrors, + workInProgressTransitions, + ); break; } case RootSuspended: { @@ -1104,7 +1109,11 @@ function finishConcurrentRender(root, exitStatus, lanes) { } } // The work expired. Commit immediately. - commitRoot(root, workInProgressRootRecoverableErrors); + commitRoot( + root, + workInProgressRootRecoverableErrors, + workInProgressTransitions, + ); break; } case RootSuspendedWithDelay: { @@ -1143,12 +1152,20 @@ function finishConcurrentRender(root, exitStatus, lanes) { } // Commit the placeholder. - commitRoot(root, workInProgressRootRecoverableErrors); + commitRoot( + root, + workInProgressRootRecoverableErrors, + workInProgressTransitions, + ); break; } case RootCompleted: { // The work completed. Ready to commit. - commitRoot(root, workInProgressRootRecoverableErrors); + commitRoot( + root, + workInProgressRootRecoverableErrors, + workInProgressTransitions, + ); break; } default: { @@ -1233,7 +1250,8 @@ function performSyncWorkOnRoot(root) { throw new Error('Should not already be working.'); } - flushPassiveEffects(); + // workInProgressTransitions should be what it in the previous render + flushPassiveEffects(workInProgressTransitions); let lanes = getNextLanes(root, NoLanes); if (!includesSomeLane(lanes, SyncLane)) { @@ -1272,7 +1290,11 @@ function performSyncWorkOnRoot(root) { const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; - commitRoot(root, workInProgressRootRecoverableErrors); + commitRoot( + root, + workInProgressRootRecoverableErrors, + workInProgressTransitions, + ); // Before exiting, make sure there's a callback scheduled for the next // pending level. @@ -1366,7 +1388,8 @@ export function flushSync(fn) { rootWithPendingPassiveEffects.tag === LegacyRoot && (executionContext & (RenderContext | CommitContext)) === NoContext ) { - flushPassiveEffects(); + // Legacy mode doesn't have transition tracing + flushPassiveEffects(null); } const prevExecutionContext = executionContext; @@ -1954,7 +1977,11 @@ function completeUnitOfWork(unitOfWork: Fiber): void { } } -function commitRoot(root: FiberRoot, recoverableErrors: null | Array) { +function commitRoot( + root: FiberRoot, + recoverableErrors: null | Array, + transitions: Array | null, +) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. const previousUpdateLanePriority = getCurrentUpdatePriority(); @@ -1963,7 +1990,12 @@ function commitRoot(root: FiberRoot, recoverableErrors: null | Array) { try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); - commitRootImpl(root, recoverableErrors, previousUpdateLanePriority); + commitRootImpl( + root, + recoverableErrors, + transitions, + previousUpdateLanePriority, + ); } finally { ReactCurrentBatchConfig.transition = prevTransition; setCurrentUpdatePriority(previousUpdateLanePriority); @@ -1975,6 +2007,7 @@ function commitRoot(root: FiberRoot, recoverableErrors: null | Array) { function commitRootImpl( root: FiberRoot, recoverableErrors: null | Array, + transitions: Array | null, renderPriorityLevel: EventPriority, ) { do { @@ -1984,7 +2017,7 @@ function commitRootImpl( // no more pending effects. // TODO: Might be better if `flushPassiveEffects` did not automatically // flush synchronous work at the end, to avoid factoring hazards like this. - flushPassiveEffects(); + flushPassiveEffects(transitions); } while (rootWithPendingPassiveEffects !== null); flushRenderPhaseStrictModeWarningsInDEV(); @@ -2071,7 +2104,7 @@ function commitRootImpl( rootDoesHavePassiveEffects = true; pendingPassiveEffectsRemainingLanes = remainingLanes; scheduleCallback(NormalSchedulerPriority, () => { - flushPassiveEffects(); + flushPassiveEffects(transitions); // This render triggered passive effects: release the root cache pool // *after* passive effects fire to avoid freeing a cache pool that may // be referenced by a node in the tree (HostRoot, Cache boundary etc) @@ -2276,7 +2309,7 @@ function commitRootImpl( includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && root.tag !== LegacyRoot ) { - flushPassiveEffects(); + flushPassiveEffects(transitions); } // Read this again, since a passive effect might have updated it @@ -2301,27 +2334,6 @@ function commitRootImpl( // If layout work was scheduled, flush it now. flushSyncCallbacks(); - if (enableTransitionTracing) { - const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks; - const prevRootTransitionCallbacks = root.transitionCallbacks; - if ( - prevPendingTransitionCallbacks !== null && - prevRootTransitionCallbacks !== null - ) { - // TODO(luna) Refactor this code into the Host Config - const endTime = now(); - currentPendingTransitionCallbacks = null; - - scheduleCallback(IdleSchedulerPriority, () => - processTransitionCallbacks( - prevPendingTransitionCallbacks, - endTime, - prevRootTransitionCallbacks, - ), - ); - } - } - if (__DEV__) { if (enableDebugTracing) { logCommitStopped(); @@ -2350,7 +2362,9 @@ function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { } } -export function flushPassiveEffects(): boolean { +export function flushPassiveEffects( + transitions: Array | null, +): boolean { // Returns whether passive effects were flushed. // TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should // probably just combine the two functions. I believe they were only separate @@ -2375,7 +2389,7 @@ export function flushPassiveEffects(): boolean { try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(priority); - return flushPassiveEffectsImpl(); + return flushPassiveEffectsImpl(transitions); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; @@ -2395,14 +2409,15 @@ export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void { if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { - flushPassiveEffects(); + // TODO: pass transitions to the Profiler + flushPassiveEffects(null); return null; }); } } } -function flushPassiveEffectsImpl() { +function flushPassiveEffectsImpl(transitions: Array | null) { if (rootWithPendingPassiveEffects === null) { return false; } @@ -2433,7 +2448,7 @@ function flushPassiveEffectsImpl() { executionContext |= CommitContext; commitPassiveUnmountEffects(root.current); - commitPassiveMountEffects(root, root.current); + commitPassiveMountEffects(root, root.current, lanes, transitions); // TODO: Move to commitPassiveMountEffects if (enableProfilerTimer && enableProfilerCommitHooks) { @@ -2463,6 +2478,34 @@ function flushPassiveEffectsImpl() { flushSyncCallbacks(); + if (enableTransitionTracing) { + const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks; + const prevRootTransitionCallbacks = root.transitionCallbacks; + if ( + prevPendingTransitionCallbacks !== null && + prevRootTransitionCallbacks !== null + ) { + // TODO(luna) Refactor this code into the Host Config + // TODO(luna) The end time here is not necessarily accurate + // because passive effects could be called before paint + // (synchronously) or after paint (normally). We need + // to come up with a way to get the correct end time for both cases. + // One solution is in the host config, if the passive effects + // have not yet been run, make a call to flush the passive effects + // right after paint. + const endTime = now(); + currentPendingTransitionCallbacks = null; + + scheduleCallback(IdleSchedulerPriority, () => + processTransitionCallbacks( + prevPendingTransitionCallbacks, + endTime, + prevRootTransitionCallbacks, + ), + ); + } + } + // If additional passive effects were scheduled, increment a counter. If this // exceeds the limit, we'll fire a warning. nestedPassiveUpdateCount =