预取

社区组草案报告,

本版本:
https://wicg.github.io/nav-speculation/prefetch.html
问题追踪:
GitHub
规范内索引
编辑:
(谷歌)
(谷歌)

摘要

导航预取规范

本文档状态

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

1. 概念

鉴于存储分区,本规范定义了将在同一分区内发生导航的预取(例如,同一网站内的顶级导航)和将在不同分区内发生导航的预取(例如,前往不同网站的顶级导航)。

存在冲突的凭据,针对响应 response,给定可导航对象navigable和网络分区键sourcePartitionKey,如以下步骤返回true:
  1. 令hypotheticalEnvironment为创建保留客户端的结果,参数为navigable、response的URL和null。

  2. 令hypotheticalPartitionKey为确定网络分区键的结果,参数为hypotheticalEnvironment。

  3. 如果hypotheticalPartitionKey等于sourcePartitionKey,或没有与URL和hypotheticalPartitionKey相关联的凭据,则返回false。

  4. 返回true。


交换记录是一个结构体,包含如下条目:

这些记录可用于延后原本应在导航获取期间进行的检查,以及用于检查已修改的凭据。

重定向链是一个列表,包含交换记录。

更新重定向链redirectChain,给定一个请求 request和响应response:
  1. 断言:redirectChain不为空。

  2. 断言:redirectChain的最后一项请求与request相同,其响应为null。

  3. 将redirectChain最后一项的请求设置为request的克隆。

    克隆可确保在后续重定向期间对request进行的任何修改不会影响存储的请求,后者在激活时(在根据预取记录创建导航参数)需要用于获取原始重定向链的全貌。目前已知唯一在请求字段发生变更且激活时也需要查询的情况是referrer字段,但为规范的健壮性,我们克隆整个请求。

    由于预取仅对`GET`请求进行,请求主体始终为null,因此克隆过程很简单。

  4. 将redirectChain最后一项的响应设置为response。


每个Document都有预取记录,它是一个列表,包括预取记录。

预取记录是一个结构体,包含如下条目:

该结构体用于追踪预取从启动到最终被使用或丢弃的全过程。因此部分字段不可变,部分与进行中的操作有关(如获取控制器),部分(如过期时间)在预取完成时填充。

一个本应携带凭据但由于跨分区预取而无法发送凭据的请求将导致预取被放弃。

预取记录的响应为其重定向链最后一个元素的响应,若该列表为空则为null。

即使没有过期,用户代理也可取消和丢弃预取记录,比如因资源受限。已完成记录且过期时间在过去的,永远不会成为匹配预取记录,可无影响地移除。

预取记录 prefetchRecord匹配URL,给定URLurl,以下算法返回true:
  1. 如果prefetchRecord的URL等于url,返回true。

  2. 如果prefetchRecord的响应不为null:

    1. 令searchVariance为获取URL搜索变体的结果,参数为prefetchRecord的重定向链[0]的响应。

      需检查第0个响应而不是最后一个响应(即不是prefetchRecord的响应),因为预取记录的URL属于第一个请求/响应对,因此只有第一个响应的No-Vary-Search头影响匹配。

    2. 如果prefetchRecord的URL与url按照searchVariance进行搜索变体等价比较,返回true。

  3. 否则返回false。

预取记录 prefetchRecord预计匹配URL,给定URL url,以下算法返回true:
  1. 如果prefetchRecord匹配URL给定url,返回true。

  2. 如果prefetchRecord的响应为null:

    1. 令searchVariance为prefetchRecord的No-Vary-Search提示。

    2. 如果prefetchRecord的URL与url按照searchVariance进行搜索变体等价比较,返回true。

  3. 否则返回false。

取消和丢弃预取记录prefetchRecord 给定Document document,执行以下步骤:
  1. 断言:prefetchRecord在document的预取记录中。

  2. 断言:prefetchRecord的状态不为“已取消”。

  3. 将prefetchRecord的状态设置为“已取消”。

  4. 中止prefetchRecord的获取控制器。这将导致任何正在进行的获取被取消并产生网络错误。

  5. 如果prefetchRecord的预渲染可遍历对象是可遍历导航对象,则销毁它。

  6. 移除prefetchRecord从document的预取记录。

  7. 触发预取状态更新事件,参数为document的节点可导航对象、prefetchRecord和“失败”状态。

即使是已完成的预取或预渲染也不会被激活。然而,预取或预渲染过程可能已修改HTTP缓存,从而加速后续导航。

完成预取记录prefetchRecord,给定Document document,执行以下步骤:
  1. 断言:document是完全活跃。

  2. 令currentTime为当前高分辨率时间,取自document的相关全局对象。

  3. 令expiryTime=currentTime+300000(即五分钟)。

  4. 移除所有document的预取记录,其URL与prefetchRecord相同且状态等于“已完成”。

  5. 将prefetchRecord的状态设置为“已完成”,过期时间设置为expiryTime。

  6. 触发预取状态更新事件,参数为document的节点可导航对象、prefetchRecord和“就绪”状态。

查找匹配的完成预取记录,给定顶级可遍历对象navigable、源快照参数sourceSnapshotParams和URL url:
  1. 令exactRecord为null。

  2. 令inexactRecord为null。

  3. 遍历sourceSnapshotParams的预取记录:

    1. 如果record的状态不是“已完成”,则跳过。

    2. 如果record的URL等于url:

      1. 设置exactRecord为record。

      2. 跳出。

    3. 如果inexactRecord为null,且record匹配URL给定url:

      1. 设置inexactRecord为record。

  4. 令recordToUse为exactRecord(若不为null),否则为inexactRecord。

  5. 如果recordToUse不为null:

    1. 令currentTime为navigable的活跃文档的当前高分辨率时间。

    2. 如果recordToUse的过期时间小于currentTime:

      1. 触发预取状态更新事件,参数为navigable、recordToUse和“失败”状态。

      2. 返回null。

    3. 遍历recordToUse的重定向链的每个exchangeRecord:

      1. 如果存在冲突的凭据针对exchangeRecord的响应、navigable和recordToUse的来源分区键:

        1. 触发预取状态更新事件,参数为navigable、recordToUse和“失败”状态。

        2. 返回null。

      这用于处理起初没有跨分区凭据,但后来出现这种情况。用户代理可以采用更粗略的算法如监控该URL的cookie是否变动,或存储哈希值、时间戳或版本号。
    4. 返回recordToUse。

  6. 返回null。

虽然不明显,但该逻辑实际上不要求预取已接收完整主体,只需响应头即可。也就是说,导航到已预取的响应仍可能无法瞬间加载。

也许可用缓存响应头判断响应是否能多次使用,但由于预取缓冲区生存期极短,是否有必要尚不明确。

等待匹配的预取记录,给定顶级可遍历对象navigable、源快照参数sourceSnapshotParams和URL url:
  1. 断言:本操作在并行环境中运行。

  2. 令timeout为null。

  3. 可选地,将timeout设置为实现自定义的持续时间,代表实现愿意等待进行中预取响应的最长时间,超过即等待结束并重新发起导航。

    选择合适超时时间很难,且可能依赖具体导航或预取的发起者(如其推测规则积极性,或是否用于预渲染)。一般不建议用超时时间,因为很少重新导航比等预取更优。但部分实现发现某些情况下几秒的超时能取得更好体验。

  4. 令startTime为不安全共享当前时间。

  5. 执行如下步骤,当timeout不为null且不安全共享当前时间−startTime大于timeout时终止:

    1. 循环如下:

      1. 令completeRecord为查找匹配的完成预取记录的结果,参数为navigable、sourceSnapshotParams和url。

      2. 如果completeRecord不为null,则返回completeRecord。

      3. 令potentialRecords为一个空列表。

      4. 遍历sourceSnapshotParams的预取记录:

        1. 若如下全部成立:

          则追加record到potentialRecords。

        循环每次都会重新计算potentialRecords,使得后续循环的potentialRecords是上次的子集。这因sourceSnapshotParams的预取记录为快照,且上述条件不能由false变为true。

        等价做法是初次构建完整potentialRecords,随后逐步移除不再满足条件的项。

      5. 如果potentialRecords为空,返回null。

      6. 等待sourceSnapshotParams中任意预取记录状态发生变化。

  6. 返回null。

因sourceSnapshotParams的预取记录是快照,导航后启动的预取不会随该导航而被激活。

一个预取记录 prefetchRecord是否仍在推测中,给定一个推测加载候选集candidates,按如下步骤返回 true:
  1. 遍历candidates中的每个candidate:

    1. 如果prefetchRecord未能匹配URL给定 candidate的URL,则继续。

    2. 如果candidate是预取候选且 prefetchRecord的匿名策略不等于candidate的匿名策略,则继续。

    3. 如果candidate是预渲染候选且 prefetchRecord的预渲染可遍历对象 为 null,则继续。

    4. 返回 true。

  2. 返回 false。

一个Document document有匹配的预取记录,给定一个预取记录 recordUnderConsideration和一个可选布尔值checkPrerender(默认 false),如下算法返回 true:
  1. 遍历document的预取记录中的每个record:

    1. 如果record的状态为"已取消",则继续。

    2. 如果checkPrerender为 true:

      1. 如果record的预渲染可遍历对象为 null,则继续。

      2. 如果record的预渲染目标可导航名称提示不等于 recordUnderConsideration的预渲染目标可导航名称提示,则用户代理可继续。

        对于依照预渲染可遍历对象和预渲染目标可导航名称提示创建独立实例的用户代理,会继续该分支,认为此类记录是不同的。能激活已有预渲染可遍历对象且不区分预渲染目标可导航名称提示的用户代理则认为此类记录等价。

    3. 令recordNVS为 null。

    4. 如果record的响应不为 null,则设置 recordNVS为获取URL搜索变体的结果, 参数为record的响应。

    5. 否则,设置recordNVS为record的No-Vary-Search提示。

    6. 如果recordUnderConsideration的No-Vary-Search提示不等于recordNVS,则继续。

    7. 如果recordUnderConsideration的URL与 record的URL根据recordUnderConsideration的No-Vary-Search提示变体等价,返回true。

  2. 返回false。

参见 HTML 标准中No-Vary-Search比对相关讨论。

根据预取记录创建导航参数,给定一个顶级可遍历对象navigable、文档状态documentState、导航idnavigationId、NavigationTimingType navTimingType、请求request、预取记录record、目标快照参数targetSnapshotParams和源快照参数sourceSnapshotParams,按如下步骤:
  1. 令responseOrigin为null。

  2. 令responseCOOP为null。

  3. 令coopEnforcementResult为为导航创建跨域窗口打开策略执行结果,参数为navigable的活跃文档与documentState的发起者源。

  4. 令finalSandboxFlags为一个空沙箱标志集合。

  5. 令responsePolicyContainer为null。

  6. 令urlList为一个空列表。

  7. 令response为record的响应。

  8. 遍历record的重定向链中的每个exchangeRecord:

    1. 令redirectChainRequest为exchangeRecord的请求。

    2. 令redirectChainResponse为exchangeRecord的响应。

    3. 追加redirectChainRequest的URL到urlList。

    4. 令responsePolicyContainer为根据获取响应创建策略容器的结果,参数为redirectChainResponse及redirectChainRequest的保留客户端。

    5. 令finalSandboxFlags为targetSnapshotParams的沙箱标志与responsePolicyContainer的CSP列表的CSP派生沙箱标志的并集。

    6. 令responseOrigin为确定源的结果,参数为redirectChainResponse的URL、finalSandboxFlags、documentState的发起者源与null。

    7. 令responseCOOP为获取跨域窗口打开策略(参数为redirectChainResponse及redirectChainRequest的保留客户端)。

    8. 令coopEnforcementResult为执行响应的跨域窗口打开策略的结果,参数为navigable的活跃浏览环境、redirectChainResponse的URL、responseOrigin、responseCOOP、coopEnforcementResult以及redirectChainRequest的referrer。

    9. 如果finalSandboxFlags非空且responseCOOP的值为"unsafe-none",则将response设为适当的网络错误并跳出。

  9. 如果request的URL不等于urlList[0],则将request的URL插入urlList第0项之后。

    此时导航的是request的URL,但响应是来自urlList[0],因 No-Vary-Search。 视为第0个响应到URL的重定向。插入后若urlList的长度为2,则生成的Document使用导航到的URL,否则长度更大时无效。

    假设我们预取了/page?param=1,服务器设置了No-Vary-Search: params=("param")且状态码为200。

    之后导航到/page?param=2即可复用预取。此步骤让urlList变为« /page?param=1, /page?param=2 »,于是生成的Document 的URL为/page?param=2。

    假设我们预取/page?param=1,服务器设置No-Vary-Search: params=("param"),并301跳转,Location: https://other.example/。

    之后导航到/page?param=2即可复用预取。此步骤让urlList变为« /page?param=1, /page?param=2, https://other.example/ »,于是生成的Document 的URL为https://other.example/。

  10. 将request的URL列表设为urlList。

  11. 将request的保留客户端设为创建保留客户端的结果,参数为navigable与request的URL。

    这将用作生成Document 与环境设置对象的环境。每次消耗一个预取记录时需独立环境,不能用record的重定向链最后项的请求的保留客户端。否则复用同一预取记录会导致创建的环境设置对象拥有相同id。会被如clients.get()API暴露,且极为混乱。

  12. 将request的保留客户端的活跃 Service Worker设为record的重定向链最后项的请求的保留客户端的活跃 Service Worker。

    此时理论上有两种 service worker 控制生成的Document:一种是在预取过程中拦截最终响应的,一种是与导航到的 URL(即request的URL)匹配的注册。这两者大部分情况相同,但涉及 No-Vary-Search 且开发者为/page?param=1和/page?param=2分别建了不同 service worker 时可能不同。

    我们选择用预取阶段拦截最终响应的 service worker。这样实现更简单(无需额外查找),且与预渲染行为一致,“激活时 URL 更新”不会导致切换 service worker。

  13. 令resultPolicyContainer为确定导航参数策略容器的结果,参数为record的响应的URL、documentState的历史策略容器、sourceSnapshotParams的源策略容器、null和responsePolicyContainer。

  14. 如果response不是网络错误,则:

    1. 可选,设置response为克隆版本。

      实现方可选此操作若预取响应可能重复消费。例如未来被预渲染消耗后,预渲染对象可因各种原因被销毁。正常应丢弃响应,但如进行本步,则预取响应仍可服务后续导航。参见 [PRERENDERING-REVAMPED]。

    2. 如未执行上述可选步骤,则必须移除record从navigable的活跃文档的预取记录中。

  15. 返回一个新的导航参数,内容如下:

    id

    navigationId

    请求

    request

    响应

    response

    源

    responseOrigin

    策略容器

    resultPolicyContainer

    最终沙箱标志集

    finalSandboxFlags

    跨域窗口打开策略

    responseCOOP

    COOP执行结果

    coopEnforcementResult

    保留环境

    request的保留客户端

    可导航对象

    navigable

    导航时序类型

    navTimingType

    获取控制器

    record的获取控制器

    如上述可选步骤被采用,record可多次生成导航参数,均共享同一个获取控制器。因导航参数的唯一用途是统计导航时序,复用无影响。

    提前提示提交

    null

    意味着预取文档的提前提示不会被处理。若未来资源提示无法复用主响应头则可修订。因预取只在响应头到达时生效,提前提示不会早于主响应头。后续规范可考虑利用预取阶段获得的提前提示。

    交付类型

    "导航预取"

一个预取IP匿名策略policy 需要匿名,针对请求request,按如下步骤返回 true:
  1. 如果policy是跨域预取IP匿名策略:

    1. 如果request的URL的源与policy的源相同,则返回 false。

    2. 返回 true。

  2. 断言:policy为null。

  3. 返回 false。

2. HTML补丁

本节包含对[HTML]的补丁内容。

按如下方式为环境增加一项:

是否为导航预取客户端

一个布尔值(默认false)

按如下方式为导航参数增加一项:

交付类型

一个字符串(对应PerformanceResourceTiming delivery type)

更新所有创建点设为""(空字符串),除非本规范文档内明示为其他值(见根据预取记录创建导航参数)。


为源快照参数增加一项:

预取记录

一个列表, 包含预取记录。

修改快照源快照参数算法,将返回值的预取记录设为sourceDocument的预取记录的克隆。


本内容从根据获取创建导航参数提取。

创建保留客户端, 给定可导航对象navigable,一个URLurl与 一个不透明源或nullisolationOrigin:

  1. 令topLevelCreationURL为url。

  2. 令topLevelOrigin为null。

  3. 如果isolationOrigin非null,则:

    1. 设topLevelCreationURL为about:blank。

    2. 设topLevelOrigin为isolationOrigin。

  4. 否则,若navigable不是顶级可遍历对象,则:

    1. 令parentEnvironment为navigable的父对象的活跃文档的相关设置对象。

    2. 设topLevelCreationURL为parentEnvironment的顶级创建URL。

    3. 设topLevelOrigin为parentEnvironment的顶级源。

  5. 返回一个环境对象,其id为唯一不透明字符串,目标浏览环境为navigable的活跃浏览环境,创建URL为url, 顶级创建URL为topLevelCreationURL, 顶级源为topLevelOrigin。


本内容从根据获取创建导航参数提取。

为导航创建跨域窗口打开策略执行结果 ,给定一个Document activeDocument与一个源initiatorOrigin,返回一个新的跨域窗口打开策略执行结果对象,内容如下:

url

activeDocument的URL

origin

activeDocument的源

跨域窗口打开策略

activeDocument的跨域窗口打开策略

当前环境为导航源

若activeDocument的源与initiatorOrigin为同源则为true,否则为false


本内容从根据获取创建导航参数提取。

创建导航请求, 给定一个会话历史条目entry, 一个环境设置对象fetchClient, 一个可导航容器或nullcontainer, 以及一个布尔值hasTransientActivation,进行如下步骤。

  1. 令documentResource为entry的文档状态的资源。

  2. 新建一个请求对象request,内容:

    url

    entry的URL

    策略容器

    entry的文档状态的历史策略容器

    客户端

    fetchClient

    目标

    "document"

    凭据模式

    "include"

    使用URL凭据标志

    已设置

    重定向模式

    "manual"

    模式

    "navigate"

    referrer

    entry的文档状态的请求referrer

    referrer策略

    entry的文档状态的请求referrer策略

  3. 如果documentResource为POST资源,则:

    1. 设request的方法为`POST`。

    2. 设request的主体为documentResource的请求主体。

    3. 设置`Content-Type`为 documentResource的请求内容类型,加入 request的标头列表。

  4. 如果entry的文档状态的待重载为true,则 设request的重载导航标志。

  5. 否则,若entry的文档状态的曾被加载为true,则 设request的历史导航标志。

  6. 如果hasTransientActivation为true,则设request的用户激活为true。

  7. 若container非null:

    1. 若container有浏览环境范围源,则 设request的源为该浏览环境范围源。

    2. 设request的目标及发起者类型为container的本地名称。

  8. 返回request。


在填充历史条目的文档算法中, 用如下步骤替换调用根据获取创建导航参数的那一步:
  1. 否则,若下列所有条件为真:

    则:

    1. 令request为创建导航请求的结果,参数为 entry、sourceSnapshotParams的取用客户端、navigable的容器以及sourceSnapshotParams的是否瞬时激活。

    2. 设request的替换客户端id为 navigable的活跃文档的相关设置对象的id。

    3. 令prefetched为false。

    4. 如果documentResource为null且navigable为顶级可遍历对象:

      1. 令prefetchRecord为等待匹配的预取记录, 参数为navigable、sourceSnapshotParams、entry的URL。

      2. 若prefetchRecord非null:

        1. 设navigationParams为根据预取记录创建导航参数, 参数为navigable、entry的文档状态、 navigationId、navTimingType、request、 prefetchRecord、targetSnapshotParams、 sourceSnapshotParams。

        2. 复制预取Cookie,参数为prefetchRecord的隔离分区键 与navigationParams的保留环境。

          此复制在继续前完成, 即子资源获取、document.cookie 等能感知Cookie。若预取未访问跨站点URL,则无可复制的Cookie。
        3. 设prefetched为true。

        4. 触发预取状态更新事件, 参数为navigable、prefetchRecord、"成功"状态。

        上述步骤保证预取仅用于满足`GET`请求, 且仅用于激活顶级可遍历对象。

    5. 若prefetched为false,则设navigationParams为根据获取创建导航参数结果, 参数为request、entry、navigable、 sourceSnapshotParams、targetSnapshotParams、 cspNavigationType、navigationId和navTimingType。


这是对现有 通过 fetch 创建导航参数 算法的更新。

通过 fetch 创建导航参数给定一个 请求 request,一个 会话历史条目 entry,一个可导航对象 navigable,一个 源快照参数 sourceSnapshotParams,一个 目标快照参数 targetSnapshotParams,一个 字符串 cspNavigationType,一个 导航ID或null navigationId,一个 NavigationTimingType navTimingType,以及可选的 预取记录 prefetchRecord,执行以下步骤。

  1. 断言:此操作在并行环境中运行。

  2. 断言:request的URL等于entry的URL。

  3. 断言:request的mode为"navigate"。

  4. 断言:request的redirect mode为"manual"。

  5. 断言:request的保留客户端为null。

  6. 令response为null。

  7. 令responseOrigin为null。

  8. 令fetchController为null。

  9. 令coopEnforcementResult为为导航创建跨域窗口打开策略执行结果, 参数为navigable的活跃文档和entry的文档状态的发起者源。

  10. 令finalSandboxFlags为空的沙箱标志集合。

  11. 令responsePolicyContainer为null。

  12. 令responseCOOP为新的跨域窗口打开策略。

  13. 令locationURL为null。

  14. 令currentURL为request的current URL。

  15. 令commitEarlyHints为null。

  16. 令isolationOrigin为null。

  17. 如给定prefetchRecord:

    1. 令isolationSite为prefetchRecord的隔离分区键[0]。

    2. 断言:isolationSite为不透明源。

    3. 设isolationOrigin为isolationSite。

  18. 循环如下:

    1. 如果request的保留客户端非null且currentURL的源与request的保留客户端的创建URL的源不同,则:

      1. 对request的保留客户端 执行环境丢弃步骤。

      2. 设request的保留客户端为null。

      3. 设commitEarlyHints为null。

    2. 如果request的保留客户端为null,则:

      1. 设request的保留客户端为 创建保留客户端的结果,参数为 navigable、currentURL、isolationOrigin。

      2. 如果prefetchRecord给定,则设request的 是否为导航预取客户端 为 true。

    3. 如果检查是否 CSP 应阻止此导航请求类型 的结果为"Blocked",则设response为网络错误,并跳出。

    4. 如给定prefetchRecord:

      1. 令purpose为包含prefetch的列表。

      2. 如果prefetchRecord的匿名策略 需要匿名 ,针对request,则:

        1. 向prefetch令牌中添加键为"anonymous-client-ip"值为true的参数。

        2. 用户代理必须在获取request时使用匿名化客户端IP连接(如代理),否则设response为网络错误并跳出。

          目前IP匿名化尚无细致标准,实际会通过某些代理/中继实现。 理想情况应层层传递到获取连接处,未来或可进一步标准化。

      3. 如果prefetchRecord的预渲染可遍历对象 非null,则向prefetch令牌中添加键为"prerender"值为true的参数。

      4. 写入结构化字段值, 参数为 (`Sec-Purpose`, purpose),至request的标头列表。

        实现可能兼容性发送厂商定制头,如 Chromium 的 `Purpose`/`prefetch`、 Mozilla 的 `X-moz`/`prefetch`、 WebKit 的 `X-Purpose`/`preview` 等。 期望业界最终采用统一头。
      5. 如果request的当前URL的源与prefetchRecord的URL的源为同站,则:

        1. 令tags为由prefetchRecord的 标签中的每一项映射得到的 列表:字符串转为 String,null转为 Token null。

        2. 写入结构化字段值, 参数为 (`Sec-Speculation-Tags`, tags),至request标头列表。

      6. 如果request的当前URL不是潜在可信URL,则设response为网络错误并跳出。

        旨在降低预取流量被链路攻击者看到概率,并鼓励公网使用加密方案。
      7. 令proposedPartitionKey为确定网络分区键, 参数为request的 保留客户端。

      8. 如果proposedPartitionKey不等于prefetchRecord的来源分区键且 request的referrer策略 不在足够严格的推测导航referrer策略列表 中,则设response为网络错误并跳出。

        实际上,跨站预取会放弃而不暴露比来源更多的referrer信息。
      9. 如因实现自定义原因无法按prefetchRecord的匿名策略获取request,则设response为网络错误并跳出。

        此处允许实现进一步限制,比如匿名流量无法访问部分主机、或主机拒绝私有预取流量。
      10. 追加新交换记录, 其请求为request, 响应为null, 加入prefetchRecord的重定向链。

    5. 设response为null。

    6. 如fetchController为null,则将fetchController设为对request执行fetch的结果, processEarlyHintsResponse设为下述算法, processResponse设为下述算法, useParallelQueue设为true。

      令processEarlyHintsResponse为如下算法,输入response earlyResponse:

      1. 若给定prefetchRecord且commitEarlyHints为null, 设其为处理 early hint 头, 参数为earlyResponse和request的保留客户端。

      令processResponse为如下算法,输入response fetchedResponse:

      1. 设response为fetchedResponse。

    7. 否则,对fetchController执行处理下一个手动重定向。

    8. 等待直到response非null,或navigable的进行中导航不等于navigationId。

      如后者发生,则中止 fetchController,并返回。否则继续。 预取在导航开始时立刻中止,可能不是理想行为。

    9. 如request的主体为null,设entry的文档状态的资源为null。

    10. 设responsePolicyContainer为根据响应创建策略容器的结果,参数为response和 request的保留客户端。

    11. 设finalSandboxFlags为targetSnapshotParams的沙箱标志与responsePolicyContainer的CSP列表的CSP派生沙箱标志的并集。

    12. 设responseOrigin为确定源的结果,参数为response的URL、finalSandboxFlags、entry的文档状态的发起者源、null。

    13. 如果navigable为顶级可遍历对象,且prefetchRecord未给定:

      1. 令responseCOOP为获取跨域窗口打开策略的结果,参数为response和 request的保留客户端。

      2. 令coopEnforcementResult为执行响应的跨域窗口打开策略的结果,参数为navigable的活跃浏览环境、request的URL、responseOrigin、responseCOOP、coopEnforcementResult、request的referrer。

      3. 如果finalSandboxFlags非空且responseCOOP的值不是"unsafe-none",则设response为相应网络错误并跳出。

      COOP检查在预取激活时进行,可知目标浏览环境时执行。

    14. 如果response不是网络错误,navigable是子可导航对象,且对 navigable的容器文档的源、相关设置对象、request的目标、response、true 进行 跨域资源策略检查的结果是阻止,则设response为网络错误并跳出。

    15. 如给定prefetchRecord:

      1. 更新 prefetchRecord的重定向链, 参数为request和response。

      2. 如存在冲突的凭据, 针对response和navigable与 prefetchRecord的来源分区键, 则设prefetchRecord的曾有冲突凭据为true。

        不会立即中止预取或跳过后续重定向,因为这样会暴露用户是否有跨分区状态。预取继续,但不能被使用。 可建议用户代理向控制台等报告警告。

    16. 设locationURL为response的location URL, 参数为currentURL的片段。

    17. 如locationURL为failure或null,跳出。

    18. 断言:locationURL为URL。

    19. 设entry的序列化状态为StructuredSerializeForStorage(null)。

    20. 令oldDocState为entry的文档状态。

    21. 设entry的文档状态为新文档状态,内容如下:

      历史策略容器

      oldDocState的历史策略容器的克隆

      请求referrer

      oldDocState的请求referrer

      请求referrer策略

      oldDocState的请求referrer策略

      源

      oldDocState的源

      资源

      oldDocState的资源

      曾加载

      oldDocState的曾加载

      导航目标名称

      oldDocState的导航目标名称

    22. 若locationURL的方案不是HTTP(S) 方案,则:

      1. 设entry的文档状态的资源为null。

      2. 跳出。

    23. 设currentURL为locationURL。

    24. 设entry的URL为currentURL。

  19. 如果locationURL为URL且其方案不是fetch 方案,则返回新的非fetch方案导航参数,内容:

    发起源

    request的当前URL的源

  20. 如下任意为真:

    则返回null。
  21. 令resultPolicyContainer为 确定导航参数策略容器的结果, 参数为response的URL、 entry的文档状态的历史策略容器、 sourceSnapshotParams的源策略容器、null、responsePolicyContainer。

  22. 返回新的导航参数,内容如下:

    id

    navigationId

    请求

    request

    响应

    response

    源

    responseOrigin

    策略容器

    resultPolicyContainer

    最终沙箱标志集合

    finalSandboxFlags

    跨域窗口打开策略

    responseCOOP

    COOP执行结果

    coopEnforcementResult

    保留环境

    request的保留客户端

    可导航对象

    navigable

    导航时序类型

    navTimingType

    获取控制器

    fetchController

    提前提示提交

    commitEarlyHints


更新创建并初始化 Document 对象的步骤,给创建导航时序条目增加一个参数,具体为: navigationParams的交付类型。

修改为导航响应获取浏览环境的步骤,检查sourceOrigin是否与destinationOrigin同站,插入如下子步骤:
  1. 如果navigationParams的交付类型为"prefetch",则可选设swapGroup为true。

    这样让引用站点更难通过加载时序判断预取是否被使用,有助于防止关于目标站点的信息泄露到引用站点。

本节包含对[NAVIGATION-TIMING]的补丁内容。

向 create the navigation timing entry 添加一个额外参数,该参数是一个 string deliveryType,并将其作为额外的参数传递给 setup the resource timing entry。

4. Service Workers 补丁

本节包含对[SERVICE-WORKERS]的补丁内容。

修改创建 Fetch 事件并分发设置resultingClientId 的步骤如下:
如果request是非子资源请求且request的目标不是"report" 且reservedClient的是否为导航预取客户端为false ,则将e的resultingClientId 属性初始化为reservedClient的id,否则初始化为空字符串。

在预取场景下,虽然此时request有保留客户端,仅为规范层帮助请求通过, 并不代表实际客户端,不会被如clients.matchAll() 等观察到。实际客户端创建将在激活时,即FetchEvent 触发后。

5. 清除站点数据补丁

在清除站点数据 § 3.1 Clear-Site-Data HTTP 响应头字段中添加一条新的头部值说明:

"prefetchCache"

"prefetchCache" 类型表示服务器希望清除由特定源发起的任意响应对应URL触发的预取内容。

该类型属于"cache"类型的子集。

具体实现细节如下。

修改解析响应的 Clear-Site-Data 头的解析步骤:

修改为响应清除站点数据的 switch 结构,新增处理 "prefetchCache" 的分支,调用清除预取缓存, 参数为origin。

清除预取缓存,参数为源origin:
  1. 遍历用户代理的顶级可遍历对象集合中的每个顶级可遍历对象traversable:

    1. 令navigables为traversable的活跃文档的包含自身的后代可导航对象。

    2. 遍历navigables中的每个navigable:

      1. 令activeDocument为navigable的活跃文档。

      2. 如果activeDocument的源不是与origin同源,则跳过。

      3. 遍历activeDocument的预取记录中的每个prefetchRecord:

        1. 如果prefetchRecord的预渲染可遍历对象 非null,则跳过。

        2. 取消并丢弃 prefetchRecord,参数为activeDocument。

6. 预取算法

推测导航足够严格的 referrer policy 列表包括如下内容:"", "strict-origin-when-cross-origin", "strict-origin", "same-origin", "no-referrer"。

启动 referrer 发起的导航预取,给定 Document document 和 预取记录 prefetchRecord,按如下步骤:
  1. 断言:document的节点可导航对象为顶级可遍历对象。

    在子可导航对象支持预取存在一些复杂性,当前尚未定义。将来有可能定义。

  2. 如果document已有匹配的预取记录给定 prefetchRecord,则返回。

  3. 令sourceSnapshotParams为快照源快照参数,参数为document。

  4. 令targetSnapshotParams为快照目标快照参数,参数为document的节点可导航对象。

  5. 设prefetchRecord的来源分区键为确定网络分区键的结果,参数为document的 相关设置对象。

  6. 断言:prefetchRecord的URL的方案为HTTP(S) 方案。

  7. 追加prefetchRecord到document的预取记录。

  8. 设prefetchRecord的开始时间为document的当前高分辨率时间,对象为document的相关全局对象。

  9. 触发预取状态更新事件,参数为document的节点可导航对象、prefetchRecord和"待定" 状态。

  10. 令referrerPolicy为prefetchRecord的referrer 策略 (若非空字符串),否则为document的策略容器的referrer 策略。

  11. 令documentState为新文档状态,包括:

    请求referrer策略

    referrerPolicy

    发起者源

    document的源

  12. 令entry为新会话历史条目,包括:

    URL

    prefetchRecord的URL

    文档状态

    documentState

  13. 令request为创建导航请求的结果, 参数为entry、document的相关设置对象、document的 节点可导航对象的容器以及 false。

  14. 令global为document的相关全局对象。

  15. 并行执行:

    1. 令navigationParams为通过 fetch 创建导航参数的结果, 参数为request、entry、document的节点可导航对象、 sourceSnapshotParams、targetSnapshotParams、"other"、null(navigationId)、 "navigate"及prefetchRecord prefetchRecord。

      预取时用到的可导航对象,即document的节点可导航,实际激活时可不同于后续传递给navigate算法的对象(最终会调 根据预取记录创建导航参数)。

      其实这没问题。最终激活导航时的目标 navigable 会参与大部分重要检查,而这些检查发生在填充历史条目的文档之前。 而且,查看 navigateParams 受 navigable 的影响方式:

      最后,需要注意本算法内 navigationParams 仅是预取响应的包裹,不会长期储存,参数中的 navigable 不会泄露到系统其他部分。激活时由根据预取记录创建导航参数重新生成,内含正确目标导航对象。

    2. 如果navigationParams的响应不支持预取,则设 navigationParams为null。

    3. 如果prefetchRecord的曾有冲突凭据为 true,则设 navigationParams为 null。

      即重定向链中的任何跨分区 origin 存在凭据时,将丢弃预取。可减少用户看到登出页面的概率。
    4. 在全局任务队列中排队 网络任务源,参数为global,内容:

      1. 如navigationParams不是导航参数,则取消并丢弃prefetchRecord,参数为document,并中止步骤。

      2. 断言:navigationParams的响应为 prefetchRecord的重定向链最后一个元素的响应。

      3. 完成prefetchRecord给定document。

响应response支持预取,如下步骤返回 true:
  1. 令status为response的状态。

  2. 断言:status不是重定向状态。

  3. 如果status不是ok状态,则返回 false。

    即错误响应不会缓存,发生导航时会重试。可提升导航成功的概率,如错误是临时或因预取导致,也便于服务器拒绝含Sec-Purpose头请求。
  4. 返回true。

虽然 103 不是ok状态,但 Early Hint 不影响预取。支持预取谓词仅用于实际响应,不会是 1xx。

如其它注释,目前预取阶段会忽略早期提示。

7. Cookies

[COOKIES] 定义了“cookie”,可以使用 `Set-Cookie` 响应头字段进行设置。由于 "uncredentialed" 分区方案强制使用单独的网络分区键,因此需要将这些 cookie 复制到普通分区,就像它们是在导航时接收的一样。

缓存,虽然不是正确性所必需,也有用复制。
认证条目可在跨站点时获取,尽管这种情况似乎罕见。(例如,某网站可能会重定向到包含URL凭据的链接。)复制这些可能也是合理的,尽管目前尚不清楚是否真的希望鼓励这么做。
To 复制预取cookie,给定网络分区键 isolatedPartitionKey 和 环境 environment,进行如下步骤。
标准上只有一个cookie存储,但有些浏览器会对cookie进行分区,例如同一域名在不同顶级站点下的cookie存储会分开。参见 客户端存储分区(Privacy CG)。
  1. 令isolatedCookieStore为与isolatedPartitionKey关联的cookie存储。

  2. 遍历isolatedCookieStore中的每个cookie cookie。

    1. 从isolatedCookieStore移除cookie。

    2. 用户代理可以完全忽略某个cookie。如果是这种情况,则跳过。

      这与[COOKIES] 明确允许接收cookie时这种行为一致。
    3. 令topLevelSite为 null。

    4. 如果environment的目标浏览环境是顶级浏览环境:

      1. 令topLevelSite为获取站点的结果,参数为tuple origin ("https", cookie的domain, null, null)。

        这里使用"https"协议和null端口,是因为cookie可跨scheme和端口,不同于同源策略。用户代理分配cookie存储时因此不应区分。
      在顶级浏览环境进行预取时,请求(包括重定向)是为顶级导航做准备。 environment的顶级站点会因重定向改变,且重定向可能跨站点,因此收到cookie时顶级站点可能已变。不过由于是顶级导航,下发cookie的origin即为当时的顶级站点。由于cookie的domain必须与下发origin同站,所以domain可用于判定顶级站点。
    5. 否则:

      1. 令topLevelOrigin为environment的顶级源。

      2. 如topLevelOrigin为null,则设为environment的顶级创建URL的origin。

      3. 断言:topLevelOrigin为origin。

      4. 令topLevelSite为获取站点的结果,参数为topLevelOrigin。

      在子导航对象预取时,顶级站点由包含它的顶级可遍历对象决定,且不会因重定向改变,因此可用environment判定。
    6. 令secondKey为null或实现自定义的值。

      secondKey应该与普通导航下该响应应有的值相同。
    7. 令destinationPartitionKey为(topLevelSite, secondKey)。

    8. 令cookieStore为与destinationPartitionKey关联的cookie存储。

    9. 令newCookie为cookie的副本。

    10. 设置newCookie的creation-time和last-access-time为当前日期和时间。

    11. 如果cookieStore中已有与newCookie同名、同domain、同path的existingCookie:

      1. 设newCookie的creation-time为existingCookie的creation-time。

      2. 从cookieStore移除existingCookie。

    12. 插入newCookie到cookieStore。

      这种先移除后插入的处理和接收cookie时一致。

8. `Sec-Purpose` HTTP请求头

本节重定义了 `Sec-Purpose` HTTP请求头在[FETCH]中的定义。

`Sec-Purpose` HTTP请求头用于声明该请求目的不只是为了用户即时使用资源。

该头部字段为[RFC9651]结构化头,其值必须为List。

其成员可能包含一个Item,即Token prefetch。若包含该项,即表示请求目的是预先下载即将被获取的资源。

下列参数用于prefetch令牌:

9. 自动化测试

为用户代理自动化和应用测试目的,本文档定义了对[WebDriver-BiDi] 规范的扩展。

CDDL代码片段使用"text"类型而不是"browsingContext.BrowsingContext",以便对CDDL片段进行独立编程处理。目前其它模块无法被引用。

9.1. speculation模块

speculation模块包含用于管理远端预取、预渲染和推测规则行为的命令。

9.1.1. 定义

本地端定义

speculation.PreloadingStatus = "pending" / "ready" / "success" / "failure"

speculation.PreloadingStatus类型表示不同预加载状态。此状态为预取和预渲染共用。

"pending"
预取记录 prefetchRecord开始启动referrer发起的导航预取。
"ready"
预取记录 prefetchRecord已完成。
"success"
预取记录 prefetchRecord被激活(如用户代理导航时触发)。
"failure"
预取记录 prefetchRecord失败(如过期时)。

9.1.2. 事件

SpeculationEvent = (
  speculation.PrefetchStatusUpdated
)
9.1.2.1. speculation.prefetchStatusUpdated事件
speculation.PrefetchStatusUpdated = (
   method: "speculation.prefetchStatusUpdated",
   params: speculation.PrefetchStatusUpdatedParameters
)

speculation.PrefetchStatusUpdatedParameters = {
   context: text,
   url: text,
   status: speculation.PreloadingStatus
}
To 触发 prefetch 状态更新事件,给定可导航对象navigable、预取记录prefetchRecord, 以及speculation.PreloadingStatus preloadingStatus:
  1. 令params为一个有序映射,对应 speculation.PrefetchStatusUpdatedParameters定义,其 context字段为navigable的顶级可遍历对象的navigable id,url字段为prefetchRecord的URL,status字段为preloadingStatus。

  2. 令body为一个有序映射,同 speculation.PrefetchStatusUpdatedParameters定义,其 params字段为params。

  3. 令relatedNavigables为一个有序集合,包含navigable。

  4. 对于"speculation.prefetchStatusUpdated"事件和relatedNavigables,对已启用事件的会话集合中的每个session:

    1. 发送事件,参数为session和body。

10. 安全注意事项

参见 HTML § 7.6.5 安全注意事项。

11. 隐私注意事项

参见 HTML § 7.6.6 隐私注意事项。

索引

本规范定义的术语

引用文献中定义的术语

参考文献

规范性引用

[CLEAR-SITE-DATA]
Mike West. 清除站点数据. URL: https://w3c.github.io/webappsec-clear-site-data/
[COOKIES]
A. Barth. HTTP 状态管理机制. 2011 年 4 月. 提议标准. URL:https://httpwg.org/specs/rfc6265.html
[CSP]
Mike West; Antonio Sartori. 内容安全策略 第 3 级. URL:https://w3c.github.io/webappsec-csp/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL:https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch 标准. 现行 标准. URL:https://fetch.spec.whatwg.org/
[HR-TIME-3]
Yoav Weiss. 高精度时间. URL:https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL:https://infra.spec.whatwg.org/
[NAVIGATION-TIMING-2]
Yoav Weiss; Noam Rosenthal. 导航计时第 2 级. URL:https://w3c.github.io/navigation-timing/
No-Vary-Search HTTP 缓存扩展. 编辑草案. URL:https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-no-vary-search
[PRERENDERING-REVAMPED]
重新设计的预渲染. 社区组报告草案. URL:https://wicg.github.io/nav-speculation/prerendering.html
[REFERRER-POLICY]
Jochen Eisinger; Emily Stark. Referrer 策略. URL:https://w3c.github.io/webappsec-referrer-policy/
[RESOURCE-TIMING]
Yoav Weiss; Noam Rosenthal. 资源 计时. URL:https://w3c.github.io/resource-timing/
[RFC8610]
H. Birkholz; C. Vigano; C. Bormann. 简明数据 定义语言(CDDL):用于表达简明二进制对象表示 (CBOR)和 JSON 数据结构的表示法约定. 2019 年 6 月. 提议标准. URL:https://www.rfc-editor.org/info/rfc8610/
[RFC9651]
M. Nottingham; P-H. Kamp. HTTP 的结构化字段值 . 2024 年 9 月. 提议标准. URL:https://www.rfc-editor.org/info/rfc9651/
[SECURE-CONTEXTS]
Mike West. 安全上下文. URL: https://w3c.github.io/webappsec-secure-contexts/
[SERVICE-WORKERS]
Monica CHINTALA; Yoshisato Yanagisawa. Service Workers 每夜版. URL:https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL 标准. 现行标准. URL:https://url.spec.whatwg.org/
[WebDriver-BiDi]
James Graham; Alex Rudenko; Maksim Sadym. WebDriver BiDi. URL:https://w3c.github.io/webdriver-bidi/

非规范性 参考文献

[CONSOLE]
Dominic Farolino; Robert Kowalski; Terin Stock. 控制台标准. 实时标准. URL: https://console.spec.whatwg.org/
[NAVIGATION-TIMING]
Zhiheng Wang. 导航时序. 2012年12月17日. REC. URL: https://www.w3.org/TR/navigation-timing/

CDDL 索引

问题索引

或许可以根据缓存响应头判断某个响应是否适合多次复用,但考虑到预取缓冲区的寿命很短,目前还不确定这样做是否有价值。 ↵
目前IP匿名化的实现方式尚不明确。可能会用某种代理或中继来完成(实现自定义)。理想情况下,该方案应深入到底层的获取连接,甚至可能进一步标准化。 ↵
预取会在导航开始的瞬间中止,这可能不是理想的行为。 ↵
缓存,虽然不是正确性所必需,也有用复制。 ↵
认证信息也可能在跨站点时获得,不过这似乎较少。(例如,某站点可能跳转到含有URL凭据的链接。)可能也应该复制这些内容,但是否真的鼓励尚不确定。 ↵
CDDL 片段使用 "text" 类型而不是 "browsingContext.BrowsingContext",以便对 CDDL 片段进行独立的程序化处理。目前不能引用其他模块。 ↵