diff --git a/docs/index.bs b/docs/index.bs
index ee26687f..62430603 100644
--- a/docs/index.bs
+++ b/docs/index.bs
@@ -175,7 +175,7 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/
A script resource has an associated policy container (a [=/policy container=]). It is initially a new policy container.
- A [=/service worker=] has an associated script resource map which is an ordered map where the keys are [=/URLs=] and the values are [=/responses=].
+ A [=/service worker=] has an associated script resource map which is an ordered map where the keys are [=/URLs=] and the values are tuples containing a [=/responses=] and either null, failure or a [=byte sequence=].
A [=/service worker=] has an associated set of used scripts (a [=ordered set|set=]) whose [=list/item=] is a [=/URL=]. It is initially a new [=ordered set|set=].
@@ -2262,29 +2262,32 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/
{{WorkerGlobalScope/importScripts(urls)}}
- When the importScripts(|urls|)
method is called on a {{ServiceWorkerGlobalScope}} object, the user agent *must* import scripts into worker global scope, given this {{ServiceWorkerGlobalScope}} object and |urls|, and with the following steps to [=fetching scripts/perform the fetch=] given the [=/request=] |request|:
+ When the importScripts(|urls|)
method is called on a {{ServiceWorkerGlobalScope}} object, the user agent *must* import scripts into worker global scope, given this {{ServiceWorkerGlobalScope}} object and |urls|, and with the following [=fetching scripts/perform the fetch hook=] given the [=/request=] |request|, |isTopLevel| and |processCustomFetchResponse|:
1. Let |serviceWorker| be |request|'s [=request/client=]'s [=environment settings object/global object=]'s [=ServiceWorkerGlobalScope/service worker=].
1. Let |map| be |serviceWorker|'s [=script resource map=].
1. Let |url| be |request|'s [=request/url=].
- 1. If |serviceWorker|'s [=state=] is not "`parsed`" or "`installing`":
- 1. Return |map|[|url|] if it [=map/exists=] and a [=network error=] otherwise.
1. If |map|[|url|] [=map/exists=]:
1. [=set/Append=] |url| to |serviceWorker|'s [=set of used scripts=].
- 1. Return |map|[|url|].
+ 1. Invoke |processCustomFetchResponse| with |map|[|url|][0] and |map|[|url|][1].
+ 1. Return.
+ 1. If |serviceWorker|'s [=state=] is not "`parsed`" or "`installing`":
+ 1. Invoke |processCustomFetchResponse| with a [=network error=] and null
+ 1. Return.
1. Let |registration| be |serviceWorker|'s [=containing service worker registration=].
1. Set |request|'s [=service-workers mode=] to "`none`".
1. Set |request|'s [=request/cache mode=] to "`no-cache`" if any of the following are true:
* |registration|'s [=service worker registration/update via cache mode=] is "`none`".
* The [=current global object=]'s [=force bypass cache for import scripts flag=] is set.
* |registration| is [=stale=].
- 1. Let |response| be the result of [=fetch|fetching=] |request|.
- 1. If |response|’s [=response/cache state=] is not "`local`", set |registration|’s [=service worker registration/last update check time=] to the current time.
- 1. If |response|'s [=unsafe response=] is a [=bad import script response=], then return a [=network error=].
- 1. [=map/Set=] |map|[|url|] to |response|.
- 1. [=set/Append=] |url| to |serviceWorker|'s [=set of used scripts=].
- 1. Set |serviceWorker|'s [=classic scripts imported flag=].
- 1. Return |response|.
+ 1. [=Fetch=] |request| with [=fetch/processResponseConsumeBody=] set to the following algorithm given [=/response=] |response| and null, failure or a [=byte sequence=] |bodyBytes|:
+ 1. If |response|’s [=response/cache state=] is not "`local`", set |registration|’s [=service worker registration/last update check time=] to the current time.
+ 1. If |bodyBytes| is null or failure, or |response|'s [=unsafe response=] is a [=bad import script response=], then:
+ 1. Invoke |processCustomFetchResponse| with a [=network error=] and null, and abort these steps.
+ 1. [=map/Set=] |map|[|url|] to (|response|, |bodyBytes|).
+ 1. [=set/Append=] |url| to |serviceWorker|'s [=set of used scripts=].
+ 1. Set |serviceWorker|'s [=classic scripts imported flag=].
+ 1. Invoke |processCustomFetchResponse| with |response| and |bodyBytes|.
@@ -2642,19 +2645,19 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/
1. Invoke Finish Job with |job| and abort these steps.
1. Let |referrerPolicy| be the empty string.
1. Let |hasUpdatedResources| be false.
- 1. Let |updatedResourceMap| be an [=ordered map=] where the [=map/keys=] are [=/URLs=] and the [=map/values=] are [=/responses=].
+ 1. Let |updatedResourceMap| be an [=ordered map=] where the [=map/keys=] are [=/URLs=] and the [=map/values=] are tuples containing a [=/response=] and either null, failure or a [=byte sequence=].
1. Switching on |job|'s [=worker type=], run these substeps with the following options:
: "classic
"
- :: Fetch a classic worker script given |job|’s serialized [=job/script url=], |job|’s [=job/client=], "serviceworker
", and the to-be-created environment settings object for this service worker.
+ :: Fetch a classic worker script given |job|’s serialized [=job/script url=], |job|’s [=job/client=], "serviceworker
", the to-be-created environment settings object for this service worker, and |onComplete| and |performFetch| as defined below.
: "module
"
- :: Fetch a module worker script graph given |job|’s serialized [=job/script url=], |job|’s [=job/client=], "serviceworker
", "omit
", and the to-be-created environment settings object for this service worker.
+ :: Fetch a module worker script graph given |job|’s serialized [=job/script url=], |job|’s [=job/client=], "serviceworker
", "omit
", and the to-be-created environment settings object for this service worker, and |onComplete| and |performFetch| as defined below.
Issue: Using the to-be-created [=environment settings object=] rather than a concrete [=environment settings object=]. This is used due to the unique processing model of service workers compared to the processing model of other [=web workers=]. The script fetching algorithms of HTML standard originally designed for other [=web workers=] require an [=environment settings object=] of the execution environment, but service workers fetch a script separately in the [=Update=] algorithm before the script later runs multiple times through the [=Run Service Worker=] algorithm.
Issue: The [=fetch a classic worker script=] algorithm and the [=fetch a module worker script graph=] algorithm in HTML take |job|’s [=job/client=] as an argument. |job|’s [=job/client=] is null when passed from the [=Soft Update=] algorithm.
- To [=fetching scripts/perform the fetch=] given |request|, run the following steps:
+ In both cases, let |performFetch| be the following [=fetching scripts/perform the fetch hook=] given |request|, |isTopLevel| and |processCustomFetchResponse|:
1. Append \`Service-Worker
\`/\`script
\` to |request|'s [=request/header list=].
@@ -2668,90 +2671,95 @@ spec: storage; urlPrefix: https://storage.spec.whatwg.org/
Note: Even if the cache mode is not set to "no-cache
", the user agent obeys Cache-Control header's max-age value in the network layer to determine if it should bypass the browser cache.
1. Set |request|'s [=service-workers mode=] to "`none`".
- 1. If the [=fetching scripts/is top-level=] flag is unset, then return the result of [=/fetching=] |request|.
+ 1. If |isTopLevel| is false, [=fetch=] |request| with |processCustomFetchResponse| as [=fetch/processResponseConsumeBody=], and abort these steps.
1. Set |request|'s [=request/redirect mode=] to "error
".
- 1. [=/Fetch=] |request|, and asynchronously wait to run the remaining steps as part of fetch's process response for the [=/response=] |response|.
- 1. [=Extract a MIME type=] from the |response|'s [=response/header list=]. If this MIME type (ignoring parameters) is not a [=JavaScript MIME type=], then:
- 1. Invoke [=Reject Job Promise=] with |job| and "{{SecurityError}}" {{DOMException}}.
- 1. Asynchronously complete these steps with a [=network error=].
- 1. Let |serviceWorkerAllowed| be the result of [=extracting header list values=] given \`Service-Worker-Allowed
\` and |response|'s [=response/header list=].
-
- Note: See the definition of the [=Service-Worker-Allowed=] header in Appendix B: Extended HTTP headers.
-
- 1. Set |policyContainer| to the result of creating a policy container from a fetch response given |response|.
- 1. If |serviceWorkerAllowed| is failure, then:
- 1. Asynchronously complete these steps with a network error.
- 1. Let |scopeURL| be |registration|'s [=service worker registration/scope url=].
- 1. Let |maxScopeString| be null.
- 1. If |serviceWorkerAllowed| is null, then:
- 1. Let |resolvedScope| be the result of [=URL parser|parsing=] "`./`" using |job|'s [=job/script url=] as the [=base URL=].
- 1. Set |maxScopeString| to "`/`", followed by the strings in |resolvedScope|'s [=url/path=] (including empty strings), separated from each other by "`/`".
-
- Note: The final item in |resolvedScope|'s [=url/path=] will always be an empty string, so |maxScopeString| will have a trailing "`/`".
-
- 1. Else:
- 1. Let |maxScope| be the result of [=URL parser|parsing=] |serviceWorkerAllowed| using |job|'s [=job/script url=] as the [=base URL=].
- 1. If |maxScope|'s [=url/origin=] is |job|'s [=job/script url=]'s [=url/origin=], then:
- 1. Set |maxScopeString| to "`/`", followed by the strings in |maxScope|'s [=url/path=] (including empty strings), separated from each other by "`/`".
- 1. Let |scopeString| be "`/`", followed by the strings in |scopeURL|'s [=url/path=] (including empty strings), separated from each other by "`/`".
- 1. If |maxScopeString| is null or |scopeString| does not start with |maxScopeString|, then:
- 1. Invoke [=Reject Job Promise=] with |job| and "{{SecurityError}}" {{DOMException}}.
- 1. Asynchronously complete these steps with a network error.
- 1. Let |url| be |request|'s [=request/url=].
- 1. Set |updatedResourceMap|[|url|] to |response|.
- 1. If |response|'s [=response/cache state=] is not "`local`", set |registration|'s [=last update check time=] to the current time.
- 1. Set |hasUpdatedResources| to true if any of the following are true:
- * |newestWorker| is null.
- * |newestWorker|'s [=service worker/script url=] is not |url| or |newestWorker|'s [=service worker/type=] is not |job|'s [=worker type=].
- * |newestWorker|'s [=script resource map=][|url|]'s [=response/body=] is not byte-for-byte identical with |response|'s [=response/body=].
- 1. If |hasUpdatedResources| is false and |newestWorker|'s [=classic scripts imported flag=] is set, then:
-
- Note: The following checks to see if an imported script has been updated, since the main script has not changed.
-
- 1. [=map/For each=] |importUrl| → |storedResponse| of |newestWorker|'s [=script resource map=]:
- 1. If |importUrl| is |url|, then continue.
- 1. Let |importRequest| be a new [=/request=] whose [=request/url=] is |importUrl|, [=request/client=] is |job|'s [=job/client=], [=request/destination=] is "`script`", [=request/parser metadata=] is "`not parser-inserted`", [=request/synchronous flag=] is set, and whose [=request/use-URL-credentials flag=] is set.
- 1. Set |importRequest|'s [=request/cache mode=] to "`no-cache`" if any of the following are true:
- * |registration|'s [=service worker registration/update via cache mode=] is "`none`".
- * |job|'s [=force bypass cache flag=] is set.
- * |registration| is [=stale=].
- 1. Let |fetchedResponse| be the result of [=fetch|fetching=] |importRequest|.
- 1. Set |updatedResourceMap|[|importRequest|'s [=request/url=]] to |fetchedResponse|.
- 1. Set |fetchedResponse| to |fetchedResponse|'s [=unsafe response=].
- 1. If |fetchedResponse|'s [=response/cache state=] is not "`local`", set |registration|’s [=last update check time=] to the current time.
- 1. If |fetchedResponse| is a [=bad import script response=], continue.
-
- Note: Bad responses for importScripts() are ignored for the purpose of the byte-to-byte check. Only good responses for the incumbent worker and good responses for the potential update worker are considered. See issue #1374 for some rationale.
-
- 1. If |fetchedResponse|'s [=response/body=] is not byte-for-byte identical with |storedResponse|'s [=unsafe response=]'s [=response/body=], set |hasUpdatedResources| to true.
-
- Note: The control does not break the loop in this step to continue with all the imported scripts to populate the cache.
- 1. Asynchronously complete these steps with |response|.
-
- When the algorithm asynchronously completes, continue the rest of these steps, with |script| being the asynchronous completion value.
-
- 1. If |script| is null or [=Is Async Module=] with |script|'s [=script/record=], |script|'s [=script/base URL=], and « » is true, then:
- 1. Invoke [=Reject Job Promise=] with |job| and `TypeError`.
-
- Note: This will do nothing if [=Reject Job Promise=] was previously invoked with "{{SecurityError}}" {{DOMException}}.
-
- 1. If |newestWorker| is null, then [=map/remove=] [=registration map=][(|registration|'s [=service worker registration/storage key=], [=URL serializer|serialized=] |scopeURL|)].
- 1. Invoke [=Finish Job=] with |job| and abort these steps.
- 1. If |hasUpdatedResources| is false, then:
- 1. Set |registration|'s [=service worker registration/update via cache mode=] to |job|'s [=job/update via cache mode=].
- 1. Invoke [=Resolve Job Promise=] with |job| and |registration|.
- 1. Invoke [=Finish Job=] with |job| and abort these steps.
- 1. Let |worker| be a new [=/service worker=].
- 1. Set |worker|'s [=service worker/script url=] to |job|'s [=job/script url=], |worker|'s [=script resource=] to |script|, |worker|'s [=service worker/type=] to |job|'s [=worker type=], and |worker|'s [=script resource map=] to |updatedResourceMap|.
- 1. Append |url| to |worker|'s [=set of used scripts=].
- 1. Set |worker|'s script resource's [=script resource/policy container=] to |policyContainer|.
- 1. Let |forceBypassCache| be true if |job|'s [=job/force bypass cache flag=] is set, and false otherwise.
- 1. Let |runResult| be the result of running the [=Run Service Worker=] algorithm with |worker| and |forceBypassCache|.
- 1. If |runResult| is *failure* or an [=abrupt completion=], then:
- 1. Invoke [=Reject Job Promise=] with |job| and `TypeError`.
- 1. If |newestWorker| is null, then [=map/remove=] [=registration map=][(|registration|'s [=service worker registration/storage key=], [=URL serializer|serialized=] |scopeURL|)].
- 1. Invoke [=Finish Job=] with |job|.
- 1. Else, invoke [=Install=] algorithm with |job|, |worker|, and |registration| as its arguments.
+ 1. [=/Fetch=] |request|, with [=fetch/processResponseConsumeBody=] set to the following algorithm given [=/response=] |response| and null, failure or a [=byte sequence=] |bodyBytes|:
+ 1. [=Extract a MIME type=] from the |response|'s [=response/header list=]. If this MIME type (ignoring parameters) is not a [=JavaScript MIME type=], then:
+ 1. Invoke [=Reject Job Promise=] with |job| and "{{SecurityError}}" {{DOMException}}.
+ 1. Invoke |processCustomFetchResponse| with a [=network error=] and null, and abort these steps.
+ 1. Let |serviceWorkerAllowed| be the result of [=extracting header list values=] given \`Service-Worker-Allowed
\` and |response|'s [=response/header list=].
+
+ Note: See the definition of the [=Service-Worker-Allowed=] header in Appendix B: Extended HTTP headers.
+
+ 1. Set |policyContainer| to the result of creating a policy container from a fetch response given |response|.
+ 1. If |serviceWorkerAllowed| is failure, then:
+ 1. Invoke |processCustomFetchResponse| with a [=network error=] and null, and abort these steps.
+ 1. Let |scopeURL| be |registration|'s [=service worker registration/scope url=].
+ 1. Let |maxScopeString| be null.
+ 1. If |serviceWorkerAllowed| is null, then:
+ 1. Let |resolvedScope| be the result of [=URL parser|parsing=] "`./`" using |job|'s [=job/script url=] as the [=base URL=].
+ 1. Set |maxScopeString| to "`/`", followed by the strings in |resolvedScope|'s [=url/path=] (including empty strings), separated from each other by "`/`".
+
+ Note: The final item in |resolvedScope|'s [=url/path=] will always be an empty string, so |maxScopeString| will have a trailing "`/`".
+
+ 1. Else:
+ 1. Let |maxScope| be the result of [=URL parser|parsing=] |serviceWorkerAllowed| using |job|'s [=job/script url=] as the [=base URL=].
+ 1. If |maxScope|'s [=url/origin=] is |job|'s [=job/script url=]'s [=url/origin=], then:
+ 1. Set |maxScopeString| to "`/`", followed by the strings in |maxScope|'s [=url/path=] (including empty strings), separated from each other by "`/`".
+ 1. Let |scopeString| be "`/`", followed by the strings in |scopeURL|'s [=url/path=] (including empty strings), separated from each other by "`/`".
+ 1. If |maxScopeString| is null or |scopeString| does not start with |maxScopeString|, then:
+ 1. Invoke [=Reject Job Promise=] with |job| and "{{SecurityError}}" {{DOMException}}.
+ 1. Invoke |processCustomFetchResponse| with a [=network error=] and null, and abort these steps.
+ 1. Let |url| be |request|'s [=request/url=].
+ 1. Set |updatedResourceMap|[|url|] to (|response|, |bodyBytes|).
+ 1. If |response|'s [=response/cache state=] is not "`local`", set |registration|'s [=last update check time=] to the current time.
+ 1. Set |hasUpdatedResources| to true if any of the following are true:
+ * |newestWorker| is null.
+ * |newestWorker|'s [=service worker/script url=] is not |url| or |newestWorker|'s [=service worker/type=] is not |job|'s [=worker type=].
+ * |newestWorker|'s [=script resource map=][|url|][1] is not byte-for-byte identical with |bodyBytes|.
+ 1. If |hasUpdatedResources| is false and |newestWorker|'s [=classic scripts imported flag=] is set, then:
+
+ Note: The following checks to see if an imported script has been updated, since the main script has not changed.
+
+ 1. [=map/For each=] |importUrl| → (|storedResponse|, |storedBodyBytes|) of |newestWorker|'s [=script resource map=]:
+ 1. If |importUrl| is |url|, then continue.
+ 1. Let |importRequest| be a new [=/request=] whose [=request/url=] is |importUrl|, [=request/client=] is |job|'s [=job/client=], [=request/destination=] is "`script`", [=request/parser metadata=] is "`not parser-inserted`", and whose [=request/use-URL-credentials flag=] is set.
+ 1. Set |importRequest|'s [=request/cache mode=] to "`no-cache`" if any of the following are true:
+ * |registration|'s [=service worker registration/update via cache mode=] is "`none`".
+ * |job|'s [=force bypass cache flag=] is set.
+ * |registration| is [=stale=].
+ 1. Let |fetchedResponse| be null.
+ 1. Let |fetchedBodyBytes| be null.
+ 1. [=Fetch=] |importRequest|, with [=fetch/processResponseConsumeBody=] set to the following algorithm given [=/response=] |res| and null, failure or a [=byte sequence=] |bb|:
+ 1. Set |fetchedResponse| to |res|.
+ 1. Set |fetchedBodyBytes| to |bb|.
+ 1. [=Pause=] until |fetchedResponse| is not null.
+ 1. Set |updatedResourceMap|[|importRequest|'s [=request/url=]] to (|fetchedResponse|, |fetchedBodyBytes|).
+ 1. Set |fetchedResponse| to |fetchedResponse|'s [=unsafe response=].
+ 1. If |fetchedResponse|'s [=response/cache state=] is not "`local`", set |registration|’s [=last update check time=] to the current time.
+ 1. If |fetchedBodyBytes| is null or failure, or |fetchedResponse| is a [=bad import script response=], continue.
+
+ Note: Bad responses for importScripts() are ignored for the purpose of the byte-to-byte check. Only good responses for the incumbent worker and good responses for the potential update worker are considered. See issue #1374 for some rationale.
+
+ 1. If |fetchedBodyBytes| is not byte-for-byte identical with |storedBodyBytes|, set |hasUpdatedResources| to true.
+
+ Note: The control does not break the loop in this step to continue with all the imported scripts to populate the cache.
+ 1. Invoke |processCustomFetchResponse| with |response| and |bodyBytes|.
+
+ In both cases, let |onComplete| given |script| be the following algorithm:
+
+ 1. If |script| is null or [=Is Async Module=] with |script|'s [=script/record=], |script|'s [=script/base URL=], and « » is true, then:
+ 1. Invoke [=Reject Job Promise=] with |job| and `TypeError`.
+
+ Note: This will do nothing if [=Reject Job Promise=] was previously invoked with "{{SecurityError}}" {{DOMException}}.
+
+ 1. If |newestWorker| is null, then [=map/remove=] [=registration map=][(|registration|'s [=service worker registration/storage key=], [=URL serializer|serialized=] |scopeURL|)].
+ 1. Invoke [=Finish Job=] with |job| and abort these steps.
+ 1. If |hasUpdatedResources| is false, then:
+ 1. Set |registration|'s [=service worker registration/update via cache mode=] to |job|'s [=job/update via cache mode=].
+ 1. Invoke [=Resolve Job Promise=] with |job| and |registration|.
+ 1. Invoke [=Finish Job=] with |job| and abort these steps.
+ 1. Let |worker| be a new [=/service worker=].
+ 1. Set |worker|'s [=service worker/script url=] to |job|'s [=job/script url=], |worker|'s [=script resource=] to |script|, |worker|'s [=service worker/type=] to |job|'s [=worker type=], and |worker|'s [=script resource map=] to |updatedResourceMap|.
+ 1. Append |url| to |worker|'s [=set of used scripts=].
+ 1. Set |worker|'s script resource's [=script resource/policy container=] to |policyContainer|.
+ 1. Let |forceBypassCache| be true if |job|'s [=job/force bypass cache flag=] is set, and false otherwise.
+ 1. Let |runResult| be the result of running the [=Run Service Worker=] algorithm with |worker| and |forceBypassCache|.
+ 1. If |runResult| is *failure* or an [=abrupt completion=], then:
+ 1. Invoke [=Reject Job Promise=] with |job| and `TypeError`.
+ 1. If |newestWorker| is null, then [=map/remove=] [=registration map=][(|registration|'s [=service worker registration/storage key=], [=URL serializer|serialized=] |scopeURL|)].
+ 1. Invoke [=Finish Job=] with |job|.
+ 1. Else, invoke [=Install=] algorithm with |job|, |worker|, and |registration| as its arguments.