diff --git a/fixtures/schedule/index.html b/fixtures/schedule/index.html index d5b243a3c2..ad5f1042f5 100644 --- a/fixtures/schedule/index.html +++ b/fixtures/schedule/index.html @@ -23,7 +23,7 @@
  1. -

    Calls the callback with the frame when not blocked:

    +

    Calls the callback within the frame when not blocked:

    Expected:
    @@ -79,6 +79,15 @@

    IMPORTANT: Open the console when you run this! Inspect the logs there!

  2. +
  3. +

    Continues calling callbacks even when user switches away from this tab

    + +
    Click the button above, observe the counter, then switch to + another tab and switch back:
    +
    +
    +
    If the counter advanced while you were away from this tab, it's correct.
    +
@@ -464,6 +473,22 @@ function runTestSix() { console.log('scheduled cbE'); }; } + +function runTestSeven() { + // Test 7 + // Calls callbacks, continues calling them even when this tab is in the + // background + clearTestResult(7); + let counter = -1; + function incrementCounterAndScheduleNextCallback() { + const counterNode = document.getElementById('test-7'); + counter++; + counterNode.innerHTML = counter; + waitForTimeToPass(100); + scheduleWork(incrementCounterAndScheduleNextCallback); + } + scheduleWork(incrementCounterAndScheduleNextCallback); +} diff --git a/packages/react-scheduler/src/ReactScheduler.js b/packages/react-scheduler/src/ReactScheduler.js index 2b016f1275..79268af395 100644 --- a/packages/react-scheduler/src/ReactScheduler.js +++ b/packages/react-scheduler/src/ReactScheduler.js @@ -117,6 +117,7 @@ if (!canUseDOM) { }; } else { const localRequestAnimationFrame = requestAnimationFrame; + const localCancelAnimationFrame = cancelAnimationFrame; let headOfPendingCallbacksLinkedList: CallbackConfigType | null = null; let tailOfPendingCallbacksLinkedList: CallbackConfigType | null = null; @@ -128,6 +129,27 @@ if (!canUseDOM) { let isIdleScheduled = false; let isAnimationFrameScheduled = false; + // requestAnimationFrame does not run when the tab is in the background. + // if we're backgrounded we prefer for that work to happen so that the page + // continues to load in the background. + // so we also schedule a 'setTimeout' as a fallback. + const animationFrameTimeout = 100; + let rafID; + let timeoutID; + const scheduleAnimationFrameWithFallbackSupport = function(callback) { + // schedule rAF and also a setTimeout + rafID = localRequestAnimationFrame(function(timestamp) { + // cancel the setTimeout + localClearTimeout(timeoutID); + callback(timestamp); + }); + timeoutID = localSetTimeout(function() { + // cancel the requestAnimationFrame + localCancelAnimationFrame(rafID); + callback(now()); + }, animationFrameTimeout); + }; + let frameDeadline = 0; // We start out assuming that we run at 30fps but then the heuristic tracking // will adjust this value to a faster fps if we get more frequent animation @@ -266,7 +288,7 @@ if (!canUseDOM) { if (!isAnimationFrameScheduled) { // Schedule another animation callback so we retry later. isAnimationFrameScheduled = true; - localRequestAnimationFrame(animationTick); + scheduleAnimationFrameWithFallbackSupport(animationTick); } } }; @@ -347,7 +369,7 @@ if (!canUseDOM) { // might want to still have setTimeout trigger scheduleWork as a backup to ensure // that we keep performing work. isAnimationFrameScheduled = true; - localRequestAnimationFrame(animationTick); + scheduleAnimationFrameWithFallbackSupport(animationTick); } return scheduledCallbackConfig; };