Bug 1771867 - Early Hints Phase 2 - Part 2: Pass early hint preloads to preloader from IPC r=necko-reviewers,valentin
☠☠ backed out by e2b41473c7ed ☠ ☠
authorManuel Bucher <manuel@mozilla.com>
Fri, 02 Dec 2022 09:45:24 +0000
changeset 644479 5090eae24a5cfedad6feda5bc9dd400c501162f5
parent 644478 f8a03d226c73c926268d0f60f8477b4268be9bd4
child 644480 7cdf3cef27736a14fd8f9db58522da37e103d984
push id40449
push usercsabou@mozilla.com
push dateFri, 02 Dec 2022 21:26:33 +0000
treeherdermozilla-central@360f8b71c676 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnecko-reviewers, valentin
bugs1771867
milestone109.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1771867 - Early Hints Phase 2 - Part 2: Pass early hint preloads to preloader from IPC r=necko-reviewers,valentin Differential Revision: https://phabricator.services.mozilla.com/D161173
dom/base/Document.cpp
dom/base/Document.h
dom/base/moz.build
dom/base/nsContentSink.cpp
dom/base/nsContentSink.h
dom/ipc/ContentChild.cpp
netwerk/ipc/DocumentChannelChild.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/nsIHttpChannelInternal.idl
parser/html/nsHtml5TreeOpExecutor.cpp
uriloader/preload/PreloadService.cpp
uriloader/preload/PreloadService.h
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -6742,16 +6742,21 @@ void Document::SetHeaderData(nsAtom* aHe
     mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on");
   }
 
   if (aHeaderField == nsGkAtoms::handheldFriendly) {
     mViewportType = Unknown;
   }
 }
 
+void Document::SetEarlyHints(
+    nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints) {
+  mEarlyHints = std::move(aEarlyHints);
+}
+
 void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
                                  NotNull<const Encoding*>& aEncoding,
                                  nsHtml5TreeOpExecutor* aExecutor) {
   if (aChannel) {
     nsAutoCString charsetVal;
     nsresult rv = aChannel->GetContentCharset(charsetVal);
     if (NS_SUCCEEDED(rv)) {
       const Encoding* preferred = Encoding::ForLabel(charsetVal);
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -317,16 +317,17 @@ enum BFCacheStatus {
   ACTIVE_LOCK = 1 << 16,                 // Status 16
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 namespace mozilla::net {
 class ChannelEventQueue;
+class EarlyHintConnectArgs;
 }  // namespace mozilla::net
 
 // Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
 #define NS_IDOCUMENT_IID                             \
   {                                                  \
     0xce1f7627, 0x7109, 0x4977, {                    \
       0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa \
     }                                                \
@@ -1164,16 +1165,25 @@ class Document : public nsINode,
   /**
    * Access HTTP header data (this may also get set from other
    * sources, like HTML META tags).
    */
   void GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const;
   void SetHeaderData(nsAtom* aheaderField, const nsAString& aData);
 
   /**
+   * Set Early Hint data, moves the arrays into the function, leaving the
+   * passed variables empty
+   */
+  void SetEarlyHints(nsTArray<net::EarlyHintConnectArgs>&& aEarlyHints);
+  const nsTArray<net::EarlyHintConnectArgs>& GetEarlyHints() const {
+    return mEarlyHints;
+  }
+
+  /**
    * Create a new presentation shell that will use aContext for its
    * presentation context (presentation contexts <b>must not</b> be
    * shared among multiple presentation shells). The caller of this
    * method is responsible for calling BeginObservingDocument() on the
    * presshell if the presshell should observe document mutations.
    */
   already_AddRefed<PresShell> CreatePresShell(nsPresContext* aContext,
                                               nsViewManager* aViewManager);
@@ -5112,16 +5122,18 @@ class Document : public nsINode,
   // https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
   ViewportFitType mViewportFit;
 
   PLDHashTable* mSubDocuments;
 
   class HeaderData;
   UniquePtr<HeaderData> mHeaderData;
 
+  nsTArray<net::EarlyHintConnectArgs> mEarlyHints;
+
   nsRevocableEventPtr<nsRunnableMethod<Document, void, false>>
       mPendingTitleChangeEvent;
 
   RefPtr<nsDOMNavigationTiming> mTiming;
 
   // Recorded time of change to 'loading' state.
   TimeStamp mLoadingTimeStamp;
 
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -564,16 +564,17 @@ LOCAL_INCLUDES += [
     "/js/xpconnect/src",
     "/js/xpconnect/wrappers",
     "/layout/base",
     "/layout/forms",
     "/layout/generic",
     "/layout/style",
     "/layout/xul",
     "/netwerk/base",
+    "/netwerk/protocol/http",
     "/netwerk/url-classifier",
     "/parser/htmlparser",
     "/security/manager/ssl",
     "/widget",
     "/xpcom/ds",
 ]
 
 if CONFIG["MOZ_WEBRTC"]:
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -15,16 +15,18 @@
 #include "mozilla/StaticPrefs_browser.h"
 #include "mozilla/StaticPrefs_content.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/LinkStyle.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/dom/MutationObservers.h"
 #include "mozilla/dom/SRILogHelper.h"
 #include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoChannelParams.h"
 #include "nsIDocShell.h"
 #include "nsILoadContext.h"
 #include "nsIPrefetchService.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsIMIMEHeaderParam.h"
 #include "nsIProtocolHandler.h"
 #include "nsIHttpChannel.h"
@@ -217,47 +219,62 @@ nsContentSink::StyleSheetLoaded(StyleShe
 
 nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) {
   nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aChannel));
 
   if (!httpchannel) {
     return NS_OK;
   }
 
+  bool gotEarlyHints = false;
+  if (nsCOMPtr<mozilla::net::HttpBaseChannel> baseChannel =
+          do_QueryInterface(aChannel)) {
+    nsTArray<mozilla::net::EarlyHintConnectArgs> earlyHints =
+        baseChannel->TakeEarlyHints();
+    gotEarlyHints = !earlyHints.IsEmpty();
+    mDocument->SetEarlyHints(std::move(earlyHints));
+  }
+
   // Note that the only header we care about is the "link" header, since we
   // have all the infrastructure for kicking off stylesheet loads.
 
   nsAutoCString linkHeader;
 
   nsresult rv = httpchannel->GetResponseHeader("link"_ns, linkHeader);
-  if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
+  bool gotLinkHeader = NS_SUCCEEDED(rv) && !linkHeader.IsEmpty();
+  if (gotLinkHeader) {
     mDocument->SetHeaderData(nsGkAtoms::link,
                              NS_ConvertASCIItoUTF16(linkHeader));
-
+  }
+  if (gotLinkHeader || gotEarlyHints) {
     NS_ASSERTION(!mProcessLinkHeaderEvent.get(),
                  "Already dispatched an event?");
 
     mProcessLinkHeaderEvent =
         NewNonOwningRunnableMethod("nsContentSink::DoProcessLinkHeader", this,
                                    &nsContentSink::DoProcessLinkHeader);
     rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get());
     if (NS_FAILED(rv)) {
       mProcessLinkHeaderEvent.Forget();
     }
   }
 
   return NS_OK;
 }
 
 void nsContentSink::DoProcessLinkHeader() {
+  for (const auto& earlyHint : mDocument->GetEarlyHints()) {
+    ProcessLinkFromHeader(earlyHint.link(), earlyHint.earlyHintPreloaderId());
+  }
+
   nsAutoString value;
   mDocument->GetHeaderData(nsGkAtoms::link, value);
   auto linkHeaders = net::ParseLinkHeader(value);
   for (const auto& linkHeader : linkHeaders) {
-    ProcessLinkFromHeader(linkHeader);
+    ProcessLinkFromHeader(linkHeader, 0);
   }
 }
 
 // check whether the Link header field applies to the context resource
 // see <http://tools.ietf.org/html/rfc5988#section-5.2>
 
 bool nsContentSink::LinkContextIsOurDocument(const nsAString& aAnchor) {
   if (aAnchor.IsEmpty()) {
@@ -292,17 +309,18 @@ bool nsContentSink::LinkContextIsOurDocu
   if (NS_FAILED(rv)) {
     // comparison failed
     return false;
   }
 
   return same;
 }
 
-nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader) {
+nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader,
+                                              uint64_t aEarlyHintPreloaderId) {
   uint32_t linkTypes = LinkStyle::ParseLinkTypes(aHeader.mRel);
 
   // The link relation may apply to a different resource, specified
   // in the anchor parameter. For the link relations supported so far,
   // we simply abort if the link applies to a resource different to the
   // one we've loaded
   if (!LinkContextIsOurDocument(aHeader.mAnchor)) {
     return NS_OK;
@@ -320,17 +338,18 @@ nsresult nsContentSink::ProcessLinkFromH
 
     if (!aHeader.mHref.IsEmpty() && (linkTypes & LinkStyle::ePRECONNECT)) {
       Preconnect(aHeader.mHref, aHeader.mCrossOrigin);
     }
 
     if (linkTypes & LinkStyle::ePRELOAD) {
       PreloadHref(aHeader.mHref, aHeader.mAs, aHeader.mType, aHeader.mMedia,
                   aHeader.mIntegrity, aHeader.mSrcset, aHeader.mSizes,
-                  aHeader.mCrossOrigin, aHeader.mReferrerPolicy);
+                  aHeader.mCrossOrigin, aHeader.mReferrerPolicy,
+                  aEarlyHintPreloaderId);
     }
 
     if ((linkTypes & LinkStyle::eMODULE_PRELOAD) &&
         mDocument->ScriptLoader()->GetModuleLoader()) {
       // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-modulepreload-module-script-graph
       // Step 1. Disallow further import maps given settings object.
       mDocument->ScriptLoader()->GetModuleLoader()->DisallowImportMaps();
     }
@@ -430,17 +449,18 @@ void nsContentSink::PrefetchHref(const n
   }
 }
 
 void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs,
                                 const nsAString& aType, const nsAString& aMedia,
                                 const nsAString& aIntegrity,
                                 const nsAString& aSrcset,
                                 const nsAString& aSizes, const nsAString& aCORS,
-                                const nsAString& aReferrerPolicy) {
+                                const nsAString& aReferrerPolicy,
+                                uint64_t aEarlyHintPreloaderId) {
   auto encoding = mDocument->GetDocumentCharacterSet();
   nsCOMPtr<nsIURI> uri;
   NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI());
   if (!uri) {
     // URL parsing failed.
     return;
   }
 
@@ -454,19 +474,19 @@ void nsContentSink::PreloadHref(const ns
   auto policyType = mozilla::net::AsValueToContentPolicy(asAttr);
   if (policyType == nsIContentPolicy::TYPE_INVALID ||
       !mozilla::net::CheckPreloadAttrs(asAttr, mimeType, aMedia, mDocument)) {
     // Ignore preload wrong or empty attributes.
     mozilla::net::WarnIgnoredPreload(*mDocument, *uri);
     return;
   }
 
-  mDocument->Preloads().PreloadLinkHeader(uri, aHref, policyType, aAs, aType,
-                                          aIntegrity, aSrcset, aSizes, aCORS,
-                                          aReferrerPolicy);
+  mDocument->Preloads().PreloadLinkHeader(
+      uri, aHref, policyType, aAs, aType, aIntegrity, aSrcset, aSizes, aCORS,
+      aReferrerPolicy, aEarlyHintPreloaderId);
 }
 
 void nsContentSink::PrefetchDNS(const nsAString& aHref) {
   nsAutoString hostname;
   bool isHttps = false;
 
   if (StringBeginsWith(aHref, u"//"_ns)) {
     hostname = Substring(aHref, 2);
--- a/dom/base/nsContentSink.h
+++ b/dom/base/nsContentSink.h
@@ -120,30 +120,33 @@ class nsContentSink : public nsICSSLoade
  protected:
   nsContentSink();
   virtual ~nsContentSink();
 
   nsresult Init(Document* aDoc, nsIURI* aURI, nsISupports* aContainer,
                 nsIChannel* aChannel);
 
   nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
-  nsresult ProcessLinkFromHeader(const mozilla::net::LinkHeader& aHeader);
+  // aEarlyHintPreloaderId zero means no early hint channel to connect back
+  nsresult ProcessLinkFromHeader(const mozilla::net::LinkHeader& aHeader,
+                                 uint64_t aEarlyHintPreloaderId);
 
   virtual nsresult ProcessStyleLinkFromHeader(
       const nsAString& aHref, bool aAlternate, const nsAString& aTitle,
       const nsAString& aIntegrity, const nsAString& aType,
       const nsAString& aMedia, const nsAString& aReferrerPolicy);
 
   void PrefetchHref(const nsAString& aHref, const nsAString& aAs,
                     const nsAString& aType, const nsAString& aMedia);
   void PreloadHref(const nsAString& aHref, const nsAString& aAs,
                    const nsAString& aType, const nsAString& aMedia,
                    const nsAString& aIntegrity, const nsAString& aSrcset,
                    const nsAString& aSizes, const nsAString& aCORS,
-                   const nsAString& aReferrerPolicy);
+                   const nsAString& aReferrerPolicy,
+                   uint64_t aEarlyHintPreloaderId);
 
   // For PrefetchDNS() aHref can either be the usual
   // URI format or of the form "//www.hostname.com" without a scheme.
   void PrefetchDNS(const nsAString& aHref);
 
   // Gets the cache key (used to identify items in a cache) of the channel.
   nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey);
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3545,16 +3545,20 @@ mozilla::ipc::IPCResult ContentChild::Re
   nsCOMPtr<nsIChannel> newChannel;
   MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() &
               nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) ||
              aArgs.srcdocData().IsVoid());
   rv = nsDocShell::CreateRealChannelForDocument(
       getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr,
       aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri());
 
+  if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) {
+    httpChannel->SetEarlyHints(std::move(aArgs.earlyHints()));
+  }
+
   // This is used to report any errors back to the parent by calling
   // CrossProcessRedirectFinished.
   RefPtr<HttpChannelChild> httpChild = do_QueryObject(newChannel);
   auto resolve = [=](const nsresult& aRv) {
     nsresult rv = aRv;
     if (httpChild) {
       rv = httpChild->CrossProcessRedirectFinished(rv);
     }
--- a/netwerk/ipc/DocumentChannelChild.cpp
+++ b/netwerk/ipc/DocumentChannelChild.cpp
@@ -262,16 +262,20 @@ IPCResult DocumentChannelChild::RecvRedi
              aArgs.srcdocData().IsVoid());
   nsresult rv = nsDocShell::CreateRealChannelForDocument(
       getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr,
       aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri());
   if (newChannel) {
     newChannel->SetLoadGroup(mLoadGroup);
   }
 
+  if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) {
+    httpChannel->SetEarlyHints(std::move(aArgs.earlyHints()));
+  }
+
   // This is used to report any errors back to the parent by calling
   // CrossProcessRedirectFinished.
   auto scopeExit = MakeScopeExit([&]() {
     mRedirectResolver(rv);
     mRedirectResolver = nullptr;
   });
 
   if (NS_FAILED(rv)) {
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -5940,14 +5940,23 @@ bool HttpBaseChannel::Http3Allowed() con
 
 void HttpBaseChannel::SetDummyChannelForImageCache() {
   mDummyChannelForImageCache = true;
   MOZ_ASSERT(!mResponseHead,
              "SetDummyChannelForImageCache should only be called once");
   mResponseHead = MakeUnique<nsHttpResponseHead>();
 }
 
+void HttpBaseChannel::SetEarlyHints(
+    nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
+  mEarlyHints = std::move(aEarlyHints);
+}
+
+nsTArray<EarlyHintConnectArgs>&& HttpBaseChannel::TakeEarlyHints() {
+  return std::move(mEarlyHints);
+}
+
 void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
   mConnectionInfo = aCI ? aCI->Clone() : nullptr;
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -9,16 +9,17 @@
 #define mozilla_net_HttpBaseChannel_h
 
 #include <utility>
 
 #include "mozilla/AtomicBitfields.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/DOMTypes.h"
 #include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/PrivateBrowsingChannel.h"
 #include "nsCOMPtr.h"
 #include "nsHashPropertyBag.h"
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 #include "nsHttpRequestHead.h"
 #include "nsIClassOfService.h"
@@ -780,16 +781,28 @@ class HttpBaseChannel : public nsHashPro
 
   UniquePtr<ProfileChunkedBuffer> mSource;
 
   uint32_t mLoadFlags;
   uint32_t mCaps;
 
   ClassOfService mClassOfService;
 
+ public:
+  void SetEarlyHints(
+      nsTArray<mozilla::net::EarlyHintConnectArgs>&& aEarlyHints);
+  nsTArray<mozilla::net::EarlyHintConnectArgs>&& TakeEarlyHints();
+
+ protected:
+  // Storing Http 103 Early Hint preloads. The parent process is responsible to
+  // start the early hint preloads, but the http child needs to be able to look
+  // them up. They are sent via IPC and stored in this variable. This is set on
+  // main document channel
+  nsTArray<EarlyHintConnectArgs> mEarlyHints;
+
   // clang-format off
   MOZ_ATOMIC_BITFIELDS(mAtomicBitfields1, 32, (
     (uint32_t, UpgradeToSecure, 1),
     (uint32_t, ApplyConversion, 1),
     // Set to true if DoApplyContentConversions has been applied to
     // our default mListener.
     (uint32_t, HasAppliedConversion, 1),
     (uint32_t, IsPending, 1),
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -10,16 +10,17 @@
 #include "nsStringFwd.h"
 #include "nsTArrayForwardDeclare.h"
 template<class T> class nsCOMArray;
 namespace mozilla {
 class TimeStamp;
 namespace net {
 class nsHttpConnectionInfo;
 class WebSocketConnectionBase;
+class EarlyHintConnectArgs;
 }
 namespace dom {
 enum class RequestMode : uint8_t;
 }
 }
 %}
 [ptr] native nsHttpConnectionInfo(mozilla::net::nsHttpConnectionInfo);
 [ptr] native StringArray(nsTArray<nsCString>);
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -1280,29 +1280,29 @@ void nsHtml5TreeOpExecutor::PreloadFont(
                                         const nsAString& aCrossOrigin,
                                         const nsAString& aMedia,
                                         const nsAString& aReferrerPolicy) {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   if (!uri) {
     return;
   }
 
-  mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy);
+  mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0);
 }
 
 void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
                                          const nsAString& aCrossOrigin,
                                          const nsAString& aMedia,
                                          const nsAString& aReferrerPolicy) {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia);
   if (!uri) {
     return;
   }
 
-  mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy);
+  mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0);
 }
 
 void nsHtml5TreeOpExecutor::PreloadOpenPicture() {
   mDocument->PreloadPictureOpened();
 }
 
 void nsHtml5TreeOpExecutor::PreloadEndPicture() {
   mDocument->PreloadPictureClosed();
--- a/uriloader/preload/PreloadService.cpp
+++ b/uriloader/preload/PreloadService.cpp
@@ -82,52 +82,53 @@ already_AddRefed<PreloaderBase> PreloadS
   aLinkElement->GetHref(url);
   aLinkElement->GetCrossOrigin(crossOrigin);
   aLinkElement->GetIntegrity(integrity);
   aLinkElement->GetReferrerPolicy(referrerPolicy);
   aLinkElement->GetType(type);
 
   auto result = PreloadOrCoalesce(uri, url, aPolicyType, as, type, charset,
                                   srcset, sizes, integrity, crossOrigin,
-                                  referrerPolicy, /* aFromHeader = */ false);
+                                  referrerPolicy, /* aFromHeader = */ false, 0);
 
   if (!result.mPreloader) {
     NotifyNodeEvent(aLinkElement, result.mAlreadyComplete);
     return nullptr;
   }
 
   result.mPreloader->AddLinkPreloadNode(aLinkElement);
   return result.mPreloader.forget();
 }
 
 void PreloadService::PreloadLinkHeader(
     nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
     const nsAString& aAs, const nsAString& aType, const nsAString& aIntegrity,
     const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aCORS,
-    const nsAString& aReferrerPolicy) {
+    const nsAString& aReferrerPolicy, uint64_t aEarlyHintPreloaderId) {
   if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
     MOZ_ASSERT_UNREACHABLE("Caller should check");
     return;
   }
 
   if (!StaticPrefs::network_preload()) {
     return;
   }
 
   PreloadOrCoalesce(aURI, aURL, aPolicyType, aAs, aType, u""_ns, aSrcset,
                     aSizes, aIntegrity, aCORS, aReferrerPolicy,
-                    /* aFromHeader = */ true);
+                    /* aFromHeader = */ true, aEarlyHintPreloaderId);
 }
 
 PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce(
     nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
     const nsAString& aAs, const nsAString& aType, const nsAString& aCharset,
     const nsAString& aSrcset, const nsAString& aSizes,
     const nsAString& aIntegrity, const nsAString& aCORS,
-    const nsAString& aReferrerPolicy, bool aFromHeader) {
+    const nsAString& aReferrerPolicy, bool aFromHeader,
+    uint64_t aEarlyHintPreloaderId) {
   if (!aURI) {
     MOZ_ASSERT_UNREACHABLE("Should not pass null nsIURI");
     return {nullptr, false};
   }
 
   bool isImgSet = false;
   PreloadHashKey preloadKey;
   nsCOMPtr<nsIURI> uri = aURI;
@@ -158,75 +159,79 @@ PreloadService::PreloadOrCoalesceResult 
   }
 
   if (RefPtr<PreloaderBase> preload = LookupPreload(preloadKey)) {
     return {std::move(preload), false};
   }
 
   if (aAs.LowerCaseEqualsASCII("script")) {
     PreloadScript(uri, aType, aCharset, aCORS, aReferrerPolicy, aIntegrity,
-                  true /* isInHead - TODO */);
+                  true /* isInHead - TODO */, aEarlyHintPreloaderId);
   } else if (aAs.LowerCaseEqualsASCII("style")) {
     auto status = mDocument->PreloadStyle(
         aURI, Encoding::ForLabel(aCharset), aCORS,
         PreloadReferrerPolicy(aReferrerPolicy), aIntegrity,
         aFromHeader ? css::StylePreloadKind::FromLinkRelPreloadHeader
                     : css::StylePreloadKind::FromLinkRelPreloadElement);
     switch (status) {
       case dom::SheetPreloadStatus::AlreadyComplete:
         return {nullptr, /* already_complete = */ true};
       case dom::SheetPreloadStatus::Errored:
       case dom::SheetPreloadStatus::InProgress:
         break;
     }
   } else if (aAs.LowerCaseEqualsASCII("image")) {
-    PreloadImage(uri, aCORS, aReferrerPolicy, isImgSet);
+    PreloadImage(uri, aCORS, aReferrerPolicy, isImgSet, aEarlyHintPreloaderId);
   } else if (aAs.LowerCaseEqualsASCII("font")) {
-    PreloadFont(uri, aCORS, aReferrerPolicy);
+    PreloadFont(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId);
   } else if (aAs.LowerCaseEqualsASCII("fetch")) {
-    PreloadFetch(uri, aCORS, aReferrerPolicy);
+    PreloadFetch(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId);
   }
 
   return {LookupPreload(preloadKey), false};
 }
 
 void PreloadService::PreloadScript(nsIURI* aURI, const nsAString& aType,
                                    const nsAString& aCharset,
                                    const nsAString& aCrossOrigin,
                                    const nsAString& aReferrerPolicy,
                                    const nsAString& aIntegrity,
-                                   bool aScriptFromHead) {
+                                   bool aScriptFromHead,
+                                   uint64_t aEarlyHintPreloaderId) {
   mDocument->ScriptLoader()->PreloadURI(
       aURI, aCharset, aType, aCrossOrigin, aIntegrity, aScriptFromHead, false,
       false, false, true, PreloadReferrerPolicy(aReferrerPolicy));
 }
 
 void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
                                   const nsAString& aImageReferrerPolicy,
-                                  bool aIsImgSet) {
+                                  bool aIsImgSet,
+                                  uint64_t aEarlyHintPreloaderId) {
   mDocument->PreLoadImage(aURI, aCrossOrigin,
                           PreloadReferrerPolicy(aImageReferrerPolicy),
                           aIsImgSet, true);
 }
 
 void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
-                                 const nsAString& aReferrerPolicy) {
+                                 const nsAString& aReferrerPolicy,
+                                 uint64_t aEarlyHintPreloaderId) {
   CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
   auto key = PreloadHashKey::CreateAsFont(aURI, cors);
 
   // * Bug 1618549: Depending on where we decide to do the deduplication, we may
   // want to check if the font is already being preloaded here.
 
   RefPtr<FontPreloader> preloader = new FontPreloader();
   dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
   preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument);
 }
 
 void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
-                                  const nsAString& aReferrerPolicy) {
+                                  const nsAString& aReferrerPolicy,
+                                  uint64_t aEarlyHintPreloaderId) {
   CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
   auto key = PreloadHashKey::CreateAsFetch(aURI, cors);
 
   // * Bug 1618549: Depending on where we decide to do the deduplication, we may
   // want to check if a fetch is already being preloaded here.
 
   RefPtr<FetchPreloader> preloader = new FetchPreloader();
   dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
--- a/uriloader/preload/PreloadService.h
+++ b/uriloader/preload/PreloadService.h
@@ -61,36 +61,45 @@ class PreloadService {
       const PreloadHashKey& aKey) const;
 
   void SetSpeculationBase(nsIURI* aURI) { mSpeculationBaseURI = aURI; }
   already_AddRefed<nsIURI> GetPreloadURI(const nsAString& aURL);
 
   already_AddRefed<PreloaderBase> PreloadLinkElement(
       dom::HTMLLinkElement* aLink, nsContentPolicyType aPolicyType);
 
+  // a non-zero aEarlyHintPreloaderId tells this service that a preload for this
+  // link was started by the EarlyHintPreloader and the preloaders should
+  // connect back by setting earlyHintPreloaderId in nsIChannelInternal before
+  // AsyncOpen.
   void PreloadLinkHeader(nsIURI* aURI, const nsAString& aURL,
                          nsContentPolicyType aPolicyType, const nsAString& aAs,
                          const nsAString& aType, const nsAString& aIntegrity,
                          const nsAString& aSrcset, const nsAString& aSizes,
                          const nsAString& aCORS,
-                         const nsAString& aReferrerPolicy);
+                         const nsAString& aReferrerPolicy,
+                         uint64_t aEarlyHintPreloaderId);
 
   void PreloadScript(nsIURI* aURI, const nsAString& aType,
                      const nsAString& aCharset, const nsAString& aCrossOrigin,
                      const nsAString& aReferrerPolicy,
-                     const nsAString& aIntegrity, bool aScriptFromHead);
+                     const nsAString& aIntegrity, bool aScriptFromHead,
+                     uint64_t aEarlyHintPreloaderId);
 
   void PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
-                    const nsAString& aImageReferrerPolicy, bool aIsImgSet);
+                    const nsAString& aImageReferrerPolicy, bool aIsImgSet,
+                    uint64_t aEarlyHintPreloaderId);
 
   void PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
-                   const nsAString& aReferrerPolicy);
+                   const nsAString& aReferrerPolicy,
+                   uint64_t aEarlyHintPreloaderId);
 
   void PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
-                    const nsAString& aReferrerPolicy);
+                    const nsAString& aReferrerPolicy,
+                    uint64_t aEarlyHintPreloaderId);
 
   static void NotifyNodeEvent(nsINode* aNode, bool aSuccess);
 
  private:
   dom::ReferrerPolicy PreloadReferrerPolicy(const nsAString& aReferrerPolicy);
   nsIURI* BaseURIForPreload();
 
   struct PreloadOrCoalesceResult {
@@ -98,17 +107,18 @@ class PreloadService {
     bool mAlreadyComplete;
   };
 
   PreloadOrCoalesceResult PreloadOrCoalesce(
       nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
       const nsAString& aAs, const nsAString& aType, const nsAString& aCharset,
       const nsAString& aSrcset, const nsAString& aSizes,
       const nsAString& aIntegrity, const nsAString& aCORS,
-      const nsAString& aReferrerPolicy, bool aFromHeader);
+      const nsAString& aReferrerPolicy, bool aFromHeader,
+      uint64_t aEarlyHintPreloaderId);
 
  private:
   nsRefPtrHashtable<PreloadHashKey, PreloaderBase> mPreloads;
 
   // Raw pointer only, we are intended to be a direct member of dom::Document
   dom::Document* mDocument;
 
   // Set by `nsHtml5TreeOpExecutor::SetSpeculationBase`.