Early Hints
Early Hints is an informational HTTP status code allowing server to send headers likely to appear in the final response before sending the final response.
This is used to send Link headers to start preconnect
s and preload
s.
This document is about the implementation details of Early Hints in Firefox.
We focus on the preload
feature, as it is the main feature interacting with classes.
For Early Hint preconnect
the Early Hints specific code is rather small and only touches the code path on 103 Early Hints
responses.
sequenceDiagram participant F as Firefox participant S as Server autonumber F->>+S: Main document Request: GET / S-->>F: 103 Early Hints Response note over F: Firefox starts<br/>hinted requests note over S: Server Think Time S->>-F: 200 OK final response
Early Hints benefits originate from leveraging Server Think Time. The duration between response (2) and (3) arriving is the theoretical maximal benefit Early Hints can have. The server think time can originate from creating dynamic content by interacting with databases or more commonly when proxying the request to a different server.
103 Early Hints
Response on Main Document Load
On 103 Early Hints
response the nsHttpChannel
handling main document load passes the link header and a few more from the 103 Early Hints
response to the EarlyHintsService
When receiving a 103 Early Hints
response, the nsHttpChannel
forwards the Link
headers in the 103 Early Hints
response to the EarlyHintsService
When the DocumentLoadListener
receives a cross-origin redirect, it cancels all preloads in progress.
Note
Only the first 103 Early Hints
response is processed.
The remaining 103 Early Hints
responses are ignored, even after same-origin redirects.
When we receive cross origin redirects, all ongoing Early Hint preload requests are cancelled.
graph TD MainChannel[nsHttpChannel] EHS[EarlyHintsService] EHC[EarlyHintPreconnect] EHP[EarlyHintPreloader] PreloadChannel[nsIChannel] PCL[ParentChannelListener] MainChannel -- "nsIEarlyHintsObserver::EarlyHint(LinkHeader, Csp, RefererPolicy)<br/>via DocumentLoadListener" --> EHS EHS -- "rel=preconnect" --> EHC EHS -->|"rel=preload<br/>via OngoingEarlyHints"| EHP EHP -->|"CSP checks then AsyncOpen"| PreloadChannel PreloadChannel -->|mListener| PCL PCL -->|mNextListener| EHP
Main document Final Response
On the final response the DocumentLoadListener
retrieves the list of link headers from the EarlyHintsService
.
As a side effect, the EarlyHintPreloader
also starts a 10s timer to cancel itself if the content process doesn’t connect to the EarlyHintPreloader
.
The timeout shouldn’t occur in normal circumstances, because the content process connects to that EarlyHintPreloader
immediately.
The timeout currently only occurs when:
the main response has different CSP requirements disallowing the load (Bug 1815884),
the main response has COEP headers disallowing the load (Bug 1806403),
the user reloads a website and the image/css is already in the image/css-cache (Bug 1815884),
the tab gets closed before the connect happens or possibly other corner cases.
graph TD DLL[DocumentLoadListener] EHP[EarlyHintPreloader] PS[PreloadService] EHR[EarlyHintsRegistrar] Timer[nsITimer] DLL -- "(1)<br/>GetAllPreloads(newCspRequirements)<br/> via EarlyHintsService and OngoingEarlyHints" --> EHP EHP -->|"Start timer to cancel on<br/>ParentConnectTimeout<br/>after 10s"| Timer EHP -->|"Register(earlyHintPreloaderId)"| EHR Timer -->|"RefPtr"| EHP EHR -->|"RefPtr"| EHP DLL -- "(2)<br/>Send to content process via IPC<br/>List of Links+earlyHintPreloaderId" --> PS
Preload request from Content process
The Child process parses Link headers from the 103 Early Hints
response first and then from the main document response.
Preloads from the Link headers of the 103 Early Hints
response have an earlyHintPreloadId
assigned to them.
The Preloader sets this earlyHintPreloaderId
on the channel doing the preload before calling AsyncOpen
.
The HttpChannelParent
looks for the earlyHintPreloaderId
in AsyncOpen
and connects to the EarlyHintPreloader
via the EarlyHintRegistrar
instead of doing a network request.
graph TD 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
Early Hint Preload request
The EarlyHintPreloader
follows HTTP 3xx redirects and always sets the request header X-Moz: early hint
.
Early Hint Preload response
When the EarlyHintPreloader
received the OnStartRequest
it forwards all nsIRequestObserver
functions to the HttpChannelParent
as soon as it knows which HttpChannelParent
to forward the nsIRequestObserver
functions to.
graph TD OPC["EHP::OnParentConnect"] OSR["EHP::OnStartRequest"] Invoke["Invoke StreamListenerFunctions"] End(("­")) OPC -- "CancelTimer" --> Invoke OSR -- "Suspend Channel if called<br/>before OnParentReady" --> Invoke Invoke -- "Resume Channel if suspended<br/>Forward OSR+ODA+OSR<br/>Set Listener of ParentChanelListener to HttpChannelParent" --> End
Final setup
In the end all the remaining OnDataAvailable
and OnStopRequest
calls are passed down this call chain from nsIChannel
to the preloader.
graph TD 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