Browser Support
Os navegadores modernos às vezes suspendem ou descartam páginas inteiras quando os recursos do sistema estão limitados. No futuro, os navegadores querem fazer isso de forma proativa para consumir menos energia e memória. A API Page Lifecycle fornece hooks de ciclo de vida para que suas páginas possam processar com segurança essas intervenções do navegador sem afetar a experiência do usuário. Confira a API para saber se você precisa implementar esses recursos no seu aplicativo.
Contexto
O ciclo de vida do aplicativo é uma maneira fundamental de os sistemas operacionais modernos gerenciarem recursos. No Android, iOS e versões recentes do Windows, os apps podem ser iniciados e interrompidos a qualquer momento pelo SO. Isso permite que essas plataformas simplifiquem e realoquem recursos onde eles beneficiam mais o usuário.
Na Web, historicamente, não há esse ciclo de vida, e os apps podem ser mantidos ativos indefinidamente. Com um grande número de páginas da Web em execução, recursos críticos do sistema, como memória, CPU, bateria e rede, podem ser superdimensionados, resultando em uma experiência ruim para o usuário final.
Embora a plataforma da Web tenha eventos relacionados a estados de ciclo de vida, como load, unload e visibilitychange, esses eventos só permitem que os desenvolvedores respondam a mudanças de estado de ciclo de vida iniciadas pelo usuário. Para que a Web funcione
de maneira confiável em dispositivos de baixa potência (e seja mais consciente dos recursos em geral em
todas as plataformas), os navegadores precisam de uma maneira de recuperar e realocar proativamente os recursos do sistema.
Na verdade, os navegadores atuais já tomam medidas ativas para conservar recursos em páginas de guias em segundo plano, e muitos navegadores (especialmente o Chrome) gostariam de fazer muito mais isso para diminuir a pegada geral de recursos.
O problema é que os desenvolvedores não têm como se preparar para esses tipos de intervenções iniciadas pelo sistema ou sequer saber que elas estão acontecendo. Isso significa que os navegadores precisam ser conservadores ou correm o risco de quebrar páginas da Web.
A API Page Lifecycle tenta resolver esse problema:
- Apresentação e padronização do conceito de estados de ciclo de vida na Web.
- Definir novos estados iniciados pelo sistema que permitem aos navegadores limitar os recursos que podem ser consumidos por guias ocultas ou inativas.
- Criação de novas APIs e eventos que permitem que os desenvolvedores da Web respondam a transições para e de volta desses novos estados iniciados pelo sistema.
Essa solução oferece a previsibilidade de que os desenvolvedores da Web precisam para criar aplicativos resilientes a intervenções do sistema e permite que os navegadores otimizem os recursos do sistema de forma mais agressiva, beneficiando todos os usuários da Web.
O restante desta postagem vai apresentar os novos recursos do ciclo de vida da página e explicar como eles se relacionam com todos os estados e eventos da plataforma da Web. Ele também vai apresentar recomendações e práticas recomendadas sobre os tipos de trabalho que os desenvolvedores devem (e não devem) fazer em cada estado.
Visão geral dos estados e eventos do ciclo de vida da página
Todos os estados do ciclo de vida da página são discretos e mutuamente exclusivos, o que significa que uma página só pode estar em um estado por vez. E a maioria das mudanças no estado do ciclo de vida de uma página geralmente pode ser observada por eventos do DOM. Consulte as recomendações para desenvolvedores de cada estado para ver as exceções.
Talvez a maneira mais fácil de explicar os estados do ciclo de vida da página, bem como os eventos que sinalizam transições entre eles, seja com um diagrama:
Estados
A tabela a seguir explica cada estado em detalhes. Ele também lista os possíveis estados que podem vir antes e depois, bem como os eventos que os desenvolvedores podem usar para observar mudanças.
| Estado | Descrição |
|---|---|
| Ativa |
Uma página está no estado ativa se estiver visível e tiver foco de entrada.
Possíveis estados anteriores: |
| Passiva |
Uma página está no estado passivo se estiver visível e não tiver foco de entrada.
Possíveis estados anteriores:
Próximos estados possíveis: |
| Ocultos |
Uma página está no estado oculto se não estiver visível (e não tiver sido congelada, descartada ou encerrada).
Possíveis estados anteriores:
Próximos estados possíveis: |
| Congelado |
No estado congelado, o navegador suspende a execução de
tarefas
congeláveis
nas
filas de tarefas da página até que ela seja descongelada. Isso significa que itens como timers JavaScript e callbacks de busca não são executados. As tarefas já em execução podem ser concluídas (principalmente o callback
Os navegadores congelam páginas para preservar o uso de CPU/bateria/dados. Eles também fazem isso para permitir navegações para frente/para trás mais rápidas, evitando a necessidade de um recarregamento completo da página.
Possíveis estados anteriores:
Próximos estados possíveis: |
| Encerrado |
Uma página fica no estado encerrada depois que começa a ser descarregada e removida da memória pelo navegador. Nenhuma nova tarefa pode ser iniciada nesse estado, e as tarefas em andamento podem ser encerradas se forem executadas por muito tempo.
Possíveis estados anteriores:
Próximos estados possíveis: |
| Descartado |
Uma página está no estado descartada quando é descarregada pelo navegador para conservar recursos. Nenhuma tarefa, callback de evento ou JavaScript de qualquer tipo pode ser executado nesse estado, já que os descartes geralmente ocorrem em restrições de recursos, em que iniciar novos processos é impossível. No estado descartada, a guia em si (incluindo o título e o favicon) geralmente fica visível para o usuário mesmo que a página tenha sido removida.
Possíveis estados anteriores:
Próximos estados possíveis: |
Eventos
Os navegadores enviam muitos eventos, mas apenas uma pequena parte deles sinaliza uma possível mudança no estado do ciclo de vida da página. A tabela a seguir descreve todos os eventos relacionados ao ciclo de vida e lista os estados de transição.
| Nome | Detalhes |
|---|---|
focus
|
Um elemento DOM recebeu foco.
Observação:um evento
Estados anteriores possíveis:
Estados atuais possíveis: |
blur
|
Um elemento DOM perdeu o foco.
Observação:um evento
Estados anteriores possíveis:
Possíveis estados atuais: |
visibilitychange
|
O valor
|
freeze
*
|
A página acabou de ser congelada. Nenhuma tarefa congelável nas filas de tarefas da página será iniciada.
Estados anteriores possíveis:
Possíveis estados atuais: |
resume
*
|
O navegador retomou uma página congelada.
Estados anteriores possíveis:
Possíveis estados atuais: |
pageshow
|
Uma entrada do histórico de sessões está sendo percorrida. Pode ser um carregamento de página totalmente novo ou uma página retirada do cache de avanço e retorno. Se a página
foi retirada do cache de avanço e retorno, a propriedade
Possíveis estados anteriores: |
pagehide
|
Uma entrada do histórico de sessões está sendo percorrida. Se o usuário estiver navegando para outra página e o navegador puder adicionar
a página atual ao cache
de volta/avançar para ser reutilizada mais tarde, a propriedade
Estados anteriores possíveis:
Estados atuais possíveis: |
beforeunload
|
A janela, o documento e os recursos dele estão prestes a ser descarregados. O documento ainda está visível e o evento ainda pode ser cancelado neste ponto.
Importante:o evento
Estados anteriores possíveis:
Possíveis estados atuais: |
unload
|
A página está sendo descarregada.
Aviso:o uso do evento
Estados anteriores possíveis:
Possíveis estados atuais: |
* Indica um novo evento definido pela API Page Lifecycle.
Novos recursos adicionados no Chrome 68
O gráfico anterior mostra dois estados iniciados pelo sistema, e não pelo usuário: congelado e descartado. Como mencionado anteriormente, os navegadores atuais já congelam e descartam ocasionalmente guias ocultas (a critério deles), mas os desenvolvedores não têm como saber quando isso está acontecendo.
No Chrome 68, os desenvolvedores podem observar quando uma guia oculta é congelada e descongelada detectando os eventos freeze e resume em document.
document.addEventListener('freeze', (event) => {
// The page is now frozen.
});
document.addEventListener('resume', (event) => {
// The page has been unfrozen.
});
No Chrome 68, o objeto document agora inclui uma propriedade wasDiscarded no Chrome para computador. O suporte para Android está sendo acompanhado neste problema. Para determinar se uma página foi descartada em uma guia oculta, inspecione o valor dessa propriedade no tempo de carregamento da página. Observação: as páginas descartadas precisam ser recarregadas para serem usadas novamente.
if (document.wasDiscarded) {
// Page was previously discarded by the browser while in a hidden tab.
}
Para saber o que é importante fazer nos eventos freeze e resume, além de como lidar e se preparar para o descarte de páginas, consulte as recomendações para desenvolvedores em cada estado.
As próximas seções oferecem uma visão geral de como esses novos recursos se encaixam nos estados e eventos da plataforma da Web atuais.
Como observar estados do ciclo de vida da página no código
Nos estados ativo, passivo e oculto, é possível executar código JavaScript que determina o estado atual do ciclo de vida da página usando APIs da plataforma Web.
const getState = () => {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
};
Já os estados congelado e encerrado só podem ser detectados nos respectivos listeners de eventos (freeze e pagehide) à medida que o estado muda.
Como observar mudanças de estado
Com base na função getState() definida anteriormente, você pode observar todas as mudanças de estado do ciclo de vida da página com o seguinte código.
// Stores the initial state using the `getState()` function (defined above).
let state = getState();
// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
const prevState = state;
if (nextState !== prevState) {
console.log(`State change: ${prevState} >>> ${nextState}`);
state = nextState;
}
};
// Options used for all event listeners.
const opts = {capture: true};
// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
window.addEventListener(type, () => logStateChange(getState()), opts);
});
// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
// In the freeze event, the next state is always frozen.
logStateChange('frozen');
}, opts);
window.addEventListener('pagehide', (event) => {
// If the event's persisted property is `true` the page is about
// to enter the back/forward cache, which is also in the frozen state.
// If the event's persisted property is not `true` the page is
// about to be unloaded.
logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);
Esse código faz três coisas:
- Define o estado inicial usando a função
getState(). - Define uma função que aceita um próximo estado e, se houver uma mudança, registra as mudanças de estado no console.
- Adiciona listeners de eventos de captura para todos os eventos necessários do ciclo de vida, que, por sua vez, chamam
logStateChange(), transmitindo o próximo estado.
Uma coisa importante sobre o código é que todos os listeners de eventos são adicionados
a window e todos transmitem
{capture: true}.
Existem alguns motivos para isso acontecer, entre eles:
- Nem todos os eventos do ciclo de vida da página têm o mesmo destino.
pagehideepageshowsão acionados emwindow;visibilitychange,freezeeresumesão acionados emdocument, efocuseblursão acionados nos respectivos elementos DOM. - A maioria desses eventos não é propagada, o que significa que é impossível adicionar listeners de eventos que não capturam a um elemento ancestral comum e observar todos eles.
- A fase de captura é executada antes das fases de destino ou de bolha. Portanto, adicionar listeners ali ajuda a garantir que eles sejam executados antes que outro código possa cancelá-los.
Recomendações para desenvolvedores em cada estado
Como desenvolvedores, é importante entender os estados do ciclo de vida da página e saber como observá-los no código, porque o tipo de trabalho que você deve (e não deve) fazer depende muito do estado da página.
Por exemplo, não faz sentido mostrar uma notificação transitória ao usuário se a página estiver oculta. Embora esse exemplo seja bem óbvio, há outras recomendações que não são tão óbvias e que valem a pena enumerar.
| Estado | Recomendações para desenvolvedores |
|---|---|
Active |
O estado ativo é o momento mais importante para o usuário e, portanto, o mais importante para que sua página seja responsiva à entrada do usuário. Qualquer trabalho que não seja da interface e possa bloquear a linha de execução principal precisa ser despriorizado para períodos ociosos ou descarregado para um service worker. |
Passive |
No estado passivo, o usuário não está interagindo com a página, mas ainda pode vê-la. Isso significa que as atualizações e animações da interface ainda serão uniformes, mas o momento em que essas atualizações ocorrem é menos crítico. Quando a página muda de ativa para passiva, é um bom momento para manter o estado do aplicativo não salvo. |
|
Quando a página muda de passiva para oculta, é possível que o usuário não interaja mais com ela até que seja recarregada. A transição para oculto também costuma ser a última mudança de estado
que pode ser observada de forma confiável pelos desenvolvedores. Isso é especialmente verdadeiro em
dispositivos móveis, já que os usuários podem fechar guias ou o próprio app do navegador, e os
eventos Isso significa que você deve tratar o estado hidden como o provável fim da sessão do usuário. Em outras palavras, mantenha qualquer estado de aplicativo não salvo e envie todos os dados de análise não enviados. Você também precisa parar de fazer atualizações na interface (já que elas não serão vistas pelo usuário) e interromper todas as tarefas que um usuário não gostaria de executar em segundo plano. |
|
Frozen |
No estado congelado, as tarefas congeláveis nas filas de tarefas são suspensas até que a página seja descongelada, o que pode nunca acontecer (por exemplo, se a página for descartada). Isso significa que, quando a página muda de oculta para congelada, é essencial interromper todos os timers ou encerrar todas as conexões que, se congeladas, podem afetar outras guias abertas na mesma origem ou a capacidade do navegador de colocar a página no cache de voltar/avançar. Em especial, é importante que você:
Também é necessário manter qualquer estado de visualização dinâmica (por exemplo, posição de rolagem
em uma visualização de lista infinita) em
Se a página passar de congelada para oculta, você poderá reabrir as conexões fechadas ou reiniciar as pesquisas que foram interrompidas quando a página foi congelada inicialmente. |
Terminated |
Em geral, não é necessário fazer nada quando uma página passa para o estado encerrado. Como as páginas descarregadas como resultado da ação do usuário sempre passam pelo estado hidden antes de entrar no estado terminated, é no estado hidden que a lógica de encerramento da sessão (por exemplo, persistência do estado do aplicativo e geração de relatórios para o Google Analytics) deve ser realizada. Além disso, conforme mencionado nas recomendações para o estado oculto, é muito importante que os desenvolvedores percebam que a transição para o estado encerrado não pode ser detectada de maneira confiável em muitos casos, especialmente em dispositivos móveis. Portanto, os desenvolvedores que dependem de eventos de encerramento (por exemplo, |
Discarded |
O estado descartado não é observável pelos desenvolvedores no momento em que uma página está sendo descartada. Isso acontece porque as páginas geralmente são descartadas sob restrições de recursos, e descongelar uma página apenas para permitir que o script seja executado em resposta a um evento de descarte simplesmente não é possível na maioria dos casos. Como resultado, prepare-se para a possibilidade de um descarte na mudança de hidden para frozen. Depois, você pode reagir à restauração de uma página descartada no tempo de carregamento da página verificando |
Mais uma vez, como a confiabilidade e a ordenação de eventos do ciclo de vida não são implementadas de forma consistente em todos os navegadores, a maneira mais fácil de seguir o conselho na tabela é usar PageLifecycle.js.
APIs legadas do ciclo de vida a serem evitadas
Evite os seguintes eventos sempre que possível.
O evento de descarregamento
Muitos desenvolvedores tratam o evento unload como um callback garantido e o usam como
um sinal de fim de sessão para salvar o estado e enviar dados de análise. No entanto, isso
é extremamente não confiável, principalmente em dispositivos móveis. O evento unload não é acionado em muitas situações típicas de descarregamento, incluindo o fechamento de uma guia no seletor de guias em dispositivos móveis ou o fechamento do app do navegador no seletor de apps.
Por isso, é sempre melhor confiar no evento
visibilitychange para determinar quando uma sessão
termina e considerar o estado oculto como o
último momento confiável para salvar dados do app e do usuário.
Além disso, a mera presença de um manipulador de eventos unload registrado (via
onunload ou addEventListener()) pode impedir que os navegadores coloquem páginas no cache de avanço e retorno para cargas
mais rápidas.
Em todos os navegadores modernos, é recomendável sempre usar o evento
pagehide para detectar possíveis descarregamentos de página (também conhecido como estado
terminated) em vez do evento unload. Se você
precisar oferecer suporte ao Internet Explorer 10 e versões anteriores, detecte
o evento pagehide e use unload somente se o navegador não for compatível com
pagehide:
const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';
window.addEventListener(terminationEvent, (event) => {
// Note: if the browser is able to cache the page, `event.persisted`
// is `true`, and the state is frozen rather than terminated.
});
O evento beforeunload
O evento beforeunload tem um problema semelhante ao evento unload. Historicamente, a presença de um evento beforeunload podia impedir que as páginas se qualificassem para o cache de avanço e retorno. Os navegadores modernos
não têm essa restrição. No entanto, alguns navegadores, por precaução, não acionam
o evento beforeunload ao tentar colocar uma página no cache
de volta/avançar, o que significa que o evento não é confiável como um indicador de fim de sessão.
Além disso, alguns navegadores (incluindo o Chrome) exigem uma interação do usuário na página antes de permitir que o evento beforeunload seja acionado, afetando ainda mais a confiabilidade dele.
Uma diferença entre beforeunload e unload é que há usos legítimos de beforeunload. Por exemplo, quando você quer avisar o usuário
que ele tem mudanças não salvas que serão perdidas se continuar descarregando a página.
Como há motivos válidos para usar beforeunload, recomendamos que você
apenas adicione listeners beforeunload quando um usuário tiver mudanças não salvas e
remova-os imediatamente após o salvamento.
Em outras palavras, não faça isso (já que adiciona um listener beforeunload
incondicionalmente):
addEventListener('beforeunload', (event) => {
// A function that returns `true` if the page has unsaved changes.
if (pageHasUnsavedChanges()) {
event.preventDefault();
// Legacy support for older browsers.
event.returnValue = true;
}
});
Em vez disso, faça o seguinte (já que ele só adiciona o listener beforeunload quando é necessário e o remove quando não é):
const beforeUnloadListener = (event) => {
event.preventDefault();
// Legacy support for older browsers.
event.returnValue = true;
};
// A function that adds a `beforeunload` listener if there are unsaved changes.
onPageHasUnsavedChanges(() => {
addEventListener('beforeunload', beforeUnloadListener);
});
// A function that removes the `beforeunload` listener when the page's unsaved
// changes are resolved.
onAllChangesSaved(() => {
removeEventListener('beforeunload', beforeUnloadListener);
});
Perguntas frequentes
Por que não há um estado de "carregamento"?
A API Page Lifecycle define estados discretos e mutuamente exclusivos. Como uma página pode ser carregada no estado ativo, passivo ou oculto, e como ela pode mudar de estado ou até mesmo ser encerrada antes de terminar o carregamento, um estado de carregamento separado não faz sentido nesse paradigma.
Minha página faz um trabalho importante quando está oculta. Como posso evitar que ela seja congelada ou descartada?
Há muitos motivos legítimos para que as páginas da Web não sejam congeladas enquanto estão em estado oculto. O exemplo mais óbvio é um app que toca música.
Também há situações em que seria arriscado para o Chrome descartar uma página, como se ela contiver um formulário com entrada do usuário não enviada ou se tiver um manipulador de beforeunload que avisa quando a página está sendo descarregada.
Por enquanto, o Chrome vai ser conservador ao descartar páginas e só fará isso quando tiver certeza de que não vai afetar os usuários. Por exemplo, páginas que foram observadas fazendo qualquer uma das seguintes ações enquanto estavam no estado oculto não serão descartadas, a menos que haja restrições extremas de recursos:
- Tocando áudio
- Como usar o WebRTC
- Como atualizar o título ou o favicon da tabela
- Como mostrar alertas
- Envio de notificações push
Para conferir os recursos da lista atual usados para determinar se uma guia pode ser congelada ou descartada com segurança, consulte: Heurísticas para congelamento e descarte no Chrome.
O que é o cache de avanço e retorno?
O cache de avanço e retorno é um termo usado para descrever uma otimização de navegação implementada por alguns navegadores que torna o uso dos botões de retorno e avanço mais rápido.
Quando um usuário sai de uma página, esses navegadores congelam uma versão dela para que ela possa ser retomada rapidamente caso o usuário volte usando os botões "Voltar" ou "Avançar". Lembre-se de que adicionar um unload
processador de eventos impede essa otimização.
Para todos os efeitos, esse congelamento é funcionalmente o mesmo que o congelamento que os navegadores fazem para economizar CPU/bateria. Por isso, ele é considerado parte do estado do ciclo de vida congelado.
Se não for possível executar APIs assíncronas nos estados congelado ou encerrado, como posso salvar dados no IndexedDB?
Nos estados congelado e encerrado, as tarefas congeláveis nas filas de tarefas de uma página são suspensas, o que significa que as APIs assíncronas e baseadas em callback não podem ser usadas de maneira confiável.
Embora a maioria das APIs IndexedDB seja baseada em callbacks, o método
commit()
na interface
IDBTransaction
oferece uma maneira de iniciar o processo de commit em uma
transação ativa sem esperar que os eventos de solicitações pendentes sejam
enviados. Isso
oferece uma maneira confiável de salvar dados em um banco de dados IndexedDB em um listener de eventos freeze ou
visibilitychange, porque o commit é executado imediatamente
em vez de ser enfileirado em uma tarefa separada.
Como testar o app nos estados congelado e descartado
Para testar como o app se comporta nos estados congelado e descartado, acesse
chrome://discards para congelar ou descartar qualquer uma das
guias abertas.
Isso permite garantir que sua página processe corretamente os eventos freeze e resume, bem como a flag document.wasDiscarded quando as páginas são recarregadas após
um descarte.
Resumo
Os desenvolvedores que querem respeitar os recursos do sistema dos dispositivos dos usuários precisam criar apps considerando os estados do ciclo de vida da página. É fundamental que as páginas da Web não consumam recursos excessivos do sistema em situações que o usuário não esperaria.
Quanto mais desenvolvedores implementarem as novas APIs Page Lifecycle, mais seguro será para os navegadores congelar e descartar páginas que não estão sendo usadas. Isso significa que os navegadores vão consumir menos memória, CPU, bateria e recursos de rede, o que é uma vantagem para os usuários.