W jaki sposób przeglądarki wstrzymują / zmieniają JavaScript, gdy karta lub okno nie jest aktywne?

168

Kontekst: Przeprowadzam testy interfejsu użytkownika, które muszą wykryć, czy ludzie zwracają na to uwagę, czy nie. Ale to pytanie nie dotyczy interfejsu API widoczności strony .

W szczególności chciałbym wiedzieć, jak wpłynie to na mój kod JavaScript, jeśli bieżąca karta nie jest aktywna lub okno przeglądarki nie jest aktywne w różnych przeglądarkach. Do tej pory wykopałem:

Mam następujące pytania:

  • Czy inne niż przeglądarki mobilne przeglądarki komputerowe wstrzymują wykonywanie JS, gdy karta nie jest aktywna? Kiedy i jakie przeglądarki?
  • Które przeglądarki zmniejszają liczbę setIntervalpowtórzeń? Czy to tylko ograniczenie, czy procent? Na przykład, jeśli mam powtórzenie 10 ms w porównaniu z powtórzeniem 5000 ms, jak to wpłynie na każdy z nich?
  • Czy te zmiany zachodzą, gdy okno jest nieostre, a nie tylko karta? (Wyobrażam sobie, że byłoby to trudniejsze do wykrycia, ponieważ wymaga interfejsu API systemu operacyjnego).
  • Czy są jakieś inne efekty, których nie byłoby widać na aktywnej karcie? Czy mogliby zepsuć rzeczy, które w przeciwnym razie działałyby poprawnie (tj. Wspomniane wyżej testy Jasmine)?
Andrew Mao
źródło
Jeśli są wstrzymane, strony takie jak Facebook nie otrzymają żadnych wiadomości czatu na kartach w tle.
Joseph
1
Tak, nie ma przerwy, ale pamiętam, że czytałem, że setInterval/ setTimeoutczasy poniżej 1000 ms są zmieniane na 1000 ms, gdy karta / okno jest rozmyte
Ian
19
@ProfPickle Webmasterzy? Naprawdę? To jest pytanie programistyczne JS.
Andrew Mao
1
@lan setInterval/ setTimeoutczasy poniżej 1000 ms są zmieniane na 1000 ms, gdy karta / okno jest rozmyte. Nie jest jasne, co próbowałeś przekazać
Amol M Kulkarni
4
+1 Świetne pytanie. Dobrze byłoby zobaczyć porównanie zachowań przeglądarek obok siebie, ponieważ uważam, że zachowanie blokowania, gdy karty nie są aktywne, nie jest częścią żadnego standardu.
UpTheCreek

Odpowiedzi:

190

Test One

Napisałem test specjalnie w tym celu:
Frame Rate Distribution: setInterval vs requestAnimationFrame

Uwaga: ten test mocno obciąża procesor. requestAnimationFramenie jest obsługiwany przez IE 9- i Opera 12-.

Test rejestruje rzeczywisty czas potrzebny na uruchomienie setIntervala i requestAnimationFramew różnych przeglądarkach i przedstawia wyniki w postaci dystrybucji. Możesz zmienić liczbę milisekund, setIntervalaby zobaczyć, jak działa przy różnych ustawieniach. setTimeoutdziała podobnie jak a setIntervalw odniesieniu do opóźnień. requestAnimationFramezwykle domyślnie wynosi 60 klatek na sekundę, w zależności od przeglądarki. Aby zobaczyć, co się stanie, gdy przełączysz się na inną kartę lub masz nieaktywne okno, po prostu otwórz stronę, przełącz się na inną kartę i poczekaj chwilę. Będzie nadal rejestrował rzeczywisty czas potrzebny na te funkcje na nieaktywnej karcie.

Test drugi

Innym sposobem przetestowania tego jest wielokrotne rejestrowanie znacznika czasu za pomocą setIntervali requestAnimationFramei przeglądanie go w odłączonej konsoli. Możesz zobaczyć, jak często jest aktualizowany (lub czy kiedykolwiek jest aktualizowany), gdy wyłączysz kartę lub okno.

Wyniki

Chrome
Chrome ogranicza minimalny interwał setIntervaldo około 1000 ms, gdy karta jest nieaktywna. Jeśli interwał jest dłuższy niż 1000 ms, będzie działał z określonym interwałem. Nie ma znaczenia, czy okno jest nieostre, interwał jest ograniczony tylko wtedy, gdy przełączysz się na inną kartę. requestAnimationFramejest wstrzymywany, gdy karta jest nieaktywna.

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

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

Firefox
Podobnie jak Chrome, Firefox ogranicza minimalny interwał setIntervaldo około 1000 ms, gdy karta (nie okno) jest nieaktywna. Jednak requestAnimationFramedziała wykładniczo wolniej, gdy karta jest nieaktywna, a każda klatka zajmuje 1s, 2s, 4s, 8s i tak dalej.

// 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 nie ogranicza opóźnienia, setIntervalgdy karta jest nieaktywna, ale zatrzymuje się requestAnimationFramena nieaktywnych kartach. Nie ma znaczenia, czy okno jest nieostre, czy nie.

Krawędź
Począwszy od krawędzi 14, setIntervaljest ograniczona do 1000 ms w nieaktywnych zakładkach. requestAnimationFramejest zawsze wstrzymywany w nieaktywnych kartach.

Safari
Podobnie jak Chrome, Safari ogranicza czas setIntervaldo 1000 ms, gdy karta jest nieaktywna. requestAnimationFramejest również wstrzymana.

Opera
Od czasu przyjęcia silnika Webkit Opera zachowuje się tak samo jak Chrome. setIntervaljest ograniczony do 1000 ms i requestAnimationFramejest wstrzymywany, gdy karta jest nieaktywna.

Podsumowanie

Powtarzanie odstępów czasu dla nieaktywnych kart:

           setInterval      requestAnimationFrame 
Chrome
9 - nie dotyczy, nie jest obsługiwany
10 nie dotyczy wstrzymano
11+> = 1000 ms wstrzymano

Firefox
3 - nie dotyczy, nie jest obsługiwany
4 nie dotyczy 1s
5+> = 1000 ms 2 n s (n = liczba klatek od braku aktywności)

TO ZNACZY
9 - nie dotyczy, nie jest obsługiwany
10+ nie dotyczy wstrzymano

Krawędź
13- nie dotyczy wstrzymano
14+> = 1000 ms wstrzymano

Safari
5- nie dotyczy nie jest obsługiwany
6 nie dotyczy wstrzymano
7+> = 1000 ms wstrzymano

Opera
12 - nie dotyczy nie jest obsługiwany
15+> = 1000 ms wstrzymano
Antony
źródło
Świetna odpowiedź. Jakiekolwiek inne możliwe znane różnice dla funkcji innych niż setIntervali requestAnimationFrame?
Andrew Mao,
1
@AndrewMao Nie jestem tego świadomy. Natknąłem się na ten problem, gdy pracowałem nad biblioteką, aby niezawodnie wykryć, czy JS ponownie włączono z setIntervali requestAnimationFrame. Wiem o tym, że setTimeoutzachowuje się podobnie do tego setInterval, że oba mają ten sam minimalny interwał tła w Firefoksie i Chrome i nie mają widocznego ograniczenia w innych przeglądarkach.
Antony
2
Minimalną wartość Firefox setInterval można najwyraźniej zmienić, otwierając adres URL about:configw przeglądarce i zmieniając dom.min_background_timeout_valuewartość na inną niż 1000.
Jonas Berlinie
czy mogę użyć tego do ponownego ładowania strony co 5 sekund, gdy przeglądarka jest zminimalizowana?, oto moje pytanie.
shaijut
1
Zwróć uwagę, że chrome nie wstrzymuje / nie zmniejsza szybkości requestAnimationFramewywołania, jeśli użytkownik po prostu przełączy aplikację (Alt + Tab poza Chrome). Dopóki karta jest aktywna w Chrome, „liczba klatek na sekundę” jest mniej więcej stała.
Marc
11

Co zaobserwowałem: na nieaktywnych kartach w Chrome wszystkie twoje setTimeout(musi być takie samo setInterval) czekanie mniej niż 1000 ms są zaokrąglane do 1000 ms . Myślę, że dłuższe limity czasu nie są modyfikowane.

Wygląda na to, że zachowuje się tak od wersji Chrome 11 i Firefox 5.0 : https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs

Co więcej, nie sądzę, aby zachowywał się w ten sposób, gdy całe okno jest nieaktywne (ale wydaje się dość łatwe do zbadania).


źródło
1
Wydaje się, że jQuery focusi blurzdarzenia wykrywają zarówno przełączniki kart, jak i okien, więc może działać w obie strony. Ale zastanawiam się, jak okno wykrywa, czy jest rzeczywiście widoczne, czy nie.
Andrew Mao,
2
W rzeczywistości nie ma połączenia z jQuery ani JavaScript, ponieważ jest to wewnętrzna implementacja przeglądarki.
Czy możesz to potwierdzić pod koniec 2016 roku?
vsync
0

Nowsza odpowiedź uzupełniająca te: na chrome 78.0.3904.108 zauważam, że wszystkie te limity czasu (nie tylko te poniżej 1000 ms ) trwają nieco dłużej niż oczekiwano, gdy przechodzę do innej karty, a potem wracam. Zachowanie, które widzę, jest dokładniej opisane jako „Wszystkie limity czasu na nieaktywnych kartach mogą być opóźnione o dodatkową wartość, do maksymalnie 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
źródło