Don't group Idle/Offscreen work with other work (#17456)
When we suspend we always try a lower level but we shouldn't try offscreen.
Sebastian Markbåge committed
Dec 3, 2019 at 13:38 UTC
dc18b8b8d24d188517caeff624e999f197f3b9b6
9 files changed
+137
-5
packages/react-reconciler/src/ReactFiberWorkLoop.js
+15
-4
@@ -25,6 +25,7 @@ import {
25
warnAboutUnmockedScheduler,
26
flushSuspenseFallbacksInTests,
27
disableSchedulerTimeoutBasedOnReactExpirationTime,
28
+ enableTrainModelFix,
29
} from 'shared/ReactFeatureFlags';
30
import ReactSharedInternals from 'shared/ReactSharedInternals';
31
import invariant from 'shared/invariant';
@@ -539,9 +540,19 @@ function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
540
// on whichever is higher priority.
541
const lastPingedTime = root.lastPingedTime;
542
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;
556
}
557
558
// Use this function to schedule a task for a root. There's only one task per
@@ -2362,7 +2373,7 @@ export function pingSuspendedRoot(
2373
// Mark the time at which this ping was scheduled.
2374
root.lastPingedTime = suspendedTime;
2375
2365
- if (root.finishedExpirationTime === suspendedTime) {
2376
+ if (!enableTrainModelFix && root.finishedExpirationTime === suspendedTime) {
2377
// If there's a pending fallback waiting to commit, throw it away.
2378
root.finishedExpirationTime = NoWork;
2379
root.finishedWork = null;
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+114
-1
@@ -2222,7 +2222,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2222
Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () =>
2223
ReactNoop.render(<Foo renderContent={2} />),
2224
);
2225
- expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading A...']);
2225
+ // We won't even work on Idle priority.
2226
+ expect(Scheduler).toFlushAndYield([]);
2227
2228
// We're still suspended.
2229
expect(ReactNoop.getChildren()).toEqual([]);
@@ -2789,4 +2790,116 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2790
2791
expect(root).toMatchRenderedOutput(<span prop="Foo" />);
2792
});
2793
+
2794
+ it('should not render hidden content while suspended on higher pri', async () => {
2795
+ function Offscreen() {
2796
+ Scheduler.unstable_yieldValue('Offscreen');
2797
+ return 'Offscreen';
2798
+ }
2799
+ function App({showContent}) {
2800
+ React.useLayoutEffect(() => {
2801
+ Scheduler.unstable_yieldValue('Commit');
2802
+ });
2803
+ return (
2804
+ <>
2805
+ <div hidden={true}>
2806
+ <Offscreen />
2807
+ </div>
2808
+ <Suspense fallback={<Text text="Loading..." />}>
2809
+ {showContent ? <AsyncText text="A" ms={2000} /> : null}
2810
+ </Suspense>
2811
+ </>
2812
+ );
2813
+ }
2814
+
2815
+ // Initial render.
2816
+ ReactNoop.render(<App showContent={false} />);
2817
+ expect(Scheduler).toFlushAndYieldThrough(['Commit']);
2818
+ expect(ReactNoop).toMatchRenderedOutput(<div hidden={true} />);
2819
+
2820
+ // Start transition.
2821
+ React.unstable_withSuspenseConfig(
2822
+ () => {
2823
+ ReactNoop.render(<App showContent={true} />);
2824
+ },
2825
+ {timeoutMs: 2000},
2826
+ );
2827
+
2828
+ expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2829
+ Scheduler.unstable_advanceTime(2000);
2830
+ await advanceTimers(2000);
2831
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2832
+ expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2833
+ expect(ReactNoop).toMatchRenderedOutput(
2834
+ <>
2835
+ <div hidden={true} />
2836
+ <span prop="A" />
2837
+ </>,
2838
+ );
2839
+ expect(Scheduler).toFlushAndYield(['Offscreen']);
2840
+ expect(ReactNoop).toMatchRenderedOutput(
2841
+ <>
2842
+ <div hidden={true}>Offscreen</div>
2843
+ <span prop="A" />
2844
+ </>,
2845
+ );
2846
+ });
2847
+
2848
+ it('should be able to unblock higher pri content before suspended hidden', async () => {
2849
+ function Offscreen() {
2850
+ Scheduler.unstable_yieldValue('Offscreen');
2851
+ return 'Offscreen';
2852
+ }
2853
+ function App({showContent}) {
2854
+ React.useLayoutEffect(() => {
2855
+ Scheduler.unstable_yieldValue('Commit');
2856
+ });
2857
+ return (
2858
+ <Suspense fallback={<Text text="Loading..." />}>
2859
+ <div hidden={true}>
2860
+ <AsyncText text="A" ms={2000} />
2861
+ <Offscreen />
2862
+ </div>
2863
+ {showContent ? <AsyncText text="A" ms={2000} /> : null}
2864
+ </Suspense>
2865
+ );
2866
+ }
2867
+
2868
+ // Initial render.
2869
+ ReactNoop.render(<App showContent={false} />);
2870
+ expect(Scheduler).toFlushAndYieldThrough(['Commit']);
2871
+ expect(ReactNoop).toMatchRenderedOutput(<div hidden={true} />);
2872
+
2873
+ // Partially render through the hidden content.
2874
+ expect(Scheduler).toFlushAndYieldThrough(['Suspend! [A]']);
2875
+
2876
+ // Start transition.
2877
+ React.unstable_withSuspenseConfig(
2878
+ () => {
2879
+ ReactNoop.render(<App showContent={true} />);
2880
+ },
2881
+ {timeoutMs: 5000},
2882
+ );
2883
+
2884
+ expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2885
+ Scheduler.unstable_advanceTime(2000);
2886
+ await advanceTimers(2000);
2887
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2888
+ expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
2889
+ expect(ReactNoop).toMatchRenderedOutput(
2890
+ <>
2891
+ <div hidden={true} />
2892
+ <span prop="A" />
2893
+ </>,
2894
+ );
2895
+ expect(Scheduler).toFlushAndYield(['A', 'Offscreen']);
2896
+ expect(ReactNoop).toMatchRenderedOutput(
2897
+ <>
2898
+ <div hidden={true}>
2899
+ <span prop="A" />Offscreen
2900
+ </div>
2901
+ <span prop="A" />
2902
+ </>,
2903
+ );
2904
+ });
2905
});
packages/shared/ReactFeatureFlags.js
+2
@@ -88,6 +88,8 @@ export const disableLegacyContext = false;
88
89
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
90
91
+export const enableTrainModelFix = __EXPERIMENTAL__;
92
+
93
export const enableTrustedTypesIntegration = false;
94
95
// Flag to turn event.target and event.currentTarget in ReactNative from a reactTag to a component instance
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -42,6 +42,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
42
export const warnAboutStringRefs = false;
43
export const disableLegacyContext = false;
44
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
45
+export const enableTrainModelFix = false;
46
export const enableTrustedTypesIntegration = false;
47
48
// Only used in www builds.
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
36
export const warnAboutStringRefs = false;
37
export const disableLegacyContext = false;
38
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39
+export const enableTrainModelFix = false;
40
export const enableTrustedTypesIntegration = false;
41
export const enableNativeTargetAsInstance = false;
42
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
36
export const warnAboutStringRefs = false;
37
export const disableLegacyContext = false;
38
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39
+export const enableTrainModelFix = false;
40
export const enableTrustedTypesIntegration = false;
41
export const enableNativeTargetAsInstance = false;
42
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
36
export const warnAboutStringRefs = false;
37
export const disableLegacyContext = false;
38
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
39
+export const enableTrainModelFix = false;
40
export const enableTrustedTypesIntegration = false;
41
export const enableNativeTargetAsInstance = false;
42
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -34,6 +34,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
34
export const warnAboutStringRefs = false;
35
export const disableLegacyContext = false;
36
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
37
+export const enableTrainModelFix = false;
38
export const enableTrustedTypesIntegration = false;
39
export const enableNativeTargetAsInstance = false;
40
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -16,6 +16,7 @@ export const {
16
disableInputAttributeSyncing,
17
enableTrustedTypesIntegration,
18
enableSelectiveHydration,
19
+ enableTrainModelFix,
20
} = require('ReactFeatureFlags');
21
22
// In www, we have experimental support for gathering data