Skip to content

Commit 5d38d58

Browse files
committed
[scheduler] Eagerly schedule rAF at beginning of frame
Eagerly schedule the next animation callback at the beginning of the frame. If the scheduler queue is not empty at the end of the frame, it will continue flushing inside that callback. If the queue *is* empty, then it will exit immediately. Posting the callback at the start of the frame ensures it's fired within the earliest possible frame. If we waited until the end of the frame to post the callback, we risk the browser skipping a frame and not firing the callback until the frame after that.
1 parent d836010 commit 5d38d58

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

packages/scheduler/src/Scheduler.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,22 @@ if (typeof window !== 'undefined' && window._schedMock) {
605605
window.addEventListener('message', idleTick, false);
606606

607607
var animationTick = function(rafTime) {
608-
isAnimationFrameScheduled = false;
608+
if (scheduledCallback !== null) {
609+
// Eagerly schedule the next animation callback at the beginning of the
610+
// frame. If the scheduler queue is not empty at the end of the frame, it
611+
// will continue flushing inside that callback. If the queue *is* empty,
612+
// then it will exit immediately. Posting the callback at the start of the
613+
// frame ensures it's fired within the earliest possible frame. If we
614+
// waited until the end of the frame to post the callback, we risk the
615+
// browser skipping a frame and not firing the callback until the frame
616+
// after that.
617+
requestAnimationFrameWithTimeout(animationTick);
618+
} else {
619+
// No pending work. Exit.
620+
isAnimationFrameScheduled = false;
621+
return;
622+
}
623+
609624
var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
610625
if (
611626
nextFrameTime < activeFrameTime &&

packages/scheduler/src/__tests__/SchedulerDOM-test.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,18 @@ describe('SchedulerDOM', () => {
5050
function runRAFCallbacks() {
5151
startOfLatestFrame += frameSize;
5252
currentTime = startOfLatestFrame;
53-
rAFCallbacks.forEach(cb => cb());
53+
const cbs = rAFCallbacks;
5454
rAFCallbacks = [];
55+
cbs.forEach(cb => cb());
5556
}
5657
function advanceOneFrame(config: FrameTimeoutConfigType = {}) {
5758
runRAFCallbacks();
5859
runPostMessageCallbacks(config);
5960
}
6061

6162
let frameSize = 33;
62-
let startOfLatestFrame = Date.now();
63-
let currentTime = Date.now();
63+
let startOfLatestFrame = 0;
64+
let currentTime = 0;
6465

6566
beforeEach(() => {
6667
// TODO pull this into helper method, reduce repetition.
@@ -70,7 +71,7 @@ describe('SchedulerDOM', () => {
7071
// - Date.now should return the correct thing
7172
// - test with native performance.now()
7273
delete global.performance;
73-
global.requestAnimationFrame = function(cb) {
74+
global.requestAnimationFrame = function(cb, label) {
7475
return rAFCallbacks.push(() => {
7576
cb(startOfLatestFrame);
7677
});
@@ -109,6 +110,25 @@ describe('SchedulerDOM', () => {
109110
expect(typeof cb.mock.calls[0][0].timeRemaining()).toBe('number');
110111
});
111112

113+
it('inserts its rAF callback as early into the queue as possible', () => {
114+
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
115+
const log = [];
116+
const useRAFCallback = () => {
117+
log.push('userRAFCallback');
118+
};
119+
scheduleCallback(() => {
120+
// Call rAF while idle work is being flushed.
121+
requestAnimationFrame(useRAFCallback);
122+
});
123+
advanceOneFrame({timeLeftInFrame: 1});
124+
// There should be two callbacks: the one scheduled by Scheduler at the
125+
// beginning of the frame, and the one scheduled later during that frame.
126+
expect(rAFCallbacks.length).toBe(2);
127+
// The user callback should be the second callback.
128+
rAFCallbacks[1]();
129+
expect(log).toEqual(['userRAFCallback']);
130+
});
131+
112132
describe('with multiple callbacks', () => {
113133
it('accepts multiple callbacks and calls within frame when not blocked', () => {
114134
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;

0 commit comments

Comments
 (0)