Skip to content
59 changes: 37 additions & 22 deletions docs/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -3288,29 +3288,44 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/
1. Set |routedResponse|'s [=service worker timing info=]'s [=service worker timing info/worker final router source=] be set to |result|'s [=race result/used route=].
1. Return |routedResponse|.
1. Assert: |source| is "{{RouterSourceEnum/fetch-event}}"
1. If |request| is a [=navigation request=], |registration|'s [=navigation preload enabled flag=] is set, |request|'s [=request/method=] is \`<code>GET</code>\`, |registration|'s [=active worker=]'s [=set of event types to handle=] [=set/contains=] <code>fetch</code>, and |registration|'s [=active worker=]'s [=all fetch listeners are empty flag=] is not set then:

Note: If the above is true except |registration|'s [=active worker=]'s <a>set of event types to handle</a> **does not** contain <code>fetch</code>, then the user agent may wish to show a console warning, as the developer's intent isn't clear.

1. Let |preloadRequest| be the result of [=request/cloning=] the request |request|.
1. Let |preloadRequestHeaders| be |preloadRequest|'s [=request/header list=].
1. Let |preloadResponseObject| be a new {{Response}} object associated with a new {{Headers}} object whose [=guard=] is "`immutable`".
1. [=header list/Append=] to |preloadRequestHeaders| a new [=header=] whose [=header/name=] is \`<code>Service-Worker-Navigation-Preload</code>\` and [=header/value=] is |registration|'s [=navigation preload header value=].
1. Set |preloadRequest|'s [=service-workers mode=] to "`none`".
1. Let |preloadFetchController| be null.
1. Run the following substeps [=in parallel=], but [=abort when=] |fetchController|'s [=fetch controller/state=] is "<code>terminated</code>" or "<code>aborted</code>":
1. Set |preloadFetchController| to the result of [=Fetch|fetching=] |preloadRequest|.

To [=fetch/processResponse=] for |navigationPreloadResponse|, run these substeps:

1. If |navigationPreloadResponse|'s [=response/type=] is "`error`", reject |preloadResponse| with a `TypeError` and terminate these substeps.
1. Associate |preloadResponseObject| with |navigationPreloadResponse|.
1. Resolve |preloadResponse| with |preloadResponseObject|.
1. [=If aborted=], then:
1. Let |deserializedError| be the result of [=deserialize a serialized abort reason=] given null and |workerRealm|.
1. [=fetch controller/Abort=] |preloadFetchController| with |deserializedError|.
1. Let |responseForAutoPreload| be null.
1. If |request| is a [=navigation request=], |request|'s [=request/method=] is \`<code>GET</code>\`, |registration|'s [=active worker=]'s [=set of event types to handle=] [=set/contains=] <code>fetch</code>, and |registration|'s [=active worker=]'s [=all fetch listeners are empty flag=] is not set then:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, you explicitly skip subresource auto preload. just for note to myself.
https://github.com/WICG/service-worker-auto-preload?tab=readme-ov-file#auto-preload-for-subresources

1. If |registration|'s [=navigation preload enabled flag=] is set then:

Note: If the above is true except |registration|'s [=active worker=]'s <a>set of event types to handle</a> **does not** contain <code>fetch</code>, then the user agent may wish to show a console warning, as the developer's intent isn't clear.

1. Let |preloadRequest| be the result of [=request/cloning=] the request |request|.
1. Let |preloadRequestHeaders| be |preloadRequest|'s [=request/header list=].
1. Let |preloadResponseObject| be a new {{Response}} object associated with a new {{Headers}} object whose [=guard=] is "`immutable`".
1. [=header list/Append=] to |preloadRequestHeaders| a new [=header=] whose [=header/name=] is \`<code>Service-Worker-Navigation-Preload</code>\` and [=header/value=] is |registration|'s [=navigation preload header value=].
1. Set |preloadRequest|'s [=service-workers mode=] to "`none`".
1. Let |preloadFetchController| be null.
1. Run the following substeps [=in parallel=], but [=abort when=] |fetchController|'s [=fetch controller/state=] is "<code>terminated</code>" or "<code>aborted</code>":
1. Set |preloadFetchController| to the result of [=Fetch|fetching=] |preloadRequest|.

To [=fetch/processResponse=] for |navigationPreloadResponse|, run these substeps:

1. If |navigationPreloadResponse|'s [=response/type=] is "`error`", reject |preloadResponse| with a `TypeError` and terminate these substeps.
1. Associate |preloadResponseObject| with |navigationPreloadResponse|.
1. Resolve |preloadResponse| with |preloadResponseObject|.
1. [=If aborted=], then:
1. Let |deserializedError| be the result of [=deserialize a serialized abort reason=] given null and |workerRealm|.
1. [=fetch controller/Abort=] |preloadFetchController| with |deserializedError|.
1. Else if |timingInfo|'s [=service worker timing info/worker matched router source=] is not "{{RouterSourceEnum/fetch-event}}", a user agent may run the following substeps:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated not to introduce |source| here, use |timingInfo|'s [=service worker timing info/worker matched router source=] instead.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks.

Copy link

Choose a reason for hiding this comment

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

Why doing autopreload if the route is NOT fetch-event?
I would expect to only do autopreload if the route is actually fetch-event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We frame the autopreload is an optional optimization that the browser can apply at its choosing. So we want to use the static routing API as an opt-out mechanism. If we only invoke the autopreload only when the matched router source is fetch-event, that is more like an opt-in, not opt-out.

Copy link

Choose a reason for hiding this comment

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

Ah I see, I was mislead thinking that, at this point, the source could be other values like cache and so on, but it can only be null or fetch-event really.

Maybe, it would be clearer if we would check if worker matched router source is null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! That sounds reasonable. Added Assert: [=service worker timing info/worker matched router source=] is null..


Note: A user agent may speculatively dispatch a network request in parallel with creating a fetch event in order to minimize the bootstrap cost.
Copy link

Choose a reason for hiding this comment

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

Is there sufficient description on when to do this speculative load?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the explainer, we mention two possible scenarios that the browser may invoke the autopreload.
https://github.com/WICG/service-worker-auto-preload?tab=readme-ov-file#eligibility-criteria

I don't think we can fully specify when it's used since some conditions are not deterministic, but do you think we should say more here?

Copy link

Choose a reason for hiding this comment

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

That might be ok as is. If there are cases where all browsers plan to do autopreload, we could be more precise, maybe with a SHOULD for instance. Say if the service worker is not running?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Let's keep it as is for now.

Just to note: we're happy with rephrasing it with SHOULD.


1. Let |autoPreloadFetchController| be null.
1. Set |responseForAutoPreload| to be a [=race response=] whose [=race response/value=] is "<code>pending</code>".
1. Run the following substeps [=in parallel=], but [=abort when=] |fetchController|'s [=fetch controller/state=] is "<code>terminated</code>" or "<code>aborted</code>":
1. Set |autoPreloadFetchController| to the result of calling [=fetch=] given |request|, with [=fetch/processResponse=] set to the following steps given a [=/response=] |autoPreloadRequestResponse|:
1. If |autoPreloadRequestResponse|'s [=response/status=] is [=ok status=], set |responseForAutoPreload|'s [=race response/value=] to |autoPreloadRequestResponse|.
Copy link

Choose a reason for hiding this comment

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

Why only using the auto preload response if it is a 2XX response?
What happens if the auto preload response is not a 2XX (I haven't yet looked precisely at how the race response mechanism works)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, good catch!
I guess |autoPreloadRequestResponse| should be set to |responseForAutoPreload|'s value regardless of the response status. Otherwise, deadlock at Step 21-3 in https://w3c.github.io/ServiceWorker/#create-fetch-event-and-dispatch-algorithm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Updated to set the response to responseForAutoPreload regardless of the status.

1. [=If aborted=] and |autoPreloadFetchController| is not null, then:
1. [=fetch controller/Abort=] |autoPreloadFetchController|.
1. Set |responseForAutoPreload| to a [=race response=] whose [=race response/value=] is null.
1. Resolve |preloadResponse| with undefined.
1. Else, resolve |preloadResponse| with undefined.
1. Let |fetchResult| be the result of [=Create Fetch Event and Dispatch=] with |request|, |registration|, |useHighResPerformanceTimers|, |timingInfo|, |workerRealm|, |reservedClient|, |preloadResponse|, and null.
1. Let |fetchResult| be the result of [=Create Fetch Event and Dispatch=] with |request|, |registration|, |useHighResPerformanceTimers|, |timingInfo|, |workerRealm|, |reservedClient|, |preloadResponse|, and |responseForAutoPreload|.
1. If |timingInfo|'s [=service worker timing info/worker final router source=] is not an empty string:
1. Assert |timingInfo|'s [=service worker timing info/worker final router source=] is {{RouterSourceEnum/"network"}}.
1. If |fetchResult| is null, then return |timingInfo|.
Expand Down