Browser Support
Actualmente, los navegadores modernos a veces suspenden o descartan páginas por completo cuando los recursos del sistema son limitados. En el futuro, los navegadores quieren hacer esto de forma proactiva para consumir menos energía y memoria. La API de Page Lifecycle proporciona hooks de ciclo de vida para que tus páginas puedan controlar de forma segura estas intervenciones del navegador sin afectar la experiencia del usuario. Consulta la API para ver si debes implementar estas funciones en tu aplicación.
Fondo
El ciclo de vida de la aplicación es una forma clave en la que los sistemas operativos modernos administran los recursos. En Android, iOS y las versiones recientes de Windows, el SO puede iniciar y detener las apps en cualquier momento. Esto permite que estas plataformas optimicen y reasignen recursos donde mejor benefician al usuario.
Históricamente, en la Web no existió tal ciclo de vida, y las apps pueden mantenerse activas de forma indefinida. Con una gran cantidad de páginas web en ejecución, los recursos críticos del sistema, como la memoria, la CPU, la batería y la red, pueden sobrecargarse, lo que genera una mala experiencia del usuario final.
Si bien la plataforma web siempre tuvo eventos relacionados con los estados del ciclo de vida, como load, unload y visibilitychange, estos eventos solo permiten que los desarrolladores respondan a los cambios de estado del ciclo de vida iniciados por el usuario. Para que la Web funcione de manera confiable en dispositivos de baja potencia (y sea más consciente de los recursos en general en todas las plataformas), los navegadores necesitan una forma de recuperar y reasignar de manera proactiva los recursos del sistema.
De hecho, los navegadores actuales ya toman medidas activas para conservar recursos en las páginas de las pestañas en segundo plano, y muchos navegadores (especialmente Chrome) desean hacer mucho más para reducir su huella de recursos general.
El problema es que los desarrolladores no tienen forma de prepararse para este tipo de intervenciones iniciadas por el sistema ni siquiera saben que se están produciendo. Esto significa que los navegadores deben ser conservadores o correr el riesgo de dañar las páginas web.
La API de Page Lifecycle intenta resolver este problema de la siguiente manera:
- Presentamos y estandarizamos el concepto de estados del ciclo de vida en la Web.
 - Definir nuevos estados iniciados por el sistema que permitan a los navegadores limitar los recursos que pueden consumir las pestañas ocultas o inactivas
 - Creación de nuevas APIs y eventos que permiten a los desarrolladores web responder a las transiciones hacia y desde estos nuevos estados iniciados por el sistema
 
Esta solución proporciona la previsibilidad que necesitan los desarrolladores web para crear aplicaciones resistentes a las intervenciones del sistema y permite que los navegadores optimicen los recursos del sistema de forma más agresiva, lo que, en última instancia, beneficia a todos los usuarios web.
En el resto de esta publicación, se presentarán las nuevas funciones del ciclo de vida de la página y se explorará cómo se relacionan con todos los estados y eventos existentes de la plataforma web. También proporcionará recomendaciones y prácticas recomendadas para los tipos de trabajo que los desarrolladores deberían (y no deberían) realizar en cada estado.
Descripción general de los estados y eventos del ciclo de vida de la página
Todos los estados del ciclo de vida de la página son discretos y mutuamente exclusivos, lo que significa que una página solo puede estar en un estado a la vez. La mayoría de los cambios en el estado del ciclo de vida de una página se pueden observar a través de eventos del DOM (consulta las recomendaciones para desarrolladores para cada estado y conoce las excepciones).
Quizás la forma más sencilla de explicar los estados del ciclo de vida de la página, así como los eventos que indican las transiciones entre ellos, sea con un diagrama:
Estados
En la siguiente tabla, se explica cada estado en detalle. También se enumeran los posibles estados que pueden aparecer antes y después, así como los eventos que los desarrolladores pueden usar para observar los cambios.
| Estado | Descripción | 
|---|---|
| Activo | 
         Una página está en estado activa si es visible y tiene el foco de entrada. 
          Estados anteriores posibles: 
          Próximos estados posibles:  | 
    
| Pasivo | 
         Una página está en estado pasivo si es visible y no tiene el foco de entrada. 
          Estados anteriores posibles: 
          Próximos estados posibles:  | 
    
| Oculto | 
         Una página está en estado oculta si no está visible (y no se congeló, descartó ni finalizó). 
          Estados anteriores posibles: 
          Próximos estados posibles:  | 
    
| Congelado | 
         En el estado inmovilizado, el navegador suspende la ejecución de las 
        tareas
        
        inmovilizables en las 
        colas de tareas de la página hasta que se desinmoviliza la página. Esto significa que no se ejecutan elementos como los temporizadores de JavaScript y las devoluciones de llamada de recuperación. Las tareas que ya se están ejecutando pueden finalizar (lo más importante es la devolución de llamada 
         Los navegadores congelan las páginas para preservar el uso de la CPU, la batería y los datos, y también para habilitar navegaciones hacia atrás o hacia adelante más rápidas, lo que evita la necesidad de volver a cargar la página por completo. 
          Posibles estados anteriores: 
          Próximos estados posibles:  | 
    
| Finalizada | 
         Una página se encuentra en el estado terminated una vez que el navegador comienza a descargarla y borrarla de la memoria. No se pueden iniciar tareas nuevas en este estado, y las tareas en curso pueden finalizarse si se ejecutan durante demasiado tiempo. 
          Posibles estados anteriores: 
          Próximos estados posibles:  | 
    
| Descartado | 
         Una página se encuentra en el estado descartada cuando el navegador la descarga para conservar recursos. En este estado, no se pueden ejecutar tareas, devoluciones de llamadas de eventos ni JavaScript de ningún tipo, ya que los descartes suelen ocurrir bajo restricciones de recursos, en las que es imposible iniciar procesos nuevos. En el estado descartada, la pestaña en sí (incluidos el título y el favicon) suele ser visible para el usuario, aunque la página haya desaparecido. 
          Estados anteriores posibles: 
          Próximos estados posibles:  | 
    
Eventos
Los navegadores envían muchos eventos, pero solo una pequeña parte de ellos indica un posible cambio en el estado del ciclo de vida de la página. En la siguiente tabla, se describen todos los eventos relacionados con el ciclo de vida y se enumeran los estados a los que pueden transicionar y desde los que pueden hacerlo.
| Nombre | Detalles | 
|---|---|
        
          focus
        
       | 
      
         Un elemento DOM recibió el enfoque. 
          Nota: Un evento  
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          blur
        
       | 
      
         Un elemento DOM perdió el enfoque. 
          Nota: Un evento  
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          visibilitychange
        
       | 
      
         Cambió el valor de 
          | 
    
        
          freeze
        
        *
       | 
      
         La página se acaba de inmovilizar. No se iniciará ninguna tarea congelable en las colas de tareas de la página. 
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          resume
        
        *
       | 
      
         El navegador reanudó una página inactiva. 
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          pageshow
        
       | 
      
         Se está navegando a una entrada del historial de sesiones. Puede ser una carga de página completamente nueva o una página tomada de la memoria caché atrás/adelante. Si la página se tomó de la memoria caché atrás/adelante, la propiedad  
          Estados anteriores posibles:  | 
    
        
          pagehide
        
       | 
      
         Se está navegando desde una entrada del historial de la sesión. Si el usuario navega a otra página y el navegador puede agregar la página actual a la memoria caché atrás/adelante para reutilizarla más adelante, la propiedad  
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          beforeunload
        
       | 
      
         La ventana, el documento y sus recursos están a punto de descargarse. En este punto, el documento sigue siendo visible y el evento se puede cancelar. 
          Importante: El evento  
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
        
          unload
        
       | 
      
         La página se está descargando. 
          Advertencia:
          Nunca se recomienda usar el evento  
          Estados anteriores posibles: 
          Estados actuales posibles:  | 
    
* Indica un evento nuevo definido por la API de Page Lifecycle
Nuevas funciones agregadas en Chrome 68
En el gráfico anterior, se muestran dos estados que son iniciados por el sistema en lugar de por el usuario: inactivo y descartado. Como se mencionó anteriormente, los navegadores actuales ya congelan y descartan ocasionalmente las pestañas ocultas (a su discreción), pero los desarrolladores no tienen forma de saber cuándo sucede esto.
En Chrome 68, los desarrolladores ahora pueden observar cuándo se congela y descongelan una pestaña oculta. Para ello, deben escuchar los eventos freeze y resume en document.
document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});
document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});
A partir de Chrome 68, el objeto document ahora incluye una propiedad wasDiscarded en Chrome para computadoras (el soporte para Android se está supervisando en este problema). Para determinar si se descartó una página mientras estaba en una pestaña oculta, puedes inspeccionar el valor de esta propiedad en el momento de la carga de la página (nota: Las páginas descartadas se deben volver a cargar para usarlas de nuevo).
if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}
Para obtener asesoramiento sobre qué acciones son importantes en los eventos freeze y resume, así como sobre cómo controlar y prepararse para el descarte de páginas, consulta las recomendaciones para desarrolladores para cada estado.
En las siguientes secciones, se ofrece una descripción general de cómo estas nuevas funciones se ajustan a los estados y eventos existentes de la plataforma web.
Cómo observar los estados del ciclo de vida de la página en el código
En los estados activo, pasivo y oculto, es posible ejecutar código JavaScript que determina el estado actual del ciclo de vida de la página a partir de las APIs existentes de la plataforma web.
const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};
Por otro lado, los estados inactivo y finalizado solo se pueden detectar en sus respectivos objetos de escucha de eventos (freeze y pagehide) a medida que cambia el estado.
Cómo observar los cambios de estado
A partir de la función getState() definida anteriormente, puedes observar todos los cambios de estado del ciclo de vida de la página con el siguiente 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);
Este código realiza tres acciones:
- Establece el estado inicial con la función 
getState(). - Define una función que acepta un siguiente estado y, si hay un cambio, registra los cambios de estado en la consola.
 - Agrega objetos de escucha de eventos de captura para todos los eventos de ciclo de vida necesarios, que, a su vez, llaman a 
logStateChange()y pasan el siguiente estado. 
Una cosa que debes tener en cuenta sobre el código es que todos los objetos de escucha de eventos se agregan a window y todos pasan {capture: true}.
Esto se debe a varios motivos:
- No todos los eventos del ciclo de vida de la página tienen el mismo destino. 
pagehideypageshowse activan enwindow;visibilitychange,freezeyresumese activan endocument, yfocusyblurse activan en sus respectivos elementos DOM. - La mayoría de estos eventos no se propagan, lo que significa que es imposible agregar objetos de escucha de eventos que no capturen a un elemento superior común y observarlos todos.
 - La fase de captura se ejecuta antes de las fases de destino o de burbuja, por lo que agregar objetos de escucha allí ayuda a garantizar que se ejecuten antes de que otro código pueda cancelarlos.
 
Recomendaciones para desarrolladores en cada estado
Como desarrolladores, es importante comprender los estados del ciclo de vida de la página y saber cómo observarlos en el código, ya que el tipo de trabajo que debes (y no debes) realizar depende en gran medida del estado en el que se encuentre tu página.
Por ejemplo, claramente no tiene sentido mostrarle una notificación transitoria al usuario si la página está en estado oculto. Si bien este ejemplo es bastante obvio, hay otras recomendaciones que no son tan evidentes y que vale la pena enumerar.
| Estado | Recomendaciones para desarrolladores | 
|---|---|
Active | 
    
       El estado activo es el momento más crítico para el usuario y, por lo tanto, el momento más importante para que tu página sea receptiva a la entrada del usuario. Cualquier trabajo que no sea de IU y que pueda bloquear el subproceso principal debe tener una prioridad más baja y ejecutarse durante períodos de inactividad o descargarse en un subproceso de trabajador web.  | 
  
Passive | 
    
       En el estado pasivo, el usuario no interactúa con la página, pero puede verla. Esto significa que las actualizaciones y animaciones de la IU deben seguir siendo fluidas, pero el momento en que se producen estas actualizaciones es menos crítico. Cuando la página cambia de activa a pasiva, es un buen momento para conservar el estado de la aplicación no guardado.  | 
  
| 
       Cuando la página cambia de pasiva a oculta, es posible que el usuario no vuelva a interactuar con ella hasta que se vuelva a cargar. La transición a oculto también suele ser el último cambio de estado que los desarrolladores pueden observar de manera confiable (esto es especialmente cierto en dispositivos móviles, ya que los usuarios pueden cerrar pestañas o la app del navegador, y los eventos  Esto significa que debes considerar el estado oculto como el posible final de la sesión del usuario. En otras palabras, persiste cualquier estado de la aplicación sin guardar y envía los datos de análisis que no se hayan enviado. También debes dejar de realizar actualizaciones de la IU (ya que el usuario no las verá) y detener las tareas que el usuario no querría que se ejecutaran en segundo plano.  | 
  |
Frozen | 
    
       En el estado inmovilizado, las tareas inmovilizables en las colas de tareas se suspenden hasta que se desinmoviliza la página, lo que puede que nunca suceda (p.ej., si se descarta la página). Esto significa que, cuando la página cambia de oculta a inactiva, es fundamental que detengas los temporizadores o cierres las conexiones que, si se inactivan, podrían afectar otras pestañas abiertas en el mismo origen o la capacidad del navegador para colocar la página en la caché de atrás/adelante. En particular, es importante que hagas lo siguiente: 
 También debes conservar cualquier estado de vista dinámico (p.ej., la posición de desplazamiento en una vista de lista infinita) en 
       Si la página pasa de inactiva a oculta, puedes volver a abrir las conexiones cerradas o reiniciar cualquier sondeo que hayas detenido cuando la página se inhabilitó inicialmente.  | 
  
Terminated | 
    
       Por lo general, no es necesario que realices ninguna acción cuando una página pasa al estado terminated. Dado que las páginas que se descargan como resultado de la acción del usuario siempre pasan por el estado oculto antes de ingresar al estado finalizado, el estado oculto es donde se debe realizar la lógica de finalización de la sesión (p.ej., persistir el estado de la aplicación y generar informes para Analytics). Además (como se menciona en las recomendaciones para el estado oculto), es muy importante que los desarrolladores comprendan que, en muchos casos (especialmente en dispositivos móviles), no se puede detectar de manera confiable la transición al estado finalizado, por lo que es probable que los desarrolladores que dependen de los eventos de finalización (p.ej.,   | 
  
Discarded | 
    
       Los desarrolladores no pueden observar el estado descartado en el momento en que se descarta una página. Esto se debe a que, por lo general, las páginas se descartan por limitaciones de recursos, y no es posible reactivar una página solo para permitir que se ejecute un script en respuesta a un evento de descarte en la mayoría de los casos. Por lo tanto, debes prepararte para la posibilidad de un descarte en el cambio de hidden a frozen y, luego, puedes reaccionar a la restauración de una página descartada en el tiempo de carga de la página verificando   | 
  
Una vez más, dado que la confiabilidad y el orden de los eventos del ciclo de vida no se implementan de manera coherente en todos los navegadores, la forma más sencilla de seguir los consejos de la tabla es usar PageLifecycle.js.
APIs de ciclo de vida heredadas que se deben evitar
Se deben evitar los siguientes eventos siempre que sea posible.
El evento de descarga
Muchos desarrolladores tratan el evento unload como una devolución de llamada garantizada y lo usan como un indicador de fin de sesión para guardar el estado y enviar datos de análisis, pero esto es extremadamente poco confiable, en especial en dispositivos móviles. El evento unload no se activa en muchas situaciones típicas de descarga, como cerrar una pestaña desde el selector de pestañas en dispositivos móviles o cerrar la app del navegador desde el selector de apps.
Por este motivo, siempre es mejor confiar en el evento visibilitychange para determinar cuándo finaliza una sesión y considerar el estado oculto como el último momento confiable para guardar los datos del usuario y de la app.
Además, la mera presencia de un controlador de eventos unload registrado (a través de onunload o addEventListener()) puede impedir que los navegadores puedan colocar páginas en la memoria caché atrás/adelante para cargas más rápidas hacia atrás y hacia adelante.
En todos los navegadores modernos, se recomienda usar siempre el evento pagehide para detectar posibles descargas de páginas (también conocido como el estado terminated) en lugar del evento unload. Si necesitas admitir Internet Explorer 10 y versiones anteriores, debes detectar la función del evento pagehide y solo usar unload si el navegador no admite 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.
});
El evento beforeunload
El evento beforeunload tiene un problema similar al evento unload, ya que, históricamente, la presencia de un evento beforeunload podía impedir que las páginas fueran aptas para la memoria caché atrás/adelante. Los navegadores modernos no tienen esta restricción. Sin embargo, algunos navegadores, como medida de precaución, no activan el evento beforeunload cuando se intenta colocar una página en la caché de atrás/adelante, lo que significa que el evento no es confiable como indicador de fin de sesión.
Además, algunos navegadores (incluido Chrome) requieren una interacción del usuario en la página antes de permitir que se active el evento beforeunload, lo que afecta aún más su confiabilidad.
Una diferencia entre beforeunload y unload es que hay usos legítimos de beforeunload. Por ejemplo, cuando quieres advertir al usuario que perderá los cambios sin guardar si continúa descargando la página.
Dado que existen motivos válidos para usar beforeunload, se recomienda que solo agregues objetos de escucha de beforeunload cuando un usuario tenga cambios no guardados y, luego, los quites inmediatamente después de que se guarden.
En otras palabras, no hagas lo siguiente (ya que agrega un objeto de escucha beforeunload de forma incondicional):
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;
  }
});
En cambio, haz lo siguiente (ya que solo agrega el objeto de escucha beforeunload cuando es necesario y lo quita cuando no lo es):
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);
});
Preguntas frecuentes
¿Por qué no hay un estado de "cargando"?
La API de Page Lifecycle define estados discretos y mutuamente excluyentes. Dado que una página se puede cargar en estado activo, pasivo u oculto, y que puede cambiar de estado (o incluso finalizarse) antes de que termine de cargarse, un estado de carga independiente no tiene sentido dentro de este paradigma.
Mi página realiza un trabajo importante cuando está oculta. ¿Cómo puedo evitar que se congele o descarte?
Existen muchos motivos legítimos por los que las páginas web no deberían congelarse mientras se ejecutan en estado oculto. El ejemplo más obvio es una app que reproduce música.
También hay situaciones en las que sería riesgoso que Chrome descarte una página, por ejemplo, si contiene un formulario con entrada del usuario sin enviar o si tiene un controlador de beforeunload que advierte cuando se descarga la página.
Por el momento, Chrome será conservador cuando descarte páginas y solo lo hará cuando tenga la certeza de que no afectará a los usuarios. Por ejemplo, las páginas que se observaron realizando alguna de las siguientes acciones mientras estaban en estado oculto no se descartarán, a menos que haya limitaciones extremas de recursos:
- Reproducción de audio
 - Cómo usar WebRTC
 - Actualiza el título o el favicon de la tabla
 - Cómo mostrar alertas
 - Envío de notificaciones push
 
Para conocer las funciones de la lista actual que se usan para determinar si una pestaña se puede congelar o descartar de forma segura, consulta Heurísticas para congelar y descartar en Chrome.
¿Qué es la memoria caché atrás/adelante?
La memoria caché atrás/adelante es un término que se usa para describir una optimización de navegación que implementan algunos navegadores y que hace que el uso de los botones atrás y adelante sea más rápido.
Cuando un usuario navega fuera de una página, estos navegadores congelan una versión de esa página para que se pueda reanudar rápidamente en caso de que el usuario vuelva a navegar con los botones Atrás o Adelante. Recuerda que agregar un unload controlador de eventos impide esta optimización.
A todos los efectos prácticos, esta inmovilización es funcionalmente igual a la que realizan los navegadores para conservar la CPU o la batería. Por ese motivo, se considera parte del estado del ciclo de vida inmovilizado.
Si no puedo ejecutar APIs asíncronas en los estados inactivo o finalizado, ¿cómo puedo guardar datos en IndexedDB?
En los estados inmovilizado y finalizado, se suspenden las tareas inmovilizables en las colas de tareas de una página, lo que significa que no se pueden usar de forma confiable las APIs asíncronas y basadas en devoluciones de llamada.
Si bien la mayoría de las APIs de IndexedDB se basan en devoluciones de llamada, el método commit() de la interfaz IDBTransaction proporciona una forma de iniciar el proceso de confirmación en una transacción activa sin esperar a que se envíen los eventos de las solicitudes pendientes. Esto proporciona una forma confiable de guardar datos en una base de datos de IndexedDB en un objeto de escucha de eventos freeze o visibilitychange, ya que la confirmación se ejecuta de inmediato en lugar de ponerse en cola en una tarea separada.
Cómo probar tu app en los estados inactivo y descartado
Para probar cómo se comporta tu app en los estados de congelación y descarte, puedes visitar chrome://discards para congelar o descartar cualquiera de tus pestañas abiertas.
  
  Esto te permite garantizar que tu página maneje correctamente los eventos freeze y resume, así como la marca document.wasDiscarded cuando se vuelvan a cargar las páginas después de un descarte.
Resumen
Los desarrolladores que deseen respetar los recursos del sistema de los dispositivos de sus usuarios deben crear sus apps teniendo en cuenta los estados del ciclo de vida de la página. Es fundamental que las páginas web no consuman recursos excesivos del sistema en situaciones en las que el usuario no lo esperaría.
Cuantos más desarrolladores comiencen a implementar las nuevas APIs de Page Lifecycle, más seguro será para los navegadores congelar y descartar las páginas que no se estén usando. Esto significa que los navegadores consumirán menos recursos de memoria, CPU, batería y red, lo que es beneficioso para los usuarios.