Follow a web page through the Page Lifecycle API
page-lifecycle-tracker is a small TypeScript library for monitoring and telemetry code. It follows active, passive, hidden, frozen and terminated. Your monitors record their last values before your exporter sends them.
npm install page-lifecycle-trackerLive: the Page Lifecycle state of this page
This page is active. The page is visible, and it has the focus.
Try it
Click outside the browser window. Then click in the page again.
Not done. Expect active to passive, from blur.
Go to another tab or app, or minimize the window. Then come back.
Not done. Expect hidden, then active again, from visibilitychange.
Leave this page, then come back with the Back button of the browser.
Not done. Expect frozen, then a restore from pageshow.
Transitions 0
| Time | From | To | Event |
|---|---|---|---|
| 0.0 s | Page load | active | Start |
The state strip of this visit
active0.2 spassive0.0 sfrozen0.0 sterminated0.0 s
Start with a few lines
Get the shared tracker. Read the state, and subscribe to each transition. Each transition has from, to, trigger and the timeStamp of its browser event.
The package is ESM, with TypeScript types and no dependencies. It uses visibilitychange, pagehide, pageshow, freeze and resume, and no unload listener.
import { getPageLifecycle } from "page-lifecycle-tracker";
const lifecycle = getPageLifecycle(); // one shared tracker for the page
console.log(lifecycle.getState()); // "active", "passive" or "hidden"
// A monitor observes: it records its values first.
lifecycle.subscribe(({ from, to, trigger, timestamp }) => {
console.log(`${from} to ${to} (${trigger}) at ${timestamp} ms`);
});
// An exporter sends after all observers, in each engine.
lifecycle.subscribe(({ to }) => {
if (to === "hidden" || to === "frozen" || to === "terminated") exporter.flush();
}, { phase: "export" });Observers before exporters at pagehide, in Chromium too
At the end of a page, a monitor records its final values, and an exporter sends the last batch. The order of these two listeners decides if the final values get out.
In experiment E1 of the lag project, Chromium started the pagehide listeners of window in the order of registration. Firefox and WebKit started the capture listeners first. Play one pagehide in the four setups.
Your browser: a test on window started the listeners in the order of registration, as Chromium did in experiment E1.
Chromium 145Your browser
The exporter added its pagehide listener first. Chromium starts the listeners of window in the order of registration.
pagehide- 1ExporterIts pagehide listener sends the batch.LCPCLSINP (not in the batch)
- 2TrackerIts capture listener: active to terminated.
- 3MonitorObserve subscriber: it records the final INP.
The exporter sent the batch before the monitor recorded the final INP. The value is lost.
Firefox 146 and WebKit
The same page. These engines start the capture listener of the tracker first.
pagehide- 1TrackerIts capture listener: active to terminated.
- 2MonitorObserve subscriber: it records the final INP.
- 3ExporterIts pagehide listener sends the batch.LCPCLSINP
The batch has the final INP.
Any engine, with phase: "export"
The exporter has no listener. It subscribes in the export phase, before the monitor subscribes.
pagehide- 1TrackerIts capture listener: active to terminated.
- 2MonitorObserve subscriber: it records the final INP.
- 3ExporterExport subscriber: it sends the batch.LCPCLSINP
The batch has the final INP.
Any engine, with handle(event)
The exporter keeps its listener, and it starts first. It gives the event to the tracker before it sends.
pagehide- 1ExporterIts pagehide listener starts first. It calls handle(event).
- 2Trackerhandle(event): active to terminated.
- 3MonitorObserve subscriber: it records the final INP.
- 4ExporterIt sends the batch.LCPCLSINP
- 5TrackerIts capture listener: the event is already handled. No change.
The batch has the final INP.
window that starts the listeners in the order of experiment E1. The values are examples.visibilitychange, pagehide, freeze and the bfcache in one API
getState()The current state. Each call reads
States and transitionsdocument.visibilityStateagain, thus a timer between the change and itsvisibilitychangeevent also getshidden.subscribe(listener, { phase })All
The subscriber phasesobservesubscribers of a transition start before allexportsubscribers, whatever the order of the scripts.getPageLifecycle()One tracker for the page, under a global symbol. Two libraries, or two copies of the package, share it.
The shared trackerhandle(event)An exporter whose own listener starts first gives the event to the tracker. The tracker handles each event object one time.
Capture phase and listener ordermark()andresolve()Ask which transitions occurred in a measurement window.
MarkssummarizeTransitions()gives flags, for examplewasHidden.- Back/forward cache
A restore from the bfcache is always a transition with the trigger
Back/forward cache restorespageshow, also when the state was visible already.