Compatibilidade com navegadores
- 68
- 79
- x
- x
Atualmente, os navegadores mais recentes podem suspender ou descartar páginas totalmente quando os recursos do sistema estão limitados. No futuro, os navegadores vão fazer isso proativamente para consumir menos energia e memória. A API Page Lifecycle fornece hooks de ciclo de vida para que suas páginas possam processar essas intervenções do navegador com segurança, sem afetar a experiência do usuário. Analise a API para conferir se é necessário implementar esses recursos no aplicativo.
Contexto
O ciclo de vida do aplicativo é uma forma fundamental dos sistemas operacionais modernos gerenciarem recursos. Nas versões Android, iOS e recentes do Windows, os apps podem ser iniciados e interrompidos a qualquer momento pelo SO. Isso permite que essas plataformas simplifiquem e realoquem recursos da maneira mais benéfica para o usuário.
Historicamente, na Web, esse ciclo de vida não existe, e os apps podem ser mantidos ativos indefinidamente. Com um grande número de páginas da Web em execução, recursos essenciais do sistema, como memória, CPU, bateria e rede, podem ficar com excesso de assinaturas, levando a uma experiência ruim para o usuário final.
Embora a plataforma da Web tenha há muito tempo eventos relacionados a estados do ciclo de vida,
como load
, unload
e
visibilitychange
.
Esses eventos só permitem que os desenvolvedores
respondam a mudanças de estado do 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 de recursos em geral em
todas as plataformas), os navegadores precisam de uma maneira de recuperar e realocar os recursos
do sistema de forma proativa.
Na verdade, os navegadores atuais já tomam medidas ativas para economizar recursos para páginas em guias em segundo plano, e muitos navegadores (especialmente o Chrome) gostariam de fazer muito mais disso para diminuir o consumo geral de recursos.
O problema é que os desenvolvedores não têm como se preparar para esses tipos de intervenções iniciadas pelo sistema nem saber que eles estão acontecendo. Isso significa que os navegadores precisam ser conservadores ou correr o risco de quebrar páginas da Web.
A API Page Lifecycle tenta resolver esse problema da seguinte maneira:
- Apresentação e padronização do conceito de estados do ciclo de vida na Web.
- Definição de estados novos e iniciados pelo sistema que permitem que os navegadores limitem 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 esses novos estados iniciados pelo sistema e a partir deles.
Essa solução oferece a previsibilidade que os desenvolvedores da Web precisam para criar aplicativos resistentes a intervenções do sistema. Além disso, ela permite que os navegadores otimizem os recursos do sistema de maneira mais agressiva, beneficiando todos os usuários da Web.
No restante desta postagem, apresentaremos os novos recursos de ciclo de vida da página e exploraremos como eles se relacionam com todos os estados e eventos da plataforma da Web existente. Ele também oferece recomendações e práticas recomendadas para os tipos de trabalho que os desenvolvedores podem (ou não) realizar 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. Além disso, a maioria das mudanças no estado do ciclo de vida de uma página costuma ser observável por meio de eventos DOM. Consulte as recomendações do desenvolvedor para 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 as 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 as mudanças.
Estado | Descrição |
---|---|
Ativa |
A página fica no estado ativo quando está visível e tem foco de entrada.
Possíveis estados anteriores:
Próximos estados possíveis: |
Passivo |
Uma página fica no estado passivo quando está visível e não tem foco de entrada.
Possíveis estados anteriores:
Próximos estados possíveis: |
Ocultos |
Uma página fica no estado oculto quando não está visível e não foi 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 coisas como timers do JavaScript e callbacks de busca não são executados. Tarefas já em execução
podem ser concluídas (principalmente o callback
Os navegadores congelam as páginas para preservar o uso de CPU/bateria/dados. Eles também fazem isso para permitir navegações de avanço e retorno mais rápidas, evitando a necessidade de recarregar toda a página.
Possíveis estados anteriores:
Próximos estados possíveis: |
Encerrado |
Quando uma página começa a ser descarregada e apagada do navegador, a página passa para o estado encerrado. Nenhuma nova tarefa pode ser iniciada nesse estado, e tarefas em andamento podem ser eliminadas se forem executadas por muito tempo.
Possíveis estados anteriores:
Próximos estados possíveis: |
Descartado |
Uma página fica no estado descartada quando é descarregada pelo navegador para economizar recursos. Nenhuma tarefa, callback de evento ou JavaScript de qualquer tipo pode ser executado nesse estado, já que o descarte geralmente ocorre sob restrições de recursos, em que a inicialização de novos processos é impossível. No estado descartado, a própria guia (incluindo o título e o favicon) geralmente fica visível para o usuário, mesmo que a página tenha sido removida.
Estados anteriores possíveis:
Próximos estados possíveis: |
Eventos
Os navegadores distribuem 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 abaixo descreve todos os eventos que pertencem ao ciclo de vida e lista os estados de e para os quais eles podem fazer a transição.
Nome | Detalhes |
---|---|
focus
|
Um elemento DOM recebeu foco.
Observação:um evento
Possíveis estados anteriores:
Possíveis estados atuais: |
blur
|
Um elemento DOM perdeu o foco.
Observação:um evento
Possíveis estados anteriores:
Possíveis estados atuais: |
visibilitychange
|
O valor
|
freeze
*
|
A página acaba de ser congelada. As tarefas congeláveis nas filas de tarefas da página não serão iniciadas.
Possíveis estados anteriores:
Estados atuais possíveis: |
resume
*
|
O navegador retomou a página congelada.
Possíveis estados anteriores:
Estados atuais possíveis: |
pageshow
|
Uma entrada de histórico de sessão está sendo transferida. Ele pode ser um carregamento de página novo ou uma página extraída do
cache de avanço e retorno. Se a página tiver sido retirada do cache de avanço e retorno, a propriedade
Possíveis estados anteriores: |
pagehide
|
Uma entrada de histórico de sessão está sendo processada. Se o usuário estiver navegando para outra página e o navegador conseguir adicionar a página atual ao cache de avanço e retorno para ser reutilizada depois, a propriedade
Possíveis estados anteriores:
Estados atuais possíveis: |
beforeunload
|
A janela, o documento e seus recursos estão prestes a ser descarregados. O documento ainda está visível e o evento ainda pode ser cancelado.
Importante:o evento
Possíveis estados anteriores:
Estados atuais possíveis: |
unload
|
A página está sendo descarregada.
Aviso:o uso do evento
Possíveis estados anteriores:
Estados atuais possíveis: |
* Indica um novo evento definido pela API Page Lifecycle
Novos recursos adicionados no Chrome 68
O gráfico anterior mostra dois estados que são iniciados pelo sistema, e não pelo usuário: congelado e descartado. Como mencionado anteriormente, os navegadores atuais já congelam e descartam guias ocultas (a critério deles), mas os desenvolvedores não têm como saber quando isso está acontecendo.
No Chrome 68, os desenvolvedores agora 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 ao Android está sendo acompanhado nesse problema). Para determinar se uma página foi descartada enquanto estava 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 receber orientações sobre 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 de cada estado.
As próximas seções oferecem uma visão geral de como esses novos recursos se encaixam nos estados e eventos atuais da plataforma da Web.
Como observar os estados do ciclo de vida da página no código
Nos estados ativo, passivo e oculto, é possível executar um código JavaScript que determina o estado atual do ciclo de vida da página usando APIs de plataforma da Web.
const getState = () => {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
};
Por outro lado, os estados congelado e encerrado só podem ser detectados no respectivo listener de eventos (freeze
e pagehide
) enquanto o estado está mudando.
Como observar as mudanças de estado
Ao criar na função getState()
definida anteriormente, você pode observar todas as mudanças de estado
do ciclo de vida da página com o código a seguir.
// 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 alteração, registra as mudanças de estado no console.
- Adiciona listeners de eventos de captura para todos os eventos de ciclo de vida necessários que, por sua vez, chamam
logStateChange()
, transmitindo o próximo estado.
Algo a observar sobre o código é que todos os listeners de eventos são adicionados
a window
e 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.
pagehide
epageshow
são disparados emwindow
,visibilitychange
,freeze
eresume
são disparados emdocument
, efocus
eblur
são disparados nos respectivos elementos DOM. - A maioria desses eventos não aparece, o que significa que é impossível adicionar listeners de eventos que não são de captura a um elemento ancestral comum e observar todos eles.
- A fase de captura é executada antes das fases de destino ou da bolha. Portanto, adicionar listeners nela ajuda a garantir que eles sejam executados antes que outro código possa cancelá-los.
Recomendações dos desenvolvedores para 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ê precisa (ou não) fazer depende muito do estado da sua página.
Por exemplo, claramente não faz sentido mostrar uma notificação temporária ao usuário se a página estiver oculta. Embora esse exemplo seja bastante óbvio, há outras recomendações que não são tão óbvias que vale a pena enumerar.
Estado | Recomendações para desenvolvedores |
---|---|
Active |
O estado ativo é o momento mais crítico para o usuário e, portanto, o momento mais importante para que a página responda à entrada do usuário. Qualquer trabalho que não seja da interface e que bloqueie a linha de execução principal não seja priorizado para períodos de inatividade ou descarregado para um worker da Web. |
Passive |
No estado passivo, o usuário não está interagindo com a página, mas ainda consegue vê-la. Isso significa que as atualizações e animações da interface ainda precisam ser suaves, mas o momento de ocorrência delas é menos crítico. Quando a página muda de ativa para passiva, este é 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 com ela novamente até que ela seja recarregada. A transição para oculta também costuma ser a última mudança de estado
que pode ser observada com segurança pelos desenvolvedores. Isso é especialmente válido em
dispositivos móveis, já que os usuários podem fechar guias ou o próprio app de navegação, e os eventos
Isso significa que você precisa tratar o estado oculto como o final provável da sessão do usuário. Em outras palavras, mantenha o estado do aplicativo não salvo e envie os dados de análise não enviados. Também pare de fazer atualizações de IU, já que elas não serão vistas pelo usuário, e interrompa todas as tarefas que um usuário não quer que sejam executadas em segundo plano. |
|
Frozen |
No estado congelado, as tarefas congeláveis nas filas de tarefas ficam 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 que você interrompa os timers ou desative as conexões que, se congeladam, possam afetar outras guias abertas na mesma origem ou afetar a capacidade do navegador de colocar a página no cache de avanço e retorno. Especificamente, é importante fazer o seguinte:
Também é preciso manter qualquer estado de visualização dinâmica (por exemplo, posição de rolagem
em uma visualização em lista infinita) até
Se a página passar de congelada de volta para oculta, você poderá reabrir todas as conexões fechadas ou reiniciar qualquer pesquisa interrompida quando a página estava inicialmente congelada. |
Terminated |
Geralmente, não é necessário fazer nada quando uma página faz a transição para o estado encerrado. Como as páginas que estão sendo descarregadas como resultado da ação do usuário sempre passam pelo estado hidden antes de entrar no estado encerrado, é no estado oculto que a lógica de encerramento da sessão (por exemplo, persistência do estado do aplicativo e da geração de relatórios para análise) precisa ser executada. 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 pode ser observado pelos desenvolvedores no momento em que uma página é descartada. Isso ocorre porque as páginas geralmente são descartadas sob restrições de recursos. Na maioria dos casos, não é possível congelar uma página apenas para permitir que um script seja executado em resposta a um evento de descarte. Como resultado, prepare-se para a possibilidade de descarte na
mudança de oculta para congelada e, em seguida,
poderá reagir à restauração de uma página descartada no tempo de carregamento da página
marcando |
Mais uma vez, como a confiabilidade e a ordem dos eventos do ciclo de vida não são implementadas de maneira consistente em todos os navegadores, a maneira mais fácil de seguir as recomendações da tabela é usar PageLifecycle.js.
APIs de ciclo de vida legadas que devem ser evitadas
Os eventos a seguir devem ser evitados sempre que possível.
O evento de descarregamento
Muitos desenvolvedores tratam o evento unload
como um callback garantido e o usam como
um indicador de fim de sessão para salvar o estado e enviar dados de análise, mas isso
é extremamente não confiável, especialmente em dispositivos móveis. O evento unload
não é
disparado 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 pelo seletor de apps.
Por esse motivo, é sempre melhor confiar no evento
visibilitychange
para determinar quando uma sessão
termina e considerar o estado oculto 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 (por
onunload
ou addEventListener()
) pode impedir que os navegadores possam
colocar páginas no cache de avanço e retorno para
carregar os carregamentos de avanço e retorno mais rápidos.
Em todos os navegadores modernos, é recomendável sempre usar o evento pagehide
para detectar possíveis descarregamentos de páginas (ou seja, o estado encerrado) em vez do evento unload
. Se você
precisar oferecer suporte ao Internet Explorer versões 10 e anteriores, detecte
o evento pagehide
e use unload
apenas 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 unload
, já que,
historicamente, a presença de um evento beforeunload
pode impedir que as páginas
sejam qualificadas para o cache de avanço e retorno. Os navegadores mais recentes
não têm essa restrição. No entanto, alguns navegadores, por precaução, não dispararão
o evento beforeunload
ao tentar colocar uma página no cache de avanço e
retorno, o que significa que o evento não é confiável como um sinal 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 disparado, o que afeta ainda mais a confiabilidade.
Uma diferença entre beforeunload
e unload
é que há
usos legítimos de beforeunload
. Por exemplo, quando você quiser avisar ao usuário
que ele tem mudanças não salvas, ele será perdido se continuar descarregando a página.
Como há motivos válidos para usar beforeunload
, recomendamos que você
somente adicione beforeunload
listeners quando um usuário tiver alterações não salvas e
remova-os imediatamente após salvá-las.
Em outras palavras, não faça isso, já que ele 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.
return (event.returnValue = true);
}
});
Em vez disso, faça isso, 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.
return (event.returnValue = true);
};
// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
addEventListener('beforeunload', beforeUnloadListener);
});
// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
removeEventListener('beforeunload', beforeUnloadListener);
});
Perguntas frequentes
Por que não há um estado "carregando"?
A API Page Lifecycle define os estados como discretos e mutuamente exclusivos. Como uma página pode ser carregada no estado ativo, passivo ou oculto e, como 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 impedir que ela seja congelada ou descartada?
Há muitos motivos legítimos para que páginas da Web não sejam congeladas durante a execução no estado oculto. O exemplo mais óbvio é um app que toca música.
Há também situações em que pode ser arriscado para o Chrome descartar uma página, como um formulário com entrada de usuário não enviada ou um gerenciador beforeunload
que avisa quando a página está descarregando.
Por enquanto, o Chrome vai ser conservador ao descartar páginas e fazer isso apenas quando tiver certeza de que não vai afetar os usuários. Por exemplo, as páginas que foram observadas realizando qualquer uma das ações abaixo enquanto estão no estado oculto não serão descartadas, a menos que sob restrições extremas de recursos:
- Reproduzindo áudio
- Como usar o WebRTC
- Como atualizar o título da tabela ou o favicon
- Mostrando alertas
- Como enviar notificações push
Para conferir os recursos de lista atuais usados para determinar se uma guia pode ser congelada ou descartada com segurança, consulte: Heurística 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 que alguns navegadores implementam, tornando o uso dos botões "Voltar" e "Avançar" mais rápido.
Quando um usuário sai de uma página, esses navegadores congelam uma versão dela
para que possa ser retomada rapidamente caso o usuário navegue de volta usando
os botões "Voltar" ou "Avançar". Lembre-se de que adicionar um manipulador de eventos unload
impede que essa otimização seja possível.
Para todas as intents e finalidades, esse congelamento é funcionalmente o mesmo que os navegadores fazem para economizar CPU/bateria. Por esse motivo, ele é considerado parte do estado do ciclo de vida congelado.
Se não for possível executar APIs assíncronas nos estados congelados ou encerrados, como posso salvar dados no IndexedDB?
Em estados congelados e encerrados, as tarefas congeláveis nas filas de tarefas de uma página são suspensas. Isso significa que APIs assíncronas e baseadas em callback, como a IndexedDB, não podem ser usadas de maneira confiável.
No futuro, vamos adicionar um método commit()
a objetos IDBTransaction
, o que oferecerá
aos desenvolvedores uma maneira de realizar o que são transações somente de gravação efetivamente
que não exigem callbacks. Em outras palavras, se o desenvolvedor estiver apenas gravando
dados no IndexedDB e não estiver executando uma transação complexa que consiste em leituras
e gravações, o método commit()
poderá ser concluído antes que as filas de tarefas sejam
suspensas, supondo que o banco de dados do IndexedDB já esteja aberto.
No entanto, para códigos que precisam funcionar atualmente, os desenvolvedores têm duas opções:
- Use o armazenamento de sessão:o armazenamento de sessão é síncrono e persiste entre descartes de páginas.
- Usar o IndexedDB no seu service worker: um service worker pode armazenar dados no
IndexedDB depois que a página for encerrada ou descartada. No listener de eventos
freeze
oupagehide
, é possível enviar dados para o service worker viapostMessage()
, e o service worker pode processar o salvamento dos dados.
Como testar o app nos estados congelado e descartado
Para testar como seu app se comporta nos estados congelado e descartado, acesse
chrome://discards
para congelar ou descartar qualquer uma das
guias abertas.
Isso garante que sua página processe corretamente os eventos freeze
e resume
,
bem como a sinalização document.wasDiscarded
quando as páginas são recarregadas após
um descarte.
Resumo
Os desenvolvedores que quiserem respeitar os recursos do sistema nos 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 começarem a implementar as novas APIs Page Lifecycle, mais seguro vai ser para os navegadores congelar e descartar as páginas que não estão sendo usadas. Isso significa que os navegadores consomem menos recursos de memória, CPU, bateria e rede, o que é uma vitória para os usuários.