Page Lifecycle API

Unterstützte Browser

  • Chrome: 68.
  • Edge: 79.
  • Firefox: Nicht unterstützt.
  • Safari: Nicht unterstützt.

Moderne Browser pausieren oder verwerfen manchmal Seiten, wenn die Systemressourcen knapp sind. In Zukunft sollen Browser dies proaktiv tun, um weniger Strom und Arbeitsspeicher zu verbrauchen. Die Page Lifecycle API bietet Lebenszyklus-Hooks, mit denen Ihre Seiten diese Browsereingriffe sicher verarbeiten können, ohne die Nutzerfreundlichkeit zu beeinträchtigen. Werfen Sie einen Blick auf die API, um herauszufinden, ob Sie diese Funktionen in Ihrer Anwendung implementieren sollten.

Hintergrund

Der Anwendungslebenszyklus ist eine wichtige Methode, mit der moderne Betriebssysteme Ressourcen verwalten. Unter Android, iOS und aktuellen Windows-Versionen können Apps jederzeit vom Betriebssystem gestartet und angehalten werden. So können diese Plattformen Ressourcen optimieren und dorthin umverteilen, wo sie den Nutzern am meisten nutzen.

Im Web gab es bisher keinen solchen Lebenszyklus und Apps konnten unbegrenzt weiterlaufen. Da eine große Anzahl von Webseiten ausgeführt wird, können kritische Systemressourcen wie Arbeitsspeicher, CPU, Akku und Netzwerk überlastet werden. Dies beeinträchtigt die Nutzerfreundlichkeit.

Die Webplattform hat zwar schon lange Ereignisse im Zusammenhang mit Lebenszyklusstatus, z. B. load, unload und visibilitychange, mit diesen Ereignissen können Entwickler jedoch nur auf von Nutzern initiierte Änderungen des Lebenszyklusstatus reagieren. Damit das Web auf Geräten mit geringer Leistung zuverlässig funktioniert und generell auf allen Plattformen ressourcenschonender ist, benötigen Browser eine Möglichkeit, Systemressourcen proaktiv zurückzufordern und neu zuzuweisen.

Tatsächlich ergreifen Browser heute bereits aktive Maßnahmen zur Ressourceneinsparung für Seiten in Hintergrund-Tabs. Viele Browser (insbesondere Chrome) würden dies gerne noch viel stärker tun, um ihren Ressourcenverbrauch insgesamt zu senken.

Das Problem ist, dass Entwickler sich nicht auf diese Art von systeminitiierten Eingriffen vorbereiten können und nicht einmal wissen, dass sie stattfinden. Das bedeutet, dass Browser konservativ sein müssen, da sonst Webseiten beschädigt werden können.

Die Page Lifecycle API versucht, dieses Problem auf folgende Weise zu lösen:

  • Einführung und Standardisierung des Konzepts der Lebenszyklusstatus im Web.
  • Definition neuer, systeminitiierter Status, mit denen Browser die Ressourcen begrenzen können, die von ausgeblendeten oder inaktiven Tabs verbraucht werden.
  • Erstellen neuer APIs und Ereignisse, mit denen Webentwickler auf Übergänge zu und von diesen neuen systeminitiierten Status reagieren können.

Diese Lösung bietet die Vorhersagbarkeit, die Webentwickler benötigen, um widerstandsfähige Anwendungen zu entwickeln. Außerdem können Browser die Systemressourcen aggressiver optimieren, was letztendlich allen Webnutzern zugutekommt.

Im Rest dieses Beitrags werden die neuen Funktionen für den Seitenlebenszyklus vorgestellt und ihre Beziehung zu den vorhandenen Status und Ereignissen der Webplattform erläutert. Sie enthält auch Empfehlungen und Best Practices für die Art von Arbeit, die Entwickler in jedem Bundesstaat ausführen sollten (und sollten nicht).

Übersicht über die Status und Ereignisse des Seitenlebenszyklus

Alle Status des Seitenlebenszyklus sind diskret und schließen sich gegenseitig aus, d. h., eine Seite kann immer nur einen Status haben. Die meisten Änderungen am Lebenszyklusstatus einer Seite lassen sich in der Regel über DOM-Ereignisse beobachten. Ausnahmen finden Sie in den Entwicklerempfehlungen für die einzelnen Status.

Mit einem Diagramm lassen sich die Status der Seitenlebenszyklus und die Ereignisse, die einen Wechsel zwischen ihnen signalisieren, am einfachsten mithilfe eines Diagramms erklären:

Eine visuelle Darstellung des in diesem Dokument beschriebenen Zustands- und Ereignisflusses.
API-Status und Ereignisablauf des Seitenlebenszyklus

Bundesstaaten

In der folgenden Tabelle werden die einzelnen Bundesstaaten ausführlich erläutert. Außerdem werden die möglichen Status aufgeführt, die vor und nach dem Ereignis auftreten können, sowie die Ereignisse, mit denen Entwickler Änderungen beobachten können.

Status Beschreibung
Aktiv

Eine Seite hat den Status aktiv, wenn sie sichtbar ist und den Eingabefokus hat.

Mögliche vorherige Status:
passive (über das Ereignis focus)
eingefroren (über das Ereignis resume, dann das Ereignis pageshow)

Mögliche nächste Status:
passiver (über das Ereignis blur)

Passiv

Eine Seite befindet sich im Status inaktiv, wenn sie sichtbar ist und nicht den Eingabefokus hat.

Mögliche vorherige Status:
aktiv (über das Ereignis blur)
ausgeblendet (über das Ereignis visibilitychange)
eingefroren (über das Ereignis resume, dann das Ereignis pageshow{/2)

Mögliche nächste Status:
aktiv (über das Ereignis focus)
ausgeblendet (über das Ereignis visibilitychange)

Ausgeblendet

Eine Seite hat den Status ausgeblendet, wenn sie nicht sichtbar ist und nicht eingefroren, verworfen oder beendet wurde.

Mögliche vorherige Status:
passive (über das Ereignis visibilitychange)
frozen (über das Ereignis resume und dann das Ereignis pageshow)

Mögliche nächste Status:
passive (über das Ereignis visibilitychange)
frozen (über das Ereignis freeze)
discarded (keine Ereignisse ausgelöst)
terminated (keine Ereignisse ausgelöst)

Einfrieren

Im Status gefroren hält der Browser die Ausführung von Aufgaben, die in den Aufgabenwarteschlangen der Seite gefroren werden können, an, bis die Seite entfrostet wird. Das bedeutet, dass Dinge wie JavaScript-Timer und Abruf-Callbacks nicht ausgeführt werden. Bereits laufende Aufgaben können abgeschlossen werden (vor allem der freeze-Callback), sind aber möglicherweise in ihren Funktionen und ihrer Laufzeit eingeschränkt.

Browser frieren Seiten ein, um die CPU-, Akku- und Datennutzung zu reduzieren. Außerdem wird dadurch die Navigation zurück/vorwärts beschleunigt, da die Seite nicht vollständig neu geladen werden muss.

Mögliche vorherige Status:
hidden (über das Ereignis freeze)

Mögliche nächste Status:
aktiv (über das Ereignis resume und dann das Ereignis pageshow)
inaktiv (über das Ereignis resume und dann das Ereignis pageshow)
ausgeblendet (über das Ereignis resume)
verworfen (keine Ereignisse ausgelöst)

Beendet

Eine Seite befindet sich im Status Beendet, sobald sie vom Browser entladen und aus dem Arbeitsspeicher gelöscht wurde. In diesem Status können keine neuen Aufgaben gestartet werden. Laufende Aufgaben werden möglicherweise abgebrochen, wenn sie zu lange ausgeführt werden.

Mögliche vorherige Status:
hidden (über das Ereignis pagehide)

Mögliche nächste Status:
NONE

Verworfen

Eine Seite hat den Status Verworfen, wenn sie vom Browser entladen wird, um Ressourcen zu sparen. In diesem Status können keine Aufgaben, Ereignis-Callbacks oder JavaScript-Code ausgeführt werden, da es in der Regel zu Ressourcenengpässen kommt, bei denen das Starten neuer Prozesse nicht möglich ist.

Im Status verworfen ist der Tab selbst (einschließlich Tabtitel und Favicon) für den Nutzer normalerweise sichtbar, auch wenn die Seite nicht mehr angezeigt wird.

Mögliche vorherige Status:
ausgeblendet (keine Ereignisse ausgelöst)
gefroren (keine Ereignisse ausgelöst)

Mögliche nächste Status:
NONE

Ereignisse

Browser senden viele Ereignisse, aber nur ein kleiner Teil davon signalisiert eine mögliche Änderung des Status des Seitenlebenszyklus. In der folgenden Tabelle sind alle Ereignisse aufgeführt, die sich auf den Lebenszyklus beziehen, und es wird aufgelistet, in welche Status sie übergehen können und von welchen Status aus.

Name Details
focus

Ein DOM-Element hat den Fokus erhalten.

Hinweis: Ein focus-Ereignis signalisiert nicht unbedingt eine Statusänderung. Es signalisiert nur einen Statuswechsel, wenn die Seite zuvor nicht den Eingabefokus hatte.

Mögliche vorherige Status:
passiv

Mögliche aktuelle Status:
active

blur

Ein DOM-Element hat den Fokus verloren.

Hinweis: Ein blur-Ereignis signalisiert nicht unbedingt eine Statusänderung. Es signalisiert nur einen Statuswechsel, wenn der Fokus nicht mehr auf der Seite liegt, d. h., der Fokus wurde nicht nur von einem Element auf ein anderes verschoben.

Mögliche vorherige Status:
aktiv

Mögliche aktuelle Status:
passiv

visibilitychange

Der Wert visibilityState des Dokuments hat sich geändert. Das kann passieren, wenn ein Nutzer zu einer neuen Seite wechselt, Tabs wechselt, einen Tab schließt, den Browser minimiert oder schließt oder auf Mobilgeräten zwischen Apps wechselt.

Mögliche vorherige Status:
passiv
ausgeblendet

Mögliche aktuelle Status:
passiv
ausgeblendet

freeze *

Die Seite wurde gerade eingefroren. Alle auf der Seite in den Aufgabenwarteschlangen befindlichen Aufgaben, die eingefroren werden können, werden nicht gestartet.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
frozen

resume *

Der Browser hat eine eingefrorene Seite fortgesetzt.

Mögliche vorherige Status:
frozen

Mögliche aktuelle Status:
aktiv (wenn gefolgt vom Ereignis pageshow)
passiver Status (falls gefolgt vom Ereignis pageshow)
ausgeblendet

pageshow

Es wird ein Sitzungsverlaufseintrag durchsucht.

Dies kann entweder ein ganz neues Seitenladevorgang oder eine Seite aus dem Back-Forward-Cache sein. Wenn die Seite aus dem Back-Forward-Cache stammt, hat die persisted-Eigenschaft des Ereignisses den Wert true. Andernfalls ist der Wert false.

Mögliche vorherige Zustände:
frozen (es wäre auch ein Ereignis vom Typ resume ausgelöst worden)

Mögliche aktuelle Status:
aktiv
passiv
ausgeblendet

pagehide

Ein Eintrag im Sitzungsverlauf wird durchlaufen.

Wenn der Nutzer zu einer anderen Seite wechselt und der Browser die aktuelle Seite dem Back-/Forward-Cache hinzufügen kann, um sie später wiederzuverwenden, hat die Property persisted des Ereignisses den Wert true. Wenn true, wechselt die Seite in den Status frozen (eingefroren). Andernfalls wechselt sie in den Status terminated (beendet).

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
frozen (event.persisted ist wahr, freeze-Ereignis folgt)
terminated (event.persisted ist falsch, unload-Ereignis folgt)

beforeunload

Das Fenster, das Dokument und die zugehörigen Ressourcen werden gleich entfernt. Das Dokument ist weiterhin sichtbar und das Ereignis kann zu diesem Zeitpunkt noch abgesagt werden.

Wichtig: Das Ereignis beforeunload sollte nur verwendet werden, um den Nutzer auf nicht gespeicherte Änderungen hinzuweisen. Sobald diese Änderungen gespeichert sind, sollte das Ereignis entfernt werden. Er sollte der Seite niemals bedingungslos hinzugefügt werden, da dies in einigen Fällen die Leistung beeinträchtigen kann. Weitere Informationen finden Sie im Abschnitt Alte APIs.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
terminated

unload

Die Seite wird entladen.

Warnung: Die Verwendung des Ereignisses unload wird nicht empfohlen, da es unzuverlässig ist und in einigen Fällen die Leistung beeinträchtigen kann. Weitere Informationen finden Sie im Abschnitt Alte APIs.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
beendet

* Weist auf ein neues Ereignis hin, das von der Page Lifecycle API definiert wurde.

In Chrome 68 hinzugefügte neue Funktionen

Das vorherige Diagramm zeigt zwei Status, die vom System und nicht vom Nutzer initiiert werden: gefroren und verworfen. Wie bereits erwähnt, frieren und schließen moderne Browser ausgeblendete Tabs gelegentlich (nach eigenem Ermessen), aber Entwickler können nicht wissen, wann dies geschieht.

In Chrome 68 können Entwickler jetzt beobachten, wann ein ausgeblendeter Tab eingefroren und wieder entfrostet wird. Dazu müssen sie nur auf die Ereignisse freeze und resume auf document achten.

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

Ab Chrome 68 enthält das document-Objekt in der Desktopversion von Chrome die Eigenschaft wasDiscarded. Die Unterstützung für Android wird in diesem Problem erfasst. Wenn Sie wissen möchten, ob eine Seite auf einem ausgeblendeten Tab verworfen wurde, können Sie den Wert dieser Property beim Laden der Seite prüfen. Hinweis: Verworfene Seiten müssen neu geladen werden, damit sie wieder verwendet werden können.

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

Informationen dazu, was Sie bei den Ereignissen vom Typ freeze und resume beachten sollten und wie Sie mit dem Zurücksetzen von Seiten umgehen und sich darauf vorbereiten können, finden Sie in den Entwicklerempfehlungen für die einzelnen Status.

In den folgenden Abschnitten erhalten Sie einen Überblick darüber, wie diese neuen Funktionen in die vorhandenen Status und Ereignisse der Webplattform passen.

Status des Seitenlebenszyklus im Code beobachten

In den Status aktiv, passiv und ausgeblendet kann JavaScript-Code ausgeführt werden, der den aktuellen Status des Seitenlebenszyklus anhand vorhandener Webplattform-APIs ermittelt.

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

Die Status frozen (eingefroren) und terminated (beendet) können dagegen nur in den jeweiligen Ereignis-Listenern (freeze und pagehide) erkannt werden, da sich der Status ändert.

Statusänderungen beobachten

Aufbauend auf der zuvor definierten Funktion getState() können Sie mit dem folgenden Code alle Änderungen des Seitenlebenszyklusstatus beobachten.

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

Dieser Code führt drei Dinge aus:

  • Legt den Anfangszustand mit der Funktion getState() fest.
  • Hiermit wird eine Funktion definiert, die einen nächsten Status akzeptiert und bei einer Änderung die Statusänderungen in der Konsole protokolliert.
  • Es werden Ereignis-Listener zum Erfassen für alle erforderlichen Lebenszyklusereignisse hinzugefügt, die wiederum logStateChange() aufrufen und den nächsten Status übergeben.

Beachten Sie, dass alle Ereignis-Listener window hinzugefügt werden und alle {capture: true} übergeben. Das kann verschiedene Gründe haben:

  • Nicht alle Ereignisse des Seitenlebenszyklus haben dasselbe Ziel. pagehide und pageshow werden bei window ausgelöst. visibilitychange, freeze und resume werden bei document ausgelöst und focus und blur werden für ihre jeweiligen DOM-Elemente ausgelöst.
  • Die meisten dieser Ereignisse werden nicht als Blasen angezeigt. Das bedeutet, dass es unmöglich ist, einem gemeinsamen Ancestor-Element Ereignis-Listener hinzuzufügen, die nicht zur Erfassung führen, und alle zu beobachten.
  • Die Erfassungsphase wird vor der Ziel- oder Blasenphase ausgeführt. Wenn Sie dort Listener hinzufügen, wird sichergestellt, dass sie ausgeführt werden, bevor anderer Code sie abbrechen kann.

Entwicklerempfehlungen für jeden Status

Als Entwickler müssen Sie die Status des Seitenlebenszyklus verstehen und wissen, wie Sie sie im Code berücksichtigen. Denn die Art der Arbeit, die Sie ausführen sollten (und nicht ausführen sollten), hängt weitgehend davon ab, in welchem Status sich Ihre Seite befindet.

Es macht beispielsweise keinen Sinn, dem Nutzer eine vorübergehende Benachrichtigung anzuzeigen, wenn die Seite ausgeblendet ist. Dieses Beispiel ist ziemlich offensichtlich, aber es gibt auch andere Empfehlungen, die nicht so offensichtlich sind, die aber dennoch erwähnt werden sollten.

Status Empfehlungen für Entwickler
Active

Der Status aktiv ist der kritischste Moment für den Nutzer und daher der wichtigste Moment, in dem Ihre Seite auf Nutzereingaben reagieren muss.

Alle Nicht-UI-Vorgänge, die den Hauptthread blockieren können, sollten in Leerlaufzeiten herabgestuft oder auf einen Web Worker ausgelagert werden.

Passive

Im Status passive interagiert der Nutzer nicht mit der Seite, kann sie aber sehen. Das bedeutet, dass UI-Änderungen und ‑Animationen weiterhin flüssig ablaufen sollten, der Zeitpunkt dieser Änderungen ist jedoch weniger wichtig.

Wenn die Seite von active zu passive wechselt, ist es angebracht, den nicht gespeicherten Anwendungsstatus beizubehalten.

Hidden

Wenn sich die Seite von passive zu hidden ändert, kann der Nutzer erst wieder damit interagieren, wenn sie neu geladen wurde.

Der Übergang zu ausgeblendet ist oft auch die letzte Statusänderung, die Entwickler zuverlässig beobachten können. Das gilt insbesondere auf Mobilgeräten, da Nutzer Tabs oder die Browser-App selbst schließen können und die Ereignisse beforeunload, pagehide und unload in diesen Fällen nicht ausgelöst werden.

Das bedeutet, dass Sie den Status ausgeblendet als wahrscheinliches Ende der Sitzung des Nutzers betrachten sollten. Mit anderen Worten: Speichern Sie nicht gespeicherte Anwendungsstatus und senden Sie nicht gesendete Analysedaten.

Sie sollten auch keine UI-Aktualisierungen mehr vornehmen, da der Nutzer diese nicht sehen kann. Außerdem sollten Sie alle Aufgaben beenden, die der Nutzer nicht im Hintergrund ausführen soll.

Frozen

Im Status gefroren werden einfrierbare Aufgaben in den Aufgabenwarteschlangen angehalten, bis die Seite entfrostet wird. Das kann nie passieren, z. B. wenn die Seite verworfen wird.

Wenn sich der Status der Seite also von ausgeblendet zu eingefroren ändert, müssen Sie alle Timer stoppen oder Verbindungen trennen, die sich bei einer Einfrierung auf andere geöffnete Tabs im selben Ursprung oder auf die Fähigkeit des Browsers auswirken könnten, die Seite in den Zurück-/Vorwärts-Cache zu legen.

Insbesondere sind folgende Schritte wichtig:

Außerdem sollten Sie jeden dynamischen Ansichtsstatus (z.B. Scrollposition in einer unendlichen Listenansicht) in sessionStorage (oder IndexedDB über commit()) beibehalten, der wiederhergestellt werden soll, wenn die Seite verworfen und später neu geladen wird.

Wenn die Seite von eingefroren zu ausgeblendet wechselt, können Sie alle geschlossenen Verbindungen wieder öffnen oder alle Abfragen neu starten, die Sie angehalten haben, als die Seite ursprünglich eingefroren wurde.

Terminated

Sie müssen in der Regel nichts unternehmen, wenn eine Seite in den Status Beendet wechselt.

Da Seiten, die aufgrund einer Nutzeraktion entladen werden, immer den Status ausgeblendet durchlaufen, bevor sie den Status beendet erreichen, sollte die Logik zum Beenden der Sitzung (z. B. Speichern des Anwendungsstatus und Erstellen von Berichten für Analytics) im Status ausgeblendet ausgeführt werden.

Wie in den Empfehlungen für den Status ausgeblendet erwähnt, ist es für Entwickler sehr wichtig zu wissen, dass der Übergang zum Status beendet in vielen Fällen (insbesondere auf Mobilgeräten) nicht zuverlässig erkannt werden kann. Entwickler, die auf Beendigungsereignisse angewiesen sind (z. B. beforeunload, pagehide und unload), verlieren daher wahrscheinlich Daten.

Discarded

Der Status discarded kann von Entwicklern zum Zeitpunkt des Verwerfens einer Seite nicht beobachtet werden. Das liegt daran, dass Seiten in der Regel aufgrund von Ressourceneinschränkungen verworfen werden und das Aufheben der Fixierung einer Seite, nur damit das Skript als Reaktion auf ein Verwerfen-Ereignis ausgeführt werden kann, in den meisten Fällen einfach nicht möglich ist.

Daher sollten Sie sich darauf vorbereiten, dass die Änderung von ausgeblendet zu eingefroren zu einer Verwerfung führt. Sie können dann beim Laden der Seite auf document.wasDiscarded klicken, um auf die Wiederherstellung einer verworfenen Seite zu reagieren.

Da Zuverlässigkeit und Reihenfolge von Lebenszyklusereignissen nicht in allen Browsern einheitlich implementiert sind, ist die Verwendung von PageLifecycle.js die einfachste Möglichkeit, der Empfehlung in der Tabelle zu folgen.

Nicht mehr unterstützte APIs für den Lebenszyklus

Die folgenden Ereignisse sollten nach Möglichkeit vermieden werden.

Das Unload-Ereignis

Viele Entwickler behandeln das Ereignis unload als garantierten Rückruf und verwenden es als Signal zum Ende der Sitzung, um den Status zu speichern und Analysedaten zu senden. Dies ist jedoch äußerst unzuverlässig, insbesondere auf Mobilgeräten. Das Ereignis unload wird in vielen gängigen Situationen nicht ausgelöst, z. B. wenn ein Tab über den Tab-Schnellzugriff auf einem Mobilgerät geschlossen oder die Browser-App über den App-Schnellzugriff geschlossen wird.

Daher ist es immer besser, anhand des visibilitychange-Ereignisses zu ermitteln, wann eine Sitzung endet, und den verborgenen Zustand als letzte zuverlässige Zeit zum Speichern von Anwendungs- und Nutzerdaten betrachten.

Außerdem kann das Vorhandensein eines registrierten unload-Ereignis-Handlers (über onunload oder addEventListener()) verhindern, dass Browser Seiten für ein schnelleres Zurück- und Vorwärtsladen in den Back-Forward-Cache legen.

In allen modernen Browsern wird empfohlen, zum Erkennen möglicher Seitenentladungen (terminated-Status) immer das Ereignis pagehide anstelle des Ereignisses unload zu verwenden. Wenn Sie Internet Explorer 10 und niedriger unterstützen müssen, sollten Sie das Ereignis pagehide erkennen und unload nur verwenden, wenn der Browser pagehide nicht unterstützt:

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.
});

Das „beforeunload“-Ereignis

Das beforeunload-Ereignis weist ein ähnliches Problem wie das unload-Ereignis auf: Bisher konnte das Vorhandensein eines beforeunload-Ereignisses verhindern, dass Seiten für den Back-Forward-Cache infrage kommen. Bei modernen Browsern gibt es diese Einschränkung nicht. Obwohl einige Browser als Vorsichtsmaßnahme das Ereignis beforeunload nicht auslösen, wenn versucht wird, eine Seite in den Back-Forward-Cache zu laden, ist das Ereignis kein zuverlässiges Signal für das Ende der Sitzung. Außerdem ist bei einigen Browsern, einschließlich Chrome, eine Nutzerinteraktion auf der Seite erforderlich, bevor das beforeunload-Ereignis ausgelöst wird. Dies beeinträchtigt die Zuverlässigkeit.

Ein Unterschied zwischen beforeunload und unload besteht darin, dass beforeunload legitim ist. Beispielsweise, wenn Sie den Nutzer warnen möchten, dass nicht gespeicherte Änderungen verloren gehen, wenn er die Seite weiterhin entlädt.

Da es gute Gründe für die Verwendung von beforeunload gibt, sollten Sie beforeunload-Listener nur hinzufügen, wenn ein Nutzer nicht gespeicherte Änderungen hat, und sie dann sofort nach dem Speichern entfernen.

Mit anderen Worten: Tun Sie Folgendes nicht, da dadurch ein beforeunload-Listener unbedingt hinzugefügt wird:

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);
  }
});

Führen Sie stattdessen Folgendes aus, da der beforeunload-Listener nur hinzugefügt und entfernt wird, wenn er benötigt wird:

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);
});

Häufig gestellte Fragen

Warum wird der Status „Wird geladen“ nicht angezeigt?

Die Page Lifecycle API definiert Status als diskret und sich gegenseitig ausschließend. Da eine Seite entweder im aktiven, passiven oder ausgeblendeten Status geladen werden kann und den Status ändern oder sogar beendet werden kann, bevor das Laden abgeschlossen ist, macht ein separater Ladestatus in diesem Paradigma keinen Sinn.

Meine Seite übernimmt wichtige Aufgaben, wenn sie ausgeblendet ist. Wie kann ich verhindern, dass sie eingefroren oder verworfen wird?

Es gibt viele legitime Gründe, warum Webseiten nicht eingefroren werden sollten, während sie im ausgeblendeten Zustand ausgeführt werden. Das offensichtlichste Beispiel ist eine App, mit der Musik abgespielt wird.

Es gibt auch Situationen, in denen es riskant wäre, wenn Chrome eine Seite verwirft, z. B. wenn sie ein Formular mit nicht gesendeter Nutzereingabe enthält oder einen beforeunload-Handler hat, der warnt, wenn die Seite entladen wird.

Aktuell wird Chrome vorsichtig sein, wenn Seiten verworfen werden, und zwar nur dann, wenn davon auszugehen ist, dass sich dies für Nutzer nicht auswirkt. Seiten, bei denen im ausgeblendeten Zustand eines der folgenden Ereignisse festgestellt wurde, werden nur bei extremen Ressourcenknappheit verworfen:

  • Audiowiedergabe
  • WebRTC verwenden
  • Tabellentitel oder Favicon aktualisieren
  • Benachrichtigungen anzeigen
  • Push-Benachrichtigungen senden

Informationen zu den aktuellen Listenfunktionen, mit denen ermittelt wird, ob ein Tab sicher eingefroren oder verworfen werden kann, finden Sie unter Heuristiken zum Einfrieren und Verwerfen in Chrome.

Was ist der Back-Forward-Cache?

Mit dem Begriff Back-Forward-Cache wird eine Navigationsoptimierung beschrieben, die einige Browser implementieren, um die Verwendung der Zurück- und Vorwärts-Schaltflächen zu beschleunigen.

Wenn ein Nutzer eine Seite verlässt, frieren diese Browser eine Version dieser Seite ein, damit sie schnell fortgesetzt werden kann, falls der Nutzer mit den Schaltflächen „Zurück“ oder „Weiter“ zurückwechselt. Hinweis: Wenn Sie einen unload-Ereignishandler hinzufügen, ist diese Optimierung nicht möglich.

Dieser Vorgang entspricht funktional dem Einfrieren von Browsern zur Schonung der CPU/des Akkus. Aus diesem Grund wird er als Teil des Lebenszyklusstatus gefroren betrachtet.

Wenn ich keine asynchronen APIs im gefrorenem oder beendeten Zustand ausführen kann, wie kann ich dann Daten in IndexedDB speichern?

Im Status „eingefrorener“ oder „beendeter“ werden einstellbare Aufgaben in den Aufgabenwarteschlangen einer Seite ausgesetzt. Das bedeutet, dass asynchrone und Callback-basierte APIs wie IndexedDB nicht zuverlässig verwendet werden können.

Künftig werden wir IDBTransaction-Objekten die Methode commit() hinzufügen. Damit können Entwickler Transaktionen ausführen, die nur zum Schreiben gedacht sind und keine Callbacks erfordern. Wenn der Entwickler also nur Daten in IndexedDB schreibt und keine komplexe Transaktion aus Lese- und Schreibvorgängen ausführt, kann die commit()-Methode abgeschlossen werden, bevor Task-Queues angehalten werden (vorausgesetzt, die IndexedDB-Datenbank ist bereits geöffnet).

Für Code, der heute funktionieren muss, haben Entwickler jedoch zwei Möglichkeiten:

  • Sitzungsspeicher verwenden: Der Sitzungsspeicher ist synchron und bleibt nach dem Leeren der Seite erhalten.
  • IndexedDB über Ihren Service Worker verwenden: Ein Service Worker kann Daten in IndexedDB speichern, nachdem die Seite beendet oder verworfen wurde. Im freeze- oder pagehide-Ereignislistener können Sie Daten über postMessage() an Ihren Service Worker senden. Der Service Worker kann dann das Speichern der Daten übernehmen.

App im eingefrorenen und verworfenen Zustand testen

Wenn Sie testen möchten, wie sich Ihre App im eingefrorenen und im verworfenen Zustand verhält, können Sie chrome://discards aufrufen, um einen Ihrer geöffneten Tabs einzufrieren oder zu verwerfen.

Verwirft die Benutzeroberfläche von Chrome
Verworfene UI in Chrome

So können Sie dafür sorgen, dass Ihre Seite die Ereignisse freeze und resume sowie das Flag document.wasDiscarded richtig verarbeitet, wenn Seiten nach dem Zurücksetzen neu geladen werden.

Zusammenfassung

Entwickler, die die Systemressourcen der Geräte ihrer Nutzer respektieren möchten, sollten ihre Apps mit Blick auf die Status des Seitenlebenszyklus entwickeln. Es ist wichtig, dass Webseiten in Situationen, in denen der Nutzer dies nicht erwartet, nicht zu viele Systemressourcen verbrauchen.

Je mehr Entwickler die neuen APIs für den Seitenlebenszyklus implementieren, desto sicherer können Browser Seiten einfrieren und verwerfen, die nicht verwendet werden. Das bedeutet, dass Browser weniger Arbeitsspeicher, CPU, Akku und Netzwerkressourcen verbrauchen, was für Nutzer von Vorteil ist.