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.
Andrew Clark committed
Apr 8, 2019 at 19:44 UTC
183d1f42edecb9e303c05d1a4303d17ecffb0cc0
2 files changed
+30
-2
packages/react-reconciler/src/ReactFiberScheduler.new.js
+3
-2
@@ -228,12 +228,13 @@ let interruptedBy: Fiber | null = null;
228
// In other words, because expiration times determine how updates are batched,
229
// we want all updates of like priority that occur within the same event to
230
// receive the same expiration time. Otherwise we get tearing.
231
+let initialTimeMs: number = now();
232
let currentEventTime: ExpirationTime = NoWork;
233
234
export function requestCurrentTime() {
235
if (workPhase === RenderPhase || workPhase === CommitPhase) {
236
// We're inside React, so it's fine to read the actual time.
236
- return msToExpirationTime(now());
237
+ return msToExpirationTime(now() - initialTimeMs);
238
}
239
// We're not inside React, so we may be in the middle of a browser event.
240
if (currentEventTime !== NoWork) {
@@ -241,7 +242,7 @@ export function requestCurrentTime() {
242
return currentEventTime;
243
}
244
// This is the first update since React yielded. Compute a new start time.
244
- currentEventTime = msToExpirationTime(now());
245
+ currentEventTime = msToExpirationTime(now() - initialTimeMs);
246
return currentEventTime;
247
}
248
packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js
+27
@@ -245,4 +245,31 @@ describe('ReactExpiration', () => {
245
'1 [D] [render]',
246
]);
247
});
248
+
249
+ it('should measure expiration times relative to module initialization', () => {
250
+ // Tests an implementation detail where expiration times are computed using
251
+ // bitwise operations.
252
+
253
+ jest.resetModules();
254
+ Scheduler = require('scheduler');
255
+ // Before importing the renderer, advance the current time by a number
256
+ // larger than the maximum allowed for bitwise operations.
257
+ const maxSigned31BitInt = 1073741823;
258
+ Scheduler.advanceTime(maxSigned31BitInt * 100);
259
+
260
+ // Now import the renderer. On module initialization, it will read the
261
+ // current time.
262
+ ReactNoop = require('react-noop-renderer');
263
+
264
+ ReactNoop.render('Hi');
265
+
266
+ // The update should not have expired yet.
267
+ expect(Scheduler).toFlushExpired([]);
268
+ expect(ReactNoop).toMatchRenderedOutput(null);
269
+
270
+ // Advance the time some more to expire the update.
271
+ Scheduler.advanceTime(10000);
272
+ expect(Scheduler).toFlushExpired([]);
273
+ expect(ReactNoop).toMatchRenderedOutput('Hi');
274
+ });
275
});