Bagaimana cara browser menjeda / mengubah Javascript ketika tab atau jendela tidak aktif?

168

Latar Belakang: Saya sedang melakukan beberapa tes antarmuka pengguna yang perlu mendeteksi apakah orang memperhatikan atau tidak. Tapi, pertanyaan ini bukan tentang API visibilitas halaman .

Secara khusus, saya ingin tahu bagaimana kode Javascript saya akan terpengaruh jika tab saat ini tidak aktif, atau jendela browser tidak aktif, di browser yang berbeda. Saya telah menggali yang berikut ini sejauh ini:

Saya punya pertanyaan berikut:

  • Selain peramban seluler, apakah peramban desktop pernah menghentikan eksekusi JS ketika sebuah tab tidak aktif? Kapan dan browser apa?
  • Browser mana yang mengurangi setIntervalpengulangan? Apakah hanya dikurangi menjadi batas atau persentase? Sebagai contoh, jika saya memiliki 10ms repeat versus repeat 5000ms, bagaimana masing-masing akan terpengaruh?
  • Apakah perubahan ini terjadi jika jendela tidak fokus, bukan hanya tab? (Saya membayangkan akan lebih sulit untuk dideteksi, karena membutuhkan OS API.)
  • Apakah ada efek lain yang tidak akan diamati pada tab aktif? Bisakah mereka mengacaukan hal-hal yang seharusnya dilakukan dengan benar (yaitu tes Jasmine yang disebutkan sebelumnya)?
Andrew Mao
sumber
Jika dijeda, situs seperti Facebook tidak akan menerima pesan obrolan di tab latar belakang.
Joseph
1
Ya tidak ada jeda, tapi saya ingat pernah membaca bahwa setInterval/ setTimeoutkali di bawah 1000ms diubah menjadi 1000ms ketika tab / jendela buram
Ian
19
@ProfPickle Webmaster? Betulkah? Ini adalah pertanyaan pemrograman JS.
Andrew Mao
1
@lan setInterval/ setTimeoutwaktu di bawah 1000ms diubah menjadi 1000ms ketika tab / jendela kabur. Tidak jelas apa yang Anda coba sampaikan
Amol M Kulkarni
4
+1 Pertanyaan bagus. Akan lebih baik untuk melihat perbandingan perilaku browser secara berdampingan, karena saya percaya perilaku penjepitan saat tab tidak aktif bukan bagian dari standar apa pun.
UpTheCreek

Jawaban:

190

Tes Satu

Saya telah menulis tes khusus untuk tujuan ini:
Distribusi Frame Rate: setInterval vs requestAnimationFrame

Catatan: Tes ini cukup intensif CPU. requestAnimationFrametidak didukung oleh IE 9- dan Opera 12-.

Tes mencatat waktu aktual yang diperlukan untuk setIntervaldan requestAnimationFramemenjalankan di berbagai browser, dan memberi Anda hasil dalam bentuk distribusi. Anda dapat mengubah jumlah milidetik untuk setIntervalmelihat cara kerjanya di bawah pengaturan yang berbeda. setTimeoutbekerja mirip setIntervaldengan sehubungan dengan penundaan. requestAnimationFrameumumnya default ke 60fps tergantung pada browser. Untuk melihat apa yang terjadi ketika Anda beralih ke tab lain atau memiliki jendela tidak aktif, cukup buka halaman, beralih ke tab lain dan tunggu sebentar. Ini akan terus mencatat waktu aktual yang diperlukan untuk fungsi-fungsi ini di tab tidak aktif.

Tes Dua

Cara lain untuk menguji itu adalah untuk log timestamp berulang kali dengan setIntervaldan requestAnimationFramedan melihatnya di konsol terpisah. Anda dapat melihat seberapa sering itu diperbarui (atau jika pernah diperbarui) ketika Anda membuat tab atau jendela tidak aktif.

Hasil

Chrome
Chrome membatasi interval minimum setIntervalhingga sekitar 1000 ms ketika tab tidak aktif. Jika interval lebih tinggi dari 1000ms, itu akan berjalan pada interval yang ditentukan. Tidak masalah jika jendela tidak fokus, intervalnya dibatasi hanya ketika Anda beralih ke tab lain. requestAnimationFramedijeda ketika tab tidak aktif.

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

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

Firefox
Mirip dengan Chrome, Firefox membatasi interval minimum setIntervalhingga sekitar 1000 ms ketika tab (bukan jendela) tidak aktif. Namun, requestAnimationFrameberjalan lebih lambat secara eksponensial ketika tab tidak aktif, dengan setiap frame mengambil 1s, 2s, 4s, 8s, dan seterusnya.

// 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 tidak membatasi penundaan setIntervalketika tab tidak aktif, tetapi berhenti requestAnimationFramedi tab tidak aktif. Tidak masalah apakah jendela itu tidak fokus atau tidak.

Edge
Mulai dari Edge 14, setIntervaldibatasi pada 1000ms dalam tab tidak aktif. requestAnimationFrameselalu dijeda dalam tab tidak aktif.

Safari
Sama seperti Chrome, Safari tutup setIntervalpada 1000 ms saat tab tidak aktif. requestAnimationFramedijeda juga.

Opera
Sejak adopsi mesin Webkit, Opera menunjukkan perilaku yang sama seperti Chrome. setIntervaldibatasi pada 1000 ms dan requestAnimationFramedijeda ketika tab tidak aktif.

Ringkasan

Interval berulang untuk tab tidak aktif:

           setInterval      requestAnimationFrame 
Chrome
9- tidak terpengaruh tidak didukung
10 tidak terpengaruh dijeda
11+> = 1000 ms dijeda

Firefox
3 - tidak terpengaruh tidak didukung
4 tidak terpengaruh 1s
5+> = 1000ms 2 n s (n = jumlah frame karena tidak aktif)

YAITU
9- tidak terpengaruh tidak didukung
10+ tidak terpengaruh dijeda

Tepi
13- tidak terpengaruh dijeda
14+> = 1000ms dijeda

Safari
5- tidak terpengaruh tidak didukung
6 tidak terpengaruh dijeda
7+> = 1000ms dijeda

Opera
12- tidak terpengaruh tidak didukung
15+> = 1000ms dijeda
Antony
sumber
Jawaban yang bagus Adakah perbedaan lain yang mungkin diketahui untuk fungsi selain setIntervaldan requestAnimationFrame?
Andrew Mao
1
@AndrewMao Bukannya saya sadari. Saya menemukan masalah ini ketika saya sedang bekerja di perpustakaan untuk mendeteksi andal jika JS diaktifkan kembali dengan setIntervaldan requestAnimationFrame. Hal yang saya tahu adalah bahwa setTimeoutberperilaku serupa setInterval, dalam bahwa mereka berdua memiliki interval latar belakang minimum yang sama di Firefox dan Chrome, dan tidak ada batas yang jelas di browser lain.
Antony
2
Nilai minimum setInterval Firefox tampaknya dapat diubah dengan membuka url about:configdi browser dan mengubah dom.min_background_timeout_valuenilainya menjadi lebih dari 1000.
Jonas Berlin
dapatkah saya menggunakan ini untuk memuat ulang halaman setiap 5 detik saat browser diperkecil ?, inilah pertanyaan saya.
shaijut
1
Perhatikan bahwa chrome tidak menjeda / mengurangi laju requestAnimationFramepanggilan jika pengguna hanya mengganti aplikasi (Alt + Tab keluar dari Chrome). Selama tab aktif di Chrome, "frame rate" lebih atau kurang konstan.
Marc
11

Apa yang saya amati: pada tab tidak aktif di Chrome , semua Anda setTimeout(harus sama setInterval) menunggu kurang dari 1000ms dibulatkan menjadi 1000ms . Saya pikir batas waktu yang lebih lama tidak dimodifikasi.

Tampaknya menjadi perilaku sejak Chrome 11 dan Firefox 5.0 : https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs

Lebih jauh, saya tidak berpikir itu berlaku seperti ini ketika seluruh jendela tidak aktif (tetapi tampaknya cukup mudah untuk diselidiki).


sumber
1
jQuery's focusdan bluracara tampaknya mendeteksi kedua tab dan switch jendela, sehingga bisa bekerja dua arah. Tapi saya bertanya-tanya bagaimana jendela mendeteksi apakah itu benar-benar terlihat atau tidak.
Andrew Mao
2
Sebenarnya ia tidak memiliki koneksi dengan jQuery atau Javascript karena ini adalah implementasi browser internal.
Bisakah Anda mengonfirmasi ini sekarang di akhir 2016?
vsync
0

Jawaban yang lebih baru untuk melengkapi ini: pada chrome 78.0.3904.108 Saya perhatikan semua timeout ini (bukan hanya yang di bawah 1000 ms ) membutuhkan waktu sedikit lebih lama dari yang diharapkan ketika saya pindah ke tab yang berbeda, dan kemudian kembali. Perilaku yang saya lihat lebih tepat dideskripsikan sebagai "Semua timeout pada tab yang tidak aktif dapat ditunda dengan sejumlah tambahan, hingga maksimum 1000 ms." :

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>

Gershom
sumber