Web 周期性后台同步

社区组草案报告,

此版本:
https://wicg.github.io/periodic-background-sync/index.html
问题跟踪:
GitHub
编辑:
(Google)
(Google)

摘要

本规范描述了一种方法,允许 Web 应用在后台定期同步数据和内容。

本文档状态

本规范由 Web 平台孵化器社区组 发布。 它不是 W3C 标准,也不在 W3C 标准化轨道上。 请注意,根据 W3C Community Contributor License Agreement (CLA),存在有限的选择退出权并适用其他条件。 了解有关 W3C Community and Business Groups 的更多信息。

1. 介绍

本节为非规范性内容。

Web 应用经常在网络不可靠(例如移动电话)且生命周期未知(浏览器可能被终止或用户可能导航离开)的环境中运行。 这使得 Web 应用难以将其内容和状态与服务器保持同步。

此 API 的目的是缩短内容创建与服务器与 Web 应用之间内容同步的时间。它通过允许 Web 应用注册一个定期同步状态和数据的意图,并指定希望的最小间隔来实现。通过 service worker 事件,用户代理随后定期允许 Web 应用下载网络资源并更新状态。

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

1.1. 示例

浏览上下文注册最小间隔为一天的定期后台同步:
async function registerPeriodicNewsCheck() {
  const registration = await navigator.serviceWorker.ready;
  try {
    await registration.periodicSync.register('fetch-news', {
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch {
    console.log('Periodic Sync could not be registered!');
  }
}

periodicsync 事件service worker 中触发时的响应示例:

self.addEventListener('periodicsync', event => {
  event.waitUntil(fetchAndCacheLatestNews());
});

在上面的示例中 fetchAndCacheLatestNews 是一个开发者定义的函数是一个 开发者定义的函数,它从服务器获取最新的新闻文章并将它们本地存储,例如使用 Cache API,以便离线使用。

2. 概念

当为 periodicsync 事件 对某个 周期性同步注册 registration 触发时,如果不存在其关联的 service worker 客户端,且这些客户端的 frame type 为 "top-level", "auxiliary" 或 "nested",则该事件被视为在 后台运行,这些客户端是针对与 registration 关联的 originservice worker registration 存在的情况除外。

3. 对 service worker 注册的扩展

A service worker registration additionally has:

4. 构造

4.1. 周期性同步注册

A 周期性同步注册 consists of:
A service worker registration, which is a service worker registration.

A 标签, which is a DOMString.

注:周期性后台同步(Periodic Background Sync)不与后台同步(Background Sync)共享命名空间,因此同一 origin 可以同时拥有两种类型且使用相同标签的注册。

最小间隔(一个 long long),用于指定周期性同步应发生的最小间隔(以毫秒为单位)。最小间隔 是对用户代理的建议。

注:periodicsync 事件 实际触发的间隔必须大于或等于此值。

An 锚点时间(一个时间戳),表示此periodicsync 事件 上一次为该 周期性同步注册 触发的时间,或初始注册的时间。

A 状态,其值为 "pending"、"firing"、"suspended" 或 "reregistered-while-firing" 之一。初始值为 "pending"。

4.2. 周期性同步调度器

The 周期性同步调度器 负责调度periodicsync 事件 的触发。 响应这些触发器,调度器要么安排延迟处理以在将来的适当时间触发periodicsync 事件,要么取消此类调度。
调度器维护:

The 针对 origin 的有效最小同步间隔 origin(一个 origin),是 针对任何 origin 的最小周期性同步间隔 加上用户代理为该 origin 定义的某个值。

注:用户代理定义的该值可能基于用户与该 origin 的互动程度。每次调用 针对 origin 的有效最小同步间隔 时,这个值可以不同。

调度器会 处理周期性同步注册

注:当没有 活动的周期性同步注册 时,浏览器可能会暂停此处理循环以节省资源。

4.3. 常量

As recommended in § 5 Privacy Considerations and § 6 Resource Usage, the user agent SHOULD also define:

跨 origin 的最小周期性同步间隔 必须大于或等于 针对任何 origin 的最小周期性同步间隔。 如果未定义,这些值被设置为 43200000(以毫秒计,即十二小时)。

注:需要两种频率上限,因为对每个 origin 遵守 针对任何 origin 的最小周期性同步间隔 的限制,仍可能导致浏览器非常频繁地触发 periodicsync 事件。例如,当存在许多针对不同 origin 的 周期性同步注册 时,就可能发生这种情况。跨 origin 的最小周期性同步间隔 确保了触发这些事件的全局上限。

用户代理可以定义一个 最大重试次数,一个数字,允许用于每个 periodicsync 事件。 在选择此值时,用户代理应确保尝试 最大重试次数 所需的时间数量级小于 针对任何 origin 的最小周期性同步间隔。如果未定义,则该值为零。

5. 隐私考虑

5.1. 权限

Periodic Background Sync 仅在为具有 PermissionStatePermissionDescriptor 且其 name"periodic-background-sync" 的权限状态为 granted 时可用。 此外,用户代理应当为用户提供禁用 Periodic Background Sync 的方式。 当 Periodic Background Sync 被禁用时,periodicsync 事件不得向受此权限影响的 周期性同步注册 派发。(参见 § 7.2 响应权限撤销。)

5.2. 位置追踪

periodicsync event 内发起的 fetch 请求在 后台运行 时, 可能在用户离开页面后向服务器暴露客户端的 IP 地址。用户代理应当通过限制重试次数和 periodicsync 事件 的持续时长来减少网站能够追踪用户位置的时间。此外,用户代理还应当通过限制对一个 origin 以及跨 originsperiodicsync 事件 的频率,来限制持久的位置信息追踪。

5.3. 历史泄露

periodicsync 事件 内发起的 fetch 请求在 后台运行 时, 可能会向与创建该 周期性同步注册 所用网络不同的网络中的中间设备泄露关于客户端导航历史的信息。 例如,客户端可能访问了 https://example.com,该站点注册了一个 periodicsync 事件,但根据实现,该事件可能在用户已离开页面并切换网络后才触发。新网络上的中间设备可能会看到 periodicsync 事件 发起的 fetch 请求。由于这些 fetch 请求是使用 HTTPS 发起的,请求内容不会被泄露,但请求的目的地和域名可能会被泄露(通过 DNS 查询和请求的 IP 地址)。为防止这种浏览历史泄露,用户代理可以选择仅在创建该 周期性同步注册 时所用的网络上触发 periodicsync 事件,但这将以降低可用性为代价,因为无法机会性地进行同步。

6. 资源使用

本节为非规范性内容。

网站在处理 periodicsync 事件 时,很可能会从网络下载资源。底层操作系统可能会启动用户代理来派发这些事件,并在预定义的时长内保持其活跃以允许处理事件。两者都会消耗电池。 用户代理应当限制这些事件的持续时间和频率,以在用户已离开页面时控制网站的资源使用。

对于大型资源,应当通过在 后台下载(background fetch) 中注册来下载,使用 BackgroundFetchManager 接口。

此外,用户代理应当考虑用户与该 origin 的参与度,以及任何用户指示临时减少数据消耗(例如数据节省模式),以调整 periodicsync 事件 的频率。

7. 算法

7.1. 处理周期性同步注册

当用户代理启动时,按 并行 运行以下步骤:
  1. 重复执行:

    1. 等待 跨 origin 的最小周期性同步间隔

    2. firedPeriodicSync 为 false。

    3. firedPeriodicSync 为 false 时:

      1. 等待某个用户代理定义的时间段。

        注:这可以用于将不同 周期性同步注册 的同步合并到一次设备唤醒中。

      2. 等待直到处于 在线 状态。

      3. 对于每个不是 已注销service worker registration registration, 将以下步骤 入队registration周期性同步处理队列

        1. origin 为与 periodicSyncRegistrationservice worker registration 相关联的 origin

        2. 如果 time of last fire[origin] + 针对该 origin有效最小同步间隔 大于现在,则 继续

        3. 对于 registration活动周期性同步注册 中的每个 periodic sync registration periodicSyncRegistration

          1. 如果 periodicSyncRegistrationstate 不是 "pending",则 继续

          2. 如果 periodicSyncRegistrationanchor time + periodicSyncRegistrationminimum interval 大于现在,则 继续

          3. firedPeriodicSync 设为 true。

          4. periodicSyncRegistration 触发 periodicsync 事件

7.2. 响应权限撤销

为了对名为 name"periodic-background-sync" 的权限在某个 origin origin 上被撤销做出响应,用户代理必须将以下步骤排入 周期性同步处理队列
  1. 对于 活动周期性同步注册 中的每个 periodic sync registration registration,如果其 service worker registrationorigin 相同:

    1. registrationactive periodic sync registrations 中移除。

8. API 描述

8.1. ServiceWorkerGlobalScope 接口的扩展

partial interface ServiceWorkerGlobalScope {
    attribute EventHandler onperiodicsync;
};

8.2. ServiceWorkerRegistration 接口的扩展

ServiceWorkerRegistration/periodicSync

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

FirefoxNoneSafariNoneChrome80+
OperaNoneEdge80+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android80+Android WebView80+Samsung Internet13.0+Opera MobileNone
[Exposed=(Window,Worker)]
partial interface ServiceWorkerRegistration {
  readonly attribute PeriodicSyncManager periodicSync;
};
A ServiceWorkerRegistration 有一个 periodic sync manager(一个 PeriodicSyncManager)。

属性 periodicSync 的 getter 必须返回该 context objectperiodic sync manager,初始为一个新的 PeriodicSyncManager, 其 service worker registration 为该 context objectservice worker registration

8.3. PeriodicSyncManager 接口

PeriodicSyncManager

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

FirefoxNoneSafariNoneChrome80+
Opera67+Edge80+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android80+Android WebView80+Samsung Internet13.0+Opera Mobile57+
[Exposed=(Window,Worker)]
interface PeriodicSyncManager {
    Promise<undefined> register(DOMString tag, optional BackgroundSyncOptions options = {});
    Promise<sequence<DOMString>> getTags();
    Promise<undefined> unregister(DOMString tag);
};

dictionary BackgroundSyncOptions {
    [EnforceRange] unsigned long long minInterval = 0;
};
A PeriodicSyncManager 有一个 service worker registration(一个 service worker registration)。

PeriodicSyncManager/register

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

FirefoxNoneSafariNoneChrome80+
Opera67+Edge80+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android80+Android WebView80+Samsung Internet13.0+Opera Mobile57+
The register(tag, options) 方法在被调用时,必须返回一个新的 promise 并将以下步骤 入队周期性同步处理队列
  1. serviceWorkerRegistration 为与该上下文对象的 PeriodicSyncManager 相关联的 service worker registration

  2. 如果 serviceWorkerRegistrationactive worker 为 null,则用 InvalidStateError 拒绝 promise 并中止这些步骤。

  3. 如果名为 "periodic-background-sync" 的权限的 PermissionState 不是 granted, 则用 NotAllowedError 拒绝 promise 并中止这些步骤。

  4. isBackground(布尔值)为 true。

  5. 对于与 serviceWorkerRegistrationorigin 关联的每个 service worker clientsclient

    1. 如果 clientframe type 为 "top-level" 或 "auxiliary",则将 isBackground 设为 false。

  6. 如果 isBackground 为 true,则用 InvalidAccessError 拒绝 promise 并中止这些步骤。

  7. currentRegistration 为在 serviceWorkerRegistrationactive periodic sync registrations 中其 tag 等于 tagperiodic sync registration(若存在),否则为 null。

  8. 如果 currentRegistration 为 null:

    1. newRegistration 为一个新的 periodic sync registration

    2. newRegistrationtag 设为 tag

    3. newRegistrationminimum interval 设为 optionsminInterval 成员。

    4. newRegistrationstate 设为 "pending"。

    5. newRegistrationservice worker registration 设为 serviceWorkerRegistration

    6. newRegistrationanchor time 设为表示当前时间的时间戳。

    7. newRegistration 添加到 serviceWorkerRegistrationactive periodic sync registrations

    8. Resolve promise

  9. 否则:

    1. 如果 currentRegistrationminimum intervaloptionsminInterval 成员不同:

      1. currentRegistrationminimum interval 设为 optionsminInterval 成员。

    2. 否则,如果 currentRegistrationstate 为 "firing",则将 serviceWorkerRegistrationstate 设为 "reregistered-while-firing"。

    3. Resolve promise

PeriodicSyncManager/getTags

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

FirefoxNoneSafariNoneChrome80+
Opera67+Edge80+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android80+Android WebView80+Samsung Internet13.0+Opera Mobile57+
The getTags() 方法在被调用时,必须返回一个新的 promise 并将以下步骤 入队周期性同步处理队列
  1. serviceWorkerRegistration 为与该上下文对象的 PeriodicSyncManager 相关联的 service worker registration

  2. currentTags 为一个新的 列表

  3. 对于 serviceWorkerRegistration 的每个 active periodic sync registrations 中的 registration,将 registrationtag 追加currentTags

  4. Resolve promise 并返回 currentTags

PeriodicSyncManager/unregister

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

FirefoxNoneSafariNoneChrome80+
Opera67+Edge80+
Edge (Legacy)NoneIENone
Firefox for AndroidNoneiOS SafariNoneChrome for Android80+Android WebView80+Samsung Internet13.0+Opera Mobile57+
The unregister(tag) 方法在被调用时,必须返回一个新的 promise 并将以下步骤 入队周期性同步处理队列
  1. serviceWorkerRegistration 为与该上下文对象的 PeriodicSyncManager 相关联的 service worker registration

    1. currentRegistration 为在 serviceWorkerRegistrationactive periodic sync registrations 中其 tag 等于 tagperiodic sync registration(若存在),否则为 null。

    2. 如果 currentRegistration 非空,则从 serviceWorkerRegistrationactive periodic sync registrations 中移除 currentRegistration

    3. Resolve promise

8.4. periodicsync 事件

PeriodicSyncEvent/PeriodicSyncEvent

仅有一个主流引擎支持。

Firefox不支持Safari不支持Chrome80+
Opera67+Edge80+
Edge (旧版)不支持IE不支持
Android 版 Firefox不支持iOS Safari不支持Android 版 Chrome80+Android WebView80+三星浏览器13.0+Opera Mobile57+

PeriodicSyncEvent

仅有一个主流引擎支持。

Firefox不支持Safari不支持Chrome80+
Opera67+Edge80+
Edge (旧版)不支持IE不支持
Android 版 Firefox不支持iOS Safari不支持Android 版 Chrome80+Android WebView80+三星浏览器13.0+Opera Mobile57+
dictionary PeriodicSyncEventInit : ExtendableEventInit {
    required DOMString tag;
};
[Exposed=ServiceWorker]
interface PeriodicSyncEvent : ExtendableEvent {
    constructor(DOMString type, PeriodicSyncEventInit init);
    readonly attribute DOMString tag;
};

PeriodicSyncEvent/tag

仅有一个主流引擎支持。

Firefox不支持Safari不支持Chrome80+
Opera67+Edge80+
Edge (旧版)不支持IE不支持
Android 版 Firefox不支持iOS Safari不支持Android 版 Chrome80+Android WebView80+三星浏览器13.0+Opera Mobile57+
PeriodicSyncEvent 拥有一个 tag(一个 tag)。 tag 属性必须返回其初始化时的值。

8.4.1. 触发 periodicsync 事件

Note: 用户代理可以对 PeriodicSyncEvent 的生命周期扩展和执行时间施加比一般 ExtendableEvent 更严格的时间限制。尤其是对 PeriodicSyncEvent 的任何重试可能有明显缩短的时间限制。
要为 periodic sync registration registration 触发 periodicsync 事件,用户代理必须执行以下步骤:
  1. serviceWorkerRegistrationregistrationservice worker 注册

  2. 如果 registration 不再属于 serviceWorkerRegistration活跃 periodic sync 注册,则终止这些步骤。

  3. 断言registrationstate 为 "pending"。

  4. 令 retryCount 为 0。

  5. registrationstate 设为 "firing"。

  6. 当条件为真,循环:

    1. continue 为 false。

    2. success 为 false。

    3. 触发功能事件 "periodicsync",使用 PeriodicSyncEventserviceWorkerRegistration,其 tag 设置为 registrationtag。记 dispatchedEvent,类型为 ExtendableEvent,表示派发出的 periodicsync 事件 并使用 dispatchedEvent 执行以下步骤:

    4. waitUntilPromise等待全部 dispatchedEvent延长生命周期 promises 的结果。

    5. 对于 fulfilled(满足) waitUntilPromise 时,执行以下步骤:

      1. success 设为 true。

      2. continue 设为 true。

    6. 对于 rejection(拒绝) waitUntilPromise 时,执行以下步骤:

      1. continue 设为 true。

    7. 并行

      1. 等待 continue 为 true。

      2. origin 为与 registrationservice worker 注册 关联的 源(origin)

      3. 如果 success 为 true,则将 key 为 origin最后触发时间 设置为当前时间。

      4. 如果 success 为 true,或 retryCount 大于 最大重试次数,或 registration 的 state 为 "reregistered-while-firing",则:

        1. registrationstate 设为 "pending"。

        2. registrationanchor time 设置为当前时间戳。

        3. 终止这些步骤。

    8. retryCount 增加 1。

    9. 等待一个基于 retryCount 的小退避时间。

一致性

文档约定

一致性要求由描述性断言和 RFC 2119 术语结合表达。

本规范的规范性部分中的关键词 “MUST(必须)”、“MUST NOT(禁止)”、“REQUIRED(必需)”、“SHALL(应当)”、“SHALL NOT(不得)”、“SHOULD(建议)”、“SHOULD NOT(不建议)”、“RECOMMENDED(推荐)”、“MAY(可以)” 和 “OPTIONAL(可选的)” 应按照 RFC 2119 所述解释。 但为提升可读性, 在本规范中这些单词不都用大写字母表示。

除非特别标注为非规范性、示例或注释的部分外,本规范的所有文本均为规范性内容。[RFC2119]

本规范中的示例会以 “例如” 开头, 或以 class="example" 的形式与规范性文本区分, 例如:

这是信息性示例。

信息性注释以 “Note” 开头, 并以 class="note" 与规范性文本区分, 例如:

注:这是一条信息性注释。

一致性算法

以命令式短语表达的算法要求(如 "strip any leading space characters" 或 "return false and abort these steps") 应按照用来引入算法的关键词(“must”,“should”,“may”等)的含义来解释。

以算法或具体步骤形式表达的一致性要求,可以用任何方式实现,只要最终结果等价。 本规范中的算法特别注重易读性, 并非为了高性能实现, 鼓励实现者进行优化。

索引

本规范定义的术语

引用中定义的术语

参考文献

规范性引用

[BACKGROUND-FETCH]
后台获取(Background Fetch). cg-draft. URL: https://wicg.github.io/background-fetch/
[DOM]
Anne van Kesteren. DOM 标准. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Mounir Lamouri; Marcos Caceres; Jeffrey Yasskin. Permissions. 2020年7月20日. WD. URL: https://www.w3.org/TR/permissions/
[RFC2119]
S. Bradner. RFC 中用于指明需求级别的关键词. 1997年3月. 最佳实践. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS-1]
Alex Russell; 等. Service Workers 1. 2019年11月19日. CR. URL: https://www.w3.org/TR/service-workers-1/
[WEB-BACKGROUND-SYNC]
Web 后台同步(Web Background Synchronization). cg-draft. URL: https://wicg.github.io/background-sync/spec/
[WebIDL]
Boris Zbarsky. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/

IDL 索引

partial interface ServiceWorkerGlobalScope {
    attribute EventHandler onperiodicsync;
};

[Exposed=(Window,Worker)]
partial interface ServiceWorkerRegistration {
  readonly attribute PeriodicSyncManager periodicSync;
};

[Exposed=(Window,Worker)]
interface PeriodicSyncManager {
    Promise<undefined> register(DOMString tag, optional BackgroundSyncOptions options = {});
    Promise<sequence<DOMString>> getTags();
    Promise<undefined> unregister(DOMString tag);
};

dictionary BackgroundSyncOptions {
    [EnforceRange] unsigned long long minInterval = 0;
};

dictionary PeriodicSyncEventInit : ExtendableEventInit {
    required DOMString tag;
};

[Exposed=ServiceWorker]
interface PeriodicSyncEvent : ExtendableEvent {
    constructor(DOMString type, PeriodicSyncEventInit init);
    readonly attribute DOMString tag;
};