From 808529a4e47fef2f7b748c3e95d8d32a16a40658 Mon Sep 17 00:00:00 2001 From: Manuel Bucher Date: Fri, 2 Dec 2022 09:45:24 +0000 Subject: [PATCH] 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 | 5 +++ dom/base/Document.h | 12 ++++++ dom/base/moz.build | 1 + dom/base/nsContentSink.cpp | 38 ++++++++++++++----- dom/base/nsContentSink.h | 7 +++- dom/ipc/ContentChild.cpp | 4 ++ netwerk/ipc/DocumentChannelChild.cpp | 4 ++ netwerk/protocol/http/HttpBaseChannel.cpp | 9 +++++ netwerk/protocol/http/HttpBaseChannel.h | 13 +++++++ .../protocol/http/nsIHttpChannelInternal.idl | 1 + parser/html/nsHtml5TreeOpExecutor.cpp | 4 +- uriloader/preload/PreloadService.cpp | 29 ++++++++------ uriloader/preload/PreloadService.h | 22 ++++++++--- 13 files changed, 118 insertions(+), 31 deletions(-) diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 5fd86278c260..0ce9962289a0 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -6747,6 +6747,11 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { } } +void Document::SetEarlyHints( + nsTArray&& aEarlyHints) { + mEarlyHints = std::move(aEarlyHints); +} + void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource, NotNull& aEncoding, nsHtml5TreeOpExecutor* aExecutor) { diff --git a/dom/base/Document.h b/dom/base/Document.h index abca2a83e8cc..2af0804074e2 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -322,6 +322,7 @@ enum BFCacheStatus { namespace mozilla::net { class ChannelEventQueue; +class EarlyHintConnectArgs; } // namespace mozilla::net // Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs @@ -1168,6 +1169,15 @@ class Document : public nsINode, 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&& aEarlyHints); + const nsTArray& GetEarlyHints() const { + return mEarlyHints; + } + /** * Create a new presentation shell that will use aContext for its * presentation context (presentation contexts must not be @@ -5117,6 +5127,8 @@ class Document : public nsINode, class HeaderData; UniquePtr mHeaderData; + nsTArray mEarlyHints; + nsRevocableEventPtr> mPendingTitleChangeEvent; diff --git a/dom/base/moz.build b/dom/base/moz.build index cc5e219ee53a..f1d931ca7e3c 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -569,6 +569,7 @@ LOCAL_INCLUDES += [ "/layout/style", "/layout/xul", "/netwerk/base", + "/netwerk/protocol/http", "/netwerk/url-classifier", "/parser/htmlparser", "/security/manager/ssl", diff --git a/dom/base/nsContentSink.cpp b/dom/base/nsContentSink.cpp index 8bf0d8b76ada..36732871d2c1 100644 --- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -20,6 +20,8 @@ #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" @@ -222,16 +224,27 @@ nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) { return NS_OK; } + bool gotEarlyHints = false; + if (nsCOMPtr baseChannel = + do_QueryInterface(aChannel)) { + nsTArray 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?"); @@ -248,11 +261,15 @@ nsresult nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel) { } 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); } } @@ -297,7 +314,8 @@ bool nsContentSink::LinkContextIsOurDocument(const nsAString& aAnchor) { 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 @@ -325,7 +343,8 @@ nsresult nsContentSink::ProcessLinkFromHeader(const net::LinkHeader& aHeader) { 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) && @@ -435,7 +454,8 @@ void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs, 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 uri; NS_NewURI(getter_AddRefs(uri), aHref, encoding, mDocument->GetDocBaseURI()); @@ -459,9 +479,9 @@ void nsContentSink::PreloadHref(const nsAString& aHref, const nsAString& aAs, 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) { diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h index a42b36265f69..ba93947a8d82 100644 --- a/dom/base/nsContentSink.h +++ b/dom/base/nsContentSink.h @@ -125,7 +125,9 @@ class nsContentSink : public nsICSSLoaderObserver, 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, @@ -138,7 +140,8 @@ class nsContentSink : public nsICSSLoaderObserver, 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. diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index d60077ddcf8f..af627892b942 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -3550,6 +3550,10 @@ mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect( getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr, aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri()); + if (RefPtr 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 httpChild = do_QueryObject(newChannel); diff --git a/netwerk/ipc/DocumentChannelChild.cpp b/netwerk/ipc/DocumentChannelChild.cpp index 33b4169d271d..55724a31a352 100644 --- a/netwerk/ipc/DocumentChannelChild.cpp +++ b/netwerk/ipc/DocumentChannelChild.cpp @@ -267,6 +267,10 @@ IPCResult DocumentChannelChild::RecvRedirectToRealChannel( newChannel->SetLoadGroup(mLoadGroup); } + if (RefPtr 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([&]() { diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index d1fc5546ec84..d78a2bef070c 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -5945,6 +5945,15 @@ void HttpBaseChannel::SetDummyChannelForImageCache() { mResponseHead = MakeUnique(); } +void HttpBaseChannel::SetEarlyHints( + nsTArray&& aEarlyHints) { + mEarlyHints = std::move(aEarlyHints); +} + +nsTArray&& HttpBaseChannel::TakeEarlyHints() { + return std::move(mEarlyHints); +} + void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) { mConnectionInfo = aCI ? aCI->Clone() : nullptr; } diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 92c3b45e890e..a5cec23f1584 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -14,6 +14,7 @@ #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" @@ -785,6 +786,18 @@ class HttpBaseChannel : public nsHashPropertyBag, ClassOfService mClassOfService; + public: + void SetEarlyHints( + nsTArray&& aEarlyHints); + nsTArray&& 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 mEarlyHints; + // clang-format off MOZ_ATOMIC_BITFIELDS(mAtomicBitfields1, 32, ( (uint32_t, UpgradeToSecure, 1), diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 8173ef67b46c..4608c6e24e88 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -15,6 +15,7 @@ class TimeStamp; namespace net { class nsHttpConnectionInfo; class WebSocketConnectionBase; +class EarlyHintConnectArgs; } namespace dom { enum class RequestMode : uint8_t; diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp index a09f98a73f4f..9cf6af3de53a 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -1285,7 +1285,7 @@ void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL, return; } - mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy); + mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0); } void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL, @@ -1297,7 +1297,7 @@ void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL, return; } - mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy); + mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0); } void nsHtml5TreeOpExecutor::PreloadOpenPicture() { diff --git a/uriloader/preload/PreloadService.cpp b/uriloader/preload/PreloadService.cpp index 313d0b06a748..c2cd42f4f594 100644 --- a/uriloader/preload/PreloadService.cpp +++ b/uriloader/preload/PreloadService.cpp @@ -87,7 +87,7 @@ already_AddRefed PreloadService::PreloadLinkElement( 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); @@ -102,7 +102,7 @@ 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; @@ -114,7 +114,7 @@ void PreloadService::PreloadLinkHeader( PreloadOrCoalesce(aURI, aURL, aPolicyType, aAs, aType, u""_ns, aSrcset, aSizes, aIntegrity, aCORS, aReferrerPolicy, - /* aFromHeader = */ true); + /* aFromHeader = */ true, aEarlyHintPreloaderId); } PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce( @@ -122,7 +122,8 @@ PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce( 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}; @@ -163,7 +164,7 @@ PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce( 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, @@ -178,11 +179,11 @@ PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce( 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}; @@ -193,7 +194,8 @@ void PreloadService::PreloadScript(nsIURI* aURI, const nsAString& aType, 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)); @@ -201,14 +203,16 @@ void PreloadService::PreloadScript(nsIURI* aURI, const nsAString& aType, 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); @@ -221,7 +225,8 @@ void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin, } 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); diff --git a/uriloader/preload/PreloadService.h b/uriloader/preload/PreloadService.h index 8561b0b12441..ea2fd1cd1768 100644 --- a/uriloader/preload/PreloadService.h +++ b/uriloader/preload/PreloadService.h @@ -66,26 +66,35 @@ class PreloadService { already_AddRefed 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); @@ -103,7 +112,8 @@ class PreloadService { 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 mPreloads;