Background Fetch

Editor’s Draft,

This version:
https://wicg.github.io/background-fetch/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Google)

Abstract

An API to handle large uploads/downloads in the background with user visibility.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

A service worker is capable of fetching and caching assets, the size of which is restricted only by origin storage. However, if the user navigates away from the site or closes the browser, the service worker is likely to be killed. This can happen even if there’s a pending promise passed to waitUntil(); if it hasn’t resolved within a few minutes the browser may consider it an abuse of service worker and kill the process.

This makes it difficult to download and cache large assets such as podcasts and movies, and upload video and images. Even if the service worker isn’t killed, having to keep the service worker in memory during this potentially long operation is wasteful.

This specification aims to:

2. Realms

All platform objects are created in the context object's relevant Realm unless otherwise specified.

3. Infrastructure

The background fetch task source is a task source.

To queue a bgfetch task on an optional eventLoop (an event loop, defaulting to the caller’s context object's relevant settings object's responsible event loop) with steps (steps), queue a task on eventLoop using the background fetch task source to run steps.

3.1. Extensions to service worker registration

A service worker registration additionally has:

To determine whether an active background fetches contains bgFetch (a background fetch), run these steps:
  1. Let id be bgFetch’s id.

  2. If activeBgFetches[id] does not exist, then return false.

  3. If activeBgFetches[id] does not equal bgFetch, then return false.

  4. Return true.

3.2. Background fetch

A background fetch consists of:

To display a background fetch (bgFetch), the user agent must present a user interface that follows these rules:
  • The bgFetch’s service worker registration's scope url's origin must be displayed.

  • The UI may display bgFetch’s title.

  • The UI may display one of bgFetch’s icons. TODO: when to fetch this?

  • The UI may use bgFetch’s download total, upload total, last reported download, and paused flag to present the progress of the fetch. TODO what about bytes uploaded? TODO how can I tell when the operation is complete?

  • The UI must provide a way for the user to abort bgFetch. TODO link up to algo.

  • The UI may provide a way to toggle bgFetch’s paused flag.

  • The UI may be activated (for example, by clicking), in which case it must TODO hook this up to the event.

  • The UI may remain once the operation is complete, in which case it can still be activated.

The user agent may initially set bgFetch’s paused flag, and allow the user to either accept the background fetch (unsetting bgFetch’s paused flag), or refuse the background fetch (aborting TODO hook this up).

Note: The user agent may consider initially setting bgFetch’s paused flag if the user is on a metered connection, or the background fetch was started in the background.

3.3. Background fetch record

A background fetch record consists of:

To fail a record with a given networkError (a network error), set record’s response body source to an empty byte sequence and set record’s response to networkError.

4. Algorithms

4.1. Perform a background fetch

To perform a background fetch for bgFetch (a background fetch), run these steps:
  1. Display bgFetch.

  2. Let swRegistration be bgFetch’s service worker registration.

  3. Let downloadTotal be bgFetch’s download total if it is not 0, otherwise infinity.

  4. Let successfulFetches be 0.

  5. Let abandoned be false.

  6. Let aborted be false.

  7. For each record in bgFetch’s records, run these steps in parallel:

    1. Complete a record for bgFetch and record.

    2. If bgFetch’s stored body bytes is greater than downloadTotal, then set abandoned to true.

      Accessing stored body bytes is racey. It may have been decremented below the failure value.

    3. Otherwise, if record’s response's aborted flag is set, then set aborted and abandoned to true.

    4. Otherwise, if record’s response's status is not an ok status, then set abandoned to true.

    5. Otherwise, increment successfulFetches by 1.

  8. Wait for either abandoned to be true, or successfulFetches to be bgFetch’s records's size.

  9. Let operationAborted be aborted.

  10. If abandoned is true:

    1. TODO: terminate all related fetches.

  11. Enqueue the following steps to swRegistration’s active background fetches edit queue:

    1. Let activeBgFetches be swRegistration’s active background fetches.

    2. Let id be bgFetch’s id.

    3. If activeBgFetches contains bgFetch, then remove activeBgFetches[id].

    4. Otherwise, set operationAborted to true.

      Note: This handles a race condition where abort() was successfully called but one of the fetches failed at the same time. If we’ve returned true from abort(), this ensures we fire the related abort event.

    5. If operationAborted is true, then fire a functional event named "backgroundfetchabort" using BackgroundFetchSettledEvent on swRegistration with the following properties:

      id

      id

      And with records set to bgFetch’s records.

    6. Otherwise, if abandoned is true, then fire a functional event named "backgroundfetchfail" using BackgroundFetchUpdateEvent on swRegistration with the following properties:

      id

      id

      And with records set to bgFetch’s records.

    7. Otherwise, fire a functional event named "backgroundfetched" using BackgroundFetchUpdateEvent on swRegistration with the following properties:

      id

      id

      And with records set to bgFetch’s records.

4.2. Complete a record

To complete a record for bgFetch (a background fetch) and record (a background fetch record), run these steps:
  1. Let downloadTotal be bgFetch’s download total if it is not 0, otherwise infinity.

  2. Wait for bgFetch’s paused flag to be unset.

  3. Let request be a copy of record’s request.

  4. Set request’s keepalive flag.

  5. Let rangeStart be the length of record’s response body source.

  6. If rangeStart is not 0, then add a range header to request with currentLength.

    Note: This allows the initial request to make use of content encoding, since Accept-Encoding: identity is added to requests with a range header.

  7. Let fetchAttemptComplete be false.

  8. Let continue be false.

  9. Fetch request.

    The remainder of this step uses fetch "callbacks" which currently queue tasks. This isn’t desirable here, so let’s pretend that tasks aren’t queued. (issue)

    To process request body, run these steps:

    1. TODO: handle upload progress.

    To process response for response, run these steps:

    1. If response is a network error, then:

      1. If the user agent is offline and request’s method is `GET`, wait until the user agent is not offline and set continue| and fetchAttemptComplete to true, and abort these steps.

        Is "user agent is offline" hand-waving too much? Also discussed in fetch/526.

        Note: If request’s method is not `GET`, reissuing the request may have unwanted side effects. If a standard method to resume requests becomes available, it’ll be adopted here.

      2. If response is an aborted network error, then fail record with response, set fetchAttemptComplete to true, and abort these steps.

    2. If response’s status is 206, then:

      1. If validate a partial response for rangeStart, response, and record’s response returns invalid, then fail record with response, set fetchAttemptComplete to true, and abort these steps.

    3. Otherwise:

      1. Decrement bgFetch’s stored body bytes by the length of record’s response body source.

      2. Set record’s response body source to an empty byte sequence.

    4. Set record’s response to a copy of response except for its body.

      Note: This may be replacing an existing value. It’s acceptable for earlier responses to be missing things like `ETag` headers, and complete lengths from `Content-Range` headers, but once a later response has them, we want to compare them against subsequent responses.

    5. Let stream be response’s body's stream.

    6. Whenever one or more bytes are transmitted from stream, let bytes be the transmitted bytes and run these steps:

      1. Increment beFetch’s stored body bytes by bytes’ size.

      2. If bgFetch’s stored body bytes is greater than downloadTotal, then:

        1. Cancel stream.

        2. Fail record with an aborted network error.

        3. Set fetchAttemptComplete to true and abort these steps.

      3. Append bytes to record’s response body source.

      4. Report progress for background fetch bgFetch.

    7. If at any point the bytes transmission for stream is done normally, then:

      1. If response’s status is 206, then:

        1. Let firstBytePos, lastBytePos, and completeLength be the result of extracting content-range values from response.

        2. If completeLength is null, or not equal to the length of record’s response body source, set continue to true.

          Note: Although we ask for the whole resource, or the remainder of the resource, the server may not have returned the remainder, in which case we need to make an additional request.

      2. Set fetchAttemptComplete to true.

    8. If at any point stream becomes errored, then fail record with a network error and fetchAttemptComplete to true.

  10. Wait for fetchAttemptComplete to be true.

  11. If continue is true, then complete a record for bgFetch and record.

TODO: handle "paused" being set after the first step in this block.

4.3. Report progress for background fetch

To report progress for background fetch bgFetch (a background fetch), enqueue the following steps to bgFetch’s progress handling queue:
  1. Let downloaded be bgFetch’s stored body bytes.

  2. If downloaded is equal to bgFetch’s last reported download, then abort these steps.

  3. Set bgFetch’s last reported download to downloaded.

  4. For each environment settings object env whose origin is equal to bgFetch’s service worker registration's scope URL's origin, queue a bgfetch task on env’s responsible event loop to run these steps:

    1. Let bgFetchRegistration be the instance of BackgroundFetchRegistration within the relevant realm whose background fetch is equal to bgFetch, or null if none exists.

      Note: There will be at most one per environment, due to the get a BackgroundFetchRegistration instance algorithm.

    2. If bgFetchRegistration is null, then abort these steps.

    3. Set downloaded to downloaded.

    4. Fire an event named "progress" at bgFetchRegistration.

4.4. Get a BackgroundFetchRegistration instance

To get a BackgroundFetchRegistration instance for an instancesMap (a BackgroundFetchRegistration instances) and a bgFetch (a background fetch), run these steps:
  1. If instancesMap[bgFetch] exists, then return instancesMap[bgFetch].

  2. Let instance be a new BackgroundFetchRegistration in the parent BackgroundFetchManager's relevant Realm, and set its background fetch to bgFetch.

  3. Set instancesMap[bgFetch] to instance.

  4. Return instance.

Note: This is to ensure the same instance is returned for a given background fetch throughout the life of a BackgroundFetchManager. It’s okay for browsers to optimise this, as long as there’s no way to tell that more than one instance has been created for a given background fetch (e.g through equality, expandos, or weakly-associated data).

4.5. Validate a partial response

To validate a partial response for an expectedRangeStart (a number), a partialResponse (a response), and an optional previousResponse (a response or null, null unless otherwise specified), run these steps:
  1. Assert: partialResponse’s status is 206.

  2. Let responseFirstBytePos, responseLastBytePos, and responseCompleteLength be the result of extracting content-range values from partialResponse. If this fails, then return invalid.

  3. If requestFirstBytePos does not equal expectedRangeStart, then return invalid.

  4. If previousResponse is not null, then:

    1. If previousResponse’s header list contains `ETag`, and header equality for `ETag`, previousResponse’s header list, and partialResponse’s header list is false, then return invalid.

    2. If previousResponse’s header list contains `Last-Modified`, and header equality for `Last-Modified`, previousResponse’s header list, and partialResponse’s header list is false, then return invalid.

    3. If previousResponse’s status is 206, then:

      1. Let previousResponseFirstBytePos, previousResponseLastBytePos, and previousResponseCompleteLength be the result of extracting content-range values from previousResponse. If this fails, then return invalid.

      2. If previousResponseCompleteLength is not null, and responseCompleteLength does not equal previousResponseCompleteLength, then return invalid.

  5. Return valid.

4.6. Extract content-range values

To extract content-range values from a response (a response), run these steps:
  1. If response’s header list does not contain `Content-Range`, then return failure.

  2. Let contentRangeValue be the value of the first header whose name is a byte-case-insensitive match for `Content-Range` in response’s header list.

  3. If parsing contentRangeValue per single byte content-range fails, then return failure.

  4. Let firstBytePos be the portion of contentRangeValue named first-byte-pos when parsed as single byte content-range, parsed as an integer.

  5. Let lastBytePos be the portion of contentRangeValue named last-byte-pos when parsed as single byte content-range, parsed as an integer.

  6. Let completeLength be the portion of contentRangeValue named complete-length when parsed as single byte content-range.

  7. If completeLength is "*", then set completeLength to null, otherwise set completeLength to completeLength parsed as an integer.

  8. Return firstBytePos, lastBytePos, and completeLength.

Parsing as an integer infra/189.

4.7. Header equality

To determine header equality for a headerName (a byte sequence), in headerList1 (a header list), and headerList2 (a header list), run these steps:
  1. If headerList1 does not contain headerName, and headerList2 does not contain headerName, then return true.

  2. If headerList1 contains headerName, and headerList2 contains headerName, then:

    1. Let header1Value be the value of the first header whose name is a byte-case-insensitive match for headerName in headerList1.

    2. Let header2Value be the value of the first header whose name is a byte-case-insensitive match for headerName in headerList2.

    3. If header1Value is identical to header2Value, then return true, otherwise return false.

  3. Return false.

4.8. Create settled fetches

To create settled fetches from records (a list of background fetch records) in realm (a Realm), run these steps:
  1. Let settledFetches be a new list.

  2. For each record of records:

    1. Let settledFetch be a new BackgroundFetchSettledFetch.

    2. Let requestObject be a new Request object with the following set:

      Request

      A copy of record’s request.

      Headers

      A new Headers object associated with this Request's request's header list.

    3. Set settledFetch’s request to requestObject.

    4. If record’s body complete flag is set, then:

      1. Let response be copy of record’s response.

      2. Let transmittedBytes be 0.

      3. Let stream be a new ReadableStream object with a pull action that returns a new promise bytePromise and runs these steps in parallel:

        1. Let bytes be a user agent determined slice of record’s response body source, starting from an offset of transmittedBytes.

        2. Increment transmittedBytes by byteslength.

        3. Resolve bytePromise with a new Uint8Array wrapping a new ArrayBuffer of bytes.

      4. Let body be a new body with the following set:

        Stream

        stream.

        Source

        record’s response body source.

        Total bytes

        The length of record’s response body source.

      5. Set response’s body to body.

      6. Let responseObject be a new Response object with the following set:

        Response

        A copy of record’s response.

        Headers

        A new Headers object associated with this Response's response's header list.

      7. Resolve responseObject’s trailer promise with a new Headers object associated with responseObject’s response's trailer.

        Trailer promise isn’t exported. fetch/771.

      8. Set settledFetch’s response to responseObject.

    5. Append settledFetch to settledFetches.

  3. Return settledFetches.

I need to make sure the response objects have the correct content-length and no content-range. Although, maybe I don’t need to, since compression is already an issue here. Check with Ben.

5. Header syntax

The following is HTTP ABNF for a single byte content-range:

"bytes=" first-byte-pos "-" last-byte-pos "/" complete-length
first-byte-pos = 1*DIGIT
last-byte-pos  = 1*DIGIT
complete-length = ( 1*DIGIT / "*" )

6. API

6.1. Extensions to ServiceWorkerGlobalScope

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onbackgroundfetched;
  attribute EventHandler onbackgroundfetchfail;
  attribute EventHandler onbackgroundfetchabort;
  attribute EventHandler onbackgroundfetchclick;
};

6.1.1. Events

The following is the event handler (and its corresponding event handler event type) that must be supported, as event handler IDL attributes, by all objects implementing ServiceWorker interface:

event handler event type event handler Interface
backgroundfetched onbackgroundfetched BackgroundFetchUpdateEvent
backgroundfetchfail onbackgroundfetchfail BackgroundFetchUpdateEvent
backgroundfetchabort onbackgroundfetchabort BackgroundFetchSettledEvent
backgroundfetchclick onbackgroundfetchclick BackgroundFetchClickEvent

6.2. Extensions to ServiceWorkerRegistration

partial interface ServiceWorkerRegistration {
  readonly attribute BackgroundFetchManager backgroundFetch;
};

A ServiceWorkerRegistration has a background fetch manager (a BackgroundFetchManager), initially a new BackgroundFetchManager whose service worker registration is the context object's service worker registration.

The backgroundFetch attribute’s getter must return the context object's background fetch manager.

6.3. BackgroundFetchManager

[Exposed=(Window,Worker)]
interface BackgroundFetchManager {
  Promise<BackgroundFetchRegistration> fetch(DOMString id, (RequestInfo or sequence<RequestInfo>) requests, optional BackgroundFetchOptions options);
  Promise<BackgroundFetchRegistration?> get(DOMString id);
  Promise<sequence<DOMString>> getIds();
  // TODO: in future this should become an async iterator for BackgroundFetchRegistration objects
};

dictionary BackgroundFetchOptions {
  sequence<IconDefinition> icons = [];
  DOMString title = "";
  unsigned long long downloadTotal = 0;
};

// This is taken from https://w3c.github.io/manifest/#icons-member.
// This definition should probably be moved somewhere more general.
dictionary IconDefinition {
  DOMString src;
  DOMString sizes = "";
  DOMString type = "";
};

A BackgroundFetchManager has:

6.3.1. fetch()

The fetch(id, requests, options) method, when invoked, run these steps:
  1. Let registration be the context object's service worker registration.

  2. Let bgFetchInstances be context object's BackgroundFetchRegistration instances.

  3. Let records be a new list.

  4. Let uploadTotal be 0.

  5. If requests is a RequestInfo, set requests to « requests ».

  6. If requests is empty, then return a promise rejected with a TypeError.

  7. For each request of requests:

    1. Let internalRequest be the request of the result of invoking the Request constructor with request.

    2. If internalRequest’s mode is "no-cors", then return a promise rejected with a TypeError.

    3. Set internalRequest’s client to null.

    4. Let record be a new background fetch record.

    5. Set record’s request to internalReqeust.

    6. Append record to records.

  8. Let promise be a new promise.

  9. Enqueue the following steps to registration’s active background fetches edit queue:

    1. Let bgFetchMap be registration’s active background fetches.

    2. If registration’s active worker is null, then reject promise with a TypeError and abort these steps.

    3. If bgFetchMap[id] exists, reject promise with a TypeError and abort these steps.

    4. Let requestBodiesRemaining be the size of requests.

    5. Let requestReadAborted be false.

    6. For each request of requests:

      1. If request’s body is null, then continue.

      2. Let stream be request’s body's stream.

      3. Run these steps in parallel:

        1. Run these steps but abort when requestReadAborted is true:

          1. Wait for request’s body.

          2. If stream has errored, then set requestReadAborted to true.

          Note: This ensures we have a copy of the request bytes before resolving.

        2. If aborted and stream is readable, then error stream with an AbortError DOMException and abort these steps.

        3. Increment uploadTotal by request’s body's total bytes.

        4. Decrement requestBodiesRemaining by 1.

    7. Wait for requestBodiesRemaining to be 0, or requestReadAborted to be true.

    8. If storing requests fails due to exceeding a quota limit, reject promise with a QuotaExceededError DOMException and abort these steps.

    9. If requestReadAborted is true, then reject promise with a TypeError and abort these steps.

    10. Let bgFetch be a new background fetch with:

      id

      id.

      records

      records.

      download total

      optionsdownloadTotal member.

      upload total

      uploadTotal.

      icons

      optionsicons member.

      title

      optionstitle member.

      service worker registration

      registration.

    11. Set bgFetchMap[id] to bgFetch.

    12. Queue a bgfetch task to run these steps:

      1. Resolve promise with the result of getting a BackgroundFetchRegistration instance passing bgFetchInstances and bgFetch.

    13. In parallel, perform a background fetch with bgFetch.

  10. Return promise.

6.3.2. get()

The get(id) method, when invoked, must return a new promise promise and run these steps in parallel:
  1. Let registration be the context object's associated service worker registration.

  2. Let bgFetch be registration’s active background fetches[id].

  3. If bgFetch is nothing, then resolve promise with undefined and abort these steps.

  4. Let downloaded be bgFetch’s last reported downloaded.

  5. Enqueue the following steps to bgFetch’s progress handling queue:

    1. Queue a bgfetch task task to run these steps:

      1. Let bgFetchRegistration be the result of getting a BackgroundFetchRegistration instance passing the context object's BackgroundFetchRegistration instances and bgFetch.

      2. Set bgFetchRegistration’s downloaded to downloaded.

      3. Resolve promise with bgFetchRegistration.

    2. Wait for task to complete.

      Note: This ensures the potential new instance of BackgroundFetchRegistration doesn’t miss any progress events.

6.3.3. getIds()

The getIds() method, when invoked, must return a new promise promise and run these steps in parallel:
  1. Let registration be the context object's associated service worker registration.

  2. Resolve promise with the result of getting the keys of registration’s active background fetches.

6.4. BackgroundFetchRegistration

[Exposed=(Window,Worker)]
interface BackgroundFetchRegistration : EventTarget {
  readonly attribute DOMString id;
  readonly attribute unsigned long long uploadTotal;
  readonly attribute unsigned long long uploaded;
  readonly attribute unsigned long long downloadTotal;
  readonly attribute unsigned long long downloaded;

  attribute EventHandler onprogress;

  Promise<boolean> abort();
  Promise<BackgroundFetchActiveFetch> match(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchActiveFetch>> matchAll(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchActiveFetch>> values();
};

A BackgroundFetchRegistration instance has:

Note: The above values are copied so they’re available synchronously.

The id attribute’s getter must return the context object's id.

The uploadTotal attribute’s getter must return the context object's upload total.

The downloadTotal attribute’s getter must return the context object's download total.

The uploaded attribute’s getter must return the context object's uploaded.

The downloaded attribute’s getter must return the context object's downloaded.

6.4.1. Events

The onprogress event handler has the event handler event type of progress.

The progress event uses the Event interface.

6.4.2. abort()

The abort() method, when invoked, must return a new promise promise and run these steps in parallel:
  1. Let bgFetch be the context object's associated background fetch.

  2. Let swRegistration be bgFetch’s service worker registration.

  3. Enqueue the following steps to swRegistration’s active background fetches edit queue:

    1. Let activeBgFetches be swRegistration’s active background fetches.

    2. Let id be bgFetch’s id.

    3. If activeBgFetches does not contain bgFetch, then resolve promise with false and abort these steps.

    4. Remove activeBgFetches[id].

    5. Resolve promise with true.

    6. TODO: Terminate related fetches with the aborted flag set.

6.4.3. match()

The match(request, options) method, when invoked, must run these steps:
  1. Let promise be the result of calling the algorithm matchAll() passing request and options.

  2. Return the result of transforming promise with a fulfilment handler that, when called with argument matches, returns matches[0].

Note: User agents are encouraged to optimise the above so it’s faster than calling matchAll().

6.4.4. matchAll()

The matchAll(request, options) method, when invoked, must return a new promise promise and run these steps in parallel:
  1. TODO.

6.4.5. values()

The values() method, when invoked, must return a new promise promise and run these steps in parallel:
  1. TODO.

6.5. BackgroundFetchFetch

[Exposed=(Window,Worker)]
interface BackgroundFetchFetch {
  readonly attribute Request request;
};
A BackgroundFetchFetch has a request (a request).

The request attribute’s getter must return the context object's request.

6.6. BackgroundFetchActiveFetch

[Exposed=(Window,Worker)]
interface BackgroundFetchActiveFetch : BackgroundFetchFetch {
  readonly attribute Promise<Response> responseReady;
  // TODO: In future this will include a fetch observer
};
A BackgroundFetchActiveFetch has a response promise (a Promise for a response).

The responseReady attribute’s getter must return the context object's response promise.

6.7. BackgroundFetchEvent

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchEvent : ExtendableEvent {
  readonly attribute DOMString id;
};

dictionary BackgroundFetchEventInit : ExtendableEventInit {
  required DOMString id;
};
A BackgroundFetchEvent has an associated id, a DOMString.

The id attribute must return the id.

TODO

The BackgroundFetchEvent(type, init) constructor, when invoked, must run these steps:
  1. TODO

6.8. BackgroundFetchSettledEvent

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchSettledEvent : BackgroundFetchEvent {
  Promise<BackgroundFetchSettledFetch> match(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchSettledFetch>> matchAll(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchSettledFetch>> values();
};

CacheQueryOptions includes cacheName which doesn’t make sense here. I need to abstract it out.

A BackgroundFetchSettledEvent object has records (a list of background fetch records).

6.8.1. match()

The match(request, options) method, when invoked, must run these steps:
  1. Let promise be the result of calling the algorithm matchAll() passing request and options.

  2. Return the result of transforming promise with a fulfilment handler that, when called with argument matches, returns matches[0].

Note: User agents are encouraged to optimise the above so it’s faster than calling matchAll().

6.8.2. matchAll()

The matchAll(request, options) method, when invoked, must return a new promise promise and run these steps in parallel:
  1. Let records be the result of filtering the context object's records according to the rules of cache.matchAll().

    The above needs to be abstracted.

  2. Queue a bgfetch task to resolve promise with the result of creating settled fetches from records in context object's relevant Realm.

6.8.3. values()

The values() method, when invoked, must return a new promise promise and run these steps in parallel:
  1. Let records be the context object's records.

  2. Queue a bgfetch task to resolve promise with the result of creating settled fetches from records in context object's relevant Realm.

[Exposed=ServiceWorker]
interface BackgroundFetchSettledFetch : BackgroundFetchFetch {
  readonly attribute Response? response;
};

6.9. BackgroundFetchUpdateEvent

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchUpdateEvent : BackgroundFetchSettledEvent {
  Promise<void> updateUI(DOMString title);
};
The updateUI(title) method, when invoked, must return a new promise promise and run these steps in parallel:
  1. TODO

6.10. BackgroundFetchClickEvent

[Constructor(DOMString type, BackgroundFetchClickEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchClickEvent : BackgroundFetchEvent {
  readonly attribute BackgroundFetchState state;
};

dictionary BackgroundFetchClickEventInit : BackgroundFetchEventInit {
  required BackgroundFetchState state;
};

enum BackgroundFetchState { "pending", "succeeded", "failed" };

The state attribute must return the value it was initialized to.

7. Privacy and bandwidth usage

It’s the recommendation of this standard that user agents make background fetch operations highly visible and easily abortable rather than asking permission up front. However, given that background fetches can be paused and retried, a user agent that immediately paused a background fetch and user interaction to resume would be compliant.

The origin of a background fetch should have equal or great visibility to the developer-provided information, such as a the icons and title.

A user agent may pause background downloads depending on network conditions or battery state. For example, a user agent may download automatically when connected to WiFi, but require user confirmation to download using mobile data. If an implementation has this restriction, the user should be able to persist granting mobile data use for the origin.

If the user agent provides a way for a user to abort a background fetch operation, it should terminate all fetches associated with the background fetch's records, with the abort flag set.

A background fetch exposes the user’s external IPs to the server throughout the duration of the background fetch. An attacker could use an extremely slow background fetch as a way to persistently track the user once they’ve left the site. Good visibility into the progress of the background fetch will help the user identify unwanted use.

Conformance

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.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PROMISES-GUIDE]
Domenic Denicola. Writing Promise-Using Specifications. 16 February 2016. Finding of the W3C TAG. URL: https://www.w3.org/2001/tag/doc/promises-guide
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS-2]
Service Workers URL: https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

IDL Index

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onbackgroundfetched;
  attribute EventHandler onbackgroundfetchfail;
  attribute EventHandler onbackgroundfetchabort;
  attribute EventHandler onbackgroundfetchclick;
};

partial interface ServiceWorkerRegistration {
  readonly attribute BackgroundFetchManager backgroundFetch;
};

[Exposed=(Window,Worker)]
interface BackgroundFetchManager {
  Promise<BackgroundFetchRegistration> fetch(DOMString id, (RequestInfo or sequence<RequestInfo>) requests, optional BackgroundFetchOptions options);
  Promise<BackgroundFetchRegistration?> get(DOMString id);
  Promise<sequence<DOMString>> getIds();
  // TODO: in future this should become an async iterator for BackgroundFetchRegistration objects
};

dictionary BackgroundFetchOptions {
  sequence<IconDefinition> icons = [];
  DOMString title = "";
  unsigned long long downloadTotal = 0;
};

// This is taken from https://w3c.github.io/manifest/#icons-member.
// This definition should probably be moved somewhere more general.
dictionary IconDefinition {
  DOMString src;
  DOMString sizes = "";
  DOMString type = "";
};

[Exposed=(Window,Worker)]
interface BackgroundFetchRegistration : EventTarget {
  readonly attribute DOMString id;
  readonly attribute unsigned long long uploadTotal;
  readonly attribute unsigned long long uploaded;
  readonly attribute unsigned long long downloadTotal;
  readonly attribute unsigned long long downloaded;

  attribute EventHandler onprogress;

  Promise<boolean> abort();
  Promise<BackgroundFetchActiveFetch> match(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchActiveFetch>> matchAll(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchActiveFetch>> values();
};

[Exposed=(Window,Worker)]
interface BackgroundFetchFetch {
  readonly attribute Request request;
};

[Exposed=(Window,Worker)]
interface BackgroundFetchActiveFetch : BackgroundFetchFetch {
  readonly attribute Promise<Response> responseReady;
  // TODO: In future this will include a fetch observer
};

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchEvent : ExtendableEvent {
  readonly attribute DOMString id;
};

dictionary BackgroundFetchEventInit : ExtendableEventInit {
  required DOMString id;
};

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchSettledEvent : BackgroundFetchEvent {
  Promise<BackgroundFetchSettledFetch> match(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchSettledFetch>> matchAll(RequestInfo request, optional CacheQueryOptions options);
  Promise<sequence<BackgroundFetchSettledFetch>> values();
};

[Exposed=ServiceWorker]
interface BackgroundFetchSettledFetch : BackgroundFetchFetch {
  readonly attribute Response? response;
};

[Constructor(DOMString type, BackgroundFetchEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchUpdateEvent : BackgroundFetchSettledEvent {
  Promise<void> updateUI(DOMString title);
};

[Constructor(DOMString type, BackgroundFetchClickEventInit init), Exposed=ServiceWorker]
interface BackgroundFetchClickEvent : BackgroundFetchEvent {
  readonly attribute BackgroundFetchState state;
};

dictionary BackgroundFetchClickEventInit : BackgroundFetchEventInit {
  required BackgroundFetchState state;
};

enum BackgroundFetchState { "pending", "succeeded", "failed" };

Issues Index

Accessing stored body bytes is racey. It may have been decremented below the failure value.
The remainder of this step uses fetch "callbacks" which currently queue tasks. This isn’t desirable here, so let’s pretend that tasks aren’t queued. (issue)
Is "user agent is offline" hand-waving too much? Also discussed in fetch/526.
Parsing as an integer infra/189.
Trailer promise isn’t exported. fetch/771.
I need to make sure the response objects have the correct content-length and no content-range. Although, maybe I don’t need to, since compression is already an issue here. Check with Ben.
CacheQueryOptions includes cacheName which doesn’t make sense here. I need to abstract it out.
The above needs to be abstracted.