@samitouri / QOS-React-2 / commits / 1fafac0028

Use SyncLane for discrete event hydration (#21038)

Discrete event hydration doesn't need to be interruptible, since there's nothing higher priority than discrete events. So we can use SyncLane instead of a special hydration lane.

Andrew Clark committed Mar 22, 2021 at 11:05 UTC 1fafac002838ad2d75ee9eca527c356b040285ff
5 files changed +121 -105
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+63 -56
@@ -2187,17 +2187,17 @@ describe('ReactDOMServerPartialHydration', () => {
2187 expect(container.textContent).toBe('Click meHello');
2188
2189 // We're now partially hydrated.
2190 - a.click();
2190 + await act(async () => {
2191 + a.click();
2192 + });
2193 expect(clicks).toBe(0);
2194
2195 // Resolving the promise so that rendering can complete.
2194 - suspend = false;
2195 - resolve();
2196 - await promise;
2197 -
2198 - Scheduler.unstable_flushAll();
2199 - jest.runAllTimers();
2200 -
2196 + await act(async () => {
2197 + suspend = false;
2198 + resolve();
2199 + await promise;
2200 + });
2201 expect(clicks).toBe(1);
2202
2203 expect(container.textContent).toBe('Hello');
@@ -2270,18 +2270,19 @@ describe('ReactDOMServerPartialHydration', () => {
2270 jest.runAllTimers();
2271
2272 // We're now partially hydrated.
2273 - a.click();
2273 + await act(async () => {
2274 + a.click();
2275 + });
2276 // We should not have invoked the event yet because we're not
2277 // yet hydrated.
2278 expect(onEvent).toHaveBeenCalledTimes(0);
2279
2280 // Resolving the promise so that rendering can complete.
2279 - suspend = false;
2280 - resolve();
2281 - await promise;
2282 -
2283 - Scheduler.unstable_flushAll();
2284 - jest.runAllTimers();
2281 + await act(async () => {
2282 + suspend = false;
2283 + resolve();
2284 + await promise;
2285 + });
2286
2287 expect(onEvent).toHaveBeenCalledTimes(2);
2288
@@ -2344,7 +2345,9 @@ describe('ReactDOMServerPartialHydration', () => {
2345 root.render(<App />);
2346
2347 // We'll do one click before hydrating.
2347 - a.click();
2348 + await act(async () => {
2349 + a.click();
2350 + });
2351 // This should be delayed.
2352 expect(clicks).toBe(0);
2353
@@ -2352,17 +2355,17 @@ describe('ReactDOMServerPartialHydration', () => {
2355 jest.runAllTimers();
2356
2357 // We're now partially hydrated.
2355 - a.click();
2358 + await act(async () => {
2359 + a.click();
2360 + });
2361 expect(clicks).toBe(0);
2362
2363 // Resolving the promise so that rendering can complete.
2359 - suspend = false;
2360 - resolve();
2361 - await promise;
2362 -
2363 - Scheduler.unstable_flushAll();
2364 - jest.runAllTimers();
2365 -
2364 + await act(async () => {
2365 + suspend = false;
2366 + resolve();
2367 + await promise;
2368 + });
2369 expect(clicks).toBe(2);
2370
2371 document.body.removeChild(container);
@@ -2436,19 +2439,19 @@ describe('ReactDOMServerPartialHydration', () => {
2439 jest.runAllTimers();
2440
2441 // We're now partially hydrated.
2439 - a.click();
2442 + await act(async () => {
2443 + a.click();
2444 + });
2445 // We should not have invoked the event yet because we're not
2446 // yet hydrated.
2447 expect(onEvent).toHaveBeenCalledTimes(0);
2448
2449 // Resolving the promise so that rendering can complete.
2445 - suspend = false;
2446 - resolve();
2447 - await promise;
2448 -
2449 - Scheduler.unstable_flushAll();
2450 - jest.runAllTimers();
2451 -
2450 + await act(async () => {
2451 + suspend = false;
2452 + resolve();
2453 + await promise;
2454 + });
2455 expect(onEvent).toHaveBeenCalledTimes(2);
2456
2457 document.body.removeChild(container);
@@ -2510,17 +2513,18 @@ describe('ReactDOMServerPartialHydration', () => {
2513 jest.runAllTimers();
2514
2515 // We're now partially hydrated.
2513 - span.click();
2516 + await act(async () => {
2517 + span.click();
2518 + });
2519 expect(clicksOnChild).toBe(0);
2520 expect(clicksOnParent).toBe(0);
2521
2522 // Resolving the promise so that rendering can complete.
2518 - suspend = false;
2519 - resolve();
2520 - await promise;
2521 -
2522 - Scheduler.unstable_flushAll();
2523 - jest.runAllTimers();
2523 + await act(async () => {
2524 + suspend = false;
2525 + resolve();
2526 + await promise;
2527 + });
2528
2529 expect(clicksOnChild).toBe(1);
2530 // This will be zero due to the stopPropagation.
@@ -2589,16 +2593,17 @@ describe('ReactDOMServerPartialHydration', () => {
2593 Scheduler.unstable_flushAll();
2594
2595 // The Suspense boundary is not yet hydrated.
2592 - a.click();
2596 + await act(async () => {
2597 + a.click();
2598 + });
2599 expect(clicks).toBe(0);
2600
2601 // Resolving the promise so that rendering can complete.
2596 - suspend = false;
2597 - resolve();
2598 - await promise;
2599 -
2600 - Scheduler.unstable_flushAll();
2601 - jest.runAllTimers();
2602 + await act(async () => {
2603 + suspend = false;
2604 + resolve();
2605 + await promise;
2606 + });
2607
2608 // We're now full hydrated.
2609
@@ -2858,20 +2863,22 @@ describe('ReactDOMServerPartialHydration', () => {
2863 expect(container.textContent).toBe('Click meHello');
2864
2865 // We're now partially hydrated.
2861 - form.dispatchEvent(
2862 - new Event('submit', {
2863 - bubbles: true,
2864 - }),
2865 - );
2866 + await act(async () => {
2867 + form.dispatchEvent(
2868 + new Event('submit', {
2869 + bubbles: true,
2870 + }),
2871 + );
2872 + });
2873 expect(submits).toBe(0);
2874
2875 // Resolving the promise so that rendering can complete.
2869 - suspend = false;
2870 - resolve();
2871 - await promise;
2876 + await act(async () => {
2877 + suspend = false;
2878 + resolve();
2879 + await promise;
2880 + });
2881
2873 - Scheduler.unstable_flushAll();
2874 - jest.runAllTimers();
2882 expect(submits).toBe(1);
2883 expect(container.textContent).toBe('Hello');
2884 document.body.removeChild(container);
packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+45 -36
@@ -255,22 +255,25 @@ describe('ReactDOMServerSelectiveHydration', () => {
255 expect(Scheduler).toHaveYielded([]);
256
257 // This click target cannot be hydrated yet because it's suspended.
258 - const result = dispatchClickEvent(spanD);
259 -
260 - expect(Scheduler).toHaveYielded(['App']);
261 -
262 - expect(result).toBe(true);
263 -
264 - // Continuing rendering will render B next.
265 - expect(Scheduler).toFlushAndYield(['B', 'C']);
266 -
267 - suspend = false;
268 - resolve();
269 - await promise;
258 + await act(async () => {
259 + const result = dispatchClickEvent(spanD);
260 + expect(result).toBe(true);
261 + });
262 + expect(Scheduler).toHaveYielded([
263 + 'App',
264 + // Continuing rendering will render B next.
265 + 'B',
266 + 'C',
267 + ]);
268
269 + await act(async () => {
270 + suspend = false;
271 + resolve();
272 + await promise;
273 + });
274 // After the click, we should prioritize D and the Click first,
275 // and only after that render A and C.
273 - expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
276 + expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
277
278 document.body.removeChild(container);
279 });
@@ -348,13 +351,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
351
352 expect(Scheduler).toHaveYielded(['App']);
353
351 - suspend = false;
352 - resolve();
353 - await promise;
354 + await act(async () => {
355 + suspend = false;
356 + resolve();
357 + await promise;
358 + });
359
360 // We should prioritize hydrating A, C and D first since we clicked in
361 // them. Only after they're done will we hydrate B.
357 - expect(Scheduler).toFlushAndYield([
362 + expect(Scheduler).toHaveYielded([
363 'A',
364 'Clicked A',
365 'C',
@@ -506,21 +511,21 @@ describe('ReactDOMServerSelectiveHydration', () => {
511 // Nothing has been hydrated so far.
512 expect(Scheduler).toHaveYielded([]);
513
509 - const target = createEventTarget(spanD);
510 - target.virtualclick();
511 -
512 - expect(Scheduler).toHaveYielded(['App']);
513 -
514 // Continuing rendering will render B next.
515 - expect(Scheduler).toFlushAndYield(['B', 'C']);
516 -
517 - suspend = false;
518 - resolve();
519 - await promise;
515 + await act(async () => {
516 + const target = createEventTarget(spanD);
517 + target.virtualclick();
518 + });
519 + expect(Scheduler).toHaveYielded(['App', 'B', 'C']);
520
521 // After the click, we should prioritize D and the Click first,
522 // and only after that render A and C.
523 - expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
523 + await act(async () => {
524 + suspend = false;
525 + resolve();
526 + await promise;
527 + });
528 + expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
529
530 document.body.removeChild(container);
531 });
@@ -602,13 +607,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
607
608 expect(Scheduler).toHaveYielded(['App']);
609
605 - suspend = false;
606 - resolve();
607 - await promise;
610 + await act(async () => {
611 + suspend = false;
612 + resolve();
613 + await promise;
614 + });
615
616 // We should prioritize hydrating A, C and D first since we clicked in
617 // them. Only after they're done will we hydrate B.
611 - expect(Scheduler).toFlushAndYield([
618 + expect(Scheduler).toHaveYielded([
619 'A',
620 'Clicked A',
621 'C',
@@ -701,9 +708,11 @@ describe('ReactDOMServerSelectiveHydration', () => {
708
709 expect(Scheduler).toHaveYielded(['App']);
710
704 - suspend = false;
705 - resolve();
706 - await promise;
711 + await act(async () => {
712 + suspend = false;
713 + resolve();
714 + await promise;
715 + });
716
717 // We should prioritize hydrating D first because we clicked it.
718 // Next we should hydrate C since that's the current hover target.
@@ -711,7 +720,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
720 // the same time since B was already scheduled.
721 // This is ok because it will at least not continue for nested
722 // boundary. See the next test below.
714 - expect(Scheduler).toFlushAndYield([
723 + expect(Scheduler).toHaveYielded([
724 'D',
725 'Clicked D',
726 'B', // Ideally this should be later.
packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+9 -7
@@ -67,6 +67,7 @@ describe('DOMPluginEventSystem', () => {
67 ReactDOM = require('react-dom');
68 Scheduler = require('scheduler');
69 ReactDOMServer = require('react-dom/server');
70 + act = require('react-dom/test-utils').unstable_concurrentAct;
71 container = document.createElement('div');
72 document.body.appendChild(container);
73 startNativeEventListenerClearDown();
@@ -636,16 +637,17 @@ describe('DOMPluginEventSystem', () => {
637 Scheduler.unstable_flushAll();
638
639 // The Suspense boundary is not yet hydrated.
639 - a.click();
640 + await act(async () => {
641 + a.click();
642 + });
643 expect(clicks).toBe(0);
644
645 // Resolving the promise so that rendering can complete.
643 - suspend = false;
644 - resolve();
645 - await promise;
646 -
647 - Scheduler.unstable_flushAll();
648 - jest.runAllTimers();
646 + await act(async () => {
647 + suspend = false;
648 + resolve();
649 + await promise;
650 + });
651
652 // We're now full hydrated.
653
packages/react-reconciler/src/ReactFiberReconciler.new.js
+2 -3
@@ -78,7 +78,6 @@ import {
78 import {StrictLegacyMode} from './ReactTypeOfMode';
79 import {
80 SyncLane,
81 - InputDiscreteHydrationLane,
81 SelectiveHydrationLane,
82 NoTimestamp,
83 getHighestPriorityPendingLanes,
@@ -388,7 +387,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
387 // If we're still blocked after this, we need to increase
388 // the priority of any promises resolving within this
389 // boundary so that they next attempt also has higher pri.
391 - const retryLane = InputDiscreteHydrationLane;
390 + const retryLane = SyncLane;
391 markRetryLaneIfNotHydrated(fiber, retryLane);
392 break;
393 }
@@ -422,7 +421,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
421 return;
422 }
423 const eventTime = requestEventTime();
425 - const lane = InputDiscreteHydrationLane;
424 + const lane = SyncLane;
425 scheduleUpdateOnFiber(fiber, lane, eventTime);
426 markRetryLaneIfNotHydrated(fiber, lane);
427 }
packages/react-reconciler/src/ReactFiberReconciler.old.js
+2 -3
@@ -78,7 +78,6 @@ import {
78 import {StrictLegacyMode} from './ReactTypeOfMode';
79 import {
80 SyncLane,
81 - InputDiscreteHydrationLane,
81 SelectiveHydrationLane,
82 NoTimestamp,
83 getHighestPriorityPendingLanes,
@@ -388,7 +387,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
387 // If we're still blocked after this, we need to increase
388 // the priority of any promises resolving within this
389 // boundary so that they next attempt also has higher pri.
391 - const retryLane = InputDiscreteHydrationLane;
390 + const retryLane = SyncLane;
391 markRetryLaneIfNotHydrated(fiber, retryLane);
392 break;
393 }
@@ -422,7 +421,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
421 return;
422 }
423 const eventTime = requestEventTime();
425 - const lane = InputDiscreteHydrationLane;
424 + const lane = SyncLane;
425 scheduleUpdateOnFiber(fiber, lane, eventTime);
426 markRetryLaneIfNotHydrated(fiber, lane);
427 }