Skip to content

Commit 42f15b3

Browse files
authored
[DevTools][Transition Tracing] onTransitionComplete and onTransitionStart implmentation (#23313)
* add transition name to startTransition Add a transitionName to start transition, store the transition start time and name in the batch config, and pass it to the root on render * Transition Tracing Types and Consts * Root begin work The root operates as a tracing marker that has all transitions on it. This PR only tested the root with one transition so far - Store transitions in memoizedState. Do this in updateHostRoot AND attemptEarlyBailoutIfNoScheduledUpdate. We need to do this in the latter part because even if the root itself doesn't have an update, it could still have new transitions in its transitionLanes map that we need to process. * Transition Tracing commit phase - adds a module scoped pending transition callbacks object that contains all transition callbacks that have not yet been processed. This contains all callbacks before the next paint occurs. - Add code in the mutation phase to: * For the root, if there are transitions that were initialized during this commit in the root transition lanes map, add a transition start call to the pending transition callbacks object. Then, remove the transitions from the root transition lanes map. * For roots, in the commit phase, add a transition complete call We add this code in the mutation phase because we can't add it to the passive phase because then the paint might have occurred before we even know which callbacks to call * Process Callbacks after paint At the end of the commit phase, call scheduleTransitionCallbacks to schedule all pending transition callbacks to be called after paint. Then clear the callbacks
1 parent 68cb55f commit 42f15b3

25 files changed

+831
-37
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
MutableSourceSubscribeFn,
1414
ReactContext,
1515
ReactProviderType,
16+
StartTransitionOptions,
1617
} from 'shared/ReactTypes';
1718
import type {
1819
Fiber,
@@ -291,7 +292,10 @@ function useSyncExternalStore<T>(
291292
return value;
292293
}
293294

294-
function useTransition(): [boolean, (() => void) => void] {
295+
function useTransition(): [
296+
boolean,
297+
(callback: () => void, options?: StartTransitionOptions) => void,
298+
] {
295299
// useTransition() composes multiple hooks internally.
296300
// Advance the current hook index the same number of times
297301
// so that subsequent hooks have the right memoized state.

packages/react-reconciler/src/ReactFiberBeginWork.new.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import {
226226
markSkippedUpdateLanes,
227227
getWorkInProgressRoot,
228228
pushRenderLanes,
229+
getWorkInProgressTransitions,
229230
} from './ReactFiberWorkLoop.new';
230231
import {setWorkInProgressVersion} from './ReactMutableSource.new';
231232
import {
@@ -1336,6 +1337,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
13361337
}
13371338
}
13381339

1340+
if (enableTransitionTracing) {
1341+
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
1342+
}
1343+
13391344
// Caution: React DevTools currently depends on this property
13401345
// being called "element".
13411346
const nextChildren = nextState.element;
@@ -3495,12 +3500,15 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
34953500
switch (workInProgress.tag) {
34963501
case HostRoot:
34973502
pushHostRootContext(workInProgress);
3503+
const root: FiberRoot = workInProgress.stateNode;
34983504
if (enableCache) {
3499-
const root: FiberRoot = workInProgress.stateNode;
35003505
const cache: Cache = current.memoizedState.cache;
35013506
pushCacheProvider(workInProgress, cache);
35023507
pushRootCachePool(root);
35033508
}
3509+
if (enableTransitionTracing) {
3510+
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
3511+
}
35043512
resetHydrationState();
35053513
break;
35063514
case HostComponent:

packages/react-reconciler/src/ReactFiberBeginWork.old.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import {
226226
markSkippedUpdateLanes,
227227
getWorkInProgressRoot,
228228
pushRenderLanes,
229+
getWorkInProgressTransitions,
229230
} from './ReactFiberWorkLoop.old';
230231
import {setWorkInProgressVersion} from './ReactMutableSource.old';
231232
import {
@@ -1336,6 +1337,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
13361337
}
13371338
}
13381339

1340+
if (enableTransitionTracing) {
1341+
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
1342+
}
1343+
13391344
// Caution: React DevTools currently depends on this property
13401345
// being called "element".
13411346
const nextChildren = nextState.element;
@@ -3495,12 +3500,15 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
34953500
switch (workInProgress.tag) {
34963501
case HostRoot:
34973502
pushHostRootContext(workInProgress);
3503+
const root: FiberRoot = workInProgress.stateNode;
34983504
if (enableCache) {
3499-
const root: FiberRoot = workInProgress.stateNode;
35003505
const cache: Cache = current.memoizedState.cache;
35013506
pushCacheProvider(workInProgress, cache);
35023507
pushRootCachePool(root);
35033508
}
3509+
if (enableTransitionTracing) {
3510+
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
3511+
}
35043512
resetHydrationState();
35053513
break;
35063514
case HostComponent:

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+43-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
enableSuspenseLayoutEffectSemantics,
4141
enableUpdaterTracking,
4242
enableCache,
43+
enableTransitionTracing,
4344
} from 'shared/ReactFeatureFlags';
4445
import {
4546
FunctionComponent,
@@ -132,6 +133,8 @@ import {
132133
markCommitTimeOfFallback,
133134
enqueuePendingPassiveProfilerEffect,
134135
restorePendingUpdaters,
136+
addTransitionStartCallbackToPendingTransition,
137+
addTransitionCompleteCallbackToPendingTransition,
135138
} from './ReactFiberWorkLoop.new';
136139
import {
137140
NoFlags as NoHookEffect,
@@ -156,6 +159,7 @@ import {
156159
onCommitUnmount,
157160
} from './ReactFiberDevToolsHook.new';
158161
import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
162+
import {clearTransitionsForLanes} from './ReactFiberLane.new';
159163

160164
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
161165
if (__DEV__) {
@@ -983,8 +987,10 @@ function commitLayoutEffectOnFiber(
983987
case IncompleteClassComponent:
984988
case ScopeComponent:
985989
case OffscreenComponent:
986-
case LegacyHiddenComponent:
990+
case LegacyHiddenComponent: {
987991
break;
992+
}
993+
988994
default:
989995
throw new Error(
990996
'This unit of work tag should not have side-effects. This error is ' +
@@ -2137,13 +2143,13 @@ export function commitMutationEffects(
21372143
inProgressRoot = root;
21382144
nextEffect = firstChild;
21392145

2140-
commitMutationEffects_begin(root);
2146+
commitMutationEffects_begin(root, committedLanes);
21412147

21422148
inProgressLanes = null;
21432149
inProgressRoot = null;
21442150
}
21452151

2146-
function commitMutationEffects_begin(root: FiberRoot) {
2152+
function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes) {
21472153
while (nextEffect !== null) {
21482154
const fiber = nextEffect;
21492155

@@ -2166,17 +2172,17 @@ function commitMutationEffects_begin(root: FiberRoot) {
21662172
ensureCorrectReturnPointer(child, fiber);
21672173
nextEffect = child;
21682174
} else {
2169-
commitMutationEffects_complete(root);
2175+
commitMutationEffects_complete(root, lanes);
21702176
}
21712177
}
21722178
}
21732179

2174-
function commitMutationEffects_complete(root: FiberRoot) {
2180+
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
21752181
while (nextEffect !== null) {
21762182
const fiber = nextEffect;
21772183
setCurrentDebugFiberInDEV(fiber);
21782184
try {
2179-
commitMutationEffectsOnFiber(fiber, root);
2185+
commitMutationEffectsOnFiber(fiber, root, lanes);
21802186
} catch (error) {
21812187
reportUncaughtErrorInDEV(error);
21822188
captureCommitPhaseError(fiber, fiber.return, error);
@@ -2194,13 +2200,43 @@ function commitMutationEffects_complete(root: FiberRoot) {
21942200
}
21952201
}
21962202

2197-
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
2203+
function commitMutationEffectsOnFiber(
2204+
finishedWork: Fiber,
2205+
root: FiberRoot,
2206+
lanes: Lanes,
2207+
) {
21982208
// TODO: The factoring of this phase could probably be improved. Consider
21992209
// switching on the type of work before checking the flags. That's what
22002210
// we do in all the other phases. I think this one is only different
22012211
// because of the shared reconciliation logic below.
22022212
const flags = finishedWork.flags;
22032213

2214+
if (enableTransitionTracing) {
2215+
switch (finishedWork.tag) {
2216+
case HostRoot: {
2217+
const state = finishedWork.memoizedState;
2218+
const transitions = state.transitions;
2219+
if (transitions !== null) {
2220+
transitions.forEach(transition => {
2221+
// TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2222+
addTransitionStartCallbackToPendingTransition({
2223+
transitionName: transition.name,
2224+
startTime: transition.startTime,
2225+
});
2226+
2227+
addTransitionCompleteCallbackToPendingTransition({
2228+
transitionName: transition.name,
2229+
startTime: transition.startTime,
2230+
});
2231+
});
2232+
2233+
clearTransitionsForLanes(root, lanes);
2234+
state.transitions = null;
2235+
}
2236+
}
2237+
}
2238+
}
2239+
22042240
if (flags & ContentReset) {
22052241
commitResetTextContent(finishedWork);
22062242
}

packages/react-reconciler/src/ReactFiberCommitWork.old.js

+43-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
enableSuspenseLayoutEffectSemantics,
4141
enableUpdaterTracking,
4242
enableCache,
43+
enableTransitionTracing,
4344
} from 'shared/ReactFeatureFlags';
4445
import {
4546
FunctionComponent,
@@ -132,6 +133,8 @@ import {
132133
markCommitTimeOfFallback,
133134
enqueuePendingPassiveProfilerEffect,
134135
restorePendingUpdaters,
136+
addTransitionStartCallbackToPendingTransition,
137+
addTransitionCompleteCallbackToPendingTransition,
135138
} from './ReactFiberWorkLoop.old';
136139
import {
137140
NoFlags as NoHookEffect,
@@ -156,6 +159,7 @@ import {
156159
onCommitUnmount,
157160
} from './ReactFiberDevToolsHook.old';
158161
import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
162+
import {clearTransitionsForLanes} from './ReactFiberLane.old';
159163

160164
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
161165
if (__DEV__) {
@@ -983,8 +987,10 @@ function commitLayoutEffectOnFiber(
983987
case IncompleteClassComponent:
984988
case ScopeComponent:
985989
case OffscreenComponent:
986-
case LegacyHiddenComponent:
990+
case LegacyHiddenComponent: {
987991
break;
992+
}
993+
988994
default:
989995
throw new Error(
990996
'This unit of work tag should not have side-effects. This error is ' +
@@ -2137,13 +2143,13 @@ export function commitMutationEffects(
21372143
inProgressRoot = root;
21382144
nextEffect = firstChild;
21392145

2140-
commitMutationEffects_begin(root);
2146+
commitMutationEffects_begin(root, committedLanes);
21412147

21422148
inProgressLanes = null;
21432149
inProgressRoot = null;
21442150
}
21452151

2146-
function commitMutationEffects_begin(root: FiberRoot) {
2152+
function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes) {
21472153
while (nextEffect !== null) {
21482154
const fiber = nextEffect;
21492155

@@ -2166,17 +2172,17 @@ function commitMutationEffects_begin(root: FiberRoot) {
21662172
ensureCorrectReturnPointer(child, fiber);
21672173
nextEffect = child;
21682174
} else {
2169-
commitMutationEffects_complete(root);
2175+
commitMutationEffects_complete(root, lanes);
21702176
}
21712177
}
21722178
}
21732179

2174-
function commitMutationEffects_complete(root: FiberRoot) {
2180+
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
21752181
while (nextEffect !== null) {
21762182
const fiber = nextEffect;
21772183
setCurrentDebugFiberInDEV(fiber);
21782184
try {
2179-
commitMutationEffectsOnFiber(fiber, root);
2185+
commitMutationEffectsOnFiber(fiber, root, lanes);
21802186
} catch (error) {
21812187
reportUncaughtErrorInDEV(error);
21822188
captureCommitPhaseError(fiber, fiber.return, error);
@@ -2194,13 +2200,43 @@ function commitMutationEffects_complete(root: FiberRoot) {
21942200
}
21952201
}
21962202

2197-
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
2203+
function commitMutationEffectsOnFiber(
2204+
finishedWork: Fiber,
2205+
root: FiberRoot,
2206+
lanes: Lanes,
2207+
) {
21982208
// TODO: The factoring of this phase could probably be improved. Consider
21992209
// switching on the type of work before checking the flags. That's what
22002210
// we do in all the other phases. I think this one is only different
22012211
// because of the shared reconciliation logic below.
22022212
const flags = finishedWork.flags;
22032213

2214+
if (enableTransitionTracing) {
2215+
switch (finishedWork.tag) {
2216+
case HostRoot: {
2217+
const state = finishedWork.memoizedState;
2218+
const transitions = state.transitions;
2219+
if (transitions !== null) {
2220+
transitions.forEach(transition => {
2221+
// TODO(luna) Do we want to log TransitionStart in the startTransition callback instead?
2222+
addTransitionStartCallbackToPendingTransition({
2223+
transitionName: transition.name,
2224+
startTime: transition.startTime,
2225+
});
2226+
2227+
addTransitionCompleteCallbackToPendingTransition({
2228+
transitionName: transition.name,
2229+
startTime: transition.startTime,
2230+
});
2231+
});
2232+
2233+
clearTransitionsForLanes(root, lanes);
2234+
state.transitions = null;
2235+
}
2236+
}
2237+
}
2238+
}
2239+
22042240
if (flags & ContentReset) {
22052241
commitResetTextContent(finishedWork);
22062242
}

packages/react-reconciler/src/ReactFiberCompleteWork.new.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
OffscreenComponent,
6363
LegacyHiddenComponent,
6464
CacheComponent,
65+
TracingMarkerComponent,
6566
} from './ReactWorkTags';
6667
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
6768
import {
@@ -141,6 +142,7 @@ import {
141142
enableCache,
142143
enableSuspenseLayoutEffectSemantics,
143144
enablePersistentOffscreenHostContainer,
145+
enableTransitionTracing,
144146
} from 'shared/ReactFeatureFlags';
145147
import {
146148
renderDidSuspend,
@@ -1570,8 +1572,15 @@ function completeWork(
15701572
}
15711573
popCacheProvider(workInProgress, cache);
15721574
bubbleProperties(workInProgress);
1573-
return null;
15741575
}
1576+
return null;
1577+
}
1578+
case TracingMarkerComponent: {
1579+
if (enableTransitionTracing) {
1580+
// Bubble subtree flags before so we can set the flag property
1581+
bubbleProperties(workInProgress);
1582+
}
1583+
return null;
15751584
}
15761585
}
15771586

packages/react-reconciler/src/ReactFiberCompleteWork.old.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
OffscreenComponent,
6363
LegacyHiddenComponent,
6464
CacheComponent,
65+
TracingMarkerComponent,
6566
} from './ReactWorkTags';
6667
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
6768
import {
@@ -141,6 +142,7 @@ import {
141142
enableCache,
142143
enableSuspenseLayoutEffectSemantics,
143144
enablePersistentOffscreenHostContainer,
145+
enableTransitionTracing,
144146
} from 'shared/ReactFeatureFlags';
145147
import {
146148
renderDidSuspend,
@@ -1570,8 +1572,15 @@ function completeWork(
15701572
}
15711573
popCacheProvider(workInProgress, cache);
15721574
bubbleProperties(workInProgress);
1573-
return null;
15741575
}
1576+
return null;
1577+
}
1578+
case TracingMarkerComponent: {
1579+
if (enableTransitionTracing) {
1580+
// Bubble subtree flags before so we can set the flag property
1581+
bubbleProperties(workInProgress);
1582+
}
1583+
return null;
15751584
}
15761585
}
15771586

0 commit comments

Comments
 (0)