Interfejs Page Lifecycle API

Obsługa przeglądarek

  • 68
  • 79
  • x
  • x

Nowoczesne przeglądarki czasami zawieszają strony lub całkowicie je odrzucają, gdy zasoby systemu są ograniczone. W przyszłości przeglądarki chcą to robić z wyprzedzeniem, aby zużywać mniej energii i pamięci. Interfejs Page Lifecycle API udostępnia punkty zaczepienia cyklu życia, dzięki którym strony mogą bezpiecznie obsługiwać te działania przeglądarki bez wpływu na wygodę użytkowników. Przyjrzyj się interfejsowi API, aby sprawdzić, czy warto wdrożyć te funkcje w swojej aplikacji.

Wprowadzenie

Cykl życia aplikacji to kluczowy sposób zarządzania zasobami w nowoczesnych systemach operacyjnych. Na urządzeniach z Androidem, iOS i ostatnimi wersjami Windows aplikacje mogą być w każdej chwili uruchamiane i zatrzymywane przez system operacyjny. Dzięki temu platformy mogą usprawnić i przydzielić zasoby tam, gdzie są najbardziej przydatne dla użytkowników.

W internecie Dawniej nie istniał taki cykl życia, a aplikacje mogą działać w nieskończoność. Przy dużej liczbie uruchomionych stron internetowych kluczowe zasoby systemowe, takie jak pamięć, procesor, bateria i sieć, mogą zostać przeciążone, co negatywnie wpływa na wrażenia użytkownika.

Platforma internetowa od dawna zawiera zdarzenia związane ze stanami cyklu życia, np. load, unload i visibilitychange, ale pozwalają one deweloperom reagować tylko na zmiany stanu cyklu życia inicjowane przez użytkownika. Aby sieć działała niezawodnie na urządzeniach o małej mocy (i była bardziej świadoma korzystania z zasobów na wszystkich platformach), przeglądarki muszą mieć sposób na proaktywne odzyskiwanie i ponowne przydzielanie zasobów systemowych.

Obecnie przeglądarki już podejmują aktywne działania w celu oszczędzania zasobów na stronach wyświetlanych na kartach w tle, a wiele przeglądarek (zwłaszcza Chrome) chciałoby robić to znacznie częściej, aby zmniejszyć ogólny zużycie zasobów.

Problem polega na tym, że deweloperzy nie są w stanie przygotować się na tego typu interwencje inicjowane przez system, ani nawet nie wiedzą o tym, że tak się dzieje. Oznacza to, że przeglądarki muszą być konserwatywne i umożliwiać ryzyko awarii stron internetowych.

Interfejs Page Lifecycle API próbuje rozwiązać ten problem przez:

  • Wprowadzenie i ustandaryzowanie koncepcji stanów cyklu życia w internecie.
  • Definiują nowe, inicjowane przez system stany, które umożliwiają przeglądarkom ograniczanie zasobów, które mogą wykorzystywać ukryte lub nieaktywne karty.
  • tworzenie nowych interfejsów API i zdarzeń, które umożliwiają programistom stron internetowych reagowanie na przejścia do i z tych nowych stanów inicjowanych przez system.

To rozwiązanie zapewnia programistom stron internetowych przewidywalność, które muszą tworzyć aplikacje odporne na interwencje w systemie, i pozwala przeglądarkom bardziej agresywnie optymalizować zasoby systemowe, co w rezultacie przynosi korzyści wszystkim użytkownikom internetu.

W pozostałej części tego postu omówimy nowe funkcje cyklu życia strony oraz ich związek z używanymi stanami i zdarzeniami platformy internetowej. Znajdziesz w nim też rekomendacje i sprawdzone metody dotyczące rodzajów pracy, którą deweloperzy powinni (i nie powinni) wykonywać w poszczególnych stanach.

Omówienie stanów i zdarzeń cyklu życia strony

Wszystkie stany cyklu życia strony są dyskretne i wzajemnie się wykluczają, co oznacza, że strona może mieć w danym momencie tylko jeden stan. Większość zmian w stanie cyklu życia strony można zwykle obserwować za pomocą zdarzeń DOM (wyjątki znajdziesz w zaleceniach deweloperów dotyczących poszczególnych stanów).

Być może najprostszym sposobem wyjaśnienia stanów cyklu życia strony – a także zdarzeń, które sygnalizują przejście między nimi – jest użycie diagramu:

Wizualna reprezentacja stanu i przebiegu zdarzeń opisanego w tym dokumencie.
Stan i przepływ zdarzeń w interfejsie Page Lifecycle API.

Stany

W tabeli poniżej znajdziesz szczegółowe informacje o każdym stanie. Wskazuje on też możliwe stany, które mogą wystąpić przed i po, oraz zdarzenia, których deweloperzy mogą używać do obserwowania zmian.

Stan Opis
Aktywne

Strona jest w stanie aktywna, jeśli jest widoczna i zawiera dane wejściowe.

Możliwe poprzednie stany:
pasywny (przez zdarzenie focus)
zawieszono (przez zdarzenie resume, a następnie zdarzenie pageshow)

Możliwe następne stany:
pasywny (za pomocą zdarzenia blur)

Był pasywny

Strona jest w stanie pasywnym, jeśli jest widoczna i nie ma wskazywanych przez nią danych wejściowych.

Możliwe poprzednie stany:
aktywne (przez zdarzenie blur)
ukryte (przez zdarzenie visibilitychange)
zawieszenie (przez zdarzenie resume, a następnie pageshow{/2)2

Możliwe następne stany:
aktywne (przez zdarzenie focus)
ukryte (przez zdarzenie visibilitychange)

Ukryta

Strona ma stan ukryta, jeśli nie jest widoczna (i nie została zablokowana, odrzucona ani zamknięta).

Możliwe poprzednie stany:
pasywny (przez zdarzenie visibilitychange)
zawieszono (przez zdarzenie resume, a następnie zdarzenie pageshow)

Możliwe kolejne stany:
pasywny (przez zdarzenie visibilitychange)
zawieszono (przez zdarzenie freeze)
odrzucone (nie uruchomiono żadnych zdarzeń)
zakończono (nie uruchomiono żadnych zdarzeń)

Zamrożony

W stanie zablokowana przeglądarka zawiesza wykonywanie zadań uniemożliwiających w kolejkach zadań na stronie, dopóki strona nie zostanie ponownie zablokowana. Oznacza to, że nie działają liczniki czasu JavaScript ani wywołania zwrotne pobierania. Już uruchomione zadania można zakończyć (najważniejsze wywołanie zwrotne freeze), ale ich możliwości i czas działania mogą być ograniczone.

Przeglądarki blokują strony, aby oszczędzać wykorzystanie procesora, baterii i danych, a także przyspieszać przechodzenie wstecz i do przodu, dzięki czemu nie trzeba ponownie wczytywać strony w całości.

Możliwe poprzednie stany:
ukryty (za pomocą zdarzenia freeze)

Możliwe kolejne stany:
aktywne (przez zdarzenie resume, a następnie zdarzenie pageshow)
pasywne resume, a następnie pageshowpageshow zostało wywołane przez zdarzenie ukryte.
ukryteresume
ukryte

Zakończono

Strona jest w stanie zakończona, gdy rozpocznie się jej wyładowywanie i usunięcie z pamięci przez przeglądarkę. W tym stanie nie można uruchamiać żadnych nowych zadań, a trwające zadania mogą zostać przerwane, jeśli będą trwać zbyt długo.

Możliwe poprzednie stany:
ukryty (za pomocą zdarzenia pagehide)

Możliwe następne stany:
BRAK

Odrzucono

Strona jest w stanie odrzucone, gdy została wyładowana z przeglądarki w celu oszczędzania zasobów. W tym stanie nie można uruchamiać żadnych zadań, wywołań zwrotnych zdarzeń ani JavaScriptu, ponieważ odrzucenia zwykle mają miejsce z powodu ograniczeń zasobów, gdy uruchomienie nowych procesów jest niemożliwe.

W stanie odrzucona sama karta (w tym jej tytuł i favikona) jest zwykle widoczna dla użytkownika, mimo że strona już nie ma.

Możliwe poprzednie stany:
ukryty (nie uruchomiono zdarzeń)
zablokowany (nie uruchomiono zdarzeń)

Możliwe następne stany:
BRAK

Wydarzenia

Przeglądarki wysyłają wiele zdarzeń, ale tylko niewielka ich część wskazuje na możliwość zmiany stanu cyklu życia strony. W tabeli poniżej znajdziesz wszystkie zdarzenia związane z cyklem życia oraz stany, z których mogą się one przechodzić.

Nazwa Szczegóły
focus

Element DOM został zaznaczony.

Uwaga: zdarzenie focus nie zawsze sygnalizuje zmiany stanu. Informuje o zmianie stanu tylko wtedy, gdy strona nie była wcześniej uwzględniana w danych wejściowych.

Możliwe poprzednie stany:
pasywny

Możliwe bieżące stany:
active

blur

Element DOM utracił zaznaczenie.

Uwaga: zdarzenie blur nie zawsze sygnalizuje zmiany stanu. Informują o zmianie stanu tylko wtedy, gdy strona nie jest już uwzględniana w danych wejściowych (tzn. nie przechodzi tylko z jednego elementu na inny).

Możliwe poprzednie stany:
active

Możliwe bieżące stany:
pasywny

visibilitychange

Wartość visibilityState dokumentu uległa zmianie. Może się to zdarzyć, gdy użytkownik przejdzie na nową stronę, przełączy kartę, zamknie kartę, minimalizuje lub zamyka przeglądarkę albo przełącza aplikacje w systemach operacyjnych na urządzenia mobilne.

Możliwe poprzednie stany:
pasywny
ukryty

Możliwe bieżące stany:
pasywny
ukryty

freeze *

Strona została właśnie zablokowana. Zadania blokowane w kolejkach zadań na stronie nie zostaną uruchomione.

Możliwe poprzednie stany:
ukryte

Możliwe bieżące stany:
zamrożony

resume *

Przeglądarka wznowiła zablokowaną stronę.

Możliwe poprzednie stany:
zablokowany

Możliwe bieżące stany:
aktywne (po wystąpieniu zdarzenia pageshow)
pasywny (po którym występuje zdarzenie pageshow)
ukryte

pageshow

Trwa przechodzenie do wpisu historii sesji.

Może to być zupełnie nowe wczytanie strony lub strona pobrana z pamięci podręcznej stanu strony internetowej. Jeśli strona została pobrana z pamięci podręcznej stanu strony internetowej, właściwość persisted zdarzenia ma wartość true. W przeciwnym razie ma wartość false.

Możliwe poprzednie stany:
zamrożone (mogłoby też uruchomić zdarzenie resume)

Możliwe bieżące stany:
aktywne
pasywne
ukryte

pagehide

Otwierany jest wpis historii sesji.

Jeśli użytkownik przechodzi na inną stronę, a przeglądarka może dodać bieżącą stronę do pamięci podręcznej stanu strony internetowej, której można później użyć ponownie, właściwość persisted zdarzenia ma wartość true. W momencie true strona przechodzi w stan zablokowana. W przeciwnym razie przechodzi w stan zamknięty.

Możliwe poprzednie stany:
ukryte

Możliwe bieżące stany:
zawieszono (event.persisted ma wartość prawda, freeze zdarzenie wystąpiło)
zakończony (event.persisted to fałsz, unload występuje zdarzenie)

beforeunload

Okno, dokument i jego zasoby zostaną wczytane. Dokument jest nadal widoczny, a wydarzenie nadal można anulować.

Ważne: zdarzenia beforeunload powinno być używane tylko do powiadamiania użytkownika o niezapisanych zmianach. Po zapisaniu tych zmian zdarzenie powinno zostać usunięte. Nigdy nie należy dodawać go do strony bezwarunkowo, ponieważ w niektórych przypadkach może to negatywnie wpłynąć na wydajność. Więcej informacji znajdziesz w sekcji dotyczącej starszych wersji interfejsów API.

Możliwe poprzednie stany:
ukryte

Możliwe bieżące stany:
zakończony

unload

Trwa wyładowywanie strony.

Ostrzeżenie: korzystanie ze zdarzenia unload nigdy nie jest zalecane, ponieważ jest zawodne i w niektórych przypadkach może negatywnie wpływać na wydajność. Więcej informacji znajdziesz w sekcji na temat starszych interfejsów API.

Możliwe poprzednie stany:
ukryte

Możliwe bieżące stany:
zakończony

* oznacza nowe zdarzenie zdefiniowane przez interfejs Page Lifecycle API.

Nowe funkcje w Chrome 68

Na poprzednim wykresie widać 2 stany wywoływane przez system, a nie przez użytkownika: zamrożone i odrzucone. Jak już wspomnieliśmy, obecnie przeglądarki od czasu do czasu blokują i odrzucają ukryte karty (według ich uznania), ale deweloperzy nie są w stanie sprawdzić, czy tak się dzieje.

W Chrome 68 deweloperzy mogą teraz obserwować, kiedy ukryta karta jest zablokowana i odblokowana, nasłuchując zdarzeń freeze i resume w document.

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

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

Od wersji 68 obiekt document zawiera teraz właściwość wasDiscarded w przeglądarce Chrome na komputery (w tym problemie śledzimy obsługę Androida). Aby sprawdzić, czy strona została odrzucona, gdy znajduje się na ukrytej karcie, możesz sprawdzić wartość tej właściwości w momencie jej wczytywania (uwaga: odrzucone strony należy ponownie załadować).

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

Wskazówki dotyczące tego, co jest ważne w zdarzeniach freeze i resume, a także jak postępować w przypadku odrzucania stron i się na nie przygotować, zapoznaj się z zaleceniami dla deweloperów w przypadku poszczególnych stanów.

W kilku kolejnych sekcjach omawiamy, jak te nowe funkcje pasują do istniejących stanów i zdarzeń platformy internetowej.

Jak obserwować w kodzie stany cyklu życia strony

W stanach aktywne, pasywne i ukryte można uruchomić kod JavaScript, który określa bieżący stan cyklu życia strony z interfejsów API platformy internetowej.

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

Stany zablokowane i zakończone można wykrywać tylko w odpowiednim odbiorniku zdarzeń (freeze i pagehide) w miarę zmiany stanu.

Jak obserwować zmiany stanu

Na podstawie zdefiniowanej wcześniej funkcji getState() możesz obserwować wszystkie zmiany stanu cyklu życia strony za pomocą poniższego kodu.

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

Ten kod wykonuje trzy zadania:

  • Ustawia stan początkowy za pomocą funkcji getState().
  • Definiuje funkcję, która akceptuje następny stan i w przypadku zmiany rejestruje tę zmianę w konsoli.
  • Dodaje przechwytywanie detektorów wszystkich niezbędnych zdarzeń cyklu życia, które z kolei wywołują metodę logStateChange(), która przekazuje następny stan.

Trzeba pamiętać, że kod zawiera wszystkie detektory zdarzeń, które są dodawane do interfejsu window i przekazują wszystkie dane potrzebne do wywołania {capture: true}. Możliwych jest kilka przyczyn takiego zjawiska:

  • Nie wszystkie zdarzenia cyklu życia strony mają taką samą wartość docelową. Parametry pagehide i pageshow są uruchamiane w przypadku elementów: window; visibilitychange, freeze i resume są uruchamiane document, a focus i blur na odpowiednich elementach DOM.
  • Większość z tych zdarzeń nie wyświetla się jako dymki, co oznacza, że nie można dodać detektorów zdarzeń nieprzechwytujących do wspólnego elementu nadrzędnego i obserwować je wszystkie.
  • Faza przechwytywania jest wykonywana przed fazami docelowymi lub bąbelkami, więc dodanie odbiorników w tym miejscu pomaga zapewnić ich uruchomienie, zanim inny kod je anuluje.

Rekomendacje deweloperów dla każdego stanu

Deweloperzy powinni znać stany cyklu życia strony oraz wiedzieć, jak obserwować je w kodzie, ponieważ to, co należy robić, a cze nie, zależy w dużym stopniu od stanu strony.

Na przykład wyświetlanie użytkownikowi tymczasowego powiadomienia o tym, że strona jest ukryta, nie ma sensu. Jest to oczywiste, ale istnieją też inne rekomendacje, które nie są tak oczywiste, że warto je wymienić.

Stan Rekomendacje deweloperów
Active

Stan aktywny to najważniejszy czas dla użytkownika, a tym samym jest najważniejszy, w którym strona może reagować na dane wejściowe użytkownika.

Wszelkie działania bez interfejsu użytkownika, które mogą blokować wątek główny, powinny zostać obniżone do okresów bezczynności lub przeciążone do instancji roboczej.

Passive

W stanie pasywnym użytkownik nie wchodzi w interakcję ze stroną, ale nadal może ją zobaczyć. Oznacza to, że aktualizacje interfejsu użytkownika i animacje powinny być nadal płynne, ale czas, w którym te zmiany się pojawiają, ma mniejsze znaczenie.

Gdy strona zmieni się z aktywnej na pasywną, jest to dobry moment na zachowanie niezapisanego stanu aplikacji.

Hidden

Gdy strona zmieni się z pasywnej na ukrytą, użytkownik może nie wejść z nią w interakcję, dopóki nie wczyta się ponownie.

Przejście do stanu hidden jest często ostatnią zmianą stanu, którą deweloperzy mogą łatwo dostrzec (dotyczy to zwłaszcza urządzeń mobilnych, ponieważ użytkownicy mogą zamykać karty lub samą aplikację przeglądarki, ponieważ w takich przypadkach zdarzenia beforeunload, pagehide i unload nie są uruchamiane).

Oznacza to, że stan ukryty należy traktować jako prawdopodobny koniec sesji użytkownika. Oznacza to, że zachowujesz niezapisany stan aplikacji i wysyłasz niewysłane dane analityczne.

Musisz też przestać aktualizować interfejs użytkownika (ponieważ użytkownik nie będzie ich widzieć) oraz zatrzymać wszystkie zadania, których użytkownik nie chce, aby były uruchamiane w tle.

Frozen

W stanie zablokowanym zablokowane zadania w kolejkach zadań są zawieszone do czasu odblokowania strony, co może się nigdy nie stać (np. po odrzuceniu strony).

Oznacza to, że gdy strona zmienia się z ukryta na zamrożona, należy zatrzymać wszystkie liczniki czasu lub zerwać połączenia, które w przypadku zamrożenia mogą mieć wpływ na inne otwarte karty w tym samym źródle lub utrudniać przeglądarce umieszczenie strony w pamięci podręcznej stanu strony internetowej.

W szczególności musisz:

Zachowaj też każdy dynamiczny stan widoku (np. pozycję przewijania w nieskończonym widoku listy) na sessionStorage (lub IndexedDB przez commit()), który chcesz przywrócić, jeśli strona zostanie odrzucona i ponownie załadowana później.

Jeśli strona zmieni stan z zablokowanego na ukryta, możesz ponownie otworzyć wszystkie zamknięte połączenia lub ponownie uruchomić odpytywanie zatrzymane, gdy strona została początkowo zablokowana.

Terminated

Gdy strona przejdzie do stanu zamkniętego, nie musisz nic robić.

Strony wyładowywane w wyniku działania użytkownika zawsze przechodzą przez stan hidden przed przejściem do stanu zamkniętego, dlatego w stanie ukryty należy wykonać logika zakończenia sesji (np. utrzymywać stan aplikacji i przesyłać raporty do Analytics).

Pamiętaj też, że w wielu przypadkach (zwłaszcza na urządzeniach mobilnych) nie można skutecznie wykryć przejścia do stanu ukrytego, więc deweloperzy, którzy polegają na zdarzeniach zamknięcia (np. beforeunload, pagehide i unload), prawdopodobnie utracą dane.

Discarded

Programiści nie mogą zaobserwować stanu odrzucone w momencie odrzucania strony. Dzieje się tak, ponieważ strony są zwykle odrzucane ze względu na ograniczenia zasobów, a odblokowanie strony tylko po to, by umożliwić uruchomienie skryptu w odpowiedzi na zdarzenie odrzucenia, po prostu nie jest możliwe.

Dlatego przygotuj się na możliwość odrzucenia zmiany z ukrytego na zablokowana, aby móc zareagować na przywrócenie odrzuconej strony podczas wczytywania strony, sprawdzając wartość document.wasDiscarded.

Niezawodność i kolejność zdarzeń cyklu życia nie zawsze są implementowane w spójny sposób we wszystkich przeglądarkach, więc najprostszym sposobem na zastosowanie się do zaleceń zawartych w tabeli jest użycie pliku PageLifecycle.js.

Starsze interfejsy API cyklu życia, których należy unikać

Należy unikać podanych niżej zdarzeń, gdy tylko jest to możliwe.

Zdarzenie wyładowania

Wielu deweloperów traktuje zdarzenie unload jako gwarantowane wywołanie zwrotne i używa go jako sygnału zakończenia sesji do zapisywania stanu i wysyłania danych analitycznych, ale takie działanie jest bardzo zawodne, zwłaszcza na urządzeniach mobilnych. Zdarzenie unload nie uruchamia się w wielu typowych sytuacjach wyładowywania, np. w przypadku zamknięcia karty za pomocą przełącznika kart na urządzeniu mobilnym lub zamknięcia aplikacji przeglądarki za pomocą przełącznika aplikacji.

Z tego powodu do określania końca sesji lepiej stosować zdarzenie visibilitychange, a stan ukryty to ostatni niezawodny czas na zapisanie danych aplikacji i użytkowników.

Co więcej, sama obecność zarejestrowanego modułu obsługi zdarzeń unload (za pomocą onunload lub addEventListener()) może uniemożliwić przeglądarkom umieszczanie stron w pamięci podręcznej stanu strony internetowej w celu szybszego wczytywania wstecz i dalej.

We wszystkich nowoczesnych przeglądarkach zalecamy, aby do wykrywania możliwych wyładowań stron (czyli stanu zakończenia) zawsze używać zdarzenia pagehide, a nie zdarzenia unload. Jeśli chcesz korzystać z przeglądarki Internet Explorer w wersji 10 lub starszej, włącz wykrywanie zdarzenia pagehide i używaj unload tylko wtedy, gdy przeglądarka nie obsługuje 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.
});

Zdarzenie beforeunload

Problem ze zdarzeniem beforeunload jest podobny do zdarzenia unload. W tym przypadku występowanie zdarzenia beforeunload mogło uniemożliwiać stronom kwalifikowanie się do korzystania z pamięci podręcznej stanu strony internetowej. Nowoczesne przeglądarki nie mają tego ograniczenia. Chociaż niektóre przeglądarki ze względów bezpieczeństwa nie uruchamiają zdarzenia beforeunload podczas próby umieszczenia strony w pamięci podręcznej stanu strony internetowej, co oznacza, że nie jest to wiarygodny sygnał zakończenia sesji. Poza tym niektóre przeglądarki (w tym Chrome) wymagają interakcji użytkownika na stronie przed uruchomieniem zdarzenia beforeunload, co obniża jego niezawodność.

Jedna z różnic między beforeunload a unload polega na tym, że istnieją uzasadnione przypadki użycia właściwości beforeunload. Jeśli na przykład chcesz ostrzec użytkownika, że ma niezapisane zmiany, to straci je, jeśli będzie kontynuować usuwanie wczytanej strony.

Istnieją uzasadnione powody, aby używać funkcji beforeunload, dlatego zalecamy, aby dodawać detektory beforeunload tylko wtedy, gdy użytkownik ma niezapisane zmiany, a potem usunąć je natychmiast po ich zapisaniu.

Inaczej mówiąc, nie rób tego (ponieważ spowoduje to dodanie odbiornika beforeunload bezwarunkowo):

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

Zamiast tego zrób to (ponieważ dodaje detektor beforeunload tylko wtedy, gdy jest potrzebny, i usuwa go, gdy nie jest):

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

Najczęstsze pytania

Dlaczego nie ma stanu „wczytywanie”?

Interfejs Page Lifecycle API definiuje stany jako dyskretne i wzajemnie się wykluczające. Strona może być wczytywana w stanie aktywnym, pasywnym lub ukrytym, a ponieważ przed zakończeniem ładowania może się zmieniać stan, a nawet zostać zamknięta, oddzielny stan wczytywania nie ma sensu w tym modelu.

Moja strona jest ukryta, ale spełnia ważne funkcje. Jak mogę zapobiec jej zablokowaniu lub odrzuceniu?

Jest wiele uzasadnionych powodów, dla których strony internetowe nie powinny być blokowane, gdy działają w stanie ukrytym. Najbardziej oczywistym przykładem jest aplikacja, która odtwarza muzykę.

Istnieją też sytuacje, w których Chrome może ryzykować odrzucenie strony – np. gdy zawiera ona formularz z nieprzesłanymi danymi wpisywanymi przez użytkownika lub zawiera moduł obsługi beforeunload ostrzegający podczas wyładowywania strony.

Obecnie Chrome zachowuje ostrożność podczas odrzucania stron i działa tylko wtedy, gdy ma pewność, że nie wpłynie to na użytkowników. Na przykład strony, które w stanie ukrytym wykonują dowolne z tych czynności, nie zostaną odrzucone, chyba że mają ekstremalne ograniczenia zasobów:

  • Odtwarzam dźwięk
  • Korzystanie z WebRTC
  • Aktualizowanie tytułu lub favikony tabeli
  • Wyświetlam alerty
  • Wysyłanie powiadomień push

Aktualne funkcje listy służące do określania, czy kartę można bezpiecznie zablokować lub odrzucić, znajdziesz w sekcji Heurystyka – blokowanie i odrzucanie w Chrome.

Czym jest pamięć podręczna stanu strony internetowej?

Pamięć podręczna stanu strony internetowej to termin opisujący optymalizację nawigacji w niektórych przeglądarkach, dzięki której korzystanie z przycisków Wstecz i Dalej działa szybciej.

Gdy użytkownik opuści stronę, przeglądarki te zablokują jej wersję, aby można ją było szybko wznowić, gdy użytkownik przejdzie wstecz za pomocą przycisków Wstecz lub Dalej. Pamiętaj, że dodanie modułu obsługi zdarzeń unload uniemożliwia taką optymalizację.

We wszystkich intencjach i celach blokowanie działa tak samo jak działanie blokowania przeglądarki w celu oszczędzania procesora lub baterii, dlatego jest uważane za część zawieszonego cyklu życia.

Jeśli nie mogę uruchomić asynchronicznych interfejsów API w stanie zablokowanego lub zamkniętego, jak mogę zapisać dane w IndexedDB?

W stanach zablokowania i zakończenia zablokowane zadania w kolejkach zadań na stronie są zawieszone, co oznacza, że nie można niezawodnie używać interfejsów API asynchronicznych i wykorzystujących wywołania zwrotne, takich jak IndexedDB.

W przyszłości dodamy do obiektów IDBTransaction metodę commit(), która pozwoli programistom na realizowanie transakcji tylko do zapisu, które nie wymagają wywołań zwrotnych. Inaczej mówiąc, jeśli deweloper tylko zapisuje dane w IndexedDB i nie wykonuje złożonej transakcji obejmującej odczyty i zapisy, metoda commit() może zakończyć się przed zawieszeniem kolejek zadań (przy założeniu, że baza danych IndexedDB jest już otwarta).

W przypadku kodu, który musi działać obecnie, deweloperzy mają 2 możliwości:

  • Korzystaj z pamięci sesji: funkcja Pamięć sesji jest synchroniczna i jest zachowywana po odrzuceniu strony.
  • Użyj IndexedDB ze swojego skryptu service worker: skrypt service worker może przechowywać dane w IndexedDB po zamknięciu lub odrzuceniu strony. Za pomocą detektora zdarzeń freeze lub pagehide możesz wysyłać dane do skryptu service worker za pomocą postMessage(), a ten skrypt może zapisywać dane.

Testowanie aplikacji jako zablokowanej i odrzuconej

Aby sprawdzić, jak aplikacja zachowuje się w stanach zablokowanego i odrzuconego, otwórz chrome://discards i zablokuj lub odrzucij otwarte karty.

Odrzucanie interfejsu Chrome
Interfejs odrzucania Chrome

Dzięki temu możesz mieć pewność, że strona prawidłowo obsługuje zdarzenia freeze i resume oraz flagę document.wasDiscarded, gdy strony są ponownie załadowane po odrzuceniu.

Podsumowanie

Deweloperzy, którzy chcą szanować zasoby systemowe urządzeń użytkowników, powinni tworzyć aplikacje z uwzględnieniem stanów cyklu życia strony. Strony internetowe nie mogą zużywać nadmiernej ilości zasobów systemowych w sytuacjach, których użytkownik nie spodziewałby się

Im więcej programistów zacznie wdrażać nowe interfejsy API cyklu życia strony, tym bezpieczniejsze będzie blokowanie i odrzucanie nieużywanych stron w przeglądarkach. Dzięki temu przeglądarki zużywają mniej pamięci, procesora, baterii i zasobów sieciowych, co jest korzystne dla użytkowników.