Bug 1753730 - Add EarlyHintPreloader to load 103 Early Hint responses into the cache r=necko-reviewers,ckerschb,dragana,kershaw

Currently only same origin requests are preloaded and preloads in the
secure context. This may change in the future to match W3C decisions and
Chromes behavior.

Also only images get preloaded. This will change in the future to cover
asset types.

Currently the anchor isn't parsed correctly yet[1], so this will be
fixed in a future patch.

On non-2xx responses of the main document all ongoing preloads get
canceled. Already completed preloads don't get affected and are in the
cache.

It is currently untested whether unused preloads don't have side effects.

Another future patch should cover adding the preload to the devtools.

[1]: https://datatracker.ietf.org/doc/html/rfc8288#section-3.2

Differential Revision: https://phabricator.services.mozilla.com/D137885
This commit is contained in:
Manuel Bucher 2022-05-03 19:24:42 +00:00
Родитель d2e465a0b2
Коммит 2952cf8ce7
10 изменённых файлов: 502 добавлений и 16 удалений

Просмотреть файл

@ -1264,6 +1264,10 @@ void CanonicalBrowsingContext::AddFinalDiscardListener(
mFullyDiscardedListeners.AppendElement(std::move(aListener));
}
net::EarlyHintsService* CanonicalBrowsingContext::GetEarlyHintsService() {
return &mEarlyHintsService;
}
void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
bool aPrivateBrowsing) {
if (IsDiscarded() || !EverAttached() || IsChrome()) {

Просмотреть файл

@ -7,6 +7,7 @@
#ifndef mozilla_dom_CanonicalBrowsingContext_h
#define mozilla_dom_CanonicalBrowsingContext_h
#include "mozilla/net/EarlyHintsService.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/MediaControlKeySource.h"
#include "mozilla/dom/BrowsingContextWebProgress.h"
@ -361,6 +362,8 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void AddFinalDiscardListener(std::function<void(uint64_t)>&& aListener);
net::EarlyHintsService* GetEarlyHintsService();
protected:
// Called when the browsing context is being discarded.
void CanonicalDiscard();
@ -564,6 +567,8 @@ class CanonicalBrowsingContext final : public BrowsingContext {
bool mFullyDiscarded = false;
nsTArray<std::function<void(uint64_t)>> mFullyDiscardedListeners;
net::EarlyHintsService mEarlyHintsService;
};
} // namespace dom

Просмотреть файл

@ -10338,6 +10338,12 @@
value: true
mirror: always
# Enable 103 Early Hint status code (RFC 8297)
- name: network.early-hints.enabled
type: RelaxedAtomicBool
value: false
mirror: always
# Whether to use the network process or not
# Start a separate socket process. Performing networking on the socket process
# is control by a sepparate pref

Просмотреть файл

@ -1104,7 +1104,9 @@ void DocumentLoadListener::Disconnect(bool aContinueNavigating) {
httpChannelImpl->SetEarlyHintObserver(nullptr);
}
mEarlyHintsService.Cancel();
if (GetLoadingBrowsingContext()) {
GetLoadingBrowsingContext()->mEarlyHintsService.Cancel();
}
if (auto* ctx = GetDocumentBrowsingContext()) {
ctx->EndDocumentLoad(aContinueNavigating);
@ -2472,12 +2474,15 @@ DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
}
}
if (httpChannel) {
uint32_t responseStatus;
Unused << httpChannel->GetResponseStatus(&responseStatus);
mEarlyHintsService.FinalResponse(responseStatus);
} else {
mEarlyHintsService.Cancel();
if (GetLoadingBrowsingContext()) {
if (httpChannel) {
uint32_t responseStatus;
Unused << httpChannel->GetResponseStatus(&responseStatus);
GetLoadingBrowsingContext()->mEarlyHintsService.FinalResponse(
responseStatus);
} else {
GetLoadingBrowsingContext()->mEarlyHintsService.Cancel();
}
}
// If we're going to be delivering this channel to a remote content
@ -2848,8 +2853,11 @@ NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest,
NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& linkHeader) {
LOG(("DocumentLoadListener::EarlyHint.\n"));
mEarlyHintsService.EarlyHint(linkHeader);
if (GetLoadingBrowsingContext()) {
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
GetLoadingBrowsingContext()->mEarlyHintsService.EarlyHint(
linkHeader, GetChannelCreationURI(), loadInfo);
}
return NS_OK;
}

Просмотреть файл

@ -619,8 +619,6 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
bool mOpenPromiseResolved = false;
const bool mIsDocumentLoad;
EarlyHintsService mEarlyHintsService;
};
NS_DEFINE_STATIC_IID_ACCESSOR(DocumentLoadListener, DOCUMENT_LOAD_LISTENER_IID)

Просмотреть файл

@ -0,0 +1,314 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "EarlyHintPreloader.h"
#include "EarlyHintsService.h"
#include "ErrorList.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/Logging.h"
#include "nsAttrValue.h"
#include "nsAttrValueInlines.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsICacheInfoChannel.h"
#include "nsIChannel.h"
#include "nsIHttpChannel.h"
#include "nsIInputStream.h"
#include "nsIReferrerInfo.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsStreamUtils.h"
//
// To enable logging (see mozilla/Logging.h for full details):
//
// set MOZ_LOG=EarlyHint:5
// set MOZ_LOG_FILE=earlyhint.log
//
// this enables LogLevel::Debug level information and places all output in
// the file earlyhint.log
//
static mozilla::LazyLogModule gEarlyHintLog("EarlyHint");
#undef LOG
#define LOG(args) MOZ_LOG(gEarlyHintLog, mozilla::LogLevel::Debug, args)
#undef LOG_ENABLED
#define LOG_ENABLED() MOZ_LOG_TEST(gEarlyHintLog, mozilla::LogLevel::Debug)
namespace mozilla::net {
//=============================================================================
// OngoingEarlyHints
//=============================================================================
void OngoingEarlyHints::CancelAllOngoingPreloads() {
for (auto& el : mOngoingPreloads) {
el.GetData()->CancelChannel(nsresult::NS_ERROR_ABORT);
}
}
bool OngoingEarlyHints::Contains(const PreloadHashKey& aKey) {
return mOngoingPreloads.Contains(aKey);
}
bool OngoingEarlyHints::Add(const PreloadHashKey& aKey,
RefPtr<EarlyHintPreloader> aPreloader) {
return mOngoingPreloads.InsertOrUpdate(aKey, aPreloader);
}
//=============================================================================
// EarlyHintPreloader
//=============================================================================
EarlyHintPreloader::EarlyHintPreloader(nsIURI* aURI) : mURI(aURI) {}
/* static */
Maybe<PreloadHashKey> EarlyHintPreloader::GenerateHashKey(
ASDestination aAs, nsIURI* aURI, nsIPrincipal* aPrincipal) {
if (aAs == ASDestination::DESTINATION_IMAGE) {
return Some(
PreloadHashKey::CreateAsImage(aURI, aPrincipal, CORSMode::CORS_NONE));
}
return Nothing();
}
// static
void EarlyHintPreloader::MaybeCreateAndInsertPreload(
OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aHeader,
nsIURI* aBaseURI, nsIPrincipal* aTriggeringPrincipal,
nsICookieJarSettings* aCookieJarSettings) {
if (!aHeader.mRel.LowerCaseEqualsASCII("preload")) {
return;
}
nsAttrValue as;
ParseAsValue(aHeader.mAs, as);
if (as.GetEnumValue() == ASDestination::DESTINATION_INVALID) {
// return early when it's definitly not an asset type we preload
// would be caught later as well, e.g. when creating the PreloadHashKey
return;
}
nsCOMPtr<nsIURI> uri;
// use the base uri
NS_ENSURE_SUCCESS_VOID(
NS_NewURI(getter_AddRefs(uri), aHeader.mHref, nullptr, aBaseURI));
// Only make same origin preloads, the fromPrivateWindow is only read when
// reportError is enabled, so setting both to false is safe.
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
aBaseURI, uri, /* reportError */ false,
/* fromPrivateWindow */ false))) {
return;
}
Maybe<PreloadHashKey> hashKey = GenerateHashKey(
static_cast<ASDestination>(as.GetEnumValue()), uri, aTriggeringPrincipal);
if (!hashKey) {
return;
}
if (aOngoingEarlyHints->Contains(*hashKey)) {
return;
}
nsContentPolicyType contentPolicyType = AsValueToContentPolicy(as);
if (contentPolicyType == nsContentPolicyType::TYPE_INVALID) {
return;
}
dom::ReferrerPolicy referrerPolicy =
dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
aHeader.mReferrerPolicy);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
new dom::ReferrerInfo(aBaseURI, referrerPolicy);
RefPtr<EarlyHintPreloader> earlyHintPreloader =
RefPtr(new EarlyHintPreloader(uri));
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
NS_ENSURE_SUCCESS_VOID(earlyHintPreloader->OpenChannel(
aTriggeringPrincipal, securityFlags, contentPolicyType, referrerInfo,
aCookieJarSettings));
DebugOnly<bool> result =
aOngoingEarlyHints->Add(*hashKey, earlyHintPreloader);
MOZ_ASSERT(result);
}
nsresult EarlyHintPreloader::OpenChannel(
nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
nsContentPolicyType aContentPolicyType, nsIReferrerInfo* aReferrerInfo,
nsICookieJarSettings* aCookieJarSettings) {
MOZ_ASSERT(aContentPolicyType == nsContentPolicyType::TYPE_IMAGE);
nsresult rv =
NS_NewChannel(getter_AddRefs(mChannel), mURI, aTriggeringPrincipal,
aSecurityFlags, aContentPolicyType, aCookieJarSettings,
/* aPerformanceStorage */ nullptr,
/* aLoadGroup */ nullptr,
/* aCallbacks */ this, nsIRequest::LOAD_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
// configure HTTP specific stuff
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (!httpChannel) {
mChannel = nullptr;
return NS_ERROR_ABORT;
}
DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
MOZ_ASSERT(NS_SUCCEEDED(success));
success = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
MOZ_ASSERT(NS_SUCCEEDED(success));
return mChannel->AsyncOpen(this);
}
nsresult EarlyHintPreloader::CancelChannel(nsresult aStatus) {
// clear redirect channel in case this channel is cleared between the call of
// EarlyHintPreloader::AsyncOnChannelRedirect and
// EarlyHintPreloader::OnRedirectResult
mRedirectChannel = nullptr;
if (mChannel) {
mChannel->Cancel(aStatus);
mChannel = nullptr;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// EarlyHintPreloader::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(EarlyHintPreloader, nsIRequestObserver, nsIStreamListener,
nsIChannelEventSink, nsIInterfaceRequestor,
nsIRedirectResultListener)
//-----------------------------------------------------------------------------
// EarlyHintPreloader::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EarlyHintPreloader::OnStartRequest(nsIRequest* aRequest) {
LOG(("EarlyHintPreloader::OnStartRequest\n"));
nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel = do_QueryInterface(aRequest);
if (!cacheInfoChannel) {
return NS_ERROR_ABORT;
}
// no need to prefetch an asset that is already in the cache
bool fromCache;
if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) && fromCache) {
LOG(("document is already in the cache; canceling prefetch\n"));
return NS_BINDING_ABORTED;
}
return NS_OK;
}
NS_IMETHODIMP
EarlyHintPreloader::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aStream, uint64_t aOffset,
uint32_t aCount) {
uint32_t bytesRead = 0;
nsresult rv =
aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
LOG(("prefetched %u bytes [offset=%" PRIu64 "]\n", bytesRead, aOffset));
return rv;
}
NS_IMETHODIMP
EarlyHintPreloader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
LOG(("EarlyHintPreloader::OnStopRequest\n"));
mChannel = nullptr;
return NS_OK;
}
//-----------------------------------------------------------------------------
// EarlyHintPreloader::nsIChannelEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EarlyHintPreloader::AsyncOnChannelRedirect(
nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
nsIAsyncVerifyRedirectCallback* callback) {
nsCOMPtr<nsIURI> newURI;
nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = aNewChannel->GetURI(getter_AddRefs(newURI));
if (NS_FAILED(rv)) {
callback->OnRedirectVerifyCallback(rv);
return NS_OK;
}
// abort the request if redirecting to cross origin resource, the
// fromPrivateWindow is only read when reportError is enabled, so setting both
// to false is safe.
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
mURI, newURI, /* reportError */ false,
/* fromPrivateWindow */ false))) {
callback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
return NS_OK;
}
// HTTP request headers are not automatically forwarded to the new channel.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
NS_ENSURE_STATE(httpChannel);
rv = httpChannel->SetRequestHeader("X-Moz"_ns, "early hint"_ns, false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Assign to mChannel after we get notification about success of the
// redirect in OnRedirectResult.
mRedirectChannel = aNewChannel;
callback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
//-----------------------------------------------------------------------------
// EarlyHintPreloader::nsIRedirectResultListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EarlyHintPreloader::OnRedirectResult(bool aProceeding) {
if (aProceeding && mRedirectChannel) {
mChannel = mRedirectChannel;
}
mRedirectChannel = nullptr;
return NS_OK;
}
//-----------------------------------------------------------------------------
// EarlyHintPreloader::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
EarlyHintPreloader::GetInterface(const nsIID& aIID, void** aResult) {
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
NS_ADDREF_THIS();
*aResult = static_cast<nsIChannelEventSink*>(this);
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
NS_ADDREF_THIS();
*aResult = static_cast<nsIRedirectResultListener*>(this);
return NS_OK;
}
return NS_ERROR_NO_INTERFACE;
}
} // namespace mozilla::net

Просмотреть файл

@ -0,0 +1,96 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_EarlyHintPreloader_h
#define mozilla_net_EarlyHintPreloader_h
#include "mozilla/Maybe.h"
#include "mozilla/PreloadHashKey.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIRedirectResultListener.h"
#include "nsIStreamListener.h"
#include "nsNetUtil.h"
#include "nsRefPtrHashtable.h"
class nsAttrValue;
class nsICookieJarSettings;
class nsIPrincipal;
class nsIReferrerInfo;
namespace mozilla::net {
class EarlyHintPreloader;
struct LinkHeader;
// class keeping track of all ongoing early hints
class OngoingEarlyHints final {
public:
NS_INLINE_DECL_REFCOUNTING(OngoingEarlyHints)
MOZ_DECLARE_REFCOUNTED_TYPENAME(OngoingEarlyHints)
OngoingEarlyHints() = default;
// returns whether a preload with that key already existed
bool Contains(const PreloadHashKey& aKey);
bool Add(const PreloadHashKey& aKey, RefPtr<EarlyHintPreloader> aPreloader);
void CancelAllOngoingPreloads();
private:
~OngoingEarlyHints() = default;
nsRefPtrHashtable<PreloadHashKey, EarlyHintPreloader> mOngoingPreloads;
};
class EarlyHintPreloader final : public nsIStreamListener,
public nsIChannelEventSink,
public nsIRedirectResultListener,
public nsIInterfaceRequestor {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIREDIRECTRESULTLISTENER
NS_DECL_NSIINTERFACEREQUESTOR
public:
// Create and insert a preload into OngoingEarlyHints if the same preload
// wasn't already issued and the LinkHeader can be parsed correctly.
static void MaybeCreateAndInsertPreload(
OngoingEarlyHints* aOngoingEarlyHints, const LinkHeader& aHeader,
nsIURI* aBaseURI, nsIPrincipal* aTriggeringPrincipal,
nsICookieJarSettings* aCookieJarSettings);
// Should be called by the preloader service when the preload is not needed
// after all, because the final response returns a non-2xx status code.
nsresult CancelChannel(nsresult aStatus);
private:
explicit EarlyHintPreloader(nsIURI* aURI);
~EarlyHintPreloader() = default;
static Maybe<PreloadHashKey> GenerateHashKey(ASDestination aAs, nsIURI* aURI,
nsIPrincipal* aPrincipal);
// call to start the preload
nsresult OpenChannel(nsIPrincipal* aTriggeringPrincipal,
nsSecurityFlags aSecurityFlags,
nsContentPolicyType aContentPolicyType,
nsIReferrerInfo* aReferrerInfo,
nsICookieJarSettings* aCookieJarSettings);
// keep opening uri to not preload cross origins on redirects for now
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIChannel> mRedirectChannel;
};
inline nsISupports* ToSupports(EarlyHintPreloader* aObj) {
return static_cast<nsIInterfaceRequestor*>(aObj);
}
} // namespace mozilla::net
#endif // mozilla_net_EarlyHintPreloader_h

Просмотреть файл

@ -6,26 +6,70 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "EarlyHintsService.h"
#include "EarlyHintPreloader.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/Telemetry.h"
#include "nsICookieJarSettings.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsIPrincipal.h"
#include "nsILoadInfo.h"
namespace mozilla::net {
void EarlyHintsService::EarlyHint(const nsACString& linkHeader) {
EarlyHintsService::EarlyHintsService()
: mOngoingEarlyHints(new OngoingEarlyHints()) {}
// implementing the destructor in the .cpp file to allow EarlyHintsService.h
// not to include EarlyHintPreloader.h, decoupling the two files and hopefully
// allow faster compile times
EarlyHintsService::~EarlyHintsService() = default;
void EarlyHintsService::EarlyHint(const nsACString& aLinkHeader,
nsIURI* aBaseURI, nsILoadInfo* aLoadInfo) {
mEarlyHintsCount++;
if (!mFirstEarlyHint) {
mFirstEarlyHint.emplace(TimeStamp::NowLoRes());
}
if (!StaticPrefs::network_early_hints_enabled()) {
return;
}
nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
if (NS_FAILED(
aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)))) {
return;
}
// TODO: find out why LinkHeaderParser uses utf16 and check if it can be
// changed to utf8
auto linkHeaders = ParseLinkHeader(NS_ConvertUTF8toUTF16(aLinkHeader));
for (auto& linkHeader : linkHeaders) {
EarlyHintPreloader::MaybeCreateAndInsertPreload(
mOngoingEarlyHints, linkHeader, aBaseURI, triggeringPrincipal,
cookieJarSettings);
}
}
void EarlyHintsService::FinalResponse(uint32_t aResponseStatus) {
// We will collect telemetry mosly once for a document.
// In case of a reddirect this will be called multiple times.
CollectTelemetry(Some(aResponseStatus));
if (aResponseStatus >= 300) {
mOngoingEarlyHints->CancelAllOngoingPreloads();
mCanceled = true;
}
}
void EarlyHintsService::Cancel() {
if (!mCanceled) {
CollectTelemetry(Nothing());
mOngoingEarlyHints->CancelAllOngoingPreloads();
mCanceled = true;
}
}

Просмотреть файл

@ -8,17 +8,24 @@
#ifndef mozilla_net_EarlyHintsService_h
#define mozilla_net_EarlyHintsService_h
#include "nsStringFwd.h"
#include "mozilla/Maybe.h"
#include "mozilla/TimeStamp.h"
#include "nsStringFwd.h"
#include "mozilla/RefPtr.h"
class nsILoadInfo;
class nsIURI;
namespace mozilla::net {
class OngoingEarlyHints;
class EarlyHintsService {
public:
EarlyHintsService() = default;
~EarlyHintsService() = default;
void EarlyHint(const nsACString& linkHeader);
EarlyHintsService();
~EarlyHintsService();
void EarlyHint(const nsACString& aLinkHeader, nsIURI* aBaseURI,
nsILoadInfo* aLoadInfo);
void FinalResponse(uint32_t aResponseStatus);
void Cancel();
@ -28,6 +35,8 @@ class EarlyHintsService {
Maybe<TimeStamp> mFirstEarlyHint;
uint32_t mEarlyHintsCount{0};
bool mCanceled{false};
RefPtr<OngoingEarlyHints> mOngoingEarlyHints;
};
} // namespace mozilla::net

Просмотреть файл

@ -50,6 +50,7 @@ EXPORTS.mozilla.net += [
"ClassifierDummyChannelChild.h",
"ClassifierDummyChannelParent.h",
"ClassOfService.h",
"EarlyHintPreloader.h",
"EarlyHintsService.h",
"HttpAuthUtils.h",
"HttpBackgroundChannelChild.h",
@ -101,6 +102,7 @@ UNIFIED_SOURCES += [
"ConnectionEntry.cpp",
"ConnectionHandle.cpp",
"DnsAndConnectSocket.cpp",
"EarlyHintPreloader.cpp",
"EarlyHintsService.cpp",
"Http2Compression.cpp",
"Http2ConnectTransaction.cpp",