1. Introduction
Browser implementations may have a back/forward cache, or "BFCache" for short. After a user navigates away from a document, the document might be cached in a non-fully active state, and might be reused when the user navigates back. In the past, many APIs have missed specifying support for non-fully active documents, making them hard to support in various user agents that cache pages in the BFCache, effectively making the user experience of navigating back and forth less optimal, or even introducing breakages or differences in behavior in various different implementations of BFCache.
By specifying BFCache support for new APIs, web developers do not need to choose between using the API and giving a more performant browsing experience through instant history navigations. Going forward, all features should have BFCache support by default, as documents are actually BFCached on navigation instead of getting destroyed for a sizable chunk of navigations.
Note: It is possible for a document to become non-fully active for other reasons not related to BFcaching, such as when the iframe holding the document gets detached. Some advice below might not be relevant for those cases, since the document will never return to fully active again.
2. When should features care about BFCache?
If your API does things that fall into any of the below categories:
-
Interacts with a document from the "outside" (e.g. sends information to a document)
-
Makes cross-document interaction/resource sharing possible (e.g. holding locks)
-
May malfunction when a document is kept in a non-fully active (BFCached) state (instead of getting destroyed) after the user navigates away from it or gets restored (e.g. expects that a state saved in the document won’t span multiple navigations)
You should specify how it works with non-fully active (BFCached) documents, following the guidelines below. See also the § 2.2 Antipatterns section to avoid common antipatterns.
2.1. API Design Guidance
2.1.1. Gate actions with fully active checks
When performing actions that might update the state of a document, be aware that the document might not be fully active and is considered as "non-existent" from the user’s perspective. This means they should not receive updates or perform actions.
Note: It is possible for a fully active document to be perceived as "non-existent" by users, such as when the document is displaying prerendered content. These documents might behave differently than non-fully active documents, and the guidelines here might not be applicable to them, as it is written only for handling non-fully active (BFCached) documents.
In many cases, anything that happens while the document is not fully active should be treated as if it never happened. If it makes more sense to "update" a document to ensure it does not hold stale information after it becomes fully active again, consider the § 2.1.2 Listen for changes to fully active status pattern below.
watchPosition()
should not send updates if the document is no longer fully active.
They also should not queue those updates to arrive later.
Instead, they should only resume sending updates when the document becomes active again,
possibly sending one update with the latest information then. Note: If the actions are already protected by certain checks that can only be satisfied if the document is fully active, such as checking if the top-level browsing context has system focus, fully active checks might not be needed. However, be careful of certain checks like transient user activation, which can be true even if a document is not fully active. See also the § 2.1.5 Be aware that per-document state/data might persist after navigation section.
2.1.2. Listen for changes to fully active status
When a document goes from fully active to non-fully active, it should be treated similarly to the way discarded documents are treated. The document must not retain exclusive access to shared resources and must ensure that no new requests are issued and that connections that allow for new incoming requests are terminated. When a document goes from non-fully active to fully active again, it can restore connections if appropriate.
To listen to changes from fully active to non-fully active, add a step in unloading document cleanup steps. Meanwhile, to listen to changes from non-fully active to fully active, add a step to reactivate a document.
While web authors can manually do cleanup (e.g. release the resources, sever connections)
from within the pagehide
event and restore them from the pageshow
event themselves,
doing this automatically from the API design allows the document to be kept alive after navigation by default,
and is more likely to lead to well-functioning web applications.
Note: this might not be appropriate for all types of resources, e.g. if an exclusive lock is held, we cannot just release it and reacquire when fully active since another page could then take that lock. If there is an API to signal to the page that this has happened, it may be acceptable but beware that if the only time this happens is with BFCache, then it’s likely many pages are not prepared for it. If it is not possible to support BFCache, follow the § 2.1.4 Discard non-fully active documents for situations that can’t be supported pattern described below.
Additionally, when a document becomes fully active again, it can be useful to update it with the current state of the world, if anything has changed while it is in the non-fully active state. However, care needs to be taken with events that occurred while in the BFCache. When not fully active, for some cases, all events should be dropped, in some the latest state should be delivered in a single event, in others it may be appropriate to queue events or deliver a combined event. The correct approach is case by case and should consider privacy, correctness, performance and ergonomics.
Note: Making sure the latest state is sent to a document that becomes fully active again is especially important when retrofitting existing APIs. This is because current users of these APIs expect to always have the latest information. Dropping state updates can leave the document with stale information, which can lead to unexpected and hard-to-detect breakage of existing sites.
gamepadconnected
event
can be sent to a document that becomes fully active again
if a gamepad is connected while the document is not fully active.
If the gamepad was repeatedly connected and disconnected,
only the final connected event should be delivered.
(This is not specified yet, see issue) 2.1.3. Omit non-fully active documents from APIs that span multiple documents
Non-fully active documents should not be observable, so APIs should treat them as if they no longer exist. They should not be visible to the "outside world" through document-spanning APIs (e.g.clients.matchAll()
, window.opener
).
Note: This should be rare since cross-document-spanning APIs are themselves relatively rare.
clients.matchAll()
currently does not distinguish between fully active and non-fully active clients,
but correct implementations should only return fully active clients.
(See issue) 2.1.4. Discard non-fully active documents for situations that can’t be supported
If supporting non-fully active documents is not possible for certain cases, explicitly specify it by discarding the document| if the situation happens after the user navigated away, or setting the document’s salvageable) bit to false if the situation happens before or during the navigation away from the document, to cause it to be automatically discarded after navigation.Note: this should be rare and probably should only be used when retrofitting old APIs, as new APIs should always strive to work well with BFCache.
clients.claim()
should not wait for non-fully active clients,
instead it should cause the non-fully active client documents to be discarded.
(This is currently not specified, see issue) 2.1.5. Be aware that per-document state/data might persist after navigation
As a document might be reused even after navigation, be aware that tying something to a document’s lifetime also means reusing it after navigations. If this is not desirable, consider listening to changes to the fully active state and doing cleanup as necessary (see the § 2.1.2 Listen for changes to fully active status pattern above).2.2. Antipatterns
This is basically the reverse of what is mentioned in the design guidance. When writing specifications, do not do these:
-
Expect that things kept alive in the document (connections, etc) or that are otherwise tied to the document lifetime will be automatically destroyed/disconnected/etc on navigation along with document destruction. This is wrong because documents might be kept alive in the BFCache after navigation, and can potentially get restored later. See these guides on how to handle this:
-
Expect that if a document is alive, it is also perceived as alive by the user, and thus can be treated like any other document. This is wrong because documents that are BFCached are “alive”, but they’re actually gone from the perspective of the users (as they have navigated away), and thus shouldn’t be treated the same way as other documents. See these guides on how to handle this:
-
Expect that a document is only “shown”/navigated to once. This is wrong because documents can potentially get restored on future history navigations, and thus the user can navigate to and reuse the same document multiple times with multiple navigations. See these guides on how to handle this: