Web 后台同步

社区组报告草案,

此版本:
https://wicg.github.io/background-sync/spec/
问题跟踪:
GitHub
编辑:
Google
Google

摘要

本规范描述了一种使 Web 应用能够在后台同步数据的方法。

本文档状态

本规范由 Web Platform Incubator Community Group 发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C Community Contributor License Agreement (CLA),存在有限的选择退出机制,并适用其他条件。 了解更多关于 W3C Community and Business Groups 的信息。

1. 引言

本节为非规范性内容。

Web 应用程序经常运行在网络不可靠(例如移动电话)且生命周期未知 的环境中(浏览器可能被终止,或者用户可能导航离开)。这使得 将来自 Web 应用的客户端数据(例如照片上传、文档更改或撰写的电子邮件) 与服务器同步变得困难。如果浏览器在同步完成前关闭,或用户导航离开, 应用就必须等到用户重新访问页面时再尝试。本规范提供了一个新的 onsync service worker 事件,该事件可 在后台触发, 因而即使在最初请求时处于不利条件下,同步尝试也可以继续。此 API 旨在 缩短内容创建与内容同步到服务器之间的时间。

由于此 API 依赖 service worker,因此此 API 提供的功能仅在 安全上下文中可用。

浏览上下文请求一次后台同步机会:
function sendChatMessage(message) {
  return addChatMessageToOutbox(message).then(() => {
    // 等待有作用域的 service worker 注册获得一个
    // 处于 active 状态的 service worker
    return navigator.serviceWorker.ready;
  }).then(reg => {
    return reg.sync.register('send-chats');
  }).then(() => {
    console.log('Sync registered!');
  }).catch(() => {
    console.log('Sync registration failed :(');
  });
}

在上面的示例中,addChatMessageToOutbox 是开发者定义的函数。

service worker 内响应 sync 事件:

self.addEventListener('sync', event => {
  if (event.tag == 'send-chats') {
    event.waitUntil(
      getMessagesFromOutbox().then(messages => {
        // 将消息发布到服务器
        return fetch('/send', {
          method: 'POST',
          body: JSON.stringify(messages),
          headers: { 'Content-Type': 'application/json' }
        }).then(() => {
          // 成功!将其从发件箱中移除
          return removeMessagesFromOutbox(messages);
        });
      }).then(() => {
        // 将成功情况告知页面,以便页面更新 UI
        return clients.matchAll({ includeUncontrolled: true });
      }).then(clients => {
        clients.forEach(client => client.postMessage('outbox-processed'))
      })
    );
  }
});

在上面的示例中,getMessagesFromOutboxremoveMessagesFromOutbox 是 开发者定义的函数。

2. 概念

如果对于相应 service worker 注册的源,不存在其 service worker clientsframe type 为 top-level 或 auxiliary, 则认为 sync 事件在后台运行。

如果用户代理已经建立网络连接,则认为该用户代理 在线。用户代理可以使用更严格的 在线定义。这样的更严格定义可以考虑 service worker 或某个 sync registration 所关联的特定源。

3. 构造

一个 service worker registration 具有关联的 sync registrations 列表,其元素类型是 sync registration

sync registration 是一个由 tagstate 组成的元组。

sync registration 具有关联的 tag,即一个 DOMString。

sync registration 具有关联的 registration state,其值为 pendingwaitingfiringreregisteredWhileFiring 之一。它 初始设置为 pending

sync registration 具有关联的 service worker registration。它初始 设置为 null。

在一个 sync registrations 列表中,每个 sync registration 必须具有唯一的 tag

4. 权限集成

Web Background Synchronization API 是一个 默认强大特性,由 名称 "background-sync" 标识。

5. 隐私考虑

5.1. 权限

用户代理可以为用户提供一种禁用后台同步的方式。

注: 后台同步应默认启用。权限被拒绝被视为一种例外情况。

5.2. 位置跟踪

当处于后台时,onsync 事件内的 Fetch 请求可能会在 用户离开页面后向服务器透露客户端的 IP 地址。用户代理应通过限制重试次数和 sync 事件的持续时间 来限制跟踪。

5.3. 历史泄露

当处于后台时,onsync 事件内的 Fetch 请求可能会向被动窃听者 透露有关客户端导航历史的信息。例如,客户端可能访问站点 https://example.com,该站点注册一个 sync 事件,但直到用户已经导航离开页面并更换网络后才触发。 新网络上的被动窃听者可能会看到 onsync 事件发出的 fetch 请求。这些 fetch 请求使用 HTTPS, 因而请求内容不会泄露,但域名可能会泄露(通过 DNS 查询和请求的 IP 地址)。

6. API 描述

6.1. ServiceWorkerRegistration 接口的扩展

ServiceWorkerRegistration/sync

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
Opera36+Edge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebView49+Samsung Internet4.0+Opera Mobile36+
partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

sync 属性暴露一个 SyncManager, 它具有关联的 service worker registration,由暴露该属性的 ServiceWorkerRegistration 表示。

6.2. SyncManager 接口

SyncManager

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
OperaEdge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebView49+Samsung Internet5.0+Opera Mobile
[Exposed=(Window,Worker)]
interface SyncManager {
  Promise<undefined> register(DOMString tag);
  Promise<sequence<DOMString>> getTags();
};

SyncManager/register

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
OperaEdge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebView49+Samsung Internet5.0+Opera Mobile

register(tag) 方法在被调用时,必须返回 一个 新 promise promise,并并行地运行以下步骤:

  1. serviceWorkerRegistrationSyncManager 的 关联 service worker registration
  2. 如果 serviceWorkerRegistrationactive worker 为 null,则以 InvalidStateError 拒绝 promise, 并中止这些步骤。
  3. 如果用户已禁用后台同步,则以 NotAllowedError 拒绝 promise, 并中止这些步骤。
  4. isBackground 为 true。
  5. 对于 serviceWorkerRegistration 的源的 service worker clients 中的每个 client
    1. 如果 clientframe type 为 top-level 或 auxiliary,则将 isBackground 设置为 false。
  6. 如果 isBackground 为 true,则以 InvalidAccessError 拒绝 promise, 并中止这些步骤。
  7. currentRegistrationserviceWorkerRegistrationsync registrations 列表中其 tag 等于 tagregistration(如果存在),否则为 null。
  8. 如果 currentRegistration 不为 null:
    1. 如果 currentRegistrationregistration statewaiting,则将 currentRegistrationregistration state 设置为 pending
    2. 如果 currentRegistrationregistration statefiring,则将 currentRegistrationregistration state 设置为 reregisteredWhileFiring
    3. 解决 promise
    4. 如果用户代理当前在线,且 currentRegistrationregistration statepending,则为 currentRegistration 触发 sync 事件
  9. 否则:
    1. newRegistration 为一个新的 sync registration
    2. newRegistration 的关联 tag 设置为 tag
    3. newRegistration 的关联 service worker registration 设置为 serviceWorkerRegistration
    4. newRegistration 添加到 serviceWorkerRegistrationsync registrations 列表
    5. 解决 promise
    6. 如果用户代理当前在线,则为 newRegistration 触发 sync 事件

SyncManager/getTags

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
OperaEdge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebView49+Samsung Internet5.0+Opera Mobile

getTags() 方法在被调用时,必须返回 一个新 promise promise,并并行地运行以下步骤:

  1. serviceWorkerRegistrationSyncManager 的 关联 service worker registration
  2. currentTags 为一个新的 sequence
  3. 对于 serviceWorkerRegistrationsync registrations 列表中的每个 registration,将 registration 的关联 tag 添加到 currentTags
  4. currentTags 解决 promise

6.3. sync 事件

SyncEvent

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
Opera?Edge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebViewSamsung Internet5.0+Opera Mobile?

ServiceWorkerGlobalScope/onsync

在所有当前引擎中可用。

Firefox44+Safari11.1+Chrome49+
Opera24+Edge79+
Edge (Legacy)IE
Firefox for Android44+iOS Safari11.3+Chrome for Android49+Android WebView49+Samsung Internet5.0+Opera Mobile24+

SyncEvent/SyncEvent

仅在一个当前引擎中可用。

FirefoxSafariChrome49+
Opera?Edge79+
Edge (Legacy)IE
Firefox for AndroidiOS SafariChrome for Android49+Android WebViewSamsung Internet5.0+Opera Mobile?
partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

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

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

注: SyncEvent 接口表示一个正在触发的 sync registration。如果注册该事件的页面(或 worker)正在运行, 用户代理将在网络连接可用后尽快触发 sync 事件。否则,用户代理应在最早方便的时候运行该事件。 如果 sync 事件失败,用户代理可以决定在其选择的时间重试它。如果用户代理在当前尝试后 不会再尝试此同步,则 lastChance 属性为 true。

响应 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;
      })
    );
  }
});

上面的示例通过向用户显示一个 通知来响应 lastChance。 这要求该源具有显示 通知的权限

在上面的示例中,doImportantThing 是开发者定义的函数。

每当用户代理变为在线时,用户代理应为每个其 registration statependingsync registration 触发 sync 事件。 这些事件可以按任意顺序触发。

要为 sync registration registration 触发 sync 事件,用户代理必须运行以下步骤:

  1. 断言registrationregistration statepending
  2. serviceWorkerRegistration 为与 registration 关联的 service worker registration
  3. 断言registration 存在于 与 serviceWorkerRegistration 关联的 sync registrations 列表中。
  4. registrationregistration state 设置为 firing
  5. 使用 SyncEventserviceWorkerRegistration触发功能事件 "sync", 并具有以下属性:

    SyncEvent/tag

    仅在一个当前引擎中可用。

    FirefoxSafariChrome49+
    Opera?Edge79+
    Edge (Legacy)IE
    Firefox for AndroidiOS SafariChrome for Android49+Android WebViewSamsung Internet5.0+Opera Mobile?
    tag
    registration 关联的 tag

    SyncEvent/lastChance

    仅在一个当前引擎中可用。

    FirefoxSafariChrome49+
    Opera?Edge79+
    Edge (Legacy)IE
    Firefox for AndroidiOS SafariChrome for Android49+Android WebViewSamsung Internet5.0+Opera Mobile?
    lastChance
    如果用户代理在此 sync 事件失败时会重试,则为 false;如果当前尝试后不会再进行进一步尝试,则为 true。

    然后使用 dispatchedEvent 运行以下步骤:

    1. waitUntilPromisedispatchedEventextended lifetime promises全部等待结果。
    2. waitUntilPromise 兑现时,原子地执行以下步骤:
      1. 如果 registration 的 state 为 reregisteredWhileFiring
        1. registration 的 state 设置为 pending
        2. 如果用户代理当前在线,则为 registration 触发 sync 事件
        3. 中止这些步骤的其余部分。
      2. 断言registrationregistration statefiring
      3. serviceWorkerRegistrationsync registration 列表中移除 registration
    3. waitUntilPromise 被拒绝时,或者如果脚本已因 service worker终止而中止,则原子地执行以下步骤:
      1. 如果 registration 的 state 为 reregisteredWhileFiring
        1. registration 的 state 设置为 pending
        2. 如果用户代理当前在线,则为 registration 触发 sync 事件
        3. 中止这些步骤的其余部分。
      2. 如果 dispatchedEventlastChance 属性为 false,则将 registrationregistration state 设置为 waiting, 并并行地执行以下步骤:
        1. 等待一个用户代理定义的时间长度。
        2. 如果 registrationregistration state 不是 waiting,则中止这些子步骤。
        3. registrationregistration state 设置为 pending
        4. 如果用户代理当前在线,则为 registration 触发 sync 事件
      3. 否则,从 serviceWorkerRegistrationsync registrations 列表中移除 registration

用户代理可以对 SyncEvent 的生命周期扩展和执行时间施加比一般 ExtendableEvent 更严格的时间限制。特别是,对于其 lastChance 为 true 的事件,可以有显著缩短的时间限制。

用户代理将基于一些由用户代理定义的启发式规则 重试 sync 事件

一致性

一致性要求由描述性断言和 RFC 2119 术语组合表达。 规范性部分中的关键词 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、 “MAY” 和 “OPTIONAL” 应按 RFC 2119 中所述解释。 但是,为了可读性, 这些词在本规范中并不全部以大写字母出现。

本规范的所有文本都是规范性的, 但明确标记为非规范性的章节、示例和注除外。 [RFC2119]

本规范中的示例由 “for example” 这些词引入, 或通过 class="example" 与规范性文本区分开,如下所示:

这是一个 信息性示例的示例。

信息性注以 “Note” 一词开头, 并通过 class="note" 与规范性文本区分开,如下所示:

注,这是一个信息性注。

索引

本规范定义的 术语

由引用定义的 术语

参考文献

规范性参考文献

[ECMASCRIPT]
ECMAScript 语言规范。URL:https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren; et al. HTML 标准。现行标准。URL:https://html.spec.whatwg.org/multipage/
[PERMISSIONS]
Marcos Caceres; Mike Taylor; Jeffrey Yasskin. 权限。 2021 年 10 月 23 日。WD。URL:https://www.w3.org/TR/permissions/
[PROMISES-GUIDE]
编写使用 Promise 的规范。2015 年 7 月 24 日。W3C TAG 结论。URL:https://www.w3.org/2001/tag/doc/promises-guide
[RFC2119]
S. Bradner. 用于 RFC 中表示要求 级别的关键词。1997 年 3 月。当前最佳实践。URL:https://tools.ietf.org/html/rfc2119
[SECURE-CONTEXTS]
Mike West. 安全上下文。2021 年 9 月 18 日。CR。URL: https://www.w3.org/TR/secure-contexts/
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1。2019 年 11 月 19 日。CR。URL:https://www.w3.org/TR/service-workers-1/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。现行标准。 URL:https://webidl.spec.whatwg.org/

IDL 索引

partial interface ServiceWorkerRegistration {
  readonly attribute SyncManager sync;
};

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

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onsync;
};

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

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