Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Supporting BFCached Documents" standalone guide #1

Merged
merged 3 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# bfcache-guide
# Supporting Back/Forward Cached Documents

This [document](https://w3ctag.github.io/bfcache-guide/) 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.
250 changes: 250 additions & 0 deletions index.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<pre class="metadata">
Title: Supporting BFCached Documents
Status: UD
Shortname: bfcache-guide
Repository: w3ctag/bfcache-guide
ED: https://w3ctag.github.io/bfcache-guide/
Level: None
Editor: Rakina Zata Amni, Google https://www.google.com/, rakina@google.com
Group: tag
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.
Default Biblio Status: current
</pre>

<h2 id="intro">Introduction</h2>

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-[=Document/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, <b>all features should have BFCache support by default</b>,
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-[=Document/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 [=Document/fully active=] again.

<h2 id="when-to-use">When should features care about BFCache?</h2>

If your API does things that fall into <b>any</b> 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-[=Document/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-[=Document/fully active=] (BFCached)
documents, following the guidelines below.
See also the [[#antipatterns]] section to avoid common antipatterns.

<h3 id="api-design-guide">API Design Guidance</h3>

<h4 id="gate-fully-active">Gate actions with [=Document/fully active=] checks</h4>

When performing actions that might update the state of a document,
be aware that the document might not be [=Document/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 [=Document/fully active=] document to be perceived as "non-existent" by users,
such as when the document is <a href="https://wicg.github.io/nav-speculation/prerendering.html">displaying prerendered content</a>.
These documents might behave differently than non-[=Document/fully active=] documents,
and the guidelines here might not be applicable to them,
as it is written only for handling non-[=Document/fully active=] (BFCached) documents.

In many cases,
anything that happens while the document is not [=Document/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 [=Document/fully active=] again, consider the [[#listen-fully-active]] pattern below.
<div class="example">
APIs that periodically send information updates,
such as Geolocation API's {{Geolocation/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.
</div>

Note: If the actions are already protected by certain checks that can only be satisfied if the document is [=Document/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 [[#per-document-state]] section.

<h4 id="listen-fully-active">Listen for changes to [=Document/fully active=] status</h4>

When a document goes from [=Document/fully active=] to non-[=Document/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-[=Document/fully active=] to [=Document/fully active=] again,
it can restore connections if appropriate.

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.

<div class="example">
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).
</div>
<div class="example">
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 [=Document/fully active=] again
(Screen Wake Lock API is already <a href="https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-full-activity">doing</a> the first part).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty poor example in that there's no explicit hook tying this to some algorithm that runs when the fully active state changes. Are there hooks for that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we don't have hooks for "fully active" state changes. Maybe we should add that? Do you know what's the best way to do that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we want something similar to HTML's "update the rendering" where specifications have an algorithm that HTML ends up invoking when it changes the state.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all valid points though not necessarily strong enough to hold back the forking of this document. My suggestion is to open separate issues against the newly forked BFCache document.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that a bit rude. I was asked for feedback here and only now you're telling me I should have been filing issues instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @atanassov, I think the merge of this PR wasn't urgently needed. The HTML file of the bikeshed file is already part of the gh-pages branch so the document was already accessible from https://w3ctag.github.io/bfcache-guide/. I had intended to merge the reviewed version of the document to that branch after all the issues are resolved and this got LGTMs from other browsers.

@annevk, I'm very sorry for the delay in addressing the reviews here and the sudden merge, I assure you that I will address the issues and update the guide. I have filed an issue for the "in parallel" one, and another issue for the "fully active" state change listener. Thanks a lot for reviewing this PR and all the help!!

</div>

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 [=Document/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 [[#discard]] pattern described below.

Additionally, when a document becomes [=Document/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-[=Document/fully active=] state.
However, care needs to be taken with events that occurred while in the BFCache.
When not [=Document/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
[=Document/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.

<div class="example">
The {{gamepadconnected}} event
can be sent to a document that becomes [=Document/fully active=] again
if a gamepad is connected while the document is not [=Document/fully active=].
If the gamepad was repeatedly connected and disconnected,
only the final connected event should be delivered.
(This is not specified yet, see <a href="https://github.com/w3c/gamepad/issues/149">issue</a>)
</div>
<div class="example">
For geolocation or other physical sensors,
no information about what happened while not [=Document/fully active=] should be delivered.
The events should simply resume from when the document became [=Document/fully active=].
However, these APIs should check the state when the document becomes [=Document/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.
</div>
<div class="example">
For network connections or streams,
the data received while not [=Document/fully active=] should be delivered only
when the document becomes [=Document/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.
</div>

<h4 id="omit-non-fully-active">Omit non-[=Document/fully active=] documents from APIs that span multiple documents</h4>
Non-[=Document/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()|clients.matchAll()}}, {{Window/opener|window.opener}}).

Note: This should be rare since cross-document-spanning APIs are themselves relatively rare.

<div class="example">
{{BroadcastChannel}} <a href="https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts:fully-active">checks</a> for [=Document/fully active=] before sending messages to other browsing contexts.
</div>
<div class="example">
{{Clients/matchAll()|clients.matchAll()}}
currently does not distinguish between [=Document/fully active=]
and non-[=Document/fully active=] clients,
but correct implementations should only return [=Document/fully active=] clients.
(See <a href="https://github.com/w3c/ServiceWorker/issues/1594">issue</a>)
</div>

<h4 id="discard">Discard non-[=Document/fully active=] documents for situations that can't be supported</h4>
If supporting non-[=Document/fully active=] documents is not possible for certain cases,
explicitly specify it by [=discard a document|discarding the document|=] if the situation happens after the user navigated away,
or setting the document's <a href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#concept-document-salvageable">salvageable</a>)
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.

<div class="example">
WebSockets <a href="https://html.spec.whatwg.org/#unloading-documents:concept-document-salvageable-7">sets the salvageable bit to false</a> during unload.
</div>
<div class="example">
Calling {{Clients/claim()|clients.claim()}}
should not wait for non-[=Document/fully active=] clients,
instead it should cause the non-[=Document/fully active=] client documents to be discarded.
(This is currently not specified, see <a href="https://github.com/w3c/ServiceWorker/issues/1594">issue</a>)
</div>

<h4 id="per-document-state">Be aware that per-document state/data might persist after navigation</h4>
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 [=Document/fully active=] state
and doing cleanup as necessary (see the [[#listen-fully-active]] pattern above).
<div class=example>
[=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 <a href="https://github.com/whatwg/html/issues/6588#issuecomment-1157244943">discussion</a>
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.
</div>

<h3 id="antipatterns">Antipatterns</h3>

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

* 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:
* [[#gate-fully-active]]
* [[#listen-fully-active]]
* 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:
* [[#omit-non-fully-active]]
* [[#gate-fully-active]]
* 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:
* [[#listen-fully-active]]
* [[#per-document-state]]

Loading