Web Background Synchronization

Draft Community Group Report,

This version:
https://wicg.github.io/BackgroundSync/spec/
Issue Tracking:
GitHub
Editors:
(Google)
(Google)

Abstract

This specification describes a method that enables web applications to synchronize data in the background.

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

This section is non-normative.

Web Applications often run in environments with unreliable networks (e.g., mobile phones) and unknown lifetimes (the browser might be killed or the user might navigate away). This makes it difficult to synchronize client data from web apps (such as photo uploads, document changes, or composed emails) with servers. If the browser closes or the user navigates away before synchronization can complete, the app must wait until the user revisits the page to try again. This specification provides a new onsync service worker event which can fire in the background so that synchronization attempts can continue despite adverse conditions when initially requested. This API is intended to reduce the time between content creation and content synchronization with the server.

As this API relies on service workers, functionality provided by this API is only available in a secure context.

Requesting a background sync opportunity from a browsing context:
function sendChatMessage(message) {
  return addChatMessageToOutbox(message).then(() => {
    // Wait for the scoped service worker registration to get a
    // service worker with an active state
    return navigator.serviceWorker.ready;
  }).then(reg => {
    return reg.sync.register('send-chats');
  }).then(() => {
    console.log('Sync registered!');
  }).catch(() => {
    console.log('Sync registration failed :(');
  });
}

In the above example addChatMessageToOutbox is a developer-defined function.

Reacting to a sync event within a service worker:

self.addEventListener('sync', event => {
  if (event.tag == 'send-chats') {
    event.waitUntil(
      getMessagesFromOutbox().then(messages => {
        // Post the messages to the server
        return fetch('/send', {
          method: 'POST',
          body: JSON.stringify(messages),
          headers: { 'Content-Type': 'application/json' }
        }).then(() => {
          // Success! Remove them from the outbox
          return removeMessagesFromOutbox(messages);
        });
      }).then(() => {
        // Tell pages of your success so they can update UI
        return clients.matchAll({ includeUncontrolled: true });
      }).then(clients => {
        clients.forEach(client => client.postMessage('outbox-processed'))
      })
    );
  }
});

In the above example getMessagesFromOutbox and removeMessagesFromOutbox are developer-defined functions.

2. Concepts

The sync event is considered to run in the background if no service worker clients whose frame type is top-level or auxiliary exist for the origin of the corresponding service worker registration.

The user agent is considered to be online if the user agent has established a network connection. A user agent MAY use a stricter definition of being online. Such a stricter definition MAY take into account the particular service worker or origin a sync registration is associated with.

3. Constructs

A service worker registration has an associated list of sync registrations whose element type is a sync registration.

A sync registration is a tuple consisting of a tag and a state.

A sync registration has an associated tag, a DOMString.

A sync registration has an associated registration state, which is one of pending, waiting, firing, or reregisteredWhileFiring. It is initially set to pending.

A sync registration has an associated service worker registration. It is initially set to null.

Within one list of sync registrations each sync registration MUST have a unique tag.

4. Privacy Considerations

4.1. Permission

User agents MAY offer a way for the user to disable background sync.

Note: Background sync SHOULD be enabled by default. Having the permission denied is considered an exceptional case.

4.2. Location Tracking

Fetch requests within the onsync event while in the background may reveal the client’s IP address to the server after the user left the page. The user agent SHOULD limit tracking by capping the number of retries and duration of sync events.

4.3. History Leaking

Fetch requests within the onsync event while in the background may reveal something about the client’s navigation history to passive eavesdroppers. For instance, the client might visit site https://example.com, which registers a sync event, but doesn’t fire until after the user has navigated away from the page and changed networks. Passive eavesdroppers on the new network may see the fetch requests that the onsync event makes. The fetch requests are HTTPS so the request contents will not be leaked but the domain may be (via DNS lookups and IP address of the request).

5. API Description

5.1. Extensions to the ServiceWorkerRegistration interface

partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

The sync attribute exposes a SyncManager, which has an associated service worker registration represented by the ServiceWorkerRegistration on which the attribute is exposed.

5.2. SyncManager interface

[Exposed=(Window,Worker)]
interface SyncManager {
  Promise<void> register(DOMString tag);
  Promise<sequence<DOMString>> getTags();
};

The register(tag) method, when invoked, MUST return a new promise promise and run the following steps in parallel:

  1. Let serviceWorkerRegistration be the SyncManager's associated service worker registration.
  2. If serviceWorkerRegistration’s active worker is null:
    1. If serviceWorkerRegistration’s installing worker is null and serviceWorkerRegistration’s waiting worker is null, reject promise with an InvalidStateError and abort these steps.
    2. Wait for the installing worker or the waiting worker of serviceWorkerRegistration to become its active worker.
    3. If serviceWorkerRegistration fails to activate either worker, reject promise with an InvalidStateError and abort these steps.
    4. Once serviceWorkerRegistration’s active worker is not null, proceed with the steps below.
  3. If the user has disabled background sync, reject promise with an NotAllowedError and abort these steps.
  4. Let isBackground be true.
  5. For each client in the service worker clients for the serviceWorkerRegistration’s origin:
    1. If client’s frame type is top-level or auxiliary, set isBackground to false.
  6. If isBackground is true, reject promise with an InvalidAccessError and abort these steps.
  7. Let currentRegistration be the registration in serviceWorkerRegistration’s list of sync registrations whose tag equals tag if it exists, else null.
  8. If currentRegistration is not null:
    1. If currentRegistration’s registration state is waiting, set currentRegistration’s registration state to pending.
    2. If currentRegistration’s registration state is firing, set currentRegistration’s registration state to reregisteredWhileFiring.
    3. Resolve promise.
    4. If the user agent is currently online and currentRegistration’s registration state is pending, fire a sync event for currentRegistration.
  9. Else:
    1. Let newRegistration be a new sync registration.
    2. Set newRegistration’s associated tag to tag.
    3. Set newRegistration’s associated service worker registration to serviceWorkerRegistration.
    4. Add newRegistration to serviceWorkerRegistration’s list of sync registrations.
    5. Resolve promise.
    6. If the user agent is currently online, fire a sync event for newRegistration.

The getTags() method when invoked, MUST return a new promise promise and run the following steps in parallel:

  1. Let serviceWorkerRegistration be the SyncManager's associated service worker registration.
  2. Let currentTags be a new sequence.
  3. For each registration in serviceWorkerRegistration’s list of sync registrations, add registration’s associated tag to currentTags.
  4. Resolve promise with currentTags.

5.3. The sync event

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

[Constructor(DOMString type, SyncEventInit init), Exposed=ServiceWorker]
interface SyncEvent : ExtendableEvent {
  readonly attribute DOMString tag;
  readonly attribute boolean lastChance;
};

dictionary SyncEventInit : ExtendableEventInit {
  required DOMString tag;
  boolean lastChance = false;
};

Note: The SyncEvent interface represents a firing sync registration. If the page (or worker) that registered the event is running, the user agent will fire the sync event as soon as network connectivity is available. Otherwise, the user agent should run the event at the soonest convenience. If a sync event fails, the user agent may decide to retry it at a time of its choosing. The lastChance attribute is true if the user agent will not make further attempts to try this sync after the current attempt.

Reacting to lastChance:
self.addEventListener('sync', event => {
  if (event.tag == 'important-thing') {
    event.waitUntil(
      doImportantThing().catch(err => {
        if (event.lastChance) {
          self.registration.showNotification("Important thing failed");
        }
        throw err;
      })
    );
  }
});

The above example reacts to lastChance by showing a notification to the user. This requires the origin to have permission to show notifications.

In the above example doImportantThing is a developer-defined function.

Whenever the user agent changes to online, the user agent SHOULD fire a sync event for each sync registration whose registration state is pending. The events may be fired in any order.

To fire a sync event for a sync registration registration, the user agent MUST run the following steps:

  1. Assert: registration’s registration state is pending.
  2. Let serviceWorkerRegistration be the service worker registration associated with registration.
  3. Assert: registration exists in the list of sync registrations associated with serviceWorkerRegistration.
  4. Set registration’s registration state to firing.
  5. Invoke the Handle Functional Event algorithm with registration and the following substeps as arguments.
    1. Let globalObject be the global object these steps are called with.
    2. Create a trusted event e that uses the SyncEvent interface, with the event type sync, which does not bubble and has no default action.
    3. Let the tag attribute of e be initialized to the tag associated with registration.
    4. Let the lastChance attribute of e be initialized to false if the user agent will retry this sync event if it fails, or true if no further attempts will be made after the current attempt.
    5. Dispatch e at globalObject.
    6. Let waitUntilPromise be the result of waiting for all of e’s extended lifetime promises.
    7. Upon fulfillment of waitUntilPromise, perform the following steps atomically:
      1. If registration’s state is reregisteredWhileFiring:
        1. Set registration’s state to pending.
        2. If the user agent is currently online, fire a sync event for registration.
        3. Abort the rest of these steps.
      2. Assert: registration’s registration state is firing.
      3. Remove registration from serviceWorkerRegistration’s list of sync registration.
    8. Upon rejection of waitUntilPromise, or if the script has been aborted by the termination of the service worker, perform the following steps atomically:
      1. If registration’s state is reregisteredWhileFiring:
        1. Set registration’s state to pending.
        2. If the user agent is currently online, fire a sync event for registration.
        3. Abort the rest of these steps.
      2. If the lastChance attribute of e is false, set registration’s registration state to waiting, and perform the following steps in parallel:
        1. Wait a user agent defined length of time.
        2. If registration’s registration state is not waiting, abort these substeps.
        3. Set registration’s registration state to pending.
        4. If the user agent is currently online, fire a sync event for registration.
      3. Else remove registration from serviceWorkerRegistration’s list of sync registrations.

A user agent MAY impose a time limit on the lifetime extension and execution time of a SyncEvent which is stricter than the time limit imposed for ExtendableEvents in general. In particular an event for which lastChance is true MAY have a significantly shortened time limit.

A user agent will retry a sync event based on some user agent defined heuristics.

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

[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
[HTML]
Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PROMISES-GUIDE]
Writing Promise-Using Specifications. 24 July 2015. 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
[SECURE-CONTEXTS]
Mike West. Secure Contexts. 19 July 2016. WD. URL: https://w3c.github.io/webappsec-secure-contexts/
[SERVICE-WORKERS]
Alex Russell; Jungkee Song; Jake Archibald. Service Workers. 25 June 2015. WD. URL: https://slightlyoff.github.io/ServiceWorker/spec/service_worker/
[WebIDL-1]
Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 8 March 2016. CR. URL: https://heycam.github.io/webidl/

IDL Index

partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

[Exposed=(Window,Worker)]
interface SyncManager {
  Promise<void> register(DOMString tag);
  Promise<sequence<DOMString>> getTags();
};

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

[Constructor(DOMString type, SyncEventInit init), Exposed=ServiceWorker]
interface SyncEvent : ExtendableEvent {
  readonly attribute DOMString tag;
  readonly attribute boolean lastChance;
};

dictionary SyncEventInit : ExtendableEventInit {
  required DOMString tag;
  boolean lastChance = false;
};