Skip to content

Commit 8da36d0

Browse files
authored
Enable Suspensey Images inside <ViewTransition> subtrees (facebook#32820)
Even if the `enableSuspenseyImages` flag is off. Started View Transitions already wait for Suspensey Fonts and this is another Suspensey feature that is even more important for View Transitions - even though we eventually want it all the time. So this uses `<ViewTransition>` as an early opt-in for that tree into Suspensey Images, which we can ship in a minor. If you're doing an update inside a ViewTransition then we're eligible to start a ViewTransition in any Transition that might suspend. Even if that doesn't end up animating after all, we still consider it Suspensey. We could try to suspend inside the startViewTransition but that's not how it would work with `enableSuspenseyImages` on and we can't do that for startGestureTransition. Even so we still need some opt-in to trigger the Suspense fallback even before we know whether we'll animate or not. So the simple solution is just that `<ViewTransition>` opts in the whole subtree into Suspensey Images in general. In this PR I disable `enableSuspenseyImages` in experimental so that we can instead test the path that only enables it inside `<ViewTransition>` tree since that's the path that would next graduate to a minor.
1 parent ea05b75 commit 8da36d0

File tree

6 files changed

+42
-10
lines changed

6 files changed

+42
-10
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import {
107107
disableCommentsAsDOMContainers,
108108
enableSuspenseyImages,
109109
enableSrcObject,
110+
enableViewTransition,
110111
} from 'shared/ReactFeatureFlags';
111112
import {
112113
HostComponent,
@@ -5112,7 +5113,7 @@ export function isHostHoistableType(
51125113
}
51135114

51145115
export function maySuspendCommit(type: Type, props: Props): boolean {
5115-
if (!enableSuspenseyImages) {
5116+
if (!enableSuspenseyImages && !enableViewTransition) {
51165117
return false;
51175118
}
51185119
// Suspensey images are the default, unless you opt-out of with either
@@ -5206,7 +5207,7 @@ export function suspendInstance(
52065207
type: Type,
52075208
props: Props,
52085209
): void {
5209-
if (!enableSuspenseyImages) {
5210+
if (!enableSuspenseyImages && !enableViewTransition) {
52105211
return;
52115212
}
52125213
if (suspendedState === null) {

packages/react-reconciler/src/ReactFiber.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
disableLegacyMode,
4242
enableObjectFiber,
4343
enableViewTransition,
44+
enableSuspenseyImages,
4445
} from 'shared/ReactFeatureFlags';
4546
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
4647
import {ConcurrentRoot} from './ReactRootTags';
@@ -89,6 +90,7 @@ import {
8990
StrictLegacyMode,
9091
StrictEffectsMode,
9192
NoStrictPassiveEffectsMode,
93+
SuspenseyImagesMode,
9294
} from './ReactTypeOfMode';
9395
import {
9496
REACT_FORWARD_REF_TYPE,
@@ -875,6 +877,11 @@ export function createFiberFromViewTransition(
875877
lanes: Lanes,
876878
key: null | string,
877879
): Fiber {
880+
if (!enableSuspenseyImages) {
881+
// Render a ViewTransition component opts into SuspenseyImages mode even
882+
// when the flag is off.
883+
mode |= SuspenseyImagesMode;
884+
}
878885
const fiber = createFiber(ViewTransitionComponent, pendingProps, key, mode);
879886
fiber.elementType = REACT_VIEW_TRANSITION_TYPE;
880887
fiber.lanes = lanes;

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
disableLegacyMode,
4343
enableSiblingPrerendering,
4444
enableViewTransition,
45+
enableSuspenseyImages,
4546
} from 'shared/ReactFeatureFlags';
4647

4748
import {now} from './Scheduler';
@@ -77,7 +78,12 @@ import {
7778
ViewTransitionComponent,
7879
ActivityComponent,
7980
} from './ReactWorkTags';
80-
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
81+
import {
82+
NoMode,
83+
ConcurrentMode,
84+
ProfileMode,
85+
SuspenseyImagesMode,
86+
} from './ReactTypeOfMode';
8187
import {
8288
Placement,
8389
Update,
@@ -555,9 +561,11 @@ function preloadInstanceAndSuspendIfNeeded(
555561
renderLanes: Lanes,
556562
) {
557563
const maySuspend =
558-
oldProps === null
564+
(enableSuspenseyImages ||
565+
(workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&
566+
(oldProps === null
559567
? maySuspendCommit(type, newProps)
560-
: maySuspendCommitOnUpdate(type, oldProps, newProps);
568+
: maySuspendCommitOnUpdate(type, oldProps, newProps));
561569

562570
if (!maySuspend) {
563571
// If this flag was set previously, we can remove it. The flag

packages/react-reconciler/src/ReactTypeOfMode.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ export type TypeOfMode = number;
1212
export const NoMode = /* */ 0b0000000;
1313
// TODO: Remove ConcurrentMode by reading from the root tag instead
1414
export const ConcurrentMode = /* */ 0b0000001;
15-
export const ProfileMode = /* */ 0b0000010;
15+
export const ProfileMode = /* */ 0b0000010;
1616
//export const DebugTracingMode = /* */ 0b0000100; // Removed
1717
export const StrictLegacyMode = /* */ 0b0001000;
1818
export const StrictEffectsMode = /* */ 0b0010000;
1919
export const NoStrictPassiveEffectsMode = /* */ 0b1000000;
20+
// Keep track of if we're in a SuspenseyImages eligible subtree.
21+
// TODO: Remove this when enableSuspenseyImages ship where it's always on.
22+
export const SuspenseyImagesMode = /* */ 0b0100000;

packages/react-reconciler/src/__tests__/ReactSuspenseyCommitPhase-test.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('ReactSuspenseyCommitPhase', () => {
5050
);
5151
}
5252

53+
// @gate enableSuspenseyImages
5354
it('suspend commit during initial mount', async () => {
5455
const root = ReactNoop.createRoot();
5556
await act(async () => {
@@ -70,6 +71,7 @@ describe('ReactSuspenseyCommitPhase', () => {
7071
expect(root).toMatchRenderedOutput(<suspensey-thing src="A" />);
7172
});
7273

74+
// @gate enableSuspenseyImages
7375
it('suspend commit during update', async () => {
7476
const root = ReactNoop.createRoot();
7577
await act(() => resolveSuspenseyThing('A'));
@@ -105,6 +107,7 @@ describe('ReactSuspenseyCommitPhase', () => {
105107
expect(root).toMatchRenderedOutput(<suspensey-thing src="B" />);
106108
});
107109

110+
// @gate enableSuspenseyImages
108111
it('suspend commit during initial mount at the root', async () => {
109112
const root = ReactNoop.createRoot();
110113
await act(async () => {
@@ -121,6 +124,7 @@ describe('ReactSuspenseyCommitPhase', () => {
121124
expect(root).toMatchRenderedOutput(<suspensey-thing src="A" />);
122125
});
123126

127+
// @gate enableSuspenseyImages
124128
it('suspend commit during update at the root', async () => {
125129
const root = ReactNoop.createRoot();
126130
await act(() => resolveSuspenseyThing('A'));
@@ -147,6 +151,7 @@ describe('ReactSuspenseyCommitPhase', () => {
147151
expect(root).toMatchRenderedOutput(<suspensey-thing src="B" />);
148152
});
149153

154+
// @gate enableSuspenseyImages
150155
it('suspend commit during urgent initial mount', async () => {
151156
const root = ReactNoop.createRoot();
152157
await act(async () => {
@@ -165,6 +170,7 @@ describe('ReactSuspenseyCommitPhase', () => {
165170
expect(root).toMatchRenderedOutput(<suspensey-thing src="A" />);
166171
});
167172

173+
// @gate enableSuspenseyImages
168174
it('suspend commit during urgent update', async () => {
169175
const root = ReactNoop.createRoot();
170176
await act(() => resolveSuspenseyThing('A'));
@@ -203,6 +209,7 @@ describe('ReactSuspenseyCommitPhase', () => {
203209
expect(root).toMatchRenderedOutput(<suspensey-thing src="B" />);
204210
});
205211

212+
// @gate enableSuspenseyImages
206213
it('suspends commit during urgent initial mount at the root', async () => {
207214
const root = ReactNoop.createRoot();
208215
await act(async () => {
@@ -217,6 +224,7 @@ describe('ReactSuspenseyCommitPhase', () => {
217224
expect(root).toMatchRenderedOutput(<suspensey-thing src="A" />);
218225
});
219226

227+
// @gate enableSuspenseyImages
220228
it('suspends commit during urgent update at the root', async () => {
221229
const root = ReactNoop.createRoot();
222230
await act(() => resolveSuspenseyThing('A'));
@@ -239,6 +247,7 @@ describe('ReactSuspenseyCommitPhase', () => {
239247
expect(root).toMatchRenderedOutput(<suspensey-thing src="B" />);
240248
});
241249

250+
// @gate enableSuspenseyImages
242251
it('does suspend commit during urgent initial mount at the root when sync rendering', async () => {
243252
const root = ReactNoop.createRoot();
244253
await act(async () => {
@@ -256,6 +265,7 @@ describe('ReactSuspenseyCommitPhase', () => {
256265
expect(root).toMatchRenderedOutput(<suspensey-thing src="A" />);
257266
});
258267

268+
// @gate enableSuspenseyImages
259269
it('does suspend commit during urgent update at the root when sync rendering', async () => {
260270
const root = ReactNoop.createRoot();
261271
await act(() => resolveSuspenseyThing('A'));
@@ -283,6 +293,7 @@ describe('ReactSuspenseyCommitPhase', () => {
283293
expect(root).toMatchRenderedOutput(<suspensey-thing src="B" />);
284294
});
285295

296+
// @gate enableSuspenseyImages
286297
it('an urgent update interrupts a suspended commit', async () => {
287298
const root = ReactNoop.createRoot();
288299

@@ -305,6 +316,7 @@ describe('ReactSuspenseyCommitPhase', () => {
305316
expect(root).toMatchRenderedOutput('Something else');
306317
});
307318

319+
// @gate enableSuspenseyImages
308320
it('a transition update interrupts a suspended commit', async () => {
309321
const root = ReactNoop.createRoot();
310322

@@ -329,7 +341,7 @@ describe('ReactSuspenseyCommitPhase', () => {
329341
expect(root).toMatchRenderedOutput('Something else');
330342
});
331343

332-
// @gate enableSuspenseList
344+
// @gate enableSuspenseList && enableSuspenseyImages
333345
it('demonstrate current behavior when used with SuspenseList (not ideal)', async () => {
334346
function App() {
335347
return (
@@ -381,6 +393,7 @@ describe('ReactSuspenseyCommitPhase', () => {
381393
);
382394
});
383395

396+
// @gate enableSuspenseyImages
384397
it('avoid triggering a fallback if resource loads immediately', async () => {
385398
const root = ReactNoop.createRoot();
386399
await act(async () => {
@@ -429,7 +442,7 @@ describe('ReactSuspenseyCommitPhase', () => {
429442
);
430443
});
431444

432-
// @gate enableActivity
445+
// @gate enableActivity && enableSuspenseyImages
433446
it("host instances don't suspend during prerendering, but do suspend when they are revealed", async () => {
434447
function More() {
435448
Scheduler.log('More');
@@ -493,7 +506,7 @@ describe('ReactSuspenseyCommitPhase', () => {
493506
});
494507

495508
// FIXME: Should pass with `enableYieldingBeforePassive`
496-
// @gate !enableYieldingBeforePassive
509+
// @gate !enableYieldingBeforePassive && enableSuspenseyImages
497510
it('runs passive effects after suspended commit resolves', async () => {
498511
function Effect() {
499512
React.useEffect(() => {

packages/shared/ReactFeatureFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const enableGestureTransition = __EXPERIMENTAL__;
9696

9797
export const enableScrollEndPolyfill = __EXPERIMENTAL__;
9898

99-
export const enableSuspenseyImages = __EXPERIMENTAL__;
99+
export const enableSuspenseyImages = false;
100100

101101
export const enableSrcObject = __EXPERIMENTAL__;
102102

0 commit comments

Comments
 (0)