@samitouri / QOS-React-2 / commits / dc18b8b8d2

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