Como os navegadores pausam / alteram o Javascript quando a guia ou janela não está ativa?


168

Histórico: estou fazendo alguns testes na interface do usuário que precisam detectar se as pessoas estão prestando atenção ou não. Mas, essa pergunta não é sobre a API de visibilidade da página .

Especificamente, gostaria de saber como meu código Javascript será afetado se a guia atual não estiver ativa ou a janela do navegador não estiver ativa em diferentes navegadores. Eu desenterrei o seguinte até agora:

Tenho as seguintes perguntas:

  • Além dos navegadores móveis, os navegadores da área de trabalho pausam a execução de JS quando uma guia não está ativa? Quando e quais navegadores?
  • Quais navegadores reduzem a setIntervalrepetição? É apenas reduzido a um limite ou a uma porcentagem? Por exemplo, se eu tiver uma repetição de 10 ms contra uma repetição de 5000 ms, como cada uma será afetada?
  • Essas mudanças acontecem se a janela estiver fora de foco, em vez de apenas a guia? (Imagino que seria mais difícil de detectar, pois requer a API do SO).
  • Existem outros efeitos que não seriam observados em uma guia ativa? Eles poderiam atrapalhar coisas que seriam executadas corretamente (ou seja, os testes Jasmine mencionados)?

Se eles forem pausados, sites como o Facebook não receberão nenhuma mensagem de bate-papo nas guias em segundo plano.
Joseph

1
Sim não há nenhuma pausa, mas eu lembro de ter lido que os setInterval/ setTimeoutvezes sob 1000ms são alterados para 1000 ms quando o separador / janela está borrada
Ian

19
@ProfPickle Webmasters? Realmente? Esta é uma questão de programação JS.
Andrew Mao

1
@lan setInterval/ setTimeouttimes abaixo de 1000ms são alterados para 1000ms quando a guia / janela está embaçada. Não está claro o que você tentou transmitir
Amol M Kulkarni

4
+1 Ótima pergunta. Seria bom ver uma comparação lado a lado dos comportamentos do navegador, pois acredito que o comportamento de fixação quando as guias não estão ativas não faz parte de nenhum padrão.
UpTheCreek

Respostas:


190

Teste Um

Escrevi um teste especificamente para esse fim:
Distribuição da taxa de quadros: setInterval vs requestAnimationFrame

Nota: Este teste é bastante intensivo da CPU. requestAnimationFramenão é suportado pelo IE 9- e Opera 12-.

O teste registra o tempo real que leva para um setIntervale requestAnimationFramepara executar em diferentes navegadores, e dá-lhe os resultados sob a forma de uma distribuição. Você pode alterar o número de milissegundos para setIntervalver como ele é executado sob diferentes configurações. setTimeoutfunciona de maneira semelhante a setIntervalcom relação a atrasos. requestAnimationFramegeralmente o padrão é 60fps, dependendo do navegador. Para ver o que acontece quando você alterna para uma guia diferente ou tem uma janela inativa, basta abrir a página, alternar para uma guia diferente e aguardar um pouco. Ele continuará registrando o tempo real necessário para essas funções em uma guia inativa.

Teste Dois

Outra maneira de testá-lo é registrar o registro de data setIntervale hora repetidamente com requestAnimationFramee visualizá-lo em um console desanexado. Você pode ver com que frequência ela é atualizada (ou se alguma vez é atualizada) ao desativar a guia ou a janela.

Resultados

Chrome
Chrome limita o intervalo mínimo de setIntervalcerca de 1000 ms quando a guia está inativa. Se o intervalo for maior que 1000ms, ele será executado no intervalo especificado. Não importa se a janela está fora de foco, o intervalo é limitado apenas quando você alterna para uma guia diferente. requestAnimationFrameé pausado quando a guia está inativa.

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

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

Firefox
Semelhante ao Chrome, o Firefox limita o intervalo mínimo de setIntervalcerca de 1000ms quando a guia (não a janela) está inativa. No entanto, requestAnimationFrameé executado exponencialmente mais lento quando a guia está inativa, com cada quadro levando 1s, 2s, 4s, 8s e assim por diante.

// 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

O Internet Explorer
IE não limita o atraso setIntervalquando a guia está inativa, mas faz uma pausa requestAnimationFramenas guias inativas. Não importa se a janela está fora de foco ou não.

Borda
A partir da borda 14, o setIntervallimite é de 1000ms nas guias inativas. requestAnimationFramesempre é pausado em guias inativas.

Safari
Assim como o Chrome, o Safari setIntervalatinge os 1000ms quando a guia está inativa. requestAnimationFrametambém está em pausa.

Opera
Desde a adoção do mecanismo Webkit, o Opera exibe o mesmo comportamento do Chrome. setIntervalé limitado a 1000ms e requestAnimationFramepausado quando a guia está inativa.

Resumo

Intervalos repetidos para guias inativas:

           setInterval      requestAnimationFrame 
Chrome
9- não afetado não suportado
10 não afetado pausado
11+> = 1000ms em pausa

Raposa de fogo
3- não afetado não suportado
4 1s não afetados
5+> = 1000ms 2 n s (n = número de quadros desde inatividade)

IE
9- não afetado não suportado
10+ não afetados em pausa

Beira
13- não afetado pausado
14+> = 1000ms em pausa

Safári
5- não afetado não suportado
6 não afetado pausado
7+> = 1000ms em pausa

Ópera
12- não afetado não suportado
15+> = 1000ms em pausa

Ótima resposta. Quaisquer outras diferenças conhecidas possíveis para funções diferentes de setIntervale requestAnimationFrame?
Andrew Mao

1
@AndrewMao Não que eu saiba. Me deparei com esse problema quando estava trabalhando em uma biblioteca para detectar com segurança se o JS foi reativado com setIntervale requestAnimationFrame. O que sei é que setTimeoutse comporta de maneira semelhante setInterval, pois ambos têm o mesmo intervalo mínimo de segundo plano no Firefox e Chrome e nenhum limite aparente em outros navegadores.
Antony

2
O valor mínimo Firefox setInterval aparentemente pode ser mudado abrindo a url about:configno navegador e mudar o dom.min_background_timeout_valuevalor para algo mais do que 1000.
Jonas Berlin

posso usar isso para recarregar a página a cada 5 segundos quando o navegador é minimizado ?, eis a minha pergunta.
shaijut

1
Observe que o chrome não pausa / reduz a taxa na qual requestAnimationFrameé chamado se o usuário simplesmente alternar entre aplicativos (Alt + Tab do Chrome). Enquanto a guia estiver ativa no Chrome, a "taxa de quadros" será mais ou menos constante.
Marc

11

O que eu observei: nas guias inativas do Chrome , todas as suas setTimeout(devem ser iguais setInterval) a espera de menos de 1000ms são arredondadas para 1000ms . Penso que tempos limite mais longos não são modificados.

Parece ser o comportamento desde o Chrome 11 e Firefox 5.0 : https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs

Além disso, acho que não se comporta dessa maneira quando a janela inteira está inativa (mas parece bastante fácil investigar).


1
O jQuery focuse os blureventos parecem detectar as opções de guia e janela, de modo que poderia funcionar nos dois sentidos. Mas eu me pergunto como a janela detecta se é realmente visível ou não.
Andrew Mao

2
Na verdade, ele não tem conexão com jQuery ou Javascript , pois é uma implementação interna do navegador.

Você pode confirmar isso agora no final de 2016?
vsync

0

Uma resposta mais recente para complementar: no chrome 78.0.3904.108, percebo que todos esses tempos limite (não apenas aqueles abaixo de 1000ms) demoram um pouco mais do que o esperado quando eu mudo para uma guia diferente e depois volto. O comportamento que estou vendo é descrito mais corretamente como "Todos os tempos limites nas guias inativas podem sofrer um atraso adicional, em um valor adicional, até no máximo 1000ms". :

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>

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.