Prerendering Revamped

A Collection of Interesting Ideas,

Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Google)

Abstract

This document contains a collection of specification patches for well-specified prerendering.

The following section would be added as a new sub-section of [HTML]'s Link types section. This should replace the prerender documentation in [RESOURCE-HINTS]

The prerender keyword may be used with link elements. This keyword creates an external resource link. This keyword is body-ok.

The appropriate times to fetch and process the linked resource for such a link are:

The fetch and process the linked resource algorithm for prerender links, given a link element el, is as follows:
  1. If el’s href attribute’s value is the empty string, then return.

  2. Parse a URL given el’s href attribute, relative to el’s node document. If that fails, then return. Otherwise, let url be the resulting URL record.

  3. If url’s scheme is not an HTTP(S) scheme, then return.

  4. Let referrerPolicy be the current state of el’s referrerpolicy attribute.

  5. If url’s origin is not same origin with el’s node document's origin, then:

    1. If the list of sufficiently-strict speculative navigation referrer policies does not contain referrerPolicy, then return.

  6. Create a prerendering browsing context with url, referrerPolicy, and el’s node document.

A user agent must not delay the load event of the link element’s node document for this link type.

Note that this link type does not fire error or load events, unlike many other link types that create external resource links.

The list of sufficiently-strict speculative navigation referrer policies is a list containing the following subset of referrer policies: the empty string, "strict-origin-when-cross-origin", "strict-origin", "same-origin", "no-referrer".

2. Speculation rules

It is anticipated that prerendering will be triggered using speculation rules.

3. Prerendering browsing context infrastructure

3.1. Extensions to the Document interface

We’d modify [HTML]'s centralized definition of Document as follows:

partial interface Document {
    readonly attribute boolean prerendering;

    // Under "special event handler IDL attributes that only apply to Document objects"
    attribute EventHandler onprerenderingchange;
};

The onprerenderingchange attribute is an event handler IDL attribute corresponding to the prerenderingchange event handler event type. (We would update the corresponding table in [HTML], which currently only contains onreadystatechange.)

As is customary for [HTML], the definition of prerendering would be located in another section of the spec; we’d place it in the new section introduced below:

3.2. Prerendering browsing contexts

The following section would be added as a new sub-section of [HTML]'s Browsing contexts section.

Every browsing context has a loading mode, which is one of the following:

"default"

No special considerations are applied to content loaded in this browsing context

"prerender"

This browsing context is displaying prerendered content

"uncredentialed-prerender"

This browsing context is displaying prerendered content, and furthermore that content cannot make credentialed fetches

By default, a browsing context's loading mode is "default". A browsing context whose loading mode is either "prerender" or "uncredentialed-prerender" is known as a prerendering browsing context.

document . prerendering
Returns true if the page is being presented in a non-interactive "prerendering-like" context. In the future, this would include a visible document in a <portal> element, both when loaded into it or via predecessor adoption.

The prerendering getter steps are to return true if this has a non-null browsing context that is a prerendering browsing context; otherwise, false.


A prerendering browsing context is empty if the only entry in its session history is the initial about:blank Document.

Every Document has a prerendering browsing contexts map, which is an ordered map of (URL, referrer policy) tuples to prerendering browsing contexts. This is used to fulfill navigations to a given URL by instead activating the corresponding prerendering browsing context.

Every Document has a post-prerendering activation steps list, which is a list where each item is a series of algorithm steps. For convenience, we define the post-prerendering activation steps list for any platform object platformObject as:

If platformObject is a node
  1. Return platformObject’s node document's post-prerendering activation steps list.

Otherwise
  1. Assert: platformObject’s relevant global object is a Window object.

  1. Return platformObject’s relevant global object's associated Document's post-prerendering activation steps list.

Every Document has an activation start time, which is initially a DOMHighResTimeStamp with a time value of zero.

This is used to keep the time immediately after prompting to unload the active document of the previous browsing context.

To create a prerendering browsing context given a URL startingURL, a referrer policy referrerPolicy, and a Document referrerDoc:
  1. Assert: startingURL’s scheme is an HTTP(S) scheme.

  2. If referrerDoc’s prerendering browsing contexts map[(startingURL, referrerPolicy)] exists, then return.

  3. Let bc be the result of creating a new top-level browsing context.

  4. Set bc’s loading mode to "prerender".

  5. If startingURL’s origin is not same origin with referrerDoc’s origin, run these steps:

    1. Set bc’s loading mode to "uncredentialed-prerender".

    2. Assert: The list of sufficiently-strict speculative navigation referrer policies contains referrerPolicy.

  6. Set referrerDoc’s prerendering browsing contexts map[startingURL] to bc.

  7. Let request be a new request whose URL is startingURL and referrer policy is referrerPolicy.

  8. Navigate bc to request with the source browsing context set to referrerDoc’s browsing context.

To activate a prerendering browsing context successorBC in place of a top-level browsing context predecessorBC given a history handling behavior historyHandling:
  1. Assert: historyHandling is either "default" or "replace".

  2. Assert: successorBC is not empty.

  3. Assert: predecessorBC is a top-level browsing context.

  4. Cancel any preexisting but not yet mature attempts to navigate predecessorBC, including canceling any instances of the fetch algorithm started by those attempts. If one of those attempts has already created and initialized a new Document object, abort that Document also.

  5. Prompt to unload the active document of predecessorBC. If the user refused to allow the document to be unloaded, then return.

  6. Let unsafe time be the unsafe shared current time.

  7. Abort the active document of predecessorBC.

  8. TODO prepend the existing session history of predecessorBC into successorBC? Or, probably better, use the new "browsing session" concept to bridge them? Be sure to respect historyHandling.

  9. In parallel:

    1. Update the user agent’s user interface to replace predecessorBC with successorBC, e.g., by updating the tab/window contents and the browser chrome.

    2. Let inclusiveDescendants be « successorBC » extended with successorBC’s active document's list of the descendant browsing contexts.

    3. For each bc of inclusiveDescendants, queue a global task on the networking task source, given bc’s active window, to perform the following steps:

      1. Set bc’s loading mode to "default".

      2. Let doc be bc’s active document.

      3. If doc’s origin is the same as predecessorBC’s active document's origin, then set doc’s activation start time to the relative high resolution time for unsafe time and doc.

      4. Fire an event named prerenderingchange at doc.

      5. For each steps in doc’s post-prerendering activation steps list:

        1. Run steps.

          These steps might return something, like a Promise. That is just an artifact of how the spec is modified; such return values can always be ignored.

        2. Assert: running steps did not throw an exception.

      The order here is observable for browsing contexts that share an event loop, but not for those in separate event loops.

3.3. Modifications to creating browsing contexts

To ensure that any nested browsing contexts inherit their parent’s loading mode, modify create a new nested browsing context by appending the following step:
  1. Set browsingContext’s loading mode to element’s node document's browsing context's loading mode.

We can activate a prerender given a browsing context browsingContext, a history handling behavior historyHandling, a string navigationType, and a request request, if the following steps return true:
  1. Return true if all of the following are true:

  2. Otherwise, return false.

Patch the navigate algorithm to allow the activation of a prerendering browsing context in place of a normal navigation as follows:

In navigate, append the following steps after the fragment navigation handling (currently step 6):
  1. If resource is a request and we can activate a prerender given browsingContext, historyHandling, navigationType, and resource, then:

    1. Activate successorBC in place of browsingContext given historyHandling.

    2. Return.

Navigation redirects can also activate prerendering browsing contexts. This is defined in the § 4.2 Redirect handling section.

4.2. Redirect handling

This section contains two types of changes to the navigation redirect handling portion of the process a navigate fetch algorithm:

Patch the process a navigate fetch algorithm like so:

In process a navigate fetch, append the following steps after the first sub-step under "While true:" in order to handle redirects correctly:
  1. If we can activate a prerender given browsingContext, historyHandling, navigationType, and request, then:

    1. Activate browsingContext in place of sourceBrowsingContext given historyHandling.

    2. Return.

  2. If browsingContext is a prerendering browsing context and currentURL’s origin is not same origin with incumbentNavigationOrigin, then:

    1. Set browsingContext’s loading mode to "uncredentialed-prerender".

    2. If the list of sufficiently-strict speculative navigation referrer policies does not contain request’s referrer policy, then:

      1. Assert: response is not null.

      2. Assert: response’s location URL is a URL whose scheme is a HTTP(S) scheme.

      3. Set response to a network error and break.

4.3. Maintaining a trivial session history

Patch the navigate algorithm to ensure the session history of a prerendering browsing context stays trivial by prepending the following step before all others:
  1. If browsingContext is a prerendering browsing context, then:

    1. Assert: historyHandling is not "entry update", since prerendering browsing contexts have trivial session histories and thus will never end up traversing back to an entry with null document.

    2. If historyHandling is "default", then set historyHandling to "replace".

Patch the URL and history update steps by adding the following step after step 1:
  1. If browsingContext is a prerendering browsing context, then set isPush to false.

4.4. Preventing forbidden navigations

Patch the navigate algorithm to prevent certain navigations in a prerendering browsing context as follows:

Portals might need an extra hook to close the portal in these cases. Or should we reconsider and just do nothing for portals too? That might be more elegant. I think it just requires portals to not be so zealous about clearing the host element/browsing context link, which isn’t observable anyway?

In process a navigate response, append the following after the step which establishes the value of failure, but before the step which uses it to display an error page:
  1. If browsingContext is a prerendering browsing context, and any of the following hold:

    • failure is true;

    • navigationParams’s request is null;

    • navigationParams’s request's current URL's scheme is not a HTTP(S) scheme;

    • response has a `Content-Disposition` header specifying the attachment disposition type; or

    • response’s status is 204 or 205,

    then:

    1. Run the environment discarding steps for navigationParams’s reserved environment.

    2. Return.

In process a navigate URL scheme, insert the following step before the step which displays inline content:
  1. Otherwise, if browsingContext is a prerendering browsing context, then return.

4.5. Cleanup upon discarding a Document

Modify the discard algorithm for Documents by appending the following step:

  1. Empty document’s post-prerendering activation steps list.

5. Interaction with other specifications and concepts

5.1. Interaction with Page Visibility

Documents in prerendering browsing contexts are hidden.

What about portals? Portals might not be hidden, and portals are envisioned to be a type of prerendering browsing context.

5.2. Interaction with system focus

Prerendering browsing contexts never have system focus.

5.3. Extensions to the PerformanceNavigationTiming interface

Extend the PerformanceNavigationTiming interface as follows:

partial interface PerformanceNavigationTiming {
    readonly attribute DOMHighResTimeStamp activationStart;
};

The activationStart getter steps are:

  1. Return a DOMHighResTimeStamp with a time value equal to the current document's activation start time.

This value is 0 in cross-origin prerenders.

6. Preventing nonsensical behaviors

Some behaviors might make sense in most top-level browsing contexts, but do not make sense in prerendering browsing contexts. This section enumerates specification patches to enforce such restrictions.

6.1. APIs for creating and navigating browsing contexts by name

Modify the definition of script-closable to prevent window closing while in a prerendering browsing context:

A browsing context is script-closable if either of the following is true:

7. Preventing intrusive behaviors

Various behaviors are disallowed in prerendering browsing contexts because they would be intrusive to the user, since the prerendered content is not being actively interacted with.

7.1. Downloading resources

Modify the download the hyperlink algorithm to ensure that downloads inside prerendering browsing contexts are delayed until activation, by inserting the following before the step which goes in parallel:

  1. If subject’s node document's browsing context is a prerendering browsing context, then append the following step to subject’s post-prerendering activation steps list and return.

7.2. User prompts

Modify the cannot show simple dialogs algorithm, given a Window window, by prepending the following step:
  1. If window’s browsing context is a prerendering browsing context, then return true.

Modify the print() method steps by prepending the following step:
  1. If this's browsing context is a prerendering browsing context, then return.

7.3. Delaying async API results

Many specifications need to be patched so that, if a given algorithm invoked in a prerendering browsing context, most of its work is deferred until the browsing context is activated. This is tricky to do uniformly, as many of these specifications do not have great hygeine around using the event loop. Nevertheless, the following sections give our best attempt.

7.3.1. The [DelayWhilePrerendering] extended attribute

To abstract away some of the boilerplate involved in delaying the action of asynchronous methods until activation, we introduce the [DelayWhilePrerendering] Web IDL extended attribute. It indicates that when a given method is called in a prerendering browsing context, it will immediately return a pending promise and do nothing else. Only upon activation will the usual method steps take place, with their result being used to resolve or reject the previously-returned promise.

The [DelayWhilePrerendering] extended attribute must take no arguments, and must only appear on a regular or static operation whose return type is a promise type and whose exposure set contains only Window.

The method steps for any operation annotated with the [DelayWhilePrerendering] extended attribute are replaced with the following:
  1. Let realm be the current Realm.

  2. If the operation in question is a regular operation, then set realm to the relevant Realm of this.

  3. If realm’s global object's browsing context is a prerendering browsing context, then:

    1. Let promise be a new promise, created in realm.

    2. Append the following steps to this's post-prerendering activation steps list:

      1. Let result be the result of running the originally-specified steps for this operation, with the same this and arguments.

      2. Resolve promise with result.

    3. Return promise.

  4. Otherwise, return the result of running the originally-specified steps for this operation, with the same this and arguments.

7.3.2. Geolocation API

Modify the getCurrentPosition() method steps by prepending the following step:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then append the following steps to this's post-prerendering activation steps list and return.

Modify the watchPosition() method steps by prepending the following step:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then:

    1. Let watchProcessId be a new unique watch process ID, and note it as a post-prerendering activation geolocation watch process ID.

    2. Append the following steps to this's post-prerendering activation steps list, with the modification that the watch process generated must use watchProcessId as its ID, and return watchProcessId.

Modify the clearWatch(watchId) method steps by prepending the following step:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then:

    1. If watchId is a post-prerendering activation geolocation watch process ID, then remove its corresponding steps from this's post-prerendering activation steps list.

    2. Return.

7.3.3. Web Serial API

Add [DelayWhilePrerendering] to requestPort().

TODO: the below could probably be done by generalizing [DelayWhilePrerendering] to use owner document while in dedicated workers.

Modify the getPorts() method steps by inserting the following steps after the initial creation of promise:
  1. Let document be this's relevant global object's associated Document, if this's relevant global object is a Window, or this's relevant global object's owner document, if this's relevant global object is a DedicatedWorkerGlobalScope.

  2. If document is null, then return a promise rejected with a "SecurityError" DOMException.

  3. If document’s browsing context is a prerendering browsing context, then append the following steps to document’s post-prerendering activation steps list and return promise.

7.3.4. Notifications API

Add [DelayWhilePrerendering] to requestPermission().

Modify the Notification() constructor steps by replacing the step which goes in parallel with the following:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then append these steps to this's post-prerendering activation steps list. Otherwise, run these steps in parallel.

Modify the permission static getter steps by replacing them with the following:
  1. If the current global object's browsing context is a prerendering browsing context, then return "default".

    This allows implementations to avoid looking up the actual permission state, which might not be synchronously accessible especially in the case of prerendering browsing contexts. Web developers can then call Notification.requestPermission(), which per the above modifications will only actually do anything after activation. At that time we might discover that the permission is "granted" or "denied", so the browser might not actually ask the user like would normally be the case with "default". But that’s OK: it’s not observable to web developer code.

  2. Otherwise, return the current settings object's origin's permission.

TODO: what about the service worker API? Depends on what we’re doing for service workers in prerendering BCs...

7.3.5. Web MIDI API

Add [DelayWhilePrerendering] to requestMIDIAccess().

7.3.6. Idle Detection API

Add [DelayWhilePrerendering] to start().

The other interesting method, IdleDetector.requestPermission(), is gated on transient activation. However, even if permission was previously granted for the origin in question, we delay starting any idle detectors while prerendering.

7.3.7. Generic Sensor API

Modify the start() method steps by inserting the following steps after the state is set to "activating":
  1. If this's relevant global object's browsing context is a prerendering browsing context, then append the following steps to this's post-prerendering activation steps list and return.

  2. If this.[[state]] is "idle", then return.

    This ensures that if this portion of the algorithm was delayed due to prerendering, and in the meantime stop() was called, we do nothing upon activating the prerender.

  3. Assert: this.[[state]] is "activating".

7.3.8. Web NFC

Add [DelayWhilePrerendering] to write() and scan().

7.3.9. Battery Status API

Modify the getBattery() method steps by prepending the following step:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then append the following steps to this's post-prerendering activation steps list and return this's battery promise.

7.3.10. Screen Orientation API

Modify the apply an orientation lock algorithm steps by overwriting the first few steps, before it goes in parallel, as follows:
  1. Let promise be a new promise.

  2. If this's relevant global object's browsing context is a prerendering browsing context, then append the following steps to this's post-prerendering activation steps list and return promise.

  3. If the user agent does not support locking the screen orientation, then reject promise with a "NotSupportedError" DOMException and return promise.

  4. If the document's active sandboxing flag set has the sandboxed orientation lock browsing context flag set, or the user agent doesn’t meet the pre-lock conditions to perform an orientation change, then reject promise with a "SecurityError" DOMException and return promise.

  5. Set the document's [[orientationPendingPromise]] to promise.

Add [DelayWhilePrerendering] to unlock().

This latter modification is necessary to ensure that code that calls screen.orientation.lock() followed by screen.orientation.unlock() produces the expected results.

7.3.11. Gamepad

Modify the getGamepads() method steps by prepending the following step:
  1. If this's relevant global object's browsing context is a prerendering browsing context, then return an empty sequence.

Modify the discussion of the gamepadconnected and gamepaddisconnected events to specify that the user agent must not dispatch these events if the Window object’s browsing context is a prerendering browsing context.
Modify the gamepadconnected section to indicate that every Document document’s post-prerendering activation steps list should gain the following steps:
  1. If document is allowed to use the "gamepad" feature, and document’s relevant settings object is a secure context, and any gamepads are connected, then for each connected gamepad, fire an event named gamepadconnected at document’s relevant global object using GamepadEvent, with its gamepad attribute initialized to a new Gamepad object representing the connected gamepad.

7.3.12. Encrypted Media Extensions

Add [DelayWhilePrerendering] to requestMediaKeySystemAccess().

7.4. Implicitly restricted APIs

Some APIs do not need modifications because they will automatically fail or no-op without a property that a prerendering browsing context, its active window, or its active document will never have. These properties include:

We list known APIs here for completeness, to show which API surfaces we’ve audited.

APIs that require transient activation or sticky activation:

APIs that require system focus:

APIs that require the visible visibility state:

More complicated cases:

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CLIPBOARD-APIS]
Gary Kacmarcik; Grisha Lyukshin. Clipboard API and events. 6 August 2021. WD. URL: https://www.w3.org/TR/clipboard-apis/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ENCRYPTED-MEDIA]
David Dorwin; et al. Encrypted Media Extensions. 18 September 2017. REC. URL: https://www.w3.org/TR/encrypted-media/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[FILE-SYSTEM-ACCESS]
File System Access. cg-draft. URL: https://wicg.github.io/file-system-access/
[FULLSCREEN]
Philip Jägenstedt. Fullscreen API Standard. Living Standard. URL: https://fullscreen.spec.whatwg.org/
[GAMEPAD]
Steve Agoston; et al. Gamepad. 5 August 2021. WD. URL: https://www.w3.org/TR/gamepad/
[GENERIC-SENSOR]
Rick Waldron; et al. Generic Sensor API. 29 July 2021. CR. URL: https://www.w3.org/TR/generic-sensor/
[GEOLOCATION]
Marcos Caceres. Geolocation API. 12 October 2021. WD. URL: https://www.w3.org/TR/geolocation/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. 21 November 2019. REC. URL: https://www.w3.org/TR/hr-time-2/
[HR-TIME-3]
Yoav Weiss; et al. High Resolution Time. 12 October 2021. WD. URL: https://www.w3.org/TR/hr-time-3/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[IDLE-DETECTION]
Reilly Grant. Idle Detection API. CG-DRAFT. URL: https://wicg.github.io/idle-detection/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[NAVIGATION-TIMING]
Zhiheng Wang. Navigation Timing. 17 December 2012. REC. URL: https://www.w3.org/TR/navigation-timing/
[NAVIGATION-TIMING-2]
Yoav Weiss; et al. Navigation Timing Level 2. 27 September 2021. WD. URL: https://www.w3.org/TR/navigation-timing-2/
[NOTIFICATIONS]
Anne van Kesteren. Notifications API Standard. Living Standard. URL: https://notifications.spec.whatwg.org/
[PAGE-VISIBILITY]
Jatinder Mann; Arvind Jain. Page Visibility (Second Edition). 29 October 2013. REC. URL: https://www.w3.org/TR/page-visibility/
[PAYMENT-REQUEST]
Marcos Caceres; Rouslan Solomakhin; Ian Jacobs. Payment Request API. 30 September 2021. PR. URL: https://www.w3.org/TR/payment-request/
[PICTURE-IN-PICTURE]
Francois Beaufort; Mounir Lamouri. Picture-in-Picture. 30 August 2021. WD. URL: https://www.w3.org/TR/picture-in-picture/
[POINTERLOCK]
Vincent Scheib. Pointer Lock. 27 October 2016. REC. URL: https://www.w3.org/TR/pointerlock/
[PRESENTATION-API]
Mark Foltz; Dominik Röttsches. Presentation API. 5 November 2020. CR. URL: https://www.w3.org/TR/presentation-api/
[REFERRER-POLICY]
Jochen Eisinger; Emily Stark. Referrer Policy. 26 January 2017. CR. URL: https://www.w3.org/TR/referrer-policy/
[SCREEN-ORIENTATION]
Mounir Lamouri; Marcos Caceres; Johanna Herman. The Screen Orientation API. 6 September 2021. WD. URL: https://www.w3.org/TR/screen-orientation/
[SCREEN-WAKE-LOCK]
Kenneth Christiansen; et al. Screen Wake Lock API. 30 September 2021. WD. URL: https://www.w3.org/TR/screen-wake-lock/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEB-NFC]
Web NFC API. ED. URL: https://w3c.github.io/web-nfc/
[WEB-SHARE]
Matt Giuca; Eric Willigers; Marcos Caceres. Web Share API. 13 October 2021. WD. URL: https://www.w3.org/TR/web-share/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[WEBMIDI]
Chris Wilson; Jussi Kalliokoski. Web MIDI API. 17 March 2015. WD. URL: https://www.w3.org/TR/webmidi/

Informative References

[RESOURCE-HINTS]
Ilya Grigorik. Resource Hints. 5 October 2020. WD. URL: https://www.w3.org/TR/resource-hints/

IDL Index

partial interface Document {
    readonly attribute boolean prerendering;

    // Under "special event handler IDL attributes that only apply to Document objects"
    attribute EventHandler onprerenderingchange;
};

partial interface PerformanceNavigationTiming {
    readonly attribute DOMHighResTimeStamp activationStart;
};

Issues Index

Portals might need an extra hook to close the portal in these cases. Or should we reconsider and just do nothing for portals too? That might be more elegant. I think it just requires portals to not be so zealous about clearing the host element/browsing context link, which isn’t observable anyway?
What about portals? Portals might not be hidden, and portals are envisioned to be a type of prerendering browsing context.