Skip to content

Commit 2d72089

Browse files
committed
Trigger default transition indicator if needed
1 parent 0cac32d commit 2d72089

File tree

9 files changed

+412
-6
lines changed

9 files changed

+412
-6
lines changed

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,9 +1142,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
11421142
// TODO: Turn this on once tests are fixed
11431143
// console.error(error);
11441144
}
1145-
function onDefaultTransitionIndicator(): void | (() => void) {
1146-
// TODO: Allow this as an option.
1147-
}
1145+
function onDefaultTransitionIndicator(): void | (() => void) {}
11481146

11491147
let idCounter = 0;
11501148

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
import type {Fiber, FiberRoot} from './ReactInternalTypes';
2121
import type {Lanes} from './ReactFiberLane';
2222
import {
23+
includesLoadingIndicatorLanes,
2324
includesOnlySuspenseyCommitEligibleLanes,
2425
includesOnlyViewTransitionEligibleLanes,
2526
} from './ReactFiberLane';
@@ -60,6 +61,7 @@ import {
6061
enableViewTransition,
6162
enableFragmentRefs,
6263
enableEagerAlternateStateNodeCleanup,
64+
enableDefaultTransitionIndicator,
6365
} from 'shared/ReactFeatureFlags';
6466
import {
6567
FunctionComponent,
@@ -268,13 +270,16 @@ import {
268270
} from './ReactFiberCommitViewTransitions';
269271
import {
270272
viewTransitionMutationContext,
273+
pushRootMutationContext,
271274
pushMutationContext,
272275
popMutationContext,
276+
rootMutationContext,
273277
} from './ReactFiberMutationTracking';
274278
import {
275279
trackNamedViewTransition,
276280
untrackNamedViewTransition,
277281
} from './ReactFiberDuplicateViewTransitions';
282+
import {markIndicatorHandled} from './ReactFiberRootScheduler';
278283

279284
// Used during the commit phase to track the state of the Offscreen component stack.
280285
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
@@ -2216,6 +2221,7 @@ function commitMutationEffectsOnFiber(
22162221
case HostRoot: {
22172222
const prevProfilerEffectDuration = pushNestedEffectDurations();
22182223

2224+
pushRootMutationContext();
22192225
if (supportsResources) {
22202226
prepareToCommitHoistables();
22212227

@@ -2265,6 +2271,18 @@ function commitMutationEffectsOnFiber(
22652271
);
22662272
}
22672273

2274+
popMutationContext(false);
2275+
2276+
if (
2277+
enableDefaultTransitionIndicator &&
2278+
rootMutationContext &&
2279+
includesLoadingIndicatorLanes(lanes)
2280+
) {
2281+
// This root had a mutation. Mark this root as having rendered a manual
2282+
// loading state.
2283+
markIndicatorHandled(root);
2284+
}
2285+
22682286
break;
22692287
}
22702288
case HostPortal: {

packages/react-reconciler/src/ReactFiberLane.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
transitionLaneExpirationMs,
2828
retryLaneExpirationMs,
2929
disableLegacyMode,
30+
enableDefaultTransitionIndicator,
3031
} from 'shared/ReactFeatureFlags';
3132
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
3233
import {clz32} from './clz32';
@@ -640,6 +641,10 @@ export function includesOnlySuspenseyCommitEligibleLanes(
640641
);
641642
}
642643

644+
export function includesLoadingIndicatorLanes(lanes: Lanes): boolean {
645+
return (lanes & (SyncLane | DefaultLane)) !== NoLanes;
646+
}
647+
643648
export function includesBlockingLane(lanes: Lanes): boolean {
644649
const SyncDefaultLanes =
645650
InputContinuousHydrationLane |
@@ -766,6 +771,10 @@ export function createLaneMap<T>(initial: T): LaneMap<T> {
766771

767772
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
768773
root.pendingLanes |= updateLane;
774+
if (enableDefaultTransitionIndicator) {
775+
// Mark that this lane might need a loading indicator to be shown.
776+
root.indicatorLanes |= updateLane & TransitionLanes;
777+
}
769778

770779
// If there are any suspended transitions, it's possible this new update
771780
// could unblock them. Clear the suspended lanes so that we can try rendering
@@ -847,6 +856,10 @@ export function markRootFinished(
847856
root.pingedLanes = NoLanes;
848857
root.warmLanes = NoLanes;
849858

859+
if (enableDefaultTransitionIndicator) {
860+
root.indicatorLanes &= remainingLanes;
861+
}
862+
850863
root.expiredLanes &= remainingLanes;
851864

852865
root.entangledLanes &= remainingLanes;

packages/react-reconciler/src/ReactFiberMutationTracking.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,23 @@
77
* @flow
88
*/
99

10-
import {enableViewTransition} from 'shared/ReactFeatureFlags';
10+
import {
11+
enableDefaultTransitionIndicator,
12+
enableViewTransition,
13+
} from 'shared/ReactFeatureFlags';
1114

15+
export let rootMutationContext: boolean = false;
1216
export let viewTransitionMutationContext: boolean = false;
1317

18+
export function pushRootMutationContext(): void {
19+
if (enableDefaultTransitionIndicator) {
20+
rootMutationContext = false;
21+
}
22+
if (enableViewTransition) {
23+
viewTransitionMutationContext = false;
24+
}
25+
}
26+
1427
export function pushMutationContext(): boolean {
1528
if (!enableViewTransition) {
1629
return false;
@@ -22,12 +35,21 @@ export function pushMutationContext(): boolean {
2235

2336
export function popMutationContext(prev: boolean): void {
2437
if (enableViewTransition) {
38+
if (viewTransitionMutationContext) {
39+
rootMutationContext = true;
40+
}
2541
viewTransitionMutationContext = prev;
2642
}
2743
}
2844

2945
export function trackHostMutation(): void {
46+
// This is extremely hot function that must be inlined. Don't add more stuff.
3047
if (enableViewTransition) {
3148
viewTransitionMutationContext = true;
49+
} else if (enableDefaultTransitionIndicator) {
50+
// We only set this if enableViewTransition is not on. Otherwise we track
51+
// it on the viewTransitionMutationContext and collect it when we pop
52+
// to avoid more than a single operation in this hot path.
53+
rootMutationContext = true;
3254
}
3355
}

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ function FiberRootNode(
7979
this.pingedLanes = NoLanes;
8080
this.warmLanes = NoLanes;
8181
this.expiredLanes = NoLanes;
82+
if (enableDefaultTransitionIndicator) {
83+
this.indicatorLanes = NoLanes;
84+
}
8285
this.errorRecoveryDisabledLanes = NoLanes;
8386
this.shellSuspendCounter = 0;
8487

@@ -94,6 +97,7 @@ function FiberRootNode(
9497

9598
if (enableDefaultTransitionIndicator) {
9699
this.onDefaultTransitionIndicator = onDefaultTransitionIndicator;
100+
this.pendingIndicator = null;
97101
}
98102

99103
this.pooledCache = null;

packages/react-reconciler/src/ReactFiberRootScheduler.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
enableComponentPerformanceTrack,
2121
enableYieldingBeforePassive,
2222
enableGestureTransition,
23+
enableDefaultTransitionIndicator,
2324
} from 'shared/ReactFeatureFlags';
2425
import {
2526
NoLane,
@@ -80,6 +81,9 @@ import {
8081
} from './ReactProfilerTimer';
8182
import {peekEntangledActionLane} from './ReactFiberAsyncAction';
8283

84+
import noop from 'shared/noop';
85+
import reportGlobalError from 'shared/reportGlobalError';
86+
8387
// A linked list of all the roots with pending work. In an idiomatic app,
8488
// there's only a single root, but we do support multi root apps, hence this
8589
// extra complexity. But this module is optimized for the single root case.
@@ -316,8 +320,33 @@ function processRootScheduleInMicrotask() {
316320
flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);
317321
}
318322

319-
// Reset Event Transition Lane so that we allocate a new one next time.
320-
currentEventTransitionLane = NoLane;
323+
if (currentEventTransitionLane !== NoLane) {
324+
// Reset Event Transition Lane so that we allocate a new one next time.
325+
currentEventTransitionLane = NoLane;
326+
startDefaultTransitionIndicatorIfNeeded();
327+
}
328+
}
329+
330+
function startDefaultTransitionIndicatorIfNeeded() {
331+
if (!enableDefaultTransitionIndicator) {
332+
return;
333+
}
334+
// Check all the roots if there are any new indicators needed.
335+
let root = firstScheduledRoot;
336+
while (root !== null) {
337+
if (root.indicatorLanes !== NoLanes && root.pendingIndicator === null) {
338+
// We have new indicator lanes that requires a loading state. Start the
339+
// default transition indicator.
340+
try {
341+
const onDefaultTransitionIndicator = root.onDefaultTransitionIndicator;
342+
root.pendingIndicator = onDefaultTransitionIndicator() || noop;
343+
} catch (x) {
344+
root.pendingIndicator = noop;
345+
reportGlobalError(x);
346+
}
347+
}
348+
root = root.next;
349+
}
321350
}
322351

323352
function scheduleTaskForRootDuringMicrotask(
@@ -664,3 +693,12 @@ export function requestTransitionLane(
664693
export function didCurrentEventScheduleTransition(): boolean {
665694
return currentEventTransitionLane !== NoLane;
666695
}
696+
697+
export function markIndicatorHandled(root: FiberRoot): void {
698+
if (enableDefaultTransitionIndicator) {
699+
// The current transition event rendered a synchronous loading state.
700+
// Clear it from the indicator lanes. We don't need to show a separate
701+
// loading state for this lane.
702+
root.indicatorLanes &= ~currentEventTransitionLane;
703+
}
704+
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@ import {
5252
enableThrottledScheduling,
5353
enableViewTransition,
5454
enableGestureTransition,
55+
enableDefaultTransitionIndicator,
5556
} from 'shared/ReactFeatureFlags';
5657
import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
5758
import ReactSharedInternals from 'shared/ReactSharedInternals';
5859
import is from 'shared/objectIs';
5960

61+
import reportGlobalError from 'shared/reportGlobalError';
62+
6063
import {
6164
// Aliased because `act` will override and push to an internal queue
6265
scheduleCallback as Scheduler_scheduleCallback,
@@ -3593,6 +3596,33 @@ function flushLayoutEffects(): void {
35933596
const finishedWork = pendingFinishedWork;
35943597
const lanes = pendingEffectsLanes;
35953598

3599+
if (enableDefaultTransitionIndicator) {
3600+
const cleanUpIndicator = root.pendingIndicator;
3601+
if (cleanUpIndicator !== null && root.indicatorLanes === NoLanes) {
3602+
// We have now committed all Transitions that needed the default indicator
3603+
// so we can now run the clean up function. We do this in the layout phase
3604+
// so it has the same semantics as if you did it with a useLayoutEffect or
3605+
// if it was reset automatically with useOptimistic.
3606+
const prevTransition = ReactSharedInternals.T;
3607+
ReactSharedInternals.T = null;
3608+
const previousPriority = getCurrentUpdatePriority();
3609+
setCurrentUpdatePriority(DiscreteEventPriority);
3610+
const prevExecutionContext = executionContext;
3611+
executionContext |= CommitContext;
3612+
root.pendingIndicator = null;
3613+
try {
3614+
cleanUpIndicator();
3615+
} catch (x) {
3616+
reportGlobalError(x);
3617+
} finally {
3618+
// Reset the priority to the previous non-sync value.
3619+
executionContext = prevExecutionContext;
3620+
setCurrentUpdatePriority(previousPriority);
3621+
ReactSharedInternals.T = prevTransition;
3622+
}
3623+
}
3624+
}
3625+
35963626
const subtreeHasLayoutEffects =
35973627
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
35983628
const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;

packages/react-reconciler/src/ReactInternalTypes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ type BaseFiberRootProperties = {
248248
pingedLanes: Lanes,
249249
warmLanes: Lanes,
250250
expiredLanes: Lanes,
251+
indicatorLanes: Lanes, // enableDefaultTransitionIndicator only
251252
errorRecoveryDisabledLanes: Lanes,
252253
shellSuspendCounter: number,
253254

@@ -280,7 +281,9 @@ type BaseFiberRootProperties = {
280281
errorInfo: {+componentStack?: ?string},
281282
) => void,
282283

284+
// enableDefaultTransitionIndicator only
283285
onDefaultTransitionIndicator: () => void | (() => void),
286+
pendingIndicator: null | (() => void),
284287

285288
formState: ReactFormState<any, any> | null,
286289

0 commit comments

Comments
 (0)