From f95b8fdb7b74b2b2efe9139192fc6127ace35352 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 8 Apr 2019 19:10:34 -0700 Subject: [PATCH] Fix: Measure expiration times relative to module initialization We use bitwise operations to compute expiration times, which means they need to be smaller than 31 bits. So we measure times relative to module initialization, similar to `performance.now`. This was already working in the old fiber scheduler, but we didn't have a test for it. --- .../src/ReactFiberScheduler.new.js | 5 ++-- .../ReactExpiration-test.internal.js | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberScheduler.new.js b/packages/react-reconciler/src/ReactFiberScheduler.new.js index c86bd58ce6a..26a2e12ed1d 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.new.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.new.js @@ -228,12 +228,13 @@ let interruptedBy: Fiber | null = null; // In other words, because expiration times determine how updates are batched, // we want all updates of like priority that occur within the same event to // receive the same expiration time. Otherwise we get tearing. +let initialTimeMs: number = now(); let currentEventTime: ExpirationTime = NoWork; export function requestCurrentTime() { if (workPhase === RenderPhase || workPhase === CommitPhase) { // We're inside React, so it's fine to read the actual time. - return msToExpirationTime(now()); + return msToExpirationTime(now() - initialTimeMs); } // We're not inside React, so we may be in the middle of a browser event. if (currentEventTime !== NoWork) { @@ -241,7 +242,7 @@ export function requestCurrentTime() { return currentEventTime; } // This is the first update since React yielded. Compute a new start time. - currentEventTime = msToExpirationTime(now()); + currentEventTime = msToExpirationTime(now() - initialTimeMs); return currentEventTime; } diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js index 8001a8d2c96..9a9402ecb90 100644 --- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js @@ -245,4 +245,31 @@ describe('ReactExpiration', () => { '1 [D] [render]', ]); }); + + it('should measure expiration times relative to module initialization', () => { + // Tests an implementation detail where expiration times are computed using + // bitwise operations. + + jest.resetModules(); + Scheduler = require('scheduler'); + // Before importing the renderer, advance the current time by a number + // larger than the maximum allowed for bitwise operations. + const maxSigned31BitInt = 1073741823; + Scheduler.advanceTime(maxSigned31BitInt * 100); + + // Now import the renderer. On module initialization, it will read the + // current time. + ReactNoop = require('react-noop-renderer'); + + ReactNoop.render('Hi'); + + // The update should not have expired yet. + expect(Scheduler).toFlushExpired([]); + expect(ReactNoop).toMatchRenderedOutput(null); + + // Advance the time some more to expire the update. + Scheduler.advanceTime(10000); + expect(Scheduler).toFlushExpired([]); + expect(ReactNoop).toMatchRenderedOutput('Hi'); + }); });