Back-Forward Cache - Web-Exposed Behaviour
Back-Forward Cache - Web-Exposed Behaviour
[email protected]
Jul 21, 2020
Summary
Back/forward cache is a browser feature which improves the user experience by keeping a page
alive after the user navigates away from it and reuses it for session history navigation (browser
back/forward buttons, history.back(), etc) to make the navigation instant. The pages in the
cache are frozen and will not run any javascript.
This document outlines the web-exposed behaviour of back/forward cache and the
implementation differences between Chromium, Safari and Firefox. The goal is to standardize
the behaviour and improve the interoperability.
See Chrome launch plans section for more details about Chrome’s bfcache launch timeline.
Cross-browser support
Both Safari and Firefox implement back/forward cache. However, their implementations vary
substantially — as a rule of thumb, Safari stores the page in the cache in more situations,
prioritising better user experience, while Firefox avoids storing the page in the cache in
uncertain situations, prioritising platform predictability.
Page lifecycle
When the user tries to navigate away from the page, the following events will be dispatched:
● beforeunload
○ The user might be prompted to confirm the navigation. If the user rejects the
prompt, navigation is aborted. If the user accepts the prompt, the navigation
continues as normal.
● visibilitychange if the page wasn’t hidden
● pagehide
● If the browser attempts to store the page in bfcache:
○ freeze
● Otherwise:
○ unload
After the freeze event the page is frozen and no events will be dispatched until the page is
restored from bfcache. If a task or a promise associated with a page’s document become ready
during this period, they will run after the page is restored from the cache.
While the page is in the cache, the user agent at any point in time can decide to evict the page in
the cache. In this case the page will be destroyed without any notifications being dispatched.
When the page is navigated back to, the following events will be dispatched:
● resume
● pageshow
● visibilitychange if the navigation happened in a visible tab
Event listeners
pageshow/pagehide
[tester, spec]
For pageshow event.persisted is true iff the page was restored from back/forward cache.
For pagehide if event.persisted is false, the user navigates away from the page and the
user agent will not try to store the page in the cache. If event.persisted is true, the user
agent will try to persist the page in the back/forward cache, but it might or might not happen.
visibilitychange
[tester, spec]
If the page was stored in the cache while the tab was in the foreground, visibilitychange
event will be dispatched. document.visibilityState will be set to hidden.
If the page was restored from the cache while the tab was in the foreground,
visibilitychange event will be dispatched. document.visibilityState will be set to
visible.
Safari does not dispatch visibilitychange when the page is moved to or from the cache.
Firefox dispatches visibilitychange event.
freeze / resume
[tester, spec, API explainer]
freeze will be the last event to be dispatched before the frame is frozen. After freeze event is
dispatched, no javascript will run until the page is restored from the cache and resume event
fires.
beforeunload
[tester, spec]
beforeunload will fire during the normal navigation flow before bfcache eligibility for a
document is determined. beforeunload will fire both for same-site and cross-site navigation.
In Firefox, presence of beforeunload event listener makes page ineligible for bfcache.
In Safari, presence of beforeunload event listener does not affect page eligibility. Safari will
fire beforeunload only for same-site navigations.
unload
[tester, spec]
If the user agent decides to attempt to store the page in the back/forward cache (as indicated
by pagehide’s event.persisted attribute being set to true), the unload event will not be
dispatched.
This is going to be the biggest compat concern for existing websites given that ~60% of page
loads register unload event listener. The risk is already mitigated by:
a) unload already not firing on mobile in some circumstances (e.g. background tab killed
by OS)
b) Safari already not firing unload when the page is stored in the back/forward cache
c) unload’s bfcache-friendly analogue, pagehide, is registered for ~36% of page loads.
d) almost all possible breakage will not be user-visible and will mostly affect metrics.
Our long-term aspiration is to deprecate unload() event and eventually remove it completely in
favour of visibilitychange and pagehide.
In Firefox, presence of unload event listener makes a page ineligible for bfcache.
In Safari, presence of unload event listener does not affect page eligibility. `unload` will not be
fired when the page is stored in the back/forward cache.
Eligibility criteria
If any of the following is true, user-agent will not attempt to store the page in the back/forward
cache. Note that this is not exhaustive:
● Status of the http request of the main resource of the main document was not 200 OK.
● Method of the http request of the main resource of the main document was not GET.
● Scheme is not HTTP or HTTPS.
Cache-control: no-store
Presence of Cache-control: no-store header on the main resource of the main document of the
page will disable back/forward cache.
Safari 14 does not cache pages iff main document’s main HTTP request’s response has
Cache-Control: no-store header. (Note that this changed in Safari 13, which cached all pages
regardless of the presence of Cache-Control: no-store header).
Firefox does not cache pages with main resources having Cache-Control: no-store or
Cache-Control: no-cache directive [confirm].
Explicit opt-out
We are gathering feedback from other browsers and web developers to see if there is a need for
adding an explicit JS API for making bfcache as ineligible. Github thread:
https://github.com/whatwg/html/issues/5744
window.open
[tester, spec]
Only pages which have a trivial browsing context group (browsing context group that does not
contain auxiliary browsing contexts). That means that pages which either have a handle to a
valid WindowProxy object (i.e. have used window.open to open) or non-null window.opener (i.e.
have been opened by a different page) are not eligible for being stored in the back/forward
cache.
This applies regardless of same-origin access policy, so both cross-origin and same-origin
window.open will make both target and source page ineligible for bfcache. However, if the
opener relation is severed (Cross-Origin Opener Policy, rel=noopener, etc), a new browsing
context group is created and both pages will be eligible for bfcache.
To ensure that each page will get its own browsing context group, Chromium will proactively
swap browsing context group on top-level cross-document navigation if the browsing context
group of the old page was trivial.
We will not implement support for caching pages with non-trivial browsing context groups due
to associated complexity.
Plugins
Pages with embedded plugins are ineligible for being stored in the back/forward cache.
setTimeout/setInterval
[tester, spec]
Timers will be suspended when the page enters back/forward cache. The timer callbacks will be
dispatched after the page is restored from back/forward cache.
Chromium considers timer delays to be “real-time”, so the time page spends in back/forward
cache is taken into account when firing the timer (if the delay is 15 seconds and the page spent
10 seconds in the back/forward cache, the timer will fire after 5 seconds after the page is
restored from the cache).
Safari and Firefox consider timer delays to be “page-active time” and in the previous example
the timer callback will fire after 15 seconds after the page is restored from back/forward cache
and 25 seconds after the timer was queued.
window.postMessage()
As pages with non-trivial browser context groups are ineligible for bfcache (see window.open),
it’s impossible to use window.postMessage() to send a message to a page in the back/forward
cache.
navigation timing
`window.performance.timing` (NavigationTiming API v1) will not change, including
timing.navigationStart — it will always correspond to the navigation start of the original page
load, even after bfcache restore. Note: Firefox does the same, Safari updates
timing.navigationStart to correspond to the navigation start of the back/forward cache restore
navigation.
In the long term plan, we are planning to expose navigation timing of the bfcache restore
navigations via Navigation Timing API v2, however at the moment the exact need and the
desired shape for this API is not understood. Interested web developers are encouraged to
contact us and share their use cases with us.
As a short term solution, we will set the timestamp of the `pageshow`’s `event` to the timestamp
of the navigation start of the back/forward cache restore navigation.
DedicatedWorker
[tester, spec]
At the moment the presence of a DedicatedWorker makes page ineligible for bfcache in
Chromium.
Note: we are planning to consider supporting pages with DedicatedWorkers by pausing the
script execution in the workers.
SharedWorker
At the moment the presence of a SharedWorker makes the page ineligible for bfcache in
Chromium.
ServiceWorker can’t see the clients in back/forward cache: clients.get() and clients.matchAll()
will not return handles to the clients which are in back/forward cache. Old handles returned
from previous invocations of clients.get() / clients.matchAll() will continue to work after the
page was restored from the cache, but an attempt to send an event via client.postMessage() will
cause the page to be evicted from the back/forward cache.
If ServiceWorker tries to claim all clients via clients.claim(), all cached unclaimed clients will be
evicted from back/forward cache.
At the moment a presence of an active outstanding network request will make the page
ineligible for back/forward cache.
Note: in the medium term we are planning to consider supporting pages with outstanding
network requests by aborting them.
BroadcastChannel
At the moment the presence of the BroadcastChannel will make the page ineligible for bfcache.
Note: in the medium term we are planning to consider supporting pages with BroadcastChannel
by automatically disconnecting the page from the BroadcastChannels when the page enters
back/forward cache.
Focus
When we navigate away from a page and it gets stored into the back/forward cache, we will
remove the focus from the currently focused element (and trigger blur event, etc.). When we
navigate back and restore the page from the back/forward cache, the focus will not be restored.
On Safari and Firefox, the focus will be kept on the focused element, so when we navigate back
and restore the page, the previously focused element will stay focused.
TODO: Confirm Safari’s and Firefox’s behaviour regarding focus-related events, and maybe
reconsider Chrome’s implementation for interoperability.
See spec issue: https://github.com/whatwg/html/issues/5878
Other APIs
A number of other complex APIs, including WebUSB, WebBluetooth, IdleManager, WebGL,
WebVR, WebXR, audio- and video-capturing APIs and others will make page ineligible for
back/forward cache.
In the very short term, many APIs will prevent pages from being eligible for bfcache if they were
used at any point during the page’s lifetime (for example, all pages requesting a feature
depending on the notification permission will not be cached).
In the short-to-medium plan we will ensure that the page can be stored in the back/forward
cache if the page has performed a cleanup of these APIs in pagehide() or freeze() handler.
Mobile support
● Back/forward cache for cross-site navigations on Android in Chrome 86.
● Back/forward cache for same-site navigations on Android in a follow-up milestone.
Desktop support
Chrome is aspiring to enable back/forward cache on desktop, however there exists a substantial
amount of desktop-specific features (including extensions) which will require a non-trivial
amount of work to integrate with bfcache.
For now, focusing on mobile, as the ratio of history navigations is substantially higher (~20% on
Android vs ~7% on desktop) and the benefit of having back/forward cache on mobile is
substantially higher.
Chrome will give an update when there is a timeline for enabling back/forward cache on
desktop.
WebView support
Chrome is not planning to enable back/forward cache for Android WebView due to associated
complexities.