后台获取

社区组草案报告,

此版本:
https://wicg.github.io/background-fetch/
问题追踪:
GitHub
规范中的内联问题
编辑者:
(谷歌)
(谷歌)

摘要

一个处理后台大规模上传/下载的 API,提供用户可见性。

本文档的状态

该规范由 Web 平台孵化社区组 发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C 社区贡献者许可协议(CLA),有有限的选择退出权,其他条件适用。 了解更多关于 W3C 社区和业务组的信息。

1. 简介

Service Worker 能够获取和缓存资源,其大小仅受限于 源存储。然而,如果用户离开该站点或关闭浏览器,Service Worker可能会被 终止。即使有传递给 waitUntil() 的未完成 Promise——如果它在几分钟内没有解析,浏览器可能会认为这是对 Service Worker 的滥用,并终止该进程。

这对电池寿命和隐私非常有利,但却使得下载和缓存诸如播客和电影等大文件,以及上传视频和图片变得困难。

本规范旨在:

2. 领域

所有平台对象均在 上下文对象的 相关领域 中创建,除非另有规定。

3. 基础设施

如果用户代理认为某资源可能很快会变得可用,则该资源被视为暂时不可用。原因可能包括:

后台 获取任务源是一个任务 源。

要在可选的 eventLoop (一个事件循环,默认值为调用者上下文对象的相关设置对象的负责事件循环)上,使用 steps (步骤)将一个 bgfetch 任务排队,即在 eventLoop 上使用后台获取任务 源将一个任务排队以运行 steps。

3.1. Service Worker 注册的扩展

Service Worker 注册 另外包含:

3.2. 后台获取

后台获取 包含以下内容:

要获取 后台获取 的 (bgFetch) 已存储主体字节总数,请执行以下步骤:
  1. 令 total 为 0。

  2. 对于每一个 record,在 bgFetch 的 记录中:

    1. 通过 长度,增加 record 的 响应数据 的 字节 到 total。

  3. 返回 total。

3.2.1. 显示

对于给定的environment(一个环境设置对象),要显示一个后台获取(bgFetch),用户代理必须 呈现一个遵循以下规则的用户界面:

令 permission 为 PermissionDescriptor 的权限状态,其 name 为 "background-fetch" 且环境为 environment。如果 permission 是 "prompt",那么用户代理可以在本算法开始时设置 bgFetch 的暂停标志。 用户代理应允许用户接受后台获取(取消设置 bgFetch 的暂停 标志),或者拒绝后台获取(设置 bgFetch 的全部中止 标志)。用户代理还可以提供始终允许和始终拒绝选项,这些选项可作为此权限的关于用户意图的新信息来使用。

如果用户处于按流量计费的连接上,或后台获取是在后台启动的,用户代理也可以考虑设置 bgFetch 的暂停标志。

3.3. 后台获取记录

后台获取记录 包含以下内容:

3.4. 后台获取响应

后台获取响应 包含以下内容:

响应可以被暴露,如果其结果为 空字符串、"success" 或 "bad-status"。

4. 算法

4.1. 执行后台获取

注意: 这是管理后台获取的“后台”部分的算法。每个 后台获取 只运行该算法的一个实例。

要为 bgFetch(一个后台获取)执行一次 后台获取,运行以下步骤:
  1. 令 swRegistration 为 bgFetch 的服务工作线程注册。

  2. 令 settledFetches 为 0。

  3. 令 immediateFailure 为 false。

  4. 令 failureReason 为空字符串。

  5. 对 bgFetch 的记录中的每个 record,逐个并行运行以下步骤:

    1. 为 bgFetch 和 record完成 一条记录。

    2. 令 result 为 record 的响应数据的结果。

    3. 如果 failureReason 不是空字符串:

      1. 断言:result 不是 "redundant"。

      2. 将 failureReason 设为 result。

    4. 将 settledFetches 增加 1。

    5. 如果 result 是 "download-total-exceeded",则将 immediateFailure 设为 true。

  6. 等待直到 settledFetches 等于 bgFetch 的记录的大小,或者 immediateFailure 为 true。

  7. 如果 immediateFailure 为 true,则设置 bgFetch 的全部中止 标志。

    注:完成一条记录算法会侦听该标志,并在其被设置时终止获取。

  8. 将以下步骤入队到 swRegistration 的活动 后台获取编辑队列:

    1. 令 activeBgFetches 为 swRegistration 的活动后台 获取。

    2. 令 id 为 bgFetch 的id。

    3. 如果 activeBgFetches 包含后台获取 bgFetch,则移除 activeBgFetches[id]。

    4. 否则,将 failureReason 设为 "aborted"。

      注:这处理了一个竞态条件:abort() 已被成功调用,但其中一个获取同时失败。如果我们已经从 abort() 返回了 true,这可确保我们触发相关的中止事件。

    5. 如果 failureReason 不是空字符串,则:

      1. 将 bgFetch 的结果设为 "failure"。

      2. 将 bgFetch 的失败原因设为 failureReason。

    6. 否则,将 bgFetch 的结果设为 "success"。

    7. 为 bgFetch更新后台获取实例。

    8. 令 eventName 为空字符串。

    9. 令 eventConstructor 为 null。

    10. 如果 failureReason 是 "aborted",则:

      1. 将 eventName 设为 "backgroundfetchabort"。

      2. 将 eventConstructor 设为 BackgroundFetchEvent。

    11. 否则,如果 failureReason 不是空字符串,则:

      1. 将 eventName 设为 "backgroundfetchfail"。

      2. 将 eventConstructor 设为 BackgroundFetchUpdateUIEvent。

    12. 否则:

      1. 将 eventName 设为 "backgroundfetchsuccess"。

      2. 将 eventConstructor 设为 BackgroundFetchUpdateUIEvent。

    13. 在 swRegistration 上使用 eventConstructor,以 eventName 为名称触发一个功能事件, 并带有以下属性:

      registration

      在事件对象的相关 Realm中,为 bgFetch获取一个 BackgroundFetchRegistration 实例的结果。

      然后将以下步骤与 dispatchedEvent并行运行:

      1. 等待直到 dispatchedEvent 不再活动。

        ServiceWorker/1348。

      2. 取消设置 bgFetch 的记录可用 标志。

      3. 为 bgFetch更新后台获取 实例。

4.2. 完成记录

注意: 此算法管理 后台获取记录 的获取。每个 后台获取记录 启动此算法的一个实例,但它会递归调用以重试获取或获取部分响应的下一部分。

要为 bgFetch(一个 后台获取)和 record(一个 后台获取记录)完成一条记录,运行 以下步骤:
  1. 令 responseData 为 record 的 响应数据。

  2. 如果 bgFetch 的 下载总量 不为 0,则令 downloadTotal 为该值;否则为 infinity。

  3. 等待 bgFetch 的 暂停标志 被取消设置。

  4. 令 request 为 record 的 请求 的一个副本。

    注:此时该请求已完整保存在存储中,即使它最初是一个流。

  5. 设置 request 的 keepalive 标志。

  6. 将 request 的 service-workers 模式 设为 "none"。

  7. 令 rangeStart 为 responseData 的 字节的长度。

  8. 如果 rangeStart 不为 0,则使用 rangeStart 向 request 添加一个 range 标头。

    注:如果 rangeStart 为 0,则发出普通请求。这允许初始请求利用内容编码,因为 Accept-Encoding: identity 会被添加到带有 range 标头的请求中。

  9. 令 fetchAttemptComplete 为 false。

  10. 令 lastTransmittedSize 为 0。

  11. 获取 request。

    本步骤剩余部分使用了 fetch“回调”,而这当前会将任务排队。在这里这既不理想也不可行,因此我们假定任务不会被排队。(问题)

    要为 request 处理请求体,运行以下步骤:

    1. 令 transmittedSize 为 request 的 主体的已传输 字节。

    2. 将 bgFetch 的 已上传 增加 transmittedSize 减去 lastTransmittedSize 的值。

    3. 将 lastTransmittedSize 设为 transmittedSize。

    4. 为 bgFetch更新后台获取 实例。

    要为 response 处理响应,运行以下步骤:

    1. 如果 response 是一个 网络错误,则:

      1. 如果资源暂时不可用,并且 request 的 方法是 `GET`,则 等待直到资源不再暂时不可用,然后将 fetchAttemptComplete 设为 true 并中止这些步骤。

        注:如果 request 的 方法不是 `GET`, 重新发出该请求可能会带来不希望出现的副作用。如果将来有标准的方法来恢复请求,将会在这里采用。

      2. 如果 response 是一个 已中止的网络错误,则 将 responseData 的 结果 设为 "aborted",否则设为 "fetch-error"。

      3. 将 fetchAttemptComplete 设为 true,并中止这些步骤。

    2. 如果 response 的 状态是 206,则:

      1. 如果针对 rangeStart、response 以及 responseData 的 响应验证部分响应返回 invalid,则:

        1. 将 responseData 的 结果 设为 "fetch-error"。

        2. 将 fetchAttemptComplete 设为 true。

        3. 终止正在进行的 fetch,并中止这些步骤。

    3. 否则:

      1. 将 responseData 的 结果 设为 "redundant"。

      2. 将 responseData 设为一个新的 后台获取响应。

      3. 将 record 的 响应数据 设为 responseData。

        注:创建 记录对象算法可能持有对先前后台获取响应的引用。

      4. 为 bgFetch更新后台获取 实例。

    4. 如果 rangeStart 为 0,或者 response 的 状态不是 206,则将 responseData 的 响应 设为 response 的一个副本,但其 主体除外。

    5. 令 stream 为 response 的 主体的 流。

    6. 每当从 stream 传输了一个或多个字节时,令 bytes 为已传输字节并运行以下步骤:

      1. 如果 bgFetch 的 已存储主体字节 总量 加上 bytes 的大小大于 downloadTotal,则:

        1. 取消 stream。

        2. 将 responseData 的 结果 设为 "download-total-exceeded",将 fetchAttemptComplete 设为 true,并中止这些步骤。

      2. 将 bytes 追加到 responseData 的 字节。

      3. 如果上一步由于超出配额限制而失败,则将 responseData 的 结果 设为 "quota-exceeded",将 fetchAttemptComplete 设为 true,并 中止这些步骤。

      4. 为 bgFetch更新后台获取 实例。

    7. 如果在任意时刻 stream 的字节传输正常完成,则:

      1. 如果 response 的 状态是 206,则:

        1. 令 firstBytePos、lastBytePos 和 completeLength 为从 response 中提取 content-range 值的结果。

        2. 如果 completeLength 不为 null,且等于 responseData 的 字节的长度,则将 responseData 的 结果 设为 "success"。

          注:虽然我们请求的是整个资源,或者资源的剩余部分, 但服务器可能并未返回剩余部分,在这种情况下我们需要再发出一个请求。

      2. 否则,如果 response 的 状态不是 ok 状态,则将 responseData 的 结果 设为 "bad-status"。

      3. 否则,将 responseData 的 结果 设为 "success"。

      4. 将 fetchAttemptComplete 设为 true。

    8. 如果在任意时刻 stream 变为出错,则:

      1. 如果资源暂时不可用,并且 request 的 方法是 `GET`,则 等待直到资源不再暂时不可用,然后将 fetchAttemptComplete 设为 true。

      2. 否则,将 responseData 的 结果 设为 "fetch-error" 并将 fetchAttemptComplete 设为 true。

  12. 令 result 为空字符串。

  13. 运行以下步骤,但当 bgFetch 的暂停标志被设置时中止或 暂停 标志 或全部中止标志被设置:

    1. 等待 fetchAttemptComplete 为 true。

    2. 将 result 设为 responseData 的 结果。

  14. 如果已中止,则:

    1. 如果 bgFetch 的 暂停标志 被设置,则断言 request 的 方法是 `GET`。

    2. 如果 bgFetch 的 全部中止标志 被设置,则将 responseData 的 结果 设为 "aborted"。

    3. 将 result 设为 responseData 的 结果。

      注:现在会存储该结果,因为终止 fetch 可能会改变结果。

    4. 终止正在进行的 fetch。

  15. 如果 result 是空字符串,则为 bgFetch 和 record完成一条记录。

4.3. 更新后台获取实例

要 更新后台获取实例,用于 bgFetch(一个 后台获取),请将以下步骤排队到 bgFetch 的 更新处理队列:
  1. 令 downloaded 为 bgFetch 的 已存储主体字节总量。

  2. 令 uploaded 为 bgFetch 的 已上传。

  3. 令 result 为 bgFetch 的 结果。

  4. 令 failureReason 为 bgFetch 的 失败原因。

  5. 如果 bgFetch 的 记录可用标志 被设置,则令 recordsAvailable 为 true,否则为 false。

  6. 对于每个 环境设置对象 env,其 origin 等于 bgFetch 的 Service Worker 注册 的 范围 URL 的 origin,排队一个后台获取任务,用于 env 的 负责的事件循环,运行以下步骤:

    1. 令 bgFetchRegistration 为 BackgroundFetchRegistration 的实例,位于 相关领域,其 后台获取 等于 bgFetch,如果不存在则为 null。

      注意: 每个环境最多只有一个,因 获取 BackgroundFetchRegistration 实例 算法所致。

    2. 如果 bgFetchRegistration 为 null,则中止这些步骤。

    3. 如果 recordsAvailable 为 false,且 bgFetchRegistration 的 记录可用标志 被设置,则取消设置 bgFetchRegistration 的 记录可用标志。

    4. 如果 bgFetchRegistration 的 结果 不是空字符串,则中止这些步骤。

      注意: 这可以防止在后台获取已结算后报告进度。当操作已中止,但某些获取尚未终止时,这是可能的。

    5. 如果以下条件全为 true:

      • bgFetchRegistration 的 已下载 等于 downloaded。

      • bgFetchRegistration 的 已上传 等于 uploaded。

      • bgFetchRegistration 的 结果 等于 result。

      • bgFetchRegistration 的 失败原因 等于 failureReason。

      则中止这些步骤。

    6. 将 bgFetchRegistration 的 已下载 设置为 downloaded。

    7. 将 bgFetchRegistration 的 已上传 设置为 uploaded。

    8. 将 bgFetchRegistration 的 结果 设置为 result。

    9. 将 bgFetchRegistration 的 失败原因 设置为 failureReason。

    10. 触发一个事件,名为 "progress",在 bgFetchRegistration 上。

    我需要像鼠标移动事件那样防抖此事件。

4.4. 触发后台获取点击事件

要触发后台获取点击事件,用于 bgFetch(一个 后台获取),触发一个功能性事件,命名为 "backgroundfetchclick",使用 BackgroundFetchEvent,在 bgFetch 的 Service Worker 注册上,具备以下属性:
registration

通过 获取 BackgroundFetchRegistration 实例 的结果,应用于 bgFetch,在事件对象的 相关领域中。

4.5. 获取 BackgroundFetchRegistration 实例

注意: 该算法确保为给定的 BackgroundFetchRegistration 实例返回相同的结果,用于整个 BackgroundFetchManager 生命周期中的给定 后台获取。浏览器可以优化此过程,只要无法通过相等性、扩展或弱关联数据来检测到为给定的后台获取创建了多个实例即可。

要在 realm(一个Realm)中,为 bgFetch(一个后台获取)获取一个 BackgroundFetchRegistration 实例,运行以下步骤:
  1. 令 instancesMap 为此 realm 内唯一一个 BackgroundFetchManager 实例的BackgroundFetchRegistration 实例。

  2. 如果 instancesMap[bgFetch] 存在,则 返回 instancesMap[bgFetch]。

  3. 令 instance 为在 realm 中一个新的 BackgroundFetchRegistration, 并将其后台获取设为 bgFetch。

  4. 将 instancesMap[bgFetch] 设为 instance。

  5. 返回 instance。

4.6. 验证部分响应

注意:该算法检查部分响应是否合理匹配所请求的内容,并可选地检查是否应与先前的响应合并。

要针对 expectedRangeStart(一个数值)、partialResponse(一个 响应)以及可选的 previousResponse(一个响应或 null,除非另有说明否则为 null)验证部分响应,运行这些步骤:
  1. 断言:partialResponse 的状态是 206。

  2. 令 responseFirstBytePos、responseLastBytePos 以及 responseCompleteLength 为从 partialResponse提取 content-range 值所得的结果。如果失败,则返回 invalid。

  3. 如果 responseFirstBytePos 不等于 expectedRangeStart,则返回 invalid。

  4. 如果 previousResponse 不为 null,则:

    1. 对于 « `ETag`、`Last-Modified` » 中的 headerName:

      1. 如果 previousResponse 的标头列表包含 headerName,并且 previousResponse 的标头列表中 headerName 的合并值不等于 partialResponse 的标头列表中 headerName 的合并值,则返回 invalid。

    2. 如果 previousResponse 的状态是 206,则:

      1. 令 previousResponseFirstBytePos、 previousResponseLastBytePos 以及 previousResponseCompleteLength 为从 previousResponse提取 content-range 值所得的结果。如果失败,则返回 invalid。

      2. 如果 previousResponseCompleteLength 不为 null,且 responseCompleteLength 不等于 previousResponseCompleteLength,则返回 invalid。

  5. 返回 valid。

4.7. 提取内容范围值

注意:该算法将 `Content-Range` 解析为 单字节内容范围 并提取值。

要从一个 response(一个响应)中提取 content-range 值,运行以下步骤:
  1. 如果 response 的标头列表不包含 `Content-Range`,则返回 failure。

  2. 令 contentRangeValue 为 response 的标头列表中第一个标头的值,其名称与 `Content-Range` 按字节大小写不敏感地匹配。

  3. 如果按照单字节 content-range解析 contentRangeValue 失败,则返回 failure。

  4. 令 firstBytePos 为将 contentRangeValue 按单字节 content-range解析时,名为 first-byte-pos 的部分,并将其解析为整数后的值。

  5. 令 lastBytePos 为将 contentRangeValue 按单字节 content-range解析时,名为 last-byte-pos 的部分,并将其解析为整数后的值。

  6. 令 completeLength 为将 contentRangeValue 按单字节 content-range解析时,名为 complete-length 的部分。

  7. 如果 completeLength 是 "*",则将 completeLength 设为 null; 否则将 completeLength 设为把 completeLength 解析为整数后的值。

  8. 返回 firstBytePos、lastBytePos 和 completeLength。

解析为整数 infra/189。

4.8. 创建记录对象

注意:该算法为 后台获取记录 创建平台对象。它还管理从存储的 字节流式传输 响应。此时后台获取操作可能仍在进行中。

要在 realm(一个Realm)中,从 records(一个列表,其元素为后台获取记录)创建记录对象,运行以下步骤:

所有平台对象都必须在 realm 中创建。

  1. 令 recordObjects 为一个新的列表。

  2. 对 records 中的每个 record逐一执行:

    1. 令 responseData 为 record 的响应数据。

    2. 令 recordObject 为一个新的 BackgroundFetchRecord。

    3. 将 recordObject 的 responseReady 设为一个新的 promise。

    4. 令 requestObject 为一个新的 Request 对象,并设置以下内容:

      Request

      record 的请求的一个副本,包括其主体。

      Headers

      一个新的 Headers 对象,它与此 Request 的 request 的标头列表相关联。

    5. 将 recordObject 的 request 设为 requestObject。

    6. 令 transmittedBytes 为 0。

    7. 令 stream 为一个新的可读流,其 pull 动作返回一个新的 promise promise,并并行运行以下步骤:

      1. 等待直到 responseData 的 字节的长度大于 transmittedBytes,或者 responseData 的结果不为空字符串。

      2. 令 bytes 为 null。

      3. 如果 responseData 的 字节的长度大于 transmittedBytes,并且 responseData 可以被暴露,则:

        1. 将 bytes 设为由用户代理决定的 responseData 的字节切片,起始 偏移为 transmittedBytes。

          注:这允许用户代理以合适的速率 从存储中流式传输该资源。

        2. 将 transmittedBytes 增加 bytes 的长度。

      4. 在 stream 的相关设置对象的负责事件循环中,使用网络任务源将一个任务排队以运行以下步骤:

        1. 如果 bytes 不为 null,则:

          1. 令 array 为一个新的 Uint8Array, 它包装一个由 bytes 构成的新的 ArrayBuffer。

          2. 将 array入队 到 stream 中。

        2. 如果 responseData 可以被暴露, responseData 的结果不为空字符串, 并且 transmittedBytes 等于 responseData 的字节的长度,则关闭 stream。

        3. 否则,如果 responseData 的结果是 "aborted",则用一个 AbortError DOMException 使 stream出错。

        4. 否则,如果 responseData 不可以被暴露,则用 一个 TypeError 使 stream出错。

        5. 解决 promise。

    8. 并行运行以下步骤:

      1. 等待直到 responseData 的响应不再为 not-null。

      2. 如果 responseData 可以被暴露,则:

        1. 令 response 为 responseData 的响应的一个副本。

        2. 从 response 的标头列表中删除 `Content-Range`。

        3. 从 response 的标头列表中删除 `Content-Length`。

        4. 令 body 为一个新的主体,其流设为 stream。

        5. 将 response 的主体设为 body。

        6. 在 recordObject 的相关设置 对象的负责事件循环 中,使用网络任务源将一个任务排队以 运行以下步骤:

          1. 令 responseObject 为一个新的 Response 对象,并设置以下内容:

            Response

            response。

            Headers

            一个新的 Headers 对象,它与此 Response 的 response 的 标头 列表相关联。

          2. 解决 recordObject 的 responseReady 并以 responseObject 作为其值。

      3. 否则,如果 responseData 的 结果是 "aborted",则使用一个 AbortError DOMException拒绝 recordObject 的 responseReady。

      4. 否则,使用一个 TypeError拒绝 recordObject 的 responseReady。

    9. 将 recordObject追加到 recordObjects。

  3. 返回 recordObjects。

4.9. 包含后台提取

要确定 map (一个 map) 是否 包含后台提取 bgFetch (一个 后台提取),请执行以下步骤:
  1. 让 id 成为 bgFetch 的 id。

  2. 如果 map [id] 不 存在,则返回 false。

  3. 如果 map [id] 不等于 bgFetch,则返回 false。

  4. 返回 true。

要确定 map (一个 map) 是否 不包含后台提取 bgFetch (一个 后台提取),请执行以下步骤:
  1. 如果 map 包含后台提取 bgFetch,则返回 false。

  2. 返回 true。

5. 标头语法

以下是用于 HTTP ABNF 的 单字节内容范围 语法:

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

注意: 这是 RFC 7233 允许的子集。

以上内容的铁路图:

"bytes=" first-byte-pos digit /first-byte-pos "/" last-byte-pos digit /last-byte-pos "/" complete-length "*" digit /complete-length

6. API

6.1. 对 ServiceWorkerGlobalScope 的扩展

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

6.1.1. 事件

以下是 事件处理程序(及其对应的 事件处理程序事件类型),这些程序作为 事件处理程序 IDL 属性 必须被所有实现了 ServiceWorker 接口的对象支持:

事件处理程序事件类型 事件处理程序 接口
backgroundfetchsuccess onbackgroundfetchsuccess BackgroundFetchUpdateUIEvent
backgroundfetchfail onbackgroundfetchfail BackgroundFetchUpdateUIEvent
backgroundfetchabort onbackgroundfetchabort BackgroundFetchEvent
backgroundfetchclick onbackgroundfetchclick BackgroundFetchEvent

6.2. 对 ServiceWorkerRegistration 的扩展

partial interface ServiceWorkerRegistration {
  readonly attribute BackgroundFetchManager backgroundFetch;
};

一个 ServiceWorkerRegistration 拥有一个 背景获取管理器 (即 BackgroundFetchManager), 初始时这是一个新的 BackgroundFetchManager,其 Service Worker注册 是 上下文对象 的 Service Worker注册。

backgroundFetch 属性的 getter 应返回 上下文对象 的 背景获取管理器。

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();
};

dictionary BackgroundFetchUIOptions {
  sequence<ImageResource> icons;
  DOMString title;
};

dictionary BackgroundFetchOptions : BackgroundFetchUIOptions {
  unsigned long long downloadTotal = 0;
};

一个 BackgroundFetchManager 包含:

6.3.1. fetch()

fetch(id, requests, options) 方法执行以下步骤:
  1. 将 registration 设为 上下文对象的 Service Worker注册。

  2. 将 records 设为一个新的 列表。

  3. 将 uploadTotal 设为 0。

  4. 如果 requests 是一个 RequestInfo,将 requests 设为 « requests »。

  5. 如果 requests 是 空的,则返回 一个被拒绝的 promise,并带有 TypeError。

  6. 迭代 requests 的每个 request:

    1. 将 internalRequest 设为 请求,其结果为使用 request 调用 Request 构造函数。如果抛出异常,则返回 一个被拒绝的 promise 并带有异常。

    2. 如果 internalRequest 的 模式 为 "no-cors",则返回 一个被拒绝的 promise 并带有 TypeError。

    3. 将 internalRequest 的 client 设为 null。

    4. 将 record 设为一个新的 背景获取记录。

    5. 将 record 的 请求 设为 internalRequest。

    6. 将 record 添加到 records。

  7. 将 promise 设为 一个新的 promise。

  8. 将以下步骤入队到 registration 的 活动背景获取编辑队列:

    1. 将 permission 设为 权限状态,使用 PermissionDescriptor,其 name 为 "background-fetch",并使用 上下文对象的 相关设置对象。

    2. 如果 permission 为 "denied",则拒绝 promise 并带有 NotAllowedError DOMException 并中止这些步骤。

    3. 将 bgFetchMap 设为 registration 的 活动背景获取。

    4. 如果 registration 的 活动工作者 为 null,则拒绝 promise 并带有 TypeError 并中止这些步骤。

    5. 如果 bgFetchMap[id] 存在,拒绝 promise 并带有 TypeError 并中止这些步骤。

    6. 将 requestBodiesRemaining 设为 requests 的 大小。

    7. 将 requestReadFailed 设为 false。

    8. 迭代 requests 的每个 request:

      1. 如果 request 的 body 为 null,则 继续。

      2. 将 stream 设为 request 的 body 的 流。

      3. 并行 运行这些步骤:

        1. 运行这些步骤,但 在 requestReadFailed 为 true 时终止:

          1. 等待 request 的 body。

          2. 如果 stream 已 错误,则将 requestReadFailed 设为 true。

          注意:这确保在解决之前,我们有请求字节的副本。

        2. 如果中止 且 stream 为 可读,则使用 AbortError 和 DOMException 使 stream 错误并中止这些步骤。

        3. 将 uploadTotal 增加 request 的 body 的 总字节数。

        4. 将 requestBodiesRemaining 减 1。

    9. 如果在任何时候,存储 requests 因超出配额限制而失败,则 拒绝 promise 并带有 QuotaExceededError DOMException 并中止这些步骤。

    10. 等待 requestBodiesRemaining 为 0 或 requestReadFailed 为 true。

    11. 如果 requestReadFailed 为 true,则 拒绝 promise 并带有 TypeError 并中止这些步骤。

    12. 将 bgFetch 设为一个新的 背景获取,包含以下内容:

      id

      id。

      记录

      records。

      下载总量

      options的 downloadTotal 成员。

      上传总量

      uploadTotal。

      图标

      如果存在,options 的 icons 成员,否则为空的 列表。

      标题

      如果存在,options 的 title 成员,否则为空字符串。

      Service Worker注册

      registration。

    13. 将 bgFetchMap[id] 设为 bgFetch。

    14. 将背景获取任务入队以运行这些步骤:

      1. 解决 promise,并使用 获取背景获取注册实例,适用于 bgFetch 并在 上下文对象的 相关领域中。

    15. 并行地,从上下文对象的相关设置对象显示 bgFetch。

    16. 并行执行 背景获取,使用 bgFetch。

  9. 返回 promise。

6.3.2. get()

get(id) 方法在调用时必须返回一个新的 promise promise 并并行执行以下步骤:
  1. 将 registration 设为 上下文对象的相关 Service Worker注册。

  2. 将 bgFetch 设为 registration 的 活动的背景获取[id]。

  3. 如果 bgFetch 为 undefined,解决 promise 为 undefined 并中止这些步骤。

  4. 将以下步骤入队到 bgFetch的 更新处理队列中:

    1. 入队一个背景获取任务 task 以运行这些步骤:

      1. 将 bgFetchRegistration 设为 获取背景获取注册实例 的结果,适用于 bgFetch 并在 上下文对象的 相关领域。

      2. 解决 promise,返回 bgFetchRegistration。

    2. 等待 task 完成。

      注意:这确保了新的 BackgroundFetchRegistration 实例不会错过任何 进度事件。

6.3.3. getIds()

getIds() 方法在调用时必须返回 一个新的 promise promise 并并行执行以下步骤:
  1. 将 registration 设为 上下文对象的相关 Service Worker注册。

  2. 解决 promise,返回 registration 的 获取的键 结果,键是 registration 的 活动的背景获取。

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;
  readonly attribute BackgroundFetchResult result;
  readonly attribute BackgroundFetchFailureReason failureReason;
  readonly attribute boolean recordsAvailable;

  attribute EventHandler onprogress;

  Promise<boolean> abort();
  Promise<BackgroundFetchRecord> match(RequestInfo request, optional CacheQueryOptions options = {});
  Promise<sequence<BackgroundFetchRecord>> matchAll(optional RequestInfo request, optional CacheQueryOptions options = {});
};

enum BackgroundFetchResult { "", "success", "failure" };

enum BackgroundFetchFailureReason {
  // 后台抓取尚未完成,或已成功。
  "",
  // 操作已被用户中止,或调用了abort()。
  "aborted",
  // 响应状态不是ok。
  "bad-status",
  // 抓取因其他原因失败,例如CORS、MIX、无效的部分响应,或无法重试的网络故障。
  "fetch-error",
  // 操作期间达到存储配额。
  "quota-exceeded",
  // 超过了提供的downloadTotal。
  "download-total-exceeded"
};

一个 BackgroundFetchRegistration 实例有:

注意:上述值为副本,因此它们可以同步使用。

属性 id 的getter 必须返回 上下文对象 的 ID。

属性 uploadTotal 的getter 必须返回 上下文对象 的 上传总数。

属性 downloadTotal 的getter 必须返回 上下文对象 的 下载总数。

属性 uploaded 的getter 必须返回 上下文对象 的 已上传。

属性 downloaded 的getter 必须返回 上下文对象 的 已下载。

属性 result 的getter 必须返回 上下文对象 的 结果。

属性 failureReason 的getter 必须返回 上下文对象 的 失败原因。

属性 recordsAvailable 的getter 必须在 上下文对象 的 可用记录标志 设置时返回true, 否则返回false。

6.4.1. 事件

属性 onprogress 的 事件处理器 的 事件处理类型 为 progress。

事件 progress 使用 Event 接口。

6.4.2. abort()

方法 abort() 调用时,必须返回 一个新的promise promise 并按以下步骤 并行 运行:

  1. 令 bgFetch 为 上下文对象 关联的 background fetch。

  2. 令 swRegistration 为 bgFetch 的 service worker registration。

  3. 将以下步骤入队 到 swRegistration 的 active background fetches 编辑队列:

    1. 令 activeBgFetches 为 swRegistration 的 active background fetches。

    2. 令 id 为 bgFetch 的 id。

    3. 如果 activeBgFetches 不包含background fetch bgFetch,则 resolve promise 为 false 并中止这些步骤。

    4. 移除 activeBgFetches[id]。

    5. resolve promise 为 true。

    6. 设置 bgFetch 的 abort all flag。

6.4.3. match()

方法 match(request, options) 调用时,必须按以下步骤运行:

  1. 令 promise 为调用算法 matchAll() 的结果,传递 request 和 options。

  2. 返回 对promise反应 的结果,带有一个实现处理程序,当被调用时以参数 matches 返回 matches[0]。

注意: 鼓励用户代理对上述操作进行优化,使其比调用 matchAll() 更快。

6.4.4. matchAll()

方法 matchAll(request, options) 调用时,必须按以下步骤运行:

  1. 如果 上下文对象 的 记录可用标志 未设置,返回 一个被拒绝的promise 并带有 InvalidStateError 和 DOMException。

  2. 令 promise 为 一个新的promise。

  3. 按以下步骤 并行 运行:

    1. 令 matchingRecords 为一个空的 列表。

    2. 对于 上下文对象 的 background fetch 的每个 记录,执行以下步骤:

      1. 如果 请求与缓存项匹配,则对于 request、record 的 请求、record 的 响应数据 中的 响应,以及 options 返回 true,则 追加 record 到 matchingRecords。

    3. 入列一个background fetch任务 来 resolve promise,结果为通过 创建记录对象 从 matchingRecords 在 上下文对象 的 相关领域中。

  4. 返回 promise。

6.5. BackgroundFetchRecord

[Exposed=(Window,Worker)]
interface BackgroundFetchRecord {
  readonly attribute Request request;
  readonly attribute Promise<Response> responseReady;
};
一个 BackgroundFetchRecord 拥有:

request 属性的 getter 必须返回 上下文对象 的 请求。

responseReady 属性的 getter 必须返回 上下文对象 的 响应 promise。

6.6. BackgroundFetchEvent

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

dictionary BackgroundFetchEventInit : ExtendableEventInit {
  required BackgroundFetchRegistration registration;
};
一个 BackgroundFetchEvent 具有一个后台获取(一个后台获取),其初始值为 registration 被初始化为的值所对应的后台获取。

registration 属性 必须返回它被初始化为的值。

6.7. BackgroundFetchUpdateUIEvent

[Exposed=ServiceWorker]
interface BackgroundFetchUpdateUIEvent : BackgroundFetchEvent {
  constructor(DOMString type, BackgroundFetchEventInit init);
  Promise<undefined> updateUI(optional BackgroundFetchUIOptions options = {});
};

一个 BackgroundFetchUpdateUIEvent 拥有一个 UI 更新标志,其初始状态为未设置。

6.7.1. updateUI()

updateUI(options) 方法在被调用时,必须返回一个新的 promise promise,并并行运行以下步骤:
  1. 如果以下任一条件为真:

    抛出一个 InvalidStateError DOMException。

  2. 设置上下文对象的UI 已更新标志。

  3. 如果 options 为 null,则返回。

  4. 令 bgFetch 为上下文对象的后台获取。

  5. 如果 options 的 icons 成员存在,则将 bgFetch 的图标设为 options 的 icons。

  6. 如果 options 的 title 成员存在,则将 bgFetch 的标题设为 options 的 title。

  7. 解决 promise。

7. 自动化

为了用户代理自动化和应用程序测试的目的,本文档为扩展命令定义了以下[WebDriver]规范。

7.1. 点击

方法 URI 模板
POST /session/{session id}/backgroundfetch/{id}/click

后台 获取点击扩展命令用于模拟用户激活某个后台获取的显示界面。其远程端步骤如下:

  1. 如果当前顶级浏览上下文 已不再打开,则返回一个WebDriver 错误,其WebDriver 错误代码为no such window。

  2. 令 pageURL 为当前顶级浏览 上下文的活动文档的URL。

  3. 令 swRegistration 为 pageURL 的匹配的服务工作线程注册。

  4. 如果 swRegistration 为 null,则返回一个WebDriver 错误,其状态为 400,JSON 错误代码为 "invalid service worker state"。

  5. 令 bgFetch 为具有如下条件的最新后台获取:其id 等于URL 变量 id,并且其服务工作线程注册 为 swRegistration;如果不存在,则为 null。

  6. 如果 bgFetch 为 null,则返回一个WebDriver 错误,其状态为 404,JSON 错误代码为 "background fetch not found"。

  7. 为 bgFetch触发后台获取点击事件。

  8. 返回WebDriver 成功。

8. 隐私和带宽使用

获取操作可能很大,并且需要很长时间才能完成。在此期间,用户将从 一个或多个服务器获取数据。用户的 IP 地址在操作期间可能发生变化, 这可能被用来随着时间推移跟踪用户的位置。

为缓解这一点,显示一个后台获取的步骤要求:

这些步骤还允许用户代理在用户处于按流量计费的连接上时暂停后台获取。

所有存储的数据都与特定的服务工作线程注册相关联。清除某个服务工作线程注册将清除所有相关的 后台获取。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。文档中使用的关键字 “MUST”、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"MAY" 和 "OPTIONAL" 应按照 RFC 2119 的描述进行解释。然而,为了提高可读性,这些词在本规范中并未全部使用大写字母。

本规范的所有文本都是规范性的,除非明确标记为非规范性部分、示例或注释。[RFC2119]

本规范中的示例以“例如”开头,或者用 class="example" 与规范性文本区分开来,如下所示:

这是一个提供信息的示例。

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

注意,这是一个提供信息的注释。

一致性算法

算法中以命令式表达的要求(例如“去除任何前导空格字符”或“返回 false 并中止这些步骤”)应按照引入算法时使用的关键字(如“必须”、“应”、“可以”等)的含义进行解释。

以算法或特定步骤措辞的一致性要求可以以任何方式实现,只要最终结果等效即可。特别是,本规范中定义的算法旨在易于理解,而非高性能实现。鼓励实现者进行优化。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范. URL: https://tc39.es/ecma262/
[FETCH]
Anne van Kesteren. Fetch 标准. 现行标准. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[IMAGE-RESOURCE]
Aaron Gustafson; Rayan Kanso; Marcos Caceres. 图像资源. 2021 年 3 月 29 日. WD. URL: https://www.w3.org/TR/image-resource/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Mounir Lamouri; Marcos Caceres; Jeffrey Yasskin. 权限. 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; et al. Service Workers 1. 2019 年 11 月 19 日. CR. URL: https://www.w3.org/TR/service-workers-1/
[STREAMS]
Adam Rice; Domenic Denicola; 吉野剛史 (Takeshi Yoshino). Streams 标准. 现行标准. URL: https://streams.spec.whatwg.org/
[URL]
Anne van Kesteren. URL 标准. 现行标准. URL: https://url.spec.whatwg.org/
[WebDriver]
Simon Stewart; David Burns. WebDriver. 2018 年 6 月 5 日. REC. URL: https://www.w3.org/TR/webdriver1/
[WebIDL]
Boris Zbarsky. Web IDL. 2016 年 12 月 15 日. ED. URL: https://heycam.github.io/webidl/

IDL 索引

partial interface ServiceWorkerGlobalScope {
  attribute EventHandler onbackgroundfetchsuccess;
  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();
};

dictionary BackgroundFetchUIOptions {
  sequence<ImageResource> icons;
  DOMString title;
};

dictionary BackgroundFetchOptions : BackgroundFetchUIOptions {
  unsigned long long downloadTotal = 0;
};

[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;
  readonly attribute BackgroundFetchResult result;
  readonly attribute BackgroundFetchFailureReason failureReason;
  readonly attribute boolean recordsAvailable;

  attribute EventHandler onprogress;

  Promise<boolean> abort();
  Promise<BackgroundFetchRecord> match(RequestInfo request, optional CacheQueryOptions options = {});
  Promise<sequence<BackgroundFetchRecord>> matchAll(optional RequestInfo request, optional CacheQueryOptions options = {});
};

enum BackgroundFetchResult { "", "success", "failure" };

enum BackgroundFetchFailureReason {
  // 后台获取尚未完成,或已成功。
  "",
  // 操作被用户中止,或调用了 abort()。
  "aborted",
  // 响应状态不为 "ok"。
  "bad-status",
  // 由于其他原因(如 CORS、MIX、无效的部分响应,
  // 或无法重试的网络故障)导致获取失败。
  "fetch-error",
  // 操作期间达到了存储配额。
  "quota-exceeded",
  // 提供的 downloadTotal 被超出。
  "download-total-exceeded"
};

[Exposed=(Window,Worker)]
interface BackgroundFetchRecord {
  readonly attribute Request request;
  readonly attribute Promise<Response> responseReady;
};

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

dictionary BackgroundFetchEventInit : ExtendableEventInit {
  required BackgroundFetchRegistration registration;
};

[Exposed=ServiceWorker]
interface BackgroundFetchUpdateUIEvent : BackgroundFetchEvent {
  constructor(DOMString type, BackgroundFetchEventInit init);
  Promise<undefined> updateUI(optional BackgroundFetchUIOptions options = {});
};

问题索引

manifest/pull/710. ↵
ServiceWorker/1348. ↵
本步骤的其余部分使用了 fetch 的“回调”,当前这些回调会排队任务。这在此不理想或不可行,因此我们假装不排队任务。(问题) ↵
我需要类似于鼠标移动事件去抖动一样来处理此问题。 ↵
解析为整数 infra/189. ↵
ServiceWorker/1348. ↵