当选项卡或窗口不活动时,浏览器如何暂停/更改 Javascript?

背景: 我正在做一些用户界面测试,需要检测人们是否在关注。但是,这个问题是关于 页面可见性 API没有

具体来说,我想知道在不同的浏览器中,如果当前选项卡不活动,或者浏览器窗口不活动,我的 Javascript 代码会受到什么影响。到目前为止,我发现了以下几点:

我有以下问题:

  • 除了移动浏览器,桌面浏览器有没有在选项卡不活动时暂停 JS 执行?什么时候,哪些浏览器?
  • 哪些浏览器可以减少 setInterval的重复?是减少到一个极限还是一个百分比?例如,如果我有一个10毫秒的重复与一个5000毫秒的重复,每个将如何受到影响?
  • 如果 窗户失焦,而不仅仅是选项卡失焦,会发生这些变化吗?(我想这会更难检测,因为它需要操作系统 API。)
  • 在活动标签中是否还有其他不会被观察到的效果?他们会把原本可以正确执行的事情搞砸吗(比如前面提到的 Jasmine 测试) ?
62527 次浏览

我观察到的是: 在 铬合金的非活动标签上,所有等待小于 1000毫米setTimeout(对于 setInterval必须是相同的)都四舍五入到 1000毫米。我认为更长的超时没有被修改。

似乎是从 Chrome 11Firefox 5.0: https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs开始的行为

此外,我认为当整个窗口处于非活动状态时,它不会这样运行(但似乎很容易调查)。

测试一

我为此专门编写了一个测试:
帧速率分布: setInterval vs requestAnimationFrame

注意: 这个测试是 CPU 密集型的,IE9和 Opera 12不支持 requestAnimationFrame

测试记录 setIntervalrequestAnimationFrame在不同浏览器中运行的实际时间,并以分布的形式给出结果。您可以更改 setInterval的毫秒数,以查看它在不同设置下的运行情况。在延迟方面,setTimeout的工作方式与 setInterval类似。取决于浏览器,requestAnimationFrame通常默认为60 fps。要查看切换到另一个选项卡或有一个不活动的窗口时会发生什么,只需打开页面,切换到另一个选项卡并等待一段时间。它将继续在非活动选项卡中记录这些函数实际花费的时间。

测试二

另一种测试方法是使用 setIntervalrequestAnimationFrame重复记录时间戳,并在一个分离的控制台中查看它。当您使选项卡或窗口处于非活动状态时,您可以看到它的更新频率(或者它是否曾经更新过)。

结果

铬合金
当选项卡处于非活动状态时,Chrome 将 setInterval的最小间隔限制在1000毫秒左右。如果间隔大于1000ms,它将按指定的间隔运行。窗口是否失焦并不重要,只有当切换到不同的选项卡时,间隔才会受到限制。当选项卡处于非活动状态时,将暂停 requestAnimationFrame

// Provides control over the minimum timer interval for background tabs.
const double kBackgroundTabTimerInterval = 1.0;

Https://codereview.chromium.org/6546021/patch/1001/2001

火狐
与 Chrome 类似,当选项卡(而不是窗口)处于非活动状态时,Firefox 将 setInterval的最小间隔限制在1000毫秒左右。但是,当选项卡处于非活动状态时,requestAnimationFrame的运行速度会呈指数级减慢,每帧采用1、2、4、8等。

// 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 不会限制非活动选项卡时 setInterval的延迟,但会在非活动选项卡中暂停 requestAnimationFrame。窗口是否失焦并不重要。

Edge
从边缘14开始,setInterval在非活动选项卡中的上限为1000ms。 requestAnimationFrame总是在非活动选项卡中暂停。

旅行
和 Chrome 一样,Safari 在标签页处于非活动状态时将 setInterval设置为1000ms,同时也暂停 requestAnimationFrame

歌剧
自从采用 Webkit 引擎以来,Opera 展示了与 Chrome 相同的行为。setInterval的上限为1000ms,当选项卡处于非活动状态时,将暂停 requestAnimationFrame

摘要

非活动选项卡的重复间隔:

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

一个新的答案来补充这些: 在 chrome 78.0。3904.108我注意到这些超时的 所有(不仅仅是那些低于1000ms)比预期的时间要长一点,当我移动到一个不同的标签,然后再回来。我看到的行为是更正确地描述为 “非活动选项卡上的所有超时可能会延迟一些额外的时间,最多可达1000毫秒。”尝试运行以下内容并切换到另一个标签!

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>