Fall back to 'setTimeout' when 'requestAnimationFrame' is not called (#13091)

* Add fixture test for schedule running when tab is backgrounded

**what is the change?:**
Just adding a test to the fixture, where we can easily see whether
scheduled callbacks are called after switching away from the fixture
tab.

**why make this change?:**
We are about to fix the schedule module so that it still runs even when
the tab is in the backround.

**test plan:**
Manually tested the fixture, verified that it works as expected and
right now callbacks are not called when the tab is in the background.

**issue:**
Internal task T30754186

* Fall back to 'setTimeout' when 'requestAnimationFrame' is not called

**what is the change?:**
If 'requestAnimationFrame' is not called for 100ms we fall back to
'setTimeout' to schedule the postmessage.

**why make this change?:**
When you start loading a page, and then switch tabs,
'requestAnimationFrame' is throttled or not called until you come back
to that tab. That means React's rendering, any any other scheduled work,
are paused.

Users expect the page to continue loading, and rendering is part of the
page load in a React app. So we need to continue calling callbacks.

**test plan:**
Manually tested using the new fixture test, observed that the callbacks
were called while switched to another tab. They were called more
slowly, but that seems like a reasonable thing.

**issue:**
Internal task T30754186

* make arguments more explicit
This commit is contained in:
Flarnie Marchan
2018-06-22 09:13:47 -07:00
committed by GitHub
parent da5c87bdfa
commit 076bbeace7
2 changed files with 50 additions and 3 deletions
+26 -1
View File
@@ -23,7 +23,7 @@
<ol>
<li>
<button onClick="runTestOne()">Run Test 1</button>
<p>Calls the callback with the frame when not blocked:</p>
<p>Calls the callback within the frame when not blocked:</p>
<div><b>Expected:</b></div>
<div id="test-1-expected">
</div>
@@ -79,6 +79,15 @@
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
<button onClick="runTestSix()">Run Test 6</button>
</li>
<li>
<p>Continues calling callbacks even when user switches away from this tab</p>
<button onClick="runTestSeven()">Run Test 7</button>
<div><b>Click the button above, observe the counter, then switch to
another tab and switch back:</b></div>
<div id="test-7">
</div>
<div> If the counter advanced while you were away from this tab, it's correct.</div>
</li>
</ol>
<script src="../../build/dist/react-scheduler.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
@@ -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);
}
</script type="text/babel">
</body>
</html>
+24 -2
View File
@@ -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;
};