Skip to content

Commit

Permalink
[DevTools][Transition Tracing] onTransitionComplete and onTransitionS…
Browse files Browse the repository at this point in the history
…tart 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
  • Loading branch information
lunaruan authored Feb 24, 2022
1 parent 68cb55f commit 42f15b3
Show file tree
Hide file tree
Showing 25 changed files with 831 additions and 37 deletions.
6 changes: 5 additions & 1 deletion packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
MutableSourceSubscribeFn,
ReactContext,
ReactProviderType,
StartTransitionOptions,
} from 'shared/ReactTypes';
import type {
Fiber,
Expand Down Expand Up @@ -291,7 +292,10 @@ function useSyncExternalStore<T>(
return value;
}

function useTransition(): [boolean, (() => void) => void] {
function useTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
// useTransition() composes multiple hooks internally.
// Advance the current hook index the same number of times
// so that subsequent hooks have the right memoized state.
Expand Down
10 changes: 9 additions & 1 deletion packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ import {
markSkippedUpdateLanes,
getWorkInProgressRoot,
pushRenderLanes,
getWorkInProgressTransitions,
} from './ReactFiberWorkLoop.new';
import {setWorkInProgressVersion} from './ReactMutableSource.new';
import {
Expand Down Expand Up @@ -1336,6 +1337,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
}
}

if (enableTransitionTracing) {
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}

// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
Expand Down Expand Up @@ -3495,12 +3500,15 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
const root: FiberRoot = workInProgress.stateNode;
if (enableCache) {
const root: FiberRoot = workInProgress.stateNode;
const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
pushRootCachePool(root);
}
if (enableTransitionTracing) {
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}
resetHydrationState();
break;
case HostComponent:
Expand Down
10 changes: 9 additions & 1 deletion packages/react-reconciler/src/ReactFiberBeginWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ import {
markSkippedUpdateLanes,
getWorkInProgressRoot,
pushRenderLanes,
getWorkInProgressTransitions,
} from './ReactFiberWorkLoop.old';
import {setWorkInProgressVersion} from './ReactMutableSource.old';
import {
Expand Down Expand Up @@ -1336,6 +1337,10 @@ function updateHostRoot(current, workInProgress, renderLanes) {
}
}

if (enableTransitionTracing) {
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}

// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
Expand Down Expand Up @@ -3495,12 +3500,15 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
const root: FiberRoot = workInProgress.stateNode;
if (enableCache) {
const root: FiberRoot = workInProgress.stateNode;
const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
pushRootCachePool(root);
}
if (enableTransitionTracing) {
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}
resetHydrationState();
break;
case HostComponent:
Expand Down
50 changes: 43 additions & 7 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
enableSuspenseLayoutEffectSemantics,
enableUpdaterTracking,
enableCache,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -132,6 +133,8 @@ import {
markCommitTimeOfFallback,
enqueuePendingPassiveProfilerEffect,
restorePendingUpdaters,
addTransitionStartCallbackToPendingTransition,
addTransitionCompleteCallbackToPendingTransition,
} from './ReactFiberWorkLoop.new';
import {
NoFlags as NoHookEffect,
Expand All @@ -156,6 +159,7 @@ import {
onCommitUnmount,
} from './ReactFiberDevToolsHook.new';
import {releaseCache, retainCache} from './ReactFiberCacheComponent.new';
import {clearTransitionsForLanes} from './ReactFiberLane.new';

let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
Expand Down Expand Up @@ -983,8 +987,10 @@ function commitLayoutEffectOnFiber(
case IncompleteClassComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
case LegacyHiddenComponent: {
break;
}

default:
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
Expand Down Expand Up @@ -2137,13 +2143,13 @@ export function commitMutationEffects(
inProgressRoot = root;
nextEffect = firstChild;

commitMutationEffects_begin(root);
commitMutationEffects_begin(root, committedLanes);

inProgressLanes = null;
inProgressRoot = null;
}

function commitMutationEffects_begin(root: FiberRoot) {
function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes) {
while (nextEffect !== null) {
const fiber = nextEffect;

Expand All @@ -2166,17 +2172,17 @@ function commitMutationEffects_begin(root: FiberRoot) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root);
commitMutationEffects_complete(root, lanes);
}
}
}

function commitMutationEffects_complete(root: FiberRoot) {
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitMutationEffectsOnFiber(fiber, root);
commitMutationEffectsOnFiber(fiber, root, lanes);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
Expand All @@ -2194,13 +2200,43 @@ function commitMutationEffects_complete(root: FiberRoot) {
}
}

function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
// TODO: The factoring of this phase could probably be improved. Consider
// switching on the type of work before checking the flags. That's what
// we do in all the other phases. I think this one is only different
// 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);
}
Expand Down
50 changes: 43 additions & 7 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
enableSuspenseLayoutEffectSemantics,
enableUpdaterTracking,
enableCache,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -132,6 +133,8 @@ import {
markCommitTimeOfFallback,
enqueuePendingPassiveProfilerEffect,
restorePendingUpdaters,
addTransitionStartCallbackToPendingTransition,
addTransitionCompleteCallbackToPendingTransition,
} from './ReactFiberWorkLoop.old';
import {
NoFlags as NoHookEffect,
Expand All @@ -156,6 +159,7 @@ import {
onCommitUnmount,
} from './ReactFiberDevToolsHook.old';
import {releaseCache, retainCache} from './ReactFiberCacheComponent.old';
import {clearTransitionsForLanes} from './ReactFiberLane.old';

let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
if (__DEV__) {
Expand Down Expand Up @@ -983,8 +987,10 @@ function commitLayoutEffectOnFiber(
case IncompleteClassComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
case LegacyHiddenComponent: {
break;
}

default:
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
Expand Down Expand Up @@ -2137,13 +2143,13 @@ export function commitMutationEffects(
inProgressRoot = root;
nextEffect = firstChild;

commitMutationEffects_begin(root);
commitMutationEffects_begin(root, committedLanes);

inProgressLanes = null;
inProgressRoot = null;
}

function commitMutationEffects_begin(root: FiberRoot) {
function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes) {
while (nextEffect !== null) {
const fiber = nextEffect;

Expand All @@ -2166,17 +2172,17 @@ function commitMutationEffects_begin(root: FiberRoot) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root);
commitMutationEffects_complete(root, lanes);
}
}
}

function commitMutationEffects_complete(root: FiberRoot) {
function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitMutationEffectsOnFiber(fiber, root);
commitMutationEffectsOnFiber(fiber, root, lanes);
} catch (error) {
reportUncaughtErrorInDEV(error);
captureCommitPhaseError(fiber, fiber.return, error);
Expand All @@ -2194,13 +2200,43 @@ function commitMutationEffects_complete(root: FiberRoot) {
}
}

function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
// TODO: The factoring of this phase could probably be improved. Consider
// switching on the type of work before checking the flags. That's what
// we do in all the other phases. I think this one is only different
// 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);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {
Expand Down Expand Up @@ -141,6 +142,7 @@ import {
enableCache,
enableSuspenseLayoutEffectSemantics,
enablePersistentOffscreenHostContainer,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {
renderDidSuspend,
Expand Down Expand Up @@ -1570,8 +1572,15 @@ function completeWork(
}
popCacheProvider(workInProgress, cache);
bubbleProperties(workInProgress);
return null;
}
return null;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
// Bubble subtree flags before so we can set the flag property
bubbleProperties(workInProgress);
}
return null;
}
}

Expand Down
11 changes: 10 additions & 1 deletion packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {
Expand Down Expand Up @@ -141,6 +142,7 @@ import {
enableCache,
enableSuspenseLayoutEffectSemantics,
enablePersistentOffscreenHostContainer,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {
renderDidSuspend,
Expand Down Expand Up @@ -1570,8 +1572,15 @@ function completeWork(
}
popCacheProvider(workInProgress, cache);
bubbleProperties(workInProgress);
return null;
}
return null;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
// Bubble subtree flags before so we can set the flag property
bubbleProperties(workInProgress);
}
return null;
}
}

Expand Down
Loading

0 comments on commit 42f15b3

Please sign in to comment.