早期提示

早期提示 是一种信息性 HTTP 状态码,允许服务器在发送最终响应之前发送可能出现在最终响应中的标头。这用于发送 Link 标头 以启动 preconnectpreload

本文档介绍了 Firefox 中早期提示的实现细节。我们重点关注 preload 功能,因为它是与类交互的主要功能。对于早期提示 preconnect,早期提示的特定代码相当小,并且仅触及 103 Early Hints 响应 上的代码路径。

时序图 participant F as Firefox participant S as Server autonumber F->>+S: 主文档请求:GET / S-->>F: 103 Early Hints 响应 note over F: Firefox 启动<br/>提示请求 note over S: 服务器思考时间 S->>-F: 200 OK 最终响应

早期提示的优势源于利用服务器思考时间。响应 (2) 和 (3) 到达之间的时间是早期提示可以获得的理论最大收益。服务器思考时间可能源于通过与数据库交互创建动态内容,或者更常见的情况是将请求代理到不同的服务器。

103 Early Hints 主文档加载时的响应

103 Early Hints 响应中,处理主文档加载的 nsHttpChannel 将来自 103 Early Hints 响应的 Link 标头和一些其他标头传递给 EarlyHintsService

收到 103 Early Hints 响应时,nsHttpChannel103 Early Hints 响应中的 Link 标头转发到 EarlyHintsService。当 DocumentLoadListener 收到跨域重定向时,它会取消所有正在进行的预加载。

注意

仅处理第一个 103 Early Hints 响应。即使在同源重定向之后,其余的 103 Early Hints 响应也会被忽略。当我们收到跨域重定向时,所有正在进行的早期提示预加载请求都会被取消。

图 MainChannel[nsHttpChannel] EHS[EarlyHintsService] EHC[EarlyHintPreconnect] EHP[EarlyHintPreloader] PreloadChannel[nsIChannel] PCL[ParentChannelListener] MainChannel -- "nsIEarlyHintsObserver::EarlyHint(LinkHeader, Csp, RefererPolicy)<br/>通过 DocumentLoadListener" --> EHS EHS -- "rel=preconnect" --> EHC EHS -->|"rel=preload<br/>通过 OngoingEarlyHints"| EHP EHP -->|"CSP 检查然后 AsyncOpen"| PreloadChannel PreloadChannel -->|mListener| PCL PCL -->|mNextListener| EHP

主文档最终响应

在最终响应中,DocumentLoadListenerEarlyHintsService 中检索 Link 标头列表。作为副作用,EarlyHintPreloader 还会启动一个 10 秒的计时器,如果内容进程未连接到 EarlyHintPreloader,则会取消自身。在正常情况下,超时不应该发生,因为内容进程会立即连接到该 EarlyHintPreloader。目前,只有在以下情况下才会发生超时

  • 主要响应具有不同的 CSP 要求,不允许加载(Bug 1815884),

  • 主要响应具有 COEP 标头,不允许加载(Bug 1806403),

  • 用户重新加载网站,并且图像/css 已存在于图像/css 缓存中(Bug 1815884),

  • 在连接发生之前或其他一些极端情况下,标签被关闭。

图 DLL[DocumentLoadListener] EHP[EarlyHintPreloader] PS[PreloadService] EHR[EarlyHintsRegistrar] Timer[nsITimer] DLL -- "(1)<br/>GetAllPreloads(newCspRequirements)<br/> 通过 EarlyHintsService 和 OngoingEarlyHints" --> EHP EHP -->|"启动计时器以在<br/>ParentConnectTimeout<br/>10 秒后取消"| Timer EHP -->|"Register(earlyHintPreloaderId)"| EHR Timer -->|"RefPtr"| EHP EHR -->|"RefPtr"| EHP DLL -- "(2)<br/>通过 IPC 发送到内容进程<br/>链接列表 + earlyHintPreloaderId" --> PS

内容进程发起的预加载请求

子进程首先解析 103 Early Hints 响应中的 Link 标头,然后解析主文档响应中的 Link 标头。来自 103 Early Hints 响应的 Link 标头的预加载具有分配给它们的 earlyHintPreloadId。在调用 AsyncOpen 之前,预加载器会将此 earlyHintPreloaderId 设置在执行预加载的通道上。HttpChannelParentAsyncOpen 中查找 earlyHintPreloaderId,并通过 EarlyHintRegistrar 连接到 EarlyHintPreloader,而不是发出网络请求。

图 PS[PreloadService] Preloader["FetchPreloader<br/>FontPreloader<br/>imgLoader<br/>ScriptLoader<br/>StyleLoader"] Parent["HttpChannelParent"] EHR["EarlyHintRegistrar"] EHP["EarlyHintPreloader"] PS -- "PreloadLinkHeader" --> Preloader Preloader -- "NewChannel<br/>SetEarlyHintPreloaderId<br/>AsyncOpen" --> Parent Parent -- "EarlyHintRegistrar::OnParentReady(this, earlyHintPreloaderId)" --> EHR EHR -- "OnParentConnect" --> EHP

早期提示预加载请求

EarlyHintPreloader 遵循 HTTP 3xx 重定向,并始终设置请求标头 X-Moz: early hint

早期提示预加载响应

EarlyHintPreloader 收到 OnStartRequest 时,它会将所有 nsIRequestObserver 函数转发到 HttpChannelParent,只要它知道要将 nsIRequestObserver 函数转发到哪个 HttpChannelParent

图 OPC["EHP::OnParentConnect"] OSR["EHP::OnStartRequest"] Invoke["调用 StreamListenerFunctions"] End(("&shy;")) OPC -- "CancelTimer" --> Invoke OSR -- "如果在 OnParentReady 之前调用,则挂起通道" --> Invoke Invoke -- "恢复挂起的通道<br/>转发 OSR+ODA+OSR<br/>将 ParentChanelListener 的监听器设置为 HttpChannelParent" --> End

最终设置

最后,所有剩余的 OnDataAvailableOnStopRequest 调用都通过此调用链从 nsIChannel 传递到预加载器。

图 Channel[nsIChannel] PCL[ParentChannelListener] HCP[HttpChanelParent] HCC[HttpChannelChild] Preloader[FetchPreloader/imgLoader/...] Channel -- "mListener" --> PCL PCL -- "mNextListener" --> HCP HCP -- "mChannel" --> Channel HCP -- "..." --> HCC HCC -- "..." --> HCP HCC -- "mListener" --> Preloader Preloader -- "mChannel" --> HCC