From 183d1f42edecb9e303c05d1a4303d17ecffb0cc0 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 8 Apr 2019 19:44:06 -0700 Subject: [PATCH] Fix: Measure expiration times relative to module initialization (#15357) 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 c86bd58ce6a57..26a2e12ed1d90 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 8001a8d2c96c6..9a9402ecb90d2 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'); + }); });