Skip to content

Commit c98d396

Browse files
committed
Trigger default transition indicator if needed
1 parent b10e38d commit c98d396

9 files changed

+412
-6
lines changed

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

+1-3
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

+18
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';
@@ -59,6 +60,7 @@ import {
5960
enableComponentPerformanceTrack,
6061
enableViewTransition,
6162
enableFragmentRefs,
63+
enableDefaultTransitionIndicator,
6264
} from 'shared/ReactFeatureFlags';
6365
import {
6466
FunctionComponent,
@@ -267,13 +269,16 @@ import {
267269
} from './ReactFiberCommitViewTransitions';
268270
import {
269271
viewTransitionMutationContext,
272+
pushRootMutationContext,
270273
pushMutationContext,
271274
popMutationContext,
275+
rootMutationContext,
272276
} from './ReactFiberMutationTracking';
273277
import {
274278
trackNamedViewTransition,
275279
untrackNamedViewTransition,
276280
} from './ReactFiberDuplicateViewTransitions';
281+
import {markIndicatorHandled} from './ReactFiberRootScheduler';
277282

278283
// Used during the commit phase to track the state of the Offscreen component stack.
279284
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
@@ -2201,6 +2206,7 @@ function commitMutationEffectsOnFiber(
22012206
case HostRoot: {
22022207
const prevProfilerEffectDuration = pushNestedEffectDurations();
22032208

2209+
pushRootMutationContext();
22042210
if (supportsResources) {
22052211
prepareToCommitHoistables();
22062212

@@ -2250,6 +2256,18 @@ function commitMutationEffectsOnFiber(
22502256
);
22512257
}
22522258

2259+
popMutationContext(false);
2260+
2261+
if (
2262+
enableDefaultTransitionIndicator &&
2263+
rootMutationContext &&
2264+
includesLoadingIndicatorLanes(lanes)
2265+
) {
2266+
// This root had a mutation. Mark this root as having rendered a manual
2267+
// loading state.
2268+
markIndicatorHandled(root);
2269+
}
2270+
22532271
break;
22542272
}
22552273
case HostPortal: {

packages/react-reconciler/src/ReactFiberLane.js

+13
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

+23-1
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

+4
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

+40-2
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,
@@ -79,6 +80,9 @@ import {
7980
syncNestedUpdateFlag,
8081
} from './ReactProfilerTimer';
8182

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

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

322351
function scheduleTaskForRootDuringMicrotask(
@@ -655,3 +684,12 @@ export function requestTransitionLane(
655684
export function didCurrentEventScheduleTransition(): boolean {
656685
return currentEventTransitionLane !== NoLane;
657686
}
687+
688+
export function markIndicatorHandled(root: FiberRoot): void {
689+
if (enableDefaultTransitionIndicator) {
690+
// The current transition event rendered a synchronous loading state.
691+
// Clear it from the indicator lanes. We don't need to show a separate
692+
// loading state for this lane.
693+
root.indicatorLanes &= ~currentEventTransitionLane;
694+
}
695+
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

+30
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,
@@ -3601,6 +3604,33 @@ function flushLayoutEffects(): void {
36013604
const finishedWork = pendingFinishedWork;
36023605
const lanes = pendingEffectsLanes;
36033606

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

packages/react-reconciler/src/ReactInternalTypes.js

+3
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)