Supporting BFCached Documents

Unofficial Proposal Draft,

More details about this document
This version:
https://w3ctag.github.io/bfcache-guide/
Issue Tracking:
GitHub
Editor:
(Google)

Abstract

This document gives guidance on how to write specifications that handle BFCached documents, where a document is kept alive (instead of getting destroyed) after navigation, and potentially gets reused on future navigations back to the document.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index.

This document was published by the W3C Technical Architecture Group (TAG) as an Unofficial Proposal Draft. Publication as an Unofficial Proposal Draft does not imply endorsement by W3C and its Members. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

Feedback and comments on this specification are welcome. Please file an issue in this document’s GitHub repository.

This document is governed by the 1 March 2019 W3C Process Document.

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:

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.

APIs that periodically send information updates, such as Geolocation API’s 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.

APIs that create live connections can pause/close the connection and possibly resume/reopen it later. It’s also possible to let the connection stay open to complete existing ongoing requests, and later update the document with the result when it gets restored, if appropriate (e.g. resource loads).
APIs that hold non-exclusive resources may be able to release the resource when the document becomes not fully active, and re-acquire them when it becomes fully active again (Screen Wake Lock API is already doing the first part).

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.

The 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)
For geolocation or other physical sensors, no information about what happened while not fully active should be delivered. The events should simply resume from when the document became fully active. However, these APIs should check the state when the document becomes fully active again, to determine if a status update should be sent (e.g. is the current location far away from the location when the document becomes not fully active?), to ensure the document has the latest information, as guaranteed by the API normally.
For network connections or streams, the data received while not fully active should be delivered only when the document becomes fully active again, but whereas a stream might have created many events with a small amount of data each, it could be delivered as smaller number of events with more data in each.

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.

BroadcastChannel checks for fully active before sending messages to other browsing contexts.
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.

WebSockets sets the salvageable bit to false during unload.
Calling 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).
Sticky activation is determined by the "last activation timestamp", which is tied to a document. This means after a user triggers activation once on a document, the document will have sticky activation forever, even after the user navigated away and back to it again. The discussion around this concluded that this is OK after comparing with other behaviors (e.g. focus), but every feature specification should think about this and decide what works best for the feature.

2.2. Antipatterns

This is basically the reverse of what is mentioned in the design guidance. When writing specifications, do not do these:

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by reference

References

Normative References

[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. URL: https://w3c.github.io/ServiceWorker/

Informative References

[GAMEPAD]
Steve Agoston; Matthew Reynolds. Gamepad. URL: https://w3c.github.io/gamepad/
[GEOLOCATION]
Marcos Caceres; Reilly Grant. Geolocation API. URL: https://w3c.github.io/geolocation-api/