How do browsers pause/change Javascript when tab or window is not active?
A newer answer to complement these: on chrome 78.0.3904.108 I notice all of these timeouts (not just those below 1000ms) taking a bit longer than expected when I move to a different tab, and then come back. The behaviour I am seeing is more correctly described as "All timeouts on inactive tabs may be delayed by some additional amount, to a maximum of 1000ms." Try running the following and switching to another tab!
let timeouts = [ 500, 1000, 2000, 3000, 10000 ];
let minExcess = document.getElementsByClassName('minExcess')[0];
timeouts.forEach(ms => {
let elem = document.getElementsByClassName(`t${ms}`)[0];
let cnt = 0;
let lastMs = +new Date();
let f = () => {
let curMs = +new Date();
let disp = document.createElement('p');
let net = curMs - lastMs;
lastMs = curMs;
setTimeout(f, ms);
if (minExcess.value && (net - ms) < parseInt(minExcess.value)) return;
disp.innerText = `${net},`;
elem.appendChild(disp);
if (++cnt > 10) elem.firstElementChild.remove();
};
setTimeout(f, ms);
});
body { font-size: 80%; }
div {
max-height: 80px;
overflow-x: auto;
background-color: rgba(0, 0, 0, 0.1);
margin-bottom: 2px;
white-space: nowrap;
}
p { margin: 0; }
div > p {
margin: 0;
display: inline-block;
vertical-align: top;
margin-right: 2px;
}
input { margin: 0 0 10px 0; }
.t500:before { display: block; content: '500ms'; font-weight: bold; }
.t1000:before { display: block; content: '1000ms'; font-weight: bold; }
.t2000:before { display: block; content: '2000ms'; font-weight: bold; }
.t3000:before { display: block; content: '3000ms'; font-weight: bold; }
.t10000:before { display: block; content: '10000ms'; font-weight: bold; }
<p>Ignore any values delayed by less than this amount:</p>
<input type="text" class="minExcess" value="200" pattern="^[0-9]*$"/>
<div class="timeout t500"></div>
<div class="timeout t1000"></div>
<div class="timeout t2000"></div>
<div class="timeout t3000"></div>
<div class="timeout t10000"></div>
What I observed : on inactive tabs in Chrome, all your setTimeout
(must be the same for setInterval
) waiting less than 1000ms are rounded to 1000ms. I think longer timeouts are not modified.
Seems to be the behavior since Chrome 11 and Firefox 5.0 : https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs
Furthermore, I don't think it behaves this way when the whole window is inactive (but it seems quite easy to investigate).
Test One
I have written a test specifically for this purpose:
Frame Rate Distribution: setInterval vs requestAnimationFrame
Note: This test is quite CPU intensive. requestAnimationFrame
is not supported by IE 9- and Opera 12-.
The test logs the actual time it takes for a setInterval
and requestAnimationFrame
to run in different browsers, and gives you the results in the form of a distribution. You can change the number of milliseconds for setInterval
to see how it runs under different settings. setTimeout
works similarly to a setInterval
with respect to delays. requestAnimationFrame
generally defaults to the 60fps depending on the browser. To see what happens when you switch to a different tab or have an inactive window, simply open the page, switch to a different tab and wait for a while. It will continue to log the actual time it takes for these functions in an inactive tab.
Test Two
Another way to test it is to log the timestamp repeatedly with setInterval
and requestAnimationFrame
and view it in a detached console. You can see how frequently it is updated (or if it is ever updated) when you make the tab or window inactive.
- Test for
setInterval
- Test for
requestAnimationFrame
Results
Chrome
Chrome limits the minimum interval of setInterval
to around 1000ms when the tab is inactive. If the interval is higher than 1000ms, it will run at the specified interval. It does not matter if the window is out of focus, the interval is limited only when you switch to a different tab. requestAnimationFrame
is paused when the tab is inactive.
// Provides control over the minimum timer interval for background tabs.
const double kBackgroundTabTimerInterval = 1.0;
https://codereview.chromium.org/6546021/patch/1001/2001
Firefox
Similar to Chrome, Firefox limits the minimum interval of setInterval
to around 1000ms when the tab (not the window) is inactive. However, requestAnimationFrame
runs exponentially slower when the tab is inactive, with each frame taking 1s, 2s, 4s, 8s and so on.
// The default shortest interval/timeout we permit
#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
https://hg.mozilla.org/releases/mozilla-release/file/0bf1cadfb004/dom/base/nsGlobalWindow.cpp#l296
Internet Explorer
IE does not limit the delay in setInterval
when the tab is inactive, but it pauses requestAnimationFrame
in inactive tabs. It does not matter whether the window is out of focus or not.
Edge
Starting from Edge 14, setInterval
is capped at 1000ms in inactive tabs. requestAnimationFrame
is always paused in inactive tabs.
Safari
Just like Chrome, Safari caps setInterval
at 1000ms when the tab is inactive. requestAnimationFrame
is paused as well.
Opera
Since the adoption of the Webkit engine, Opera exhibits the same behavior as Chrome. setInterval
is capped at 1000ms and requestAnimationFrame
is paused when the tab is inactive.
Summary
Repeating intervals for inactive tabs:
setInterval requestAnimationFrame Chrome 9- not affected not supported 10 not affected paused 11+ >=1000ms paused Firefox 3- not affected not supported 4 not affected 1s 5+ >=1000ms 2ns (n = number of frames since inactivity) IE 9- not affected not supported 10+ not affected paused Edge 13- not affected paused 14+ >=1000ms paused Safari 5- not affected not supported 6 not affected paused 7+ >=1000ms paused Opera 12- not affected not supported 15+ >=1000ms paused