diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index 572c1b770adaa..d8f1ef424bd13 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -774,6 +774,8 @@ export function createFiberFromTracingMarker( tag: TransitionTracingMarker, transitions: null, pendingBoundaries: null, + aborts: null, + name: pendingProps.name, }; fiber.stateNode = tracingMarkerInstance; return fiber; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 65403e6c8ad37..9ce0a7bf89216 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -89,6 +89,7 @@ import { StaticMask, ShouldCapture, ForceClientRender, + Passive, } from './ReactFiberFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { @@ -979,10 +980,13 @@ function updateTracingMarkerComponent( const markerInstance: TracingMarkerInstance = { tag: TransitionTracingMarker, transitions: new Set(currentTransitions), - pendingBoundaries: new Map(), + pendingBoundaries: null, name: workInProgress.pendingProps.name, + aborts: null, }; workInProgress.stateNode = markerInstance; + + workInProgress.flags |= Passive; } } else { if (__DEV__) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 6c17402e08460..a02f0f7ee219e 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -30,7 +30,11 @@ import type { import type {HookFlags} from './ReactHookEffectTags'; import type {Cache} from './ReactFiberCacheComponent.old'; import type {RootState} from './ReactFiberRoot.old'; -import type {Transition} from './ReactFiberTracingMarkerComponent.old'; +import type { + Transition, + TracingMarkerInstance, + TransitionAbort, +} from './ReactFiberTracingMarkerComponent.old'; import { enableCreateEventHandleAPI, @@ -146,6 +150,7 @@ import { addTransitionProgressCallbackToPendingTransition, addTransitionCompleteCallbackToPendingTransition, addMarkerProgressCallbackToPendingTransition, + addMarkerIncompleteCallbackToPendingTransition, addMarkerCompleteCallbackToPendingTransition, setIsRunningInsertionEffect, } from './ReactFiberWorkLoop.old'; @@ -1135,6 +1140,128 @@ function commitLayoutEffectOnFiber( } } +function abortTransitionMarker( + deletedFiber: Fiber, + currentFiber: Fiber, + abort: TransitionAbort, + isInDeletedTree: boolean, +) { + const markerInstance: TracingMarkerInstance = currentFiber.stateNode; + const deletedInstance: OffscreenInstance = deletedFiber.stateNode; + const deletedTransitions = deletedFiber.stateNode.transitions; + const pendingBoundaries = markerInstance.pendingBoundaries; + + let aborts = markerInstance.aborts; + if (aborts === null) { + markerInstance.aborts = aborts = []; + } + + aborts.push(abort); + addMarkerIncompleteCallbackToPendingTransition( + currentFiber.memoizedProps.name, + deletedTransitions, + aborts, + ); + + // We only want to call onTransitionProgress when the marker hasn't been + // deleted + if ( + !isInDeletedTree && + pendingBoundaries !== null && + pendingBoundaries.has(deletedInstance) + ) { + pendingBoundaries.delete(deletedInstance); + + addMarkerProgressCallbackToPendingTransition( + currentFiber.memoizedProps.name, + deletedTransitions, + pendingBoundaries, + ); + } +} + +function abortParentMarkerTransitions( + deletedFiber: Fiber, + nearestMountedAncestor: Fiber, + abort: TransitionAbort, +) { + // Find all pending markers that are waiting on child suspense boundaries in the + // aborted subtree and cancels them + const deletedFiberInstance: OffscreenInstance = deletedFiber.stateNode; + const abortedTransitions = deletedFiberInstance.transitions; + if (abortedTransitions !== null) { + let fiber = deletedFiber; + let isInDeletedTree = true; + while (fiber !== null) { + switch (fiber.tag) { + case TracingMarkerComponent: + const markerInstance: TracingMarkerInstance = fiber.stateNode; + const markerTransitions = markerInstance.transitions; + if (markerTransitions !== null) { + // TODO: Refactor this code. Is there a way to move this code to + // the deletions phase instead of calculating it here while making sure + // complete is called appropriately? + abortedTransitions.forEach(transition => { + // If one of the transitions on the tracing marker is a transition + // that was in an aborted subtree, we will abort that tracing marker + if ( + fiber !== null && + markerTransitions.has(transition) && + (markerInstance.aborts === null || + !markerInstance.aborts.includes(abort)) + ) { + abortTransitionMarker( + deletedFiber, + fiber, + abort, + isInDeletedTree, + ); + } + }); + } + break; + case HostRoot: + const root = fiber.stateNode; + const rootTransitions = root.incompleteTransitions; + + abortedTransitions.forEach(transition => { + if (rootTransitions.has(transition)) { + const transitionInstance: TracingMarkerInstance = rootTransitions.get( + transition, + ); + if (transitionInstance.aborts === null) { + transitionInstance.aborts = []; + } + transitionInstance.aborts.push(abort); + + if ( + transitionInstance.pendingBoundaries !== null && + transitionInstance.pendingBoundaries.has(deletedFiberInstance) + ) { + transitionInstance.pendingBoundaries.delete( + deletedFiberInstance, + ); + } + } + }); + break; + default: + break; + } + + if ( + nearestMountedAncestor.deletions !== null && + nearestMountedAncestor.deletions.includes(fiber) + ) { + isInDeletedTree = false; + fiber = nearestMountedAncestor; + } else { + fiber = fiber.return; + } + } + } +} + function commitTransitionProgress(offscreenFiber: Fiber) { if (enableTransitionTracing) { // This function adds suspense boundaries to the root @@ -1180,6 +1307,7 @@ function commitTransitionProgress(offscreenFiber: Fiber) { pendingMarkers.forEach(markerInstance => { const pendingBoundaries = markerInstance.pendingBoundaries; const transitions = markerInstance.transitions; + const markerName = markerInstance.name; if ( pendingBoundaries !== null && !pendingBoundaries.has(offscreenInstance) @@ -1190,10 +1318,10 @@ function commitTransitionProgress(offscreenFiber: Fiber) { if (transitions !== null) { if ( markerInstance.tag === TransitionTracingMarker && - markerInstance.name !== undefined + markerName !== null ) { addMarkerProgressCallbackToPendingTransition( - markerInstance.name, + markerName, transitions, pendingBoundaries, ); @@ -1217,6 +1345,7 @@ function commitTransitionProgress(offscreenFiber: Fiber) { pendingMarkers.forEach(markerInstance => { const pendingBoundaries = markerInstance.pendingBoundaries; const transitions = markerInstance.transitions; + const markerName = markerInstance.name; if ( pendingBoundaries !== null && pendingBoundaries.has(offscreenInstance) @@ -1225,13 +1354,25 @@ function commitTransitionProgress(offscreenFiber: Fiber) { if (transitions !== null) { if ( markerInstance.tag === TransitionTracingMarker && - markerInstance.name !== undefined + markerName !== null ) { addMarkerProgressCallbackToPendingTransition( - markerInstance.name, + markerName, transitions, pendingBoundaries, ); + + if (pendingBoundaries.size === 0) { + if (markerInstance.aborts === null) { + addMarkerCompleteCallbackToPendingTransition( + markerName, + transitions, + ); + } + markerInstance.transitions = null; + markerInstance.pendingBoundaries = null; + markerInstance.aborts = null; + } } else if (markerInstance.tag === TransitionRoot) { transitions.forEach(transition => { addTransitionProgressCallbackToPendingTransition( @@ -1996,6 +2137,7 @@ function commitDeletionEffectsOnFiber( const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden; offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; + recursivelyTraverseDeletionEffects( finishedRoot, nearestMountedAncestor, @@ -2011,6 +2153,45 @@ function commitDeletionEffectsOnFiber( } break; } + case SuspenseComponent: { + if (enableTransitionTracing) { + // We need to mark this fiber's parents as deleted + const offscreenFiber: Fiber = (deletedFiber.child: any); + const instance: OffscreenInstance = offscreenFiber.stateNode; + const transitions = instance.transitions; + if (transitions !== null) { + abortParentMarkerTransitions(offscreenFiber, nearestMountedAncestor, { + reason: 'suspense', + name: deletedFiber.memoizedProps.unstable_name || null, + }); + } + } + recursivelyTraverseDeletionEffects( + finishedRoot, + nearestMountedAncestor, + deletedFiber, + ); + return; + } + case TracingMarkerComponent: { + if (enableTransitionTracing) { + // We need to mark this fiber's parents as deleted + const instance: TracingMarkerInstance = deletedFiber.stateNode; + const transitions = instance.transitions; + if (transitions !== null) { + abortParentMarkerTransitions(deletedFiber, nearestMountedAncestor, { + reason: 'marker', + name: deletedFiber.memoizedProps.name, + }); + } + } + recursivelyTraverseDeletionEffects( + finishedRoot, + nearestMountedAncestor, + deletedFiber, + ); + return; + } default: { recursivelyTraverseDeletionEffects( finishedRoot, @@ -2986,6 +3167,12 @@ function commitOffscreenPassiveMountEffects( } commitTransitionProgress(finishedWork); + + // TODO: Refactor this into an if/else branch + if (!isHidden) { + instance.transitions = null; + instance.pendingMarkers = null; + } } } @@ -3016,20 +3203,18 @@ function commitCachePassiveMountEffect( function commitTracingMarkerPassiveMountEffect(finishedWork: Fiber) { // Get the transitions that were initiatized during the render // and add a start transition callback for each of them + // We will only call this on initial mount of the tracing marker + // only if there are no suspense children const instance = finishedWork.stateNode; - if ( - instance.transitions !== null && - (instance.pendingBoundaries === null || - instance.pendingBoundaries.size === 0) - ) { - instance.transitions.forEach(transition => { - addMarkerCompleteCallbackToPendingTransition( - finishedWork.memoizedProps.name, - instance.transitions, - ); - }); + if (instance.transitions !== null && instance.pendingBoundaries === null) { + addMarkerCompleteCallbackToPendingTransition( + finishedWork.memoizedProps.name, + instance.transitions, + ); instance.transitions = null; instance.pendingBoundaries = null; + instance.aborts = null; + instance.name = null; } } @@ -3145,7 +3330,9 @@ function commitPassiveMountOnFiber( incompleteTransitions.forEach((markerInstance, transition) => { const pendingBoundaries = markerInstance.pendingBoundaries; if (pendingBoundaries === null || pendingBoundaries.size === 0) { - addTransitionCompleteCallbackToPendingTransition(transition); + if (markerInstance.aborts === null) { + addTransitionCompleteCallbackToPendingTransition(transition); + } incompleteTransitions.delete(transition); } }); @@ -3518,21 +3705,6 @@ function commitAtomicPassiveEffects( } break; } - case TracingMarkerComponent: { - if (enableTransitionTracing) { - recursivelyTraverseAtomicPassiveEffects( - finishedRoot, - finishedWork, - committedLanes, - committedTransitions, - ); - if (flags & Passive) { - commitTracingMarkerPassiveMountEffect(finishedWork); - } - break; - } - // Intentional fallthrough to next branch - } // eslint-disable-next-line-no-fallthrough default: { recursivelyTraverseAtomicPassiveEffects( diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 1ff1a5173ec10..3fd1bffbc3a99 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -1589,16 +1589,6 @@ function completeWork( popMarkerInstance(workInProgress); } bubbleProperties(workInProgress); - - if ( - current === null || - (workInProgress.subtreeFlags & Visibility) !== NoFlags - ) { - // If any of our suspense children toggle visibility, this means that - // the pending boundaries array needs to be updated, which we only - // do in the passive phase. - workInProgress.flags |= Passive; - } } return null; } diff --git a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js index 0e3c352d77287..9bc07be01f98c 100644 --- a/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberTracingMarkerComponent.old.js @@ -21,7 +21,14 @@ export type PendingTransitionCallbacks = { transitionStart: Array | null, transitionProgress: Map | null, transitionComplete: Array | null, - markerProgress: Map | null, + markerProgress: Map< + string, + {pendingBoundaries: PendingBoundaries, transitions: Set}, + > | null, + markerIncomplete: Map< + string, + {aborts: Array, transitions: Set}, + > | null, markerComplete: Map> | null, }; @@ -36,11 +43,18 @@ export type BatchConfigTransition = { _updatedFibers?: Set, }; +// TODO: Is there a way to not include the tag or name here? export type TracingMarkerInstance = {| tag?: TracingMarkerTag, - pendingBoundaries: PendingBoundaries | null, transitions: Set | null, - name?: string, + pendingBoundaries: PendingBoundaries | null, + aborts: Array | null, + name: string | null, +|}; + +export type TransitionAbort = {| + reason: 'error' | 'unknown' | 'marker' | 'suspense', + name?: string | null, |}; export const TransitionRoot = 0; @@ -69,6 +83,7 @@ export function processTransitionCallbacks( if (onMarkerProgress != null && markerProgress !== null) { markerProgress.forEach((markerInstance, markerName) => { if (markerInstance.transitions !== null) { + // TODO: Clone the suspense object so users can't modify it const pending = markerInstance.pendingBoundaries !== null ? Array.from(markerInstance.pendingBoundaries.values()) @@ -101,6 +116,48 @@ export function processTransitionCallbacks( }); } + const markerIncomplete = pendingTransitions.markerIncomplete; + const onMarkerIncomplete = callbacks.onMarkerIncomplete; + if (onMarkerIncomplete != null && markerIncomplete !== null) { + markerIncomplete.forEach(({transitions, aborts}, markerName) => { + transitions.forEach(transition => { + const filteredAborts = []; + aborts.forEach(abort => { + switch (abort.reason) { + case 'marker': { + filteredAborts.push({ + type: 'marker', + name: abort.name, + endTime, + }); + break; + } + case 'suspense': { + filteredAborts.push({ + type: 'suspense', + name: abort.name, + endTime, + }); + break; + } + default: { + break; + } + } + }); + + if (filteredAborts.length > 0) { + onMarkerIncomplete( + transition.name, + markerName, + transition.startTime, + filteredAborts, + ); + } + }); + }); + } + const transitionProgress = pendingTransitions.transitionProgress; const onTransitionProgress = callbacks.onTransitionProgress; if (onTransitionProgress != null && transitionProgress !== null) { @@ -154,6 +211,8 @@ export function pushRootMarkerInstance(workInProgress: Fiber): void { tag: TransitionRoot, transitions: new Set([transition]), pendingBoundaries: null, + aborts: null, + name: null, }; root.incompleteTransitions.set(transition, markerInstance); } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 14e378794bfbc..5955f43d02802 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -18,6 +18,7 @@ import type { PendingTransitionCallbacks, PendingBoundaries, Transition, + TransitionAbort, } from './ReactFiberTracingMarkerComponent.old'; import type {OffscreenInstance} from './ReactFiberOffscreenComponent'; @@ -355,6 +356,7 @@ export function addTransitionStartCallbackToPendingTransition( transitionProgress: null, transitionComplete: null, markerProgress: null, + markerIncomplete: null, markerComplete: null, }; } @@ -370,7 +372,7 @@ export function addTransitionStartCallbackToPendingTransition( export function addMarkerProgressCallbackToPendingTransition( markerName: string, transitions: Set, - pendingBoundaries: PendingBoundaries | null, + pendingBoundaries: PendingBoundaries, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { @@ -379,6 +381,7 @@ export function addMarkerProgressCallbackToPendingTransition( transitionProgress: null, transitionComplete: null, markerProgress: new Map(), + markerIncomplete: null, markerComplete: null, }; } @@ -394,6 +397,34 @@ export function addMarkerProgressCallbackToPendingTransition( } } +export function addMarkerIncompleteCallbackToPendingTransition( + markerName: string, + transitions: Set, + aborts: Array, +) { + if (enableTransitionTracing) { + if (currentPendingTransitionCallbacks === null) { + currentPendingTransitionCallbacks = { + transitionStart: null, + transitionProgress: null, + transitionComplete: null, + markerProgress: null, + markerIncomplete: new Map(), + markerComplete: null, + }; + } + + if (currentPendingTransitionCallbacks.markerIncomplete === null) { + currentPendingTransitionCallbacks.markerIncomplete = new Map(); + } + + currentPendingTransitionCallbacks.markerIncomplete.set(markerName, { + transitions, + aborts, + }); + } +} + export function addMarkerCompleteCallbackToPendingTransition( markerName: string, transitions: Set, @@ -405,6 +436,7 @@ export function addMarkerCompleteCallbackToPendingTransition( transitionProgress: null, transitionComplete: null, markerProgress: null, + markerIncomplete: null, markerComplete: new Map(), }; } @@ -431,6 +463,7 @@ export function addTransitionProgressCallbackToPendingTransition( transitionProgress: new Map(), transitionComplete: null, markerProgress: null, + markerIncomplete: null, markerComplete: null, }; } @@ -456,6 +489,7 @@ export function addTransitionCompleteCallbackToPendingTransition( transitionProgress: null, transitionComplete: [], markerProgress: null, + markerIncomplete: null, markerComplete: null, }; }