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 möchten Browser dies proaktiv tun, damit sie weniger Strom und Arbeitsspeicher 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. Auf diese Weise können diese Plattformen Ressourcen optimieren und umverteilen, wo sie für die Nutzenden am besten sind.

Im Web gab es bisher keinen solchen Lebenszyklus und Apps konnten unbegrenzt weiterlaufen. Wenn viele Webseiten ausgeführt werden, können wichtige Systemressourcen wie Arbeitsspeicher, CPU, Akku und Netzwerk überlastet werden, was zu einer schlechten Nutzererfahrung führt.

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. Außerdem enthält es Empfehlungen und Best Practices für die Arten von Arbeit, die Entwickler in den einzelnen Bundesstaaten ausführen sollten (und nicht ausführen sollten).

Übersicht über die Status und Ereignisse des Seitenlebenszyklus

Alle Status des Seitenlebenszyklus sind diskret und schließen sich gegenseitig aus. Das bedeutet, dass eine Seite immer nur einen Status haben kann. Die meisten Änderungen am Lebenszyklusstatus einer Seite können in der Regel über DOM-Ereignisse beobachtet werden. Ausnahmen finden Sie in den Entwicklerempfehlungen für jeden 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 Status- und Ereignisflusses.
API-Status und Ereignisablauf des Seitenlebenszyklus

Bundesstaaten

In der folgenden Tabelle werden die einzelnen Status 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)
gefroren (über das Ereignis resume und dann das Ereignis pageshow)

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)
eingefroren (über das Ereignis resume, 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 und laufende Aufgaben werden möglicherweise beendet, wenn sie zu lange laufen.

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 Tab-Titel und Favicon) normalerweise für den Nutzer sichtbar, obwohl die Seite nicht mehr vorhanden ist.

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 den Lebenszyklus betreffen, sowie die Status, in die sie wechseln können.

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:
passive

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:
active

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:
eingefroren

Mögliche aktuelle Status:
aktiv (wenn das Ereignis pageshow folgt)
inaktiv (wenn das Ereignis pageshow folgt)
ausgeblendet

pageshow

Ein Eintrag im Sitzungsverlauf wird durchlaufen.

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:
eingefroren (event.persisted ist „wahr“, freeze Ereignis folgt)
beendet (event.persisted ist „falsch“, unload Ereignis folgt)

beforeunload

Das Fenster, das Dokument und seine Ressourcen werden gleich entladen. Das Dokument ist weiterhin sichtbar und das Ereignis kann zu diesem Zeitpunkt noch abgesagt werden.

Wichtig:Das Ereignis beforeunload sollte nur verwendet werden, um Nutzer auf nicht gespeicherte Änderungen hinzuweisen. Sobald diese Änderungen gespeichert sind, sollte das Ereignis entfernt werden. Sie sollte der Seite niemals ohne Bedingungen 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:
beendet

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:
terminated

* Hinweist auf ein neues Ereignis, das von der Page Lifecycle API definiert wurde

Neue Funktionen in Chrome 68

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, welche Schritte bei den Ereignissen freeze und resume wichtig sind und wie du mit dem Verwerfen von Seiten umgehst und dich darauf vorbereitest, findest du in den Entwicklerempfehlungen für die einzelnen Bundesstaaten.

In den nächsten Abschnitten erhalten Sie einen Überblick darüber, wie sich diese neuen Funktionen in den Status und die Ereignisse der vorhandenen Webplattform einfügen.

Status des Seitenlebenszyklus im Code beobachten

Im Status Aktiv, Passiv und Ausgeblendet kann JavaScript-Code ausgeführt werden, der den aktuellen Status des Seitenlebenszyklus anhand vorhandener Webplattform-APIs bestimmt.

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

Der Status eingefroren und beendet kann hingegen nur im jeweiligen Event-Listener (freeze und pagehide) erkannt werden, wenn sich der Status ändert.

Zustandsä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);

Mit diesem Code werden drei Dinge umgesetzt:

  • 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.
  • Fügen Sie Ereignis-Listener für alle erforderlichen Lebenszyklusereignisse hinzu, 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 im Seitenlebenszyklus haben dasselbe Ziel. pagehide und pageshow werden bei window ausgelöst, visibilitychange, freeze und resume bei document und focus und blur bei ihren jeweiligen DOM-Elementen.
  • Die meisten dieser Ereignisse werden nicht weitergereicht. Das bedeutet, dass es nicht möglich ist, einem gemeinsamen übergeordneten Element nicht aufzeichnende Ereignis-Listener hinzuzufügen und alle zu beobachten.
  • Die Erfassungsphase wird vor der Ziel- oder Blasenphase ausgeführt. Wenn Sie dort Listener hinzufügen, werden sie ausgeführt, bevor sie von anderen Code-Elementen abgebrochen werden können.

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 ist beispielsweise offensichtlich nicht sinnvoll, 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 die kritische Zeit für den Nutzer und somit auch der wichtigste Zeitpunkt, zu dem deine Seite auf Nutzereingaben reagiert.

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 verborgen ist oft auch die letzte Statusänderung, die von Entwicklern zuverlässig beobachtet werden kann. Dies gilt insbesondere für Mobilgeräte, 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: Behalten Sie den nicht gespeicherten Anwendungsstatus bei und senden Sie alle nicht gesendeten Analysedaten.

Außerdem sollten Sie keine Updates mehr an der Benutzeroberfläche vornehmen, da sie vom Nutzer nicht gesehen werden. Außerdem sollten Sie alle Aufgaben beenden, die ein Nutzer nicht im Hintergrund ausführen möchte.

Frozen

Im Status eingefroren werden einstellbare Aufgaben in den Aufgabenwarteschlangen angehalten, bis die Fixierung der Seite aufgehoben wird. Dies kann beispielsweise nie passieren, wenn die Seite z. B. 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. die Scrollposition in einer endlosen Listenansicht) in sessionStorage (oder IndexedDB über commit()) speichern, 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 verworfen ist für Entwickler nicht sichtbar, wenn eine Seite verworfen wird. Das liegt daran, dass Seiten in der Regel aufgrund von Ressourceneinschränkungen verworfen werden. In den meisten Fällen ist es einfach nicht möglich, eine Seite aufzutauen, nur um ein Script als Reaktion auf ein Verwerfungsereignis auszuführen.

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 unload-Ereignis als garantierten Callback und verwenden es als Ende der Sitzung, um den Status zu speichern und Analysedaten zu senden. Dies ist jedoch sehr unzuverlässig, insbesondere auf Mobilgeräten. Das Ereignis unload wird in vielen typischen Unload-Situationen nicht ausgelöst, z. B. beim Schließen eines Tabs über den Tab-Wechsler auf Mobilgeräten oder beim Schließen der Browser-App über den App-Wechsler.

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. Für moderne Browser gilt 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 erfordern einige Browser (einschließlich Chrome) eine Nutzerinteraktion auf der Seite, bevor das beforeunload-Ereignis ausgelöst wird. Das wirkt sich weiter auf die Zuverlässigkeit aus.

Ein Unterschied zwischen beforeunload und unload besteht darin, dass es legitime Verwendungen von beforeunload gibt. 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 gibt es keinen Status „Laden“?

Die Page Lifecycle API definiert Status als diskret und schließen sich gegenseitig aus. 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 dafür, dass Webseiten im verborgenen Zustand nicht eingefroren werden sollten. Das naheliegendste Beispiel ist eine App, die Musik abspielt.

Es gibt auch Situationen, in denen Chrome eine Seite verwirft, z. B. wenn sie ein Formular mit nicht gesendeter Nutzereingabe enthält oder wenn ein beforeunload-Handler vorhanden ist, der beim Entladen der Seite warnt.

Vorerst wird Chrome Seiten nur dann verwerfen, wenn sicher ist, dass dies keine Auswirkungen auf die Nutzer hat. 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?

Der Back-Forward-Cache ist ein Begriff, der eine Navigationsoptimierung beschreibt, die einige Browser implementieren, um die Verwendung der Schaltflächen „Zurück“ und „Weiter“ 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.

Zukünftig werden wir IDBTransaction-Objekten eine commit()-Methode hinzufügen, mit der Entwickler quasi reine Schreibtransaktionen ausführen können, die 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 bereits heute funktionieren muss, haben Entwickler jedoch zwei Möglichkeiten:

  • Sitzungsspeicher verwenden:Sitzungsspeicher ist synchron und wird beim Verwerfen von Seiten beibehalten.
  • 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.

Chrome-Benutzeroberfläche wird geschlossen
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.