From b729510e0d58eefb40f1893db7627b3f3e810673 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 11 Mar 2021 01:28:10 -0600 Subject: [PATCH] Land enableSyncMicrotasks --- .../ReactDOMNativeEventHeuristic-test.js | 18 ++++---- .../__tests__/ChangeEventPlugin-test.js | 9 ++-- .../src/SchedulerWithReactIntegration.new.js | 7 +--- .../src/SchedulerWithReactIntegration.old.js | 7 +--- .../src/__tests__/ReactExpiration-test.js | 35 +++++++++++----- ...tIncrementalErrorHandling-test.internal.js | 8 +++- .../__tests__/ReactIncrementalUpdates-test.js | 24 ++++++++--- .../ReactSuspenseWithNoopRenderer-test.js | 41 ++++++++++++++----- packages/shared/ReactFeatureFlags.js | 2 - .../forks/ReactFeatureFlags.native-fb.js | 1 - .../forks/ReactFeatureFlags.native-oss.js | 1 - .../forks/ReactFeatureFlags.test-renderer.js | 1 - .../ReactFeatureFlags.test-renderer.native.js | 1 - .../ReactFeatureFlags.test-renderer.www.js | 1 - .../shared/forks/ReactFeatureFlags.testing.js | 1 - .../forks/ReactFeatureFlags.testing.www.js | 1 - .../forks/ReactFeatureFlags.www-dynamic.js | 1 - .../shared/forks/ReactFeatureFlags.www.js | 1 - 18 files changed, 100 insertions(+), 60 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js index c0617f9b56787..6c8d677cdbb93 100644 --- a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js @@ -243,11 +243,12 @@ describe('ReactDOMNativeEventHeuristic-test', () => { mouseOverEvent.initEvent('mouseover', true, true); dispatchAndSetCurrentEvent(target.current, mouseOverEvent); - // 3s should be enough to expire the updates - Scheduler.unstable_advanceTime(3000); - expect(Scheduler).toFlushExpired([]); - expect(container.textContent).toEqual('hovered'); + // Flush discrete updates + ReactDOM.flushSync(); + // Since mouse over is not discrete, should not have updated yet + expect(container.textContent).toEqual('not hovered'); }); + expect(container.textContent).toEqual('hovered'); }); // @gate experimental @@ -275,11 +276,12 @@ describe('ReactDOMNativeEventHeuristic-test', () => { mouseEnterEvent.initEvent('mouseenter', true, true); dispatchAndSetCurrentEvent(target.current, mouseEnterEvent); - // 3s should be enough to expire the updates - Scheduler.unstable_advanceTime(3000); - expect(Scheduler).toFlushExpired([]); - expect(container.textContent).toEqual('hovered'); + // Flush discrete updates + ReactDOM.flushSync(); + // Since mouse end is not discrete, should not have updated yet + expect(container.textContent).toEqual('not hovered'); }); + expect(container.textContent).toEqual('hovered'); }); // @gate experimental diff --git a/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js index 9b7fed1468470..3eb9edd2fecbd 100644 --- a/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js @@ -761,11 +761,12 @@ describe('ChangeEventPlugin', () => { mouseOverEvent.initEvent('mouseover', true, true); target.current.dispatchEvent(mouseOverEvent); - // 3s should be enough to expire the updates - Scheduler.unstable_advanceTime(3000); - expect(Scheduler).toFlushExpired([]); - expect(container.textContent).toEqual('hovered'); + // Flush discrete updates + ReactDOM.flushSync(); + // Since mouse enter/leave is not discrete, should not have updated yet + expect(container.textContent).toEqual('not hovered'); }); + expect(container.textContent).toEqual('hovered'); }); }); }); diff --git a/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js b/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js index cd8cb58c6cedc..70f26e5fc4167 100644 --- a/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js +++ b/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js @@ -13,10 +13,7 @@ import type {ReactPriorityLevel} from './ReactInternalTypes'; // CommonJS interop named imports. import * as Scheduler from 'scheduler'; import {__interactionsRef} from 'scheduler/tracing'; -import { - enableSchedulerTracing, - enableSyncMicroTasks, -} from 'shared/ReactFeatureFlags'; +import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import { SyncLanePriority, @@ -139,7 +136,7 @@ export function scheduleSyncCallback(callback: SchedulerCallback) { // TODO: Figure out how to remove this It's only here as a last resort if we // forget to explicitly flush. - if (enableSyncMicroTasks && supportsMicrotasks) { + if (supportsMicrotasks) { // Flush the queue in a microtask. scheduleMicrotask(flushSyncCallbackQueueImpl); } else { diff --git a/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js b/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js index 738ef2ea1afb7..3d0d0920fd22a 100644 --- a/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js +++ b/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js @@ -13,10 +13,7 @@ import type {ReactPriorityLevel} from './ReactInternalTypes'; // CommonJS interop named imports. import * as Scheduler from 'scheduler'; import {__interactionsRef} from 'scheduler/tracing'; -import { - enableSchedulerTracing, - enableSyncMicroTasks, -} from 'shared/ReactFeatureFlags'; +import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import { SyncLanePriority, @@ -139,7 +136,7 @@ export function scheduleSyncCallback(callback: SchedulerCallback) { // TODO: Figure out how to remove this It's only here as a last resort if we // forget to explicitly flush. - if (enableSyncMicroTasks && supportsMicrotasks) { + if (supportsMicrotasks) { // Flush the queue in a microtask. scheduleMicrotask(flushSyncCallbackQueueImpl); } else { diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js index ed49965318212..cf8b539433e46 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js @@ -103,23 +103,32 @@ describe('ReactExpiration', () => { return {type: 'span', children: [], prop, hidden: false}; } + function flushNextRenderIfExpired() { + // This will start rendering the next level of work. If the work hasn't + // expired yet, React will exit without doing anything. If it has expired, + // it will schedule a sync task. + Scheduler.unstable_flushExpired(); + // Flush the sync task. + ReactNoop.flushSync(); + } + it('increases priority of updates as time progresses', () => { ReactNoop.render(); expect(ReactNoop.getChildren()).toEqual([]); // Nothing has expired yet because time hasn't advanced. - ReactNoop.flushExpired(); + flushNextRenderIfExpired(); expect(ReactNoop.getChildren()).toEqual([]); // Advance time a bit, but not enough to expire the low pri update. ReactNoop.expire(4500); - ReactNoop.flushExpired(); + flushNextRenderIfExpired(); expect(ReactNoop.getChildren()).toEqual([]); // Advance by another second. Now the update should expire and flush. - ReactNoop.expire(1000); - ReactNoop.flushExpired(); + ReactNoop.expire(500); + flushNextRenderIfExpired(); expect(ReactNoop.getChildren()).toEqual([span('done')]); }); @@ -323,7 +332,8 @@ describe('ReactExpiration', () => { Scheduler.unstable_advanceTime(10000); - expect(Scheduler).toFlushExpired(['D', 'E']); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded(['D', 'E']); expect(root).toMatchRenderedOutput('ABCDE'); }); @@ -351,7 +361,8 @@ describe('ReactExpiration', () => { Scheduler.unstable_advanceTime(10000); - expect(Scheduler).toFlushExpired(['D', 'E']); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded(['D', 'E']); expect(root).toMatchRenderedOutput('ABCDE'); }); @@ -373,12 +384,14 @@ describe('ReactExpiration', () => { ReactNoop.render('Hi'); // The update should not have expired yet. - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); expect(ReactNoop).toMatchRenderedOutput(null); // Advance the time some more to expire the update. Scheduler.unstable_advanceTime(10000); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); expect(ReactNoop).toMatchRenderedOutput('Hi'); }); @@ -391,7 +404,8 @@ describe('ReactExpiration', () => { Scheduler.unstable_advanceTime(10000); ReactNoop.render('Hi'); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); expect(ReactNoop).toMatchRenderedOutput(null); // Advancing by ~5 seconds should be sufficient to expire the update. (I @@ -399,7 +413,8 @@ describe('ReactExpiration', () => { Scheduler.unstable_advanceTime(6000); ReactNoop.render('Hi'); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); expect(ReactNoop).toMatchRenderedOutput('Hi'); }); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index a14c36bfce2bb..df691e074b2a4 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -402,7 +402,7 @@ describe('ReactIncrementalErrorHandling', () => { expect(ReactNoop.getChildren()).toEqual([]); }); - it('retries one more time if an error occurs during a render that expires midway through the tree', () => { + it('retries one more time if an error occurs during a render that expires midway through the tree', async () => { function Oops({unused}) { Scheduler.unstable_yieldValue('Oops'); throw new Error('Oops'); @@ -432,7 +432,11 @@ describe('ReactIncrementalErrorHandling', () => { // Expire the render midway through Scheduler.unstable_advanceTime(10000); - expect(() => Scheduler.unstable_flushExpired()).toThrow('Oops'); + + expect(() => { + Scheduler.unstable_flushExpired(); + ReactNoop.flushSync(); + }).toThrow('Oops'); expect(Scheduler).toHaveYielded([ // The render expired, but we shouldn't throw out the partial work. diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js index 95acb7d8fc778..ec587d7231c78 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js @@ -32,6 +32,15 @@ describe('ReactIncrementalUpdates', () => { return {type: 'span', children: [], prop, hidden: false}; } + function flushNextRenderIfExpired() { + // This will start rendering the next level of work. If the work hasn't + // expired yet, React will exit without doing anything. If it has expired, + // it will schedule a sync task. + Scheduler.unstable_flushExpired(); + // Flush the sync task. + ReactNoop.flushSync(); + } + it('applies updates in order of priority', () => { let state; class Foo extends React.Component { @@ -469,7 +478,8 @@ describe('ReactIncrementalUpdates', () => { ReactNoop.act(() => { ReactNoop.render(); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); expect(Scheduler).toFlushAndYield([ 'Render: 0', 'Commit: 0', @@ -479,7 +489,8 @@ describe('ReactIncrementalUpdates', () => { Scheduler.unstable_advanceTime(10000); setCount(2); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); }); }); @@ -497,7 +508,8 @@ describe('ReactIncrementalUpdates', () => { Scheduler.unstable_advanceTime(10000); ReactNoop.render(); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); }); it('regression: does not expire soon due to previous expired work', () => { @@ -508,12 +520,14 @@ describe('ReactIncrementalUpdates', () => { ReactNoop.render(); Scheduler.unstable_advanceTime(10000); - expect(Scheduler).toFlushExpired(['A']); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded(['A']); Scheduler.unstable_advanceTime(10000); ReactNoop.render(); - expect(Scheduler).toFlushExpired([]); + flushNextRenderIfExpired(); + expect(Scheduler).toHaveYielded([]); }); it('when rebasing, does not exclude updates that were already committed, regardless of priority', async () => { diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index d7da84e67b752..692b745f97085 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -984,6 +984,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([span('C')]); }); + // TODO: This test was written against the old Expiration Times + // implementation. It doesn't really test what it was intended to test + // anymore, because all updates to the same queue get entangled together. + // Even if they haven't expired. Consider either deleting or rewriting. // @gate enableCache it('flushes all expired updates in a single batch', async () => { class Foo extends React.Component { @@ -1013,10 +1017,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { jest.advanceTimersByTime(1000); ReactNoop.render(); - Scheduler.unstable_advanceTime(10000); - jest.advanceTimersByTime(10000); - - expect(Scheduler).toFlushExpired([ + expect(Scheduler).toFlushAndYield([ 'Suspend! [goodbye]', 'Loading...', 'Commit: goodbye', @@ -1797,12 +1798,32 @@ describe('ReactSuspenseWithNoopRenderer', () => { await advanceTimers(5000); // Retry with the new content. - expect(Scheduler).toFlushAndYield([ - 'A', - // B still suspends - 'Suspend! [B]', - 'Loading more...', - ]); + if (gate(flags => flags.disableSchedulerTimeoutInWorkLoop)) { + expect(Scheduler).toFlushAndYield([ + 'A', + // B still suspends + 'Suspend! [B]', + 'Loading more...', + ]); + } else { + // In this branch, right as we start rendering, we detect that the work + // has expired (via Scheduler's didTimeout argument) and re-schedule the + // work as synchronous. Since sync work does not flow through Scheduler, + // we need to use `flushSync`. + // + // Usually we would use `act`, which fluses both sync work and Scheduler + // work, but that would also force the fallback to display, and this test + // is specifically about whether we delay or show the fallback. + expect(Scheduler).toFlushAndYield([]); + // This will flush the synchronous callback we just scheduled. + ReactNoop.flushSync(); + expect(Scheduler).toHaveYielded([ + 'A', + // B still suspends + 'Suspend! [B]', + 'Loading more...', + ]); + } // Because we've already been waiting for so long we've exceeded // our threshold and we show the next level immediately. expect(ReactNoop.getChildren()).toEqual([ diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 75ab4b6842d90..77079bba3a6a1 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -150,6 +150,4 @@ export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; - export const enableLazyContextPropagation = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index b6a8016ec8b77..2e2f64e4bc5d0 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -57,7 +57,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index cb8721af25fe9..34ee7d2b7a843 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index b504491d98de2..bd5e91885ca44 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index ddab49284e437..70c5dc4144ee8 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 7098bfed2aa5b..df6bbdf03fbed 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index a321f5ea12f92..42003be9a388b 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index bb6a952355657..5ab680976a9e4 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -56,7 +56,6 @@ export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; export const disableSchedulerTimeoutInWorkLoop = false; -export const enableSyncMicroTasks = false; export const enableLazyContextPropagation = false; // Flow magic to verify the exports of this file match the original version. diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index df98012634970..e2496510ef24c 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -55,5 +55,4 @@ export const enableUseRefAccessWarning = __VARIANT__; export const enableProfilerNestedUpdateScheduledHook = __VARIANT__; export const disableSchedulerTimeoutInWorkLoop = __VARIANT__; -export const enableSyncMicroTasks = __VARIANT__; export const enableLazyContextPropagation = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 893c2699b0d07..c29856558751e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -31,7 +31,6 @@ export const { enableUseRefAccessWarning, disableNativeComponentFrames, disableSchedulerTimeoutInWorkLoop, - enableSyncMicroTasks, enableLazyContextPropagation, } = dynamicFeatureFlags;