Skip to content

Commit e576ea1

Browse files
committed
Don't group Idle/Offscreen work with other work
When we suspend we always try a lower level but we shouldn't try offscreen.
1 parent d75323f commit e576ea1

9 files changed

+135
-4
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
warnAboutUnmockedScheduler,
2626
flushSuspenseFallbacksInTests,
2727
disableSchedulerTimeoutBasedOnReactExpirationTime,
28+
enableTrainModelFix,
2829
} from 'shared/ReactFeatureFlags';
2930
import ReactSharedInternals from 'shared/ReactSharedInternals';
3031
import invariant from 'shared/invariant';
@@ -539,9 +540,19 @@ function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
539540
// on whichever is higher priority.
540541
const lastPingedTime = root.lastPingedTime;
541542
const nextKnownPendingLevel = root.nextKnownPendingLevel;
542-
return lastPingedTime > nextKnownPendingLevel
543-
? lastPingedTime
544-
: nextKnownPendingLevel;
543+
const nextLevel =
544+
lastPingedTime > nextKnownPendingLevel
545+
? lastPingedTime
546+
: nextKnownPendingLevel;
547+
if (
548+
enableTrainModelFix &&
549+
nextLevel <= Idle &&
550+
firstPendingTime !== nextLevel
551+
) {
552+
// Don't work on Idle/Never priority unless everything else is committed.
553+
return NoWork;
554+
}
555+
return nextLevel;
545556
}
546557

547558
// Use this function to schedule a task for a root. There's only one task per
@@ -2362,7 +2373,7 @@ export function pingSuspendedRoot(
23622373
// Mark the time at which this ping was scheduled.
23632374
root.lastPingedTime = suspendedTime;
23642375

2365-
if (root.finishedExpirationTime === suspendedTime) {
2376+
if (!enableTrainModelFix && root.finishedExpirationTime === suspendedTime) {
23662377
// If there's a pending fallback waiting to commit, throw it away.
23672378
root.finishedExpirationTime = NoWork;
23682379
root.finishedWork = null;

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,4 +2630,116 @@ describe('ReactSuspenseWithNoopRenderer', () => {
26302630

26312631
expect(root).toMatchRenderedOutput(<span prop="Foo" />);
26322632
});
2633+
2634+
it('should not render hidden content while suspended on higher pri', async () => {
2635+
function Offscreen() {
2636+
Scheduler.unstable_yieldValue('Offscreen');
2637+
return 'Offscreen';
2638+
}
2639+
function App({showContent}) {
2640+
React.useLayoutEffect(() => {
2641+
Scheduler.unstable_yieldValue('Commit');
2642+
});
2643+
return (
2644+
<>
2645+
<div hidden={true}>
2646+
<Offscreen />
2647+
</div>
2648+
<Suspense fallback={<Text text="Loading..." />}>
2649+
{showContent ? <AsyncText text="A" ms={2000} /> : null}
2650+
</Suspense>
2651+
</>
2652+
);
2653+
}
2654+
2655+
// Initial render.
2656+
ReactNoop.render(<App showContent={false} />);
2657+
expect(Scheduler).toFlushAndYieldThrough(['Commit']);
2658+
expect(ReactNoop).toMatchRenderedOutput(<div hidden={true} />);
2659+
2660+
// Start transition.
2661+
React.unstable_withSuspenseConfig(
2662+
() => {
2663+
ReactNoop.render(<App showContent={true} />);
2664+
},
2665+
{timeoutMs: 2000},
2666+
);
2667+
2668+
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2669+
Scheduler.unstable_advanceTime(2000);
2670+
await advanceTimers(2000);
2671+
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2672+
expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2673+
expect(ReactNoop).toMatchRenderedOutput(
2674+
<>
2675+
<div hidden={true} />
2676+
<span prop="A" />
2677+
</>,
2678+
);
2679+
expect(Scheduler).toFlushAndYield(['Offscreen']);
2680+
expect(ReactNoop).toMatchRenderedOutput(
2681+
<>
2682+
<div hidden={true}>Offscreen</div>
2683+
<span prop="A" />
2684+
</>,
2685+
);
2686+
});
2687+
2688+
it('should be able to unblock higher pri content before suspended hidden', async () => {
2689+
function Offscreen() {
2690+
Scheduler.unstable_yieldValue('Offscreen');
2691+
return 'Offscreen';
2692+
}
2693+
function App({showContent}) {
2694+
React.useLayoutEffect(() => {
2695+
Scheduler.unstable_yieldValue('Commit');
2696+
});
2697+
return (
2698+
<Suspense fallback={<Text text="Loading..." />}>
2699+
<div hidden={true}>
2700+
<AsyncText text="A" ms={2000} />
2701+
<Offscreen />
2702+
</div>
2703+
{showContent ? <AsyncText text="A" ms={2000} /> : null}
2704+
</Suspense>
2705+
);
2706+
}
2707+
2708+
// Initial render.
2709+
ReactNoop.render(<App showContent={false} />);
2710+
expect(Scheduler).toFlushAndYieldThrough(['Commit']);
2711+
expect(ReactNoop).toMatchRenderedOutput(<div hidden={true} />);
2712+
2713+
// Partially render through the hidden content.
2714+
expect(Scheduler).toFlushAndYieldThrough(['Suspend! [A]']);
2715+
2716+
// Start transition.
2717+
React.unstable_withSuspenseConfig(
2718+
() => {
2719+
ReactNoop.render(<App showContent={true} />);
2720+
},
2721+
{timeoutMs: 5000},
2722+
);
2723+
2724+
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2725+
Scheduler.unstable_advanceTime(2000);
2726+
await advanceTimers(2000);
2727+
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2728+
expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2729+
expect(ReactNoop).toMatchRenderedOutput(
2730+
<>
2731+
<div hidden={true} />
2732+
<span prop="A" />
2733+
</>,
2734+
);
2735+
expect(Scheduler).toFlushAndYield(['A', 'Offscreen']);
2736+
expect(ReactNoop).toMatchRenderedOutput(
2737+
<>
2738+
<div hidden={true}>
2739+
<span prop="A" />Offscreen
2740+
</div>
2741+
<span prop="A" />
2742+
</>,
2743+
);
2744+
});
26332745
});

packages/shared/ReactFeatureFlags.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export const disableLegacyContext = false;
8888

8989
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
9090

91+
export const enableTrainModelFix = __EXPERIMENTAL__;
92+
9193
export const enableTrustedTypesIntegration = false;
9294

9395
// Flag to turn event.target and event.currentTarget in ReactNative from a reactTag to a component instance

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
4242
export const warnAboutStringRefs = false;
4343
export const disableLegacyContext = false;
4444
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
45+
export const enableTrainModelFix = false;
4546
export const enableTrustedTypesIntegration = false;
4647

4748
// Only used in www builds.

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
3636
export const warnAboutStringRefs = false;
3737
export const disableLegacyContext = false;
3838
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39+
export const enableTrainModelFix = false;
3940
export const enableTrustedTypesIntegration = false;
4041
export const enableNativeTargetAsInstance = false;
4142

packages/shared/forks/ReactFeatureFlags.persistent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
3636
export const warnAboutStringRefs = false;
3737
export const disableLegacyContext = false;
3838
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39+
export const enableTrainModelFix = false;
3940
export const enableTrustedTypesIntegration = false;
4041
export const enableNativeTargetAsInstance = false;
4142

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
3636
export const warnAboutStringRefs = false;
3737
export const disableLegacyContext = false;
3838
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39+
export const enableTrainModelFix = false;
3940
export const enableTrustedTypesIntegration = false;
4041
export const enableNativeTargetAsInstance = false;
4142

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
3434
export const warnAboutStringRefs = false;
3535
export const disableLegacyContext = false;
3636
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
37+
export const enableTrainModelFix = false;
3738
export const enableTrustedTypesIntegration = false;
3839
export const enableNativeTargetAsInstance = false;
3940

packages/shared/forks/ReactFeatureFlags.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const {
1616
disableInputAttributeSyncing,
1717
enableTrustedTypesIntegration,
1818
enableSelectiveHydration,
19+
enableTrainModelFix,
1920
} = require('ReactFeatureFlags');
2021

2122
// In www, we have experimental support for gathering data

0 commit comments

Comments
 (0)