1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "EarlyHintPreloader.h"
7 #include "EarlyHintRegistrar.h"
8 #include "EarlyHintsService.h"
10 #include "HttpChannelParent.h"
11 #include "MainThreadUtils.h"
12 #include "NeckoCommon.h"
13 #include "gfxPlatform.h"
14 #include "mozilla/CORSMode.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/nsCSPContext.h"
17 #include "mozilla/dom/nsMixedContentBlocker.h"
18 #include "mozilla/dom/ReferrerInfo.h"
19 #include "mozilla/glean/GleanMetrics.h"
20 #include "mozilla/ipc/BackgroundUtils.h"
21 #include "mozilla/LoadInfo.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/net/EarlyHintRegistrar.h"
24 #include "mozilla/net/NeckoChannelParams.h"
25 #include "mozilla/StaticPrefs_network.h"
26 #include "mozilla/Telemetry.h"
27 #include "nsAttrValue.h"
29 #include "nsContentPolicyUtils.h"
30 #include "nsContentSecurityManager.h"
31 #include "nsContentUtils.h"
33 #include "nsHttpChannel.h"
34 #include "nsIAsyncVerifyRedirectCallback.h"
35 #include "nsIChannel.h"
36 #include "nsIContentSecurityPolicy.h"
37 #include "nsIHttpChannel.h"
38 #include "nsIInputStream.h"
39 #include "nsILoadContext.h"
40 #include "nsILoadInfo.h"
41 #include "nsIParentChannel.h"
42 #include "nsIReferrerInfo.h"
45 #include "nsNetUtil.h"
46 #include "nsQueryObject.h"
47 #include "ParentChannelListener.h"
48 #include "nsIChannel.h"
49 #include "nsInterfaceRequestorAgg.h"
52 // To enable logging (see mozilla/Logging.h for full details):
54 // set MOZ_LOG=EarlyHint:5
55 // set MOZ_LOG_FILE=earlyhint.log
57 // this enables LogLevel::Debug level information and places all output in
58 // the file earlyhint.log
60 static mozilla::LazyLogModule
gEarlyHintLog("EarlyHint");
63 #define LOG(args) MOZ_LOG(gEarlyHintLog, mozilla::LogLevel::Debug, args)
66 #define LOG_ENABLED() MOZ_LOG_TEST(gEarlyHintLog, mozilla::LogLevel::Debug)
68 namespace mozilla::net
{
71 // This id uniquely identifies each early hint preloader in the
72 // EarlyHintRegistrar. Must only be accessed from main thread.
73 static uint64_t gEarlyHintPreloaderId
{0};
76 //=============================================================================
78 //=============================================================================
80 void OngoingEarlyHints::CancelAll(const nsACString
& aReason
) {
81 for (auto& preloader
: mPreloaders
) {
82 preloader
->CancelChannel(NS_ERROR_ABORT
, aReason
, /* aDeleteEntry */ true);
85 mStartedPreloads
.Clear();
88 bool OngoingEarlyHints::Contains(const PreloadHashKey
& aKey
) {
89 return mStartedPreloads
.Contains(aKey
);
92 bool OngoingEarlyHints::Add(const PreloadHashKey
& aKey
,
93 RefPtr
<EarlyHintPreloader
> aPreloader
) {
94 if (!mStartedPreloads
.Contains(aKey
)) {
95 mStartedPreloads
.Insert(aKey
);
96 mPreloaders
.AppendElement(aPreloader
);
102 void OngoingEarlyHints::RegisterLinksAndGetConnectArgs(
103 dom::ContentParentId aCpId
, nsTArray
<EarlyHintConnectArgs
>& aOutLinks
) {
104 // register all channels before returning
105 for (auto& preload
: mPreloaders
) {
106 EarlyHintConnectArgs args
;
107 if (preload
->Register(aCpId
, args
)) {
108 aOutLinks
.AppendElement(std::move(args
));
113 //=============================================================================
114 // EarlyHintPreloader
115 //=============================================================================
117 EarlyHintPreloader::EarlyHintPreloader() {
118 AssertIsOnMainThread();
119 mConnectArgs
.earlyHintPreloaderId() = ++gEarlyHintPreloaderId
;
122 EarlyHintPreloader::~EarlyHintPreloader() {
127 Telemetry::Accumulate(Telemetry::EH_STATE_OF_PRELOAD_REQUEST
, mState
);
131 Maybe
<PreloadHashKey
> EarlyHintPreloader::GenerateHashKey(
132 ASDestination aAs
, nsIURI
* aURI
, nsIPrincipal
* aPrincipal
,
133 CORSMode aCorsMode
, bool aIsModulepreload
) {
134 if (aIsModulepreload
) {
135 return Some(PreloadHashKey::CreateAsScript(
136 aURI
, aCorsMode
, JS::loader::ScriptKind::eModule
));
138 if (aAs
== ASDestination::DESTINATION_FONT
&& aCorsMode
!= CORS_NONE
) {
139 return Some(PreloadHashKey::CreateAsFont(aURI
, aCorsMode
));
141 if (aAs
== ASDestination::DESTINATION_IMAGE
) {
142 return Some(PreloadHashKey::CreateAsImage(aURI
, aPrincipal
, aCorsMode
));
144 if (aAs
== ASDestination::DESTINATION_SCRIPT
) {
145 return Some(PreloadHashKey::CreateAsScript(
146 aURI
, aCorsMode
, JS::loader::ScriptKind::eClassic
));
148 if (aAs
== ASDestination::DESTINATION_STYLE
) {
149 return Some(PreloadHashKey::CreateAsStyle(
150 aURI
, aPrincipal
, aCorsMode
,
151 css::SheetParsingMode::eAuthorSheetFeatures
));
153 if (aAs
== ASDestination::DESTINATION_FETCH
&& aCorsMode
!= CORS_NONE
) {
154 return Some(PreloadHashKey::CreateAsFetch(aURI
, aCorsMode
));
160 nsSecurityFlags
EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode
,
162 if (aAs
== ASDestination::DESTINATION_FONT
) {
163 return nsContentSecurityManager::ComputeSecurityFlags(
165 nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS
);
167 if (aAs
== ASDestination::DESTINATION_IMAGE
) {
168 return nsContentSecurityManager::ComputeSecurityFlags(
169 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
170 CORS_NONE_MAPS_TO_INHERITED_CONTEXT
) |
171 nsILoadInfo::SEC_ALLOW_CHROME
;
173 if (aAs
== ASDestination::DESTINATION_SCRIPT
) {
174 return nsContentSecurityManager::ComputeSecurityFlags(
175 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
176 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS
) |
177 nsILoadInfo::SEC_ALLOW_CHROME
;
179 if (aAs
== ASDestination::DESTINATION_STYLE
) {
180 return nsContentSecurityManager::ComputeSecurityFlags(
181 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
182 CORS_NONE_MAPS_TO_INHERITED_CONTEXT
) |
183 nsILoadInfo::SEC_ALLOW_CHROME
;
186 if (aAs
== ASDestination::DESTINATION_FETCH
) {
187 return nsContentSecurityManager::ComputeSecurityFlags(
188 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
189 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS
);
191 MOZ_ASSERT(false, "Unexpected ASDestination");
192 return nsContentSecurityManager::ComputeSecurityFlags(
194 nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS
);
198 void EarlyHintPreloader::MaybeCreateAndInsertPreload(
199 OngoingEarlyHints
* aOngoingEarlyHints
, const LinkHeader
& aLinkHeader
,
200 nsIURI
* aBaseURI
, nsIPrincipal
* aPrincipal
,
201 nsICookieJarSettings
* aCookieJarSettings
,
202 const nsACString
& aResponseReferrerPolicy
, const nsACString
& aCSPHeader
,
203 uint64_t aBrowsingContextID
,
204 dom::CanonicalBrowsingContext
* aLoadingBrowsingContext
,
205 bool aIsModulepreload
) {
207 ParseAsValue(aLinkHeader
.mAs
, as
);
209 ASDestination destination
= static_cast<ASDestination
>(as
.GetEnumValue());
210 CollectResourcesTypeTelemetry(destination
);
212 if (!StaticPrefs::network_early_hints_enabled()) {
216 if (destination
== ASDestination::DESTINATION_INVALID
&& !aIsModulepreload
) {
217 // return early when it's definitly not an asset type we preload
218 // would be caught later as well, e.g. when creating the PreloadHashKey
222 if (destination
== ASDestination::DESTINATION_FONT
&&
223 !gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
227 nsCOMPtr
<nsIURI
> uri
;
228 NS_ENSURE_SUCCESS_VOID(
229 NS_NewURI(getter_AddRefs(uri
), aLinkHeader
.mHref
, nullptr, aBaseURI
));
230 // The link relation may apply to a different resource, specified
231 // in the anchor parameter. For the link relations supported so far,
232 // we simply abort if the link applies to a resource different to the
234 if (!nsContentUtils::LinkContextIsURI(aLinkHeader
.mAnchor
, uri
)) {
238 // only preload secure context urls
239 if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri
)) {
243 CORSMode corsMode
= dom::Element::StringToCORSMode(aLinkHeader
.mCrossOrigin
);
245 Maybe
<PreloadHashKey
> hashKey
=
246 GenerateHashKey(destination
, uri
, aPrincipal
, corsMode
, aIsModulepreload
);
251 if (aOngoingEarlyHints
->Contains(*hashKey
)) {
255 nsContentPolicyType contentPolicyType
=
256 aIsModulepreload
? (IsScriptLikeOrInvalid(aLinkHeader
.mAs
)
257 ? nsContentPolicyType::TYPE_SCRIPT
258 : nsContentPolicyType::TYPE_INVALID
)
259 : AsValueToContentPolicy(as
);
261 if (contentPolicyType
== nsContentPolicyType::TYPE_INVALID
) {
265 dom::ReferrerPolicy linkReferrerPolicy
=
266 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
267 aLinkHeader
.mReferrerPolicy
);
269 dom::ReferrerPolicy responseReferrerPolicy
=
270 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
271 NS_ConvertUTF8toUTF16(aResponseReferrerPolicy
));
273 // The early hint may have two referrer policies, one from the response header
274 // and one from the link element.
276 // For example, in this server response:
277 // HTTP/1.1 103 Early Hints
278 // Referrer-Policy : origin
279 // Link: </style.css>; rel=preload; as=style referrerpolicy=no-referrer
281 // The link header referrer policy, if present, will take precedence over
282 // the response referrer policy
283 dom::ReferrerPolicy finalReferrerPolicy
= responseReferrerPolicy
;
284 if (linkReferrerPolicy
!= dom::ReferrerPolicy::_empty
) {
285 finalReferrerPolicy
= linkReferrerPolicy
;
287 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
=
288 new dom::ReferrerInfo(aBaseURI
, finalReferrerPolicy
);
290 RefPtr
<EarlyHintPreloader
> earlyHintPreloader
= new EarlyHintPreloader();
292 earlyHintPreloader
->mLoadContext
= aLoadingBrowsingContext
;
294 // Security flags for modulepreload's request mode are computed here directly
295 // until full support for worker destinations can be added.
297 // Implements "To fetch a single module script,"
298 // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
299 // and the top-level module fetch flag is set, then set request's
300 // mode to "same-origin".
301 nsSecurityFlags securityFlags
=
303 ? ((aLinkHeader
.mAs
.LowerCaseEqualsASCII("worker") ||
304 aLinkHeader
.mAs
.LowerCaseEqualsASCII("sharedworker") ||
305 aLinkHeader
.mAs
.LowerCaseEqualsASCII("serviceworker"))
306 ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
307 : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
) |
308 (corsMode
== CORS_USE_CREDENTIALS
309 ? nsILoadInfo::SEC_COOKIES_INCLUDE
310 : nsILoadInfo::SEC_COOKIES_SAME_ORIGIN
) |
311 nsILoadInfo::SEC_ALLOW_CHROME
312 : EarlyHintPreloader::ComputeSecurityFlags(corsMode
, destination
);
314 // Verify that the resource should be loaded.
315 // This isn't the ideal way to test the resource against the CSP.
316 // The problem comes from the fact that at the stage of Early Hint
317 // processing we have not yet created a document where we would normally store
320 // First we will create a load info,
321 // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
322 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new LoadInfo(
323 aPrincipal
, // loading principal
324 aPrincipal
, // triggering principal
325 nullptr /* aLoadingContext node */,
326 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
, contentPolicyType
);
328 if (aCSPHeader
.Length() != 0) {
329 // If the CSP header is present then create a new CSP and apply the header
331 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= new nsCSPContext();
332 nsresult rv
= csp
->SetRequestContextWithPrincipal(
333 aPrincipal
, aBaseURI
, ""_ns
, 0 /* aInnerWindowId */);
334 NS_ENSURE_SUCCESS_VOID(rv
);
335 rv
= CSP_AppendCSPFromHeader(csp
, NS_ConvertUTF8toUTF16(aCSPHeader
),
336 false /* report only */);
337 NS_ENSURE_SUCCESS_VOID(rv
);
339 // We create a temporary ClientInfo. This is required on the loadInfo as
340 // that is how the CSP is queried. More specificially, as a hack to be able
341 // to call NS_CheckContentLoadPolicy on nsILoadInfo which exclusively
342 // accesses the CSP from the ClientInfo, we create a synthetic ClientInfo to
343 // hold the CSP we are creating. This is not a safe thing to do in any other
344 // circumstance because ClientInfos are always describing a ClientSource
345 // that corresponds to a global or potential global, so creating an info
346 // without a source is unsound. For the purposes of doing things before a
347 // global exists, fetch has the concept of a
348 // https://fetch.spec.whatwg.org/#concept-request-reserved-client and
349 // nsILoadInfo explicity has methods around GiveReservedClientSource which
350 // are primarily used by ClientChannelHelper. If you are trying to do real
351 // CSP stuff and the ClientInfo is not there yet, please enhance the logic
352 // around ClientChannelHelper.
354 mozilla::ipc::PrincipalInfo principalInfo
;
355 rv
= PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
);
356 NS_ENSURE_SUCCESS_VOID(rv
);
357 dom::ClientInfo
clientInfo(nsID::GenerateUUID(), dom::ClientType::Window
,
358 principalInfo
, TimeStamp::Now());
360 // Our newly-created CSP is set on the ClientInfo via the indirect route of
361 // first serializing to CSPInfo
362 ipc::CSPInfo cspInfo
;
363 rv
= CSPToCSPInfo(csp
, &cspInfo
);
364 NS_ENSURE_SUCCESS_VOID(rv
);
365 clientInfo
.SetCspInfo(cspInfo
);
367 // This ClientInfo is then set on the new loadInfo.
368 // It can now be used to test the resource against the policy
369 secCheckLoadInfo
->SetClientInfo(clientInfo
);
372 int16_t shouldLoad
= nsIContentPolicy::ACCEPT
;
373 nsresult rv
= NS_CheckContentLoadPolicy(uri
, secCheckLoadInfo
, &shouldLoad
,
374 nsContentUtils::GetContentPolicy());
376 if (NS_FAILED(rv
) || NS_CP_REJECTED(shouldLoad
)) {
380 NS_ENSURE_SUCCESS_VOID(earlyHintPreloader
->OpenChannel(
381 uri
, aPrincipal
, securityFlags
, contentPolicyType
, referrerInfo
,
382 aCookieJarSettings
, aBrowsingContextID
));
384 earlyHintPreloader
->SetLinkHeader(aLinkHeader
);
386 DebugOnly
<bool> result
=
387 aOngoingEarlyHints
->Add(*hashKey
, earlyHintPreloader
);
391 nsresult
EarlyHintPreloader::OpenChannel(
392 nsIURI
* aURI
, nsIPrincipal
* aPrincipal
, nsSecurityFlags aSecurityFlags
,
393 nsContentPolicyType aContentPolicyType
, nsIReferrerInfo
* aReferrerInfo
,
394 nsICookieJarSettings
* aCookieJarSettings
, uint64_t aBrowsingContextID
) {
395 MOZ_ASSERT(aContentPolicyType
== nsContentPolicyType::TYPE_IMAGE
||
396 aContentPolicyType
==
397 nsContentPolicyType::TYPE_INTERNAL_FETCH_PRELOAD
||
398 aContentPolicyType
== nsContentPolicyType::TYPE_SCRIPT
||
399 aContentPolicyType
== nsContentPolicyType::TYPE_STYLESHEET
||
400 aContentPolicyType
== nsContentPolicyType::TYPE_FONT
);
403 NS_NewChannel(getter_AddRefs(mChannel
), aURI
, aPrincipal
, aSecurityFlags
,
404 aContentPolicyType
, aCookieJarSettings
,
405 /* aPerformanceStorage */ nullptr,
406 /* aLoadGroup */ nullptr,
407 /* aCallbacks */ this, nsIRequest::LOAD_NORMAL
);
409 NS_ENSURE_SUCCESS(rv
, rv
);
411 RefPtr
<nsHttpChannel
> httpChannelObject
= do_QueryObject(mChannel
);
412 if (!httpChannelObject
) {
414 return NS_ERROR_ABORT
;
417 // configure HTTP specific stuff
418 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
);
421 return NS_ERROR_ABORT
;
423 DebugOnly
<nsresult
> success
= httpChannel
->SetReferrerInfo(aReferrerInfo
);
424 MOZ_ASSERT(NS_SUCCEEDED(success
));
425 success
= httpChannel
->SetRequestHeader("X-Moz"_ns
, "early hint"_ns
, false);
426 MOZ_ASSERT(NS_SUCCEEDED(success
));
428 mParentListener
= new ParentChannelListener(this, nullptr);
432 rv
= mChannel
->AsyncOpen(mParentListener
);
434 mParentListener
= nullptr;
438 SetState(ePreloaderOpened
);
440 // Setting the BrowsingContextID here to let Early Hint requests show up in
441 // devtools. Normally that would automatically happen if we would pass the
442 // nsILoadGroup in ns_NewChannel above, but the nsILoadGroup is inaccessible
443 // here in the ParentProcess. The nsILoadGroup only exists in ContentProcess
444 // as part of the document and nsDocShell. It is also not yet determined which
445 // ContentProcess this load belongs to.
446 nsCOMPtr
<nsILoadInfo
> loadInfo
= mChannel
->LoadInfo();
447 static_cast<LoadInfo
*>(loadInfo
.get())
448 ->UpdateBrowsingContextID(aBrowsingContextID
);
453 void EarlyHintPreloader::PriorizeAsPreload() {
454 nsLoadFlags loadFlags
= nsIRequest::LOAD_NORMAL
;
455 Unused
<< mChannel
->GetLoadFlags(&loadFlags
);
456 Unused
<< mChannel
->SetLoadFlags(loadFlags
| nsIRequest::LOAD_BACKGROUND
);
458 if (nsCOMPtr
<nsIClassOfService
> cos
= do_QueryInterface(mChannel
)) {
459 Unused
<< cos
->AddClassFlags(nsIClassOfService::Unblocked
);
463 void EarlyHintPreloader::SetLinkHeader(const LinkHeader
& aLinkHeader
) {
464 mConnectArgs
.link() = aLinkHeader
;
467 bool EarlyHintPreloader::IsFromContentParent(dom::ContentParentId aCpId
) const {
468 return aCpId
== mCpId
;
471 bool EarlyHintPreloader::Register(dom::ContentParentId aCpId
,
472 EarlyHintConnectArgs
& aOut
) {
475 // Set minimum delay of 1ms to always start the timer after the function call
477 nsresult rv
= NS_NewTimerWithCallback(
478 getter_AddRefs(mTimer
), this,
479 std::max(StaticPrefs::network_early_hints_parent_connect_timeout(),
481 nsITimer::TYPE_ONE_SHOT
);
484 CancelChannel(NS_ERROR_ABORT
, "new-timer-failed"_ns
,
485 /* aDeleteEntry */ false);
489 // Create an entry in the redirect channel registrar
490 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
491 registrar
->RegisterEarlyHint(mConnectArgs
.earlyHintPreloaderId(), this);
497 nsresult
EarlyHintPreloader::CancelChannel(nsresult aStatus
,
498 const nsACString
& aReason
,
500 LOG(("EarlyHintPreloader::CancelChannel [this=%p]\n", this));
507 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
508 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
510 // clear redirect channel in case this channel is cleared between the call of
511 // EarlyHintPreloader::AsyncOnChannelRedirect and
512 // EarlyHintPreloader::OnRedirectResult
513 mRedirectChannel
= nullptr;
518 mChannel
->CancelWithReason(aStatus
, aReason
);
519 // Clearing mChannel is safe, because this EarlyHintPreloader is not in the
520 // EarlyHintRegistrar after this function call and we won't call
521 // SetHttpChannelFromEarlyHintPreloader nor OnStartRequest on mParent.
523 SetState(ePreloaderCancelled
);
528 void EarlyHintPreloader::OnParentReady(nsIParentChannel
* aParent
) {
529 AssertIsOnMainThread();
531 LOG(("EarlyHintPreloader::OnParentReady [this=%p]\n", this));
533 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
535 obs
->NotifyObservers(mChannel
, "earlyhints-connectback", nullptr);
545 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
546 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
548 if (mOnStartRequestCalled
) {
550 InvokeStreamListenerFunctions();
554 void EarlyHintPreloader::SetParentChannel() {
555 RefPtr
<HttpBaseChannel
> channel
= do_QueryObject(mChannel
);
556 RefPtr
<HttpChannelParent
> parent
= do_QueryObject(mParent
);
557 parent
->SetHttpChannelFromEarlyHintPreloader(channel
);
561 // https://searchfox.org/mozilla-central/rev/b4150d1c6fae0c51c522df2d2c939cf5ad331d4c/netwerk/ipc/DocumentLoadListener.cpp#1311
562 void EarlyHintPreloader::InvokeStreamListenerFunctions() {
563 AssertIsOnMainThread();
564 RefPtr
<EarlyHintPreloader
> self(this);
567 "EarlyHintPreloader::InvokeStreamListenerFunctions [this=%p parent=%p]\n",
568 this, mParent
.get()));
570 // If we failed to suspend the channel, then we might have received
571 // some messages while the redirected was being handled.
572 // Manually send them on now.
574 // This is safe to do, because OnStartRequest/OnStopRequest/OnDataAvailable
575 // are all called on the main thread. They can't be called until we worked
576 // through all functions in the streamListnerFunctions array.
577 mParentListener
->SetListenerAfterRedirect(mParent
);
579 nsTArray
<StreamListenerFunction
> streamListenerFunctions
=
580 std::move(mStreamListenerFunctions
);
582 ForwardStreamListenerFunctions(std::move(streamListenerFunctions
), mParent
);
584 // We don't expect to get new stream listener functions added
585 // via re-entrancy. If this ever happens, we should understand
586 // exactly why before allowing it.
587 NS_ASSERTION(mStreamListenerFunctions
.IsEmpty(),
588 "Should not have added new stream listener function!");
590 if (mChannel
&& mSuspended
) {
595 mParentListener
= nullptr;
597 SetState(ePreloaderUsed
);
600 //-----------------------------------------------------------------------------
601 // EarlyHintPreloader::nsISupports
602 //-----------------------------------------------------------------------------
604 NS_IMPL_ISUPPORTS(EarlyHintPreloader
, nsIRequestObserver
, nsIStreamListener
,
605 nsIChannelEventSink
, nsIInterfaceRequestor
,
606 nsIRedirectResultListener
, nsIMultiPartChannelListener
,
607 nsINamed
, nsITimerCallback
);
609 //-----------------------------------------------------------------------------
610 // EarlyHintPreloader::nsIStreamListener
611 //-----------------------------------------------------------------------------
613 // Implementation copied and adapted from DocumentLoadListener::OnStartRequest
614 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2317-2508
616 EarlyHintPreloader::OnStartRequest(nsIRequest
* aRequest
) {
617 LOG(("EarlyHintPreloader::OnStartRequest [this=%p]\n", this));
618 AssertIsOnMainThread();
620 mOnStartRequestCalled
= true;
622 nsCOMPtr
<nsIMultiPartChannel
> multiPartChannel
= do_QueryInterface(aRequest
);
623 if (multiPartChannel
) {
624 multiPartChannel
->GetBaseChannel(getter_AddRefs(mChannel
));
626 mChannel
= do_QueryInterface(aRequest
);
628 MOZ_DIAGNOSTIC_ASSERT(mChannel
);
630 nsresult status
= NS_OK
;
631 Unused
<< aRequest
->GetStatus(&status
);
635 mParent
->OnStartRequest(aRequest
);
636 InvokeStreamListenerFunctions();
638 // Don't suspend the chanel when the channel got cancelled with
639 // CancelChannel, because then OnStopRequest wouldn't get called and we
640 // wouldn't clean up the channel.
641 if (NS_SUCCEEDED(status
)) {
645 mStreamListenerFunctions
.AppendElement(
646 AsVariant(OnStartRequestParams
{aRequest
}));
649 // return error after adding the OnStartRequest forward. The OnStartRequest
650 // failure has to be forwarded to listener, because they called AsyncOpen on
655 // Implementation copied from DocumentLoadListener::OnStopRequest
656 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2510-2528
658 EarlyHintPreloader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
659 AssertIsOnMainThread();
660 LOG(("EarlyHintPreloader::OnStopRequest [this=%p]\n", this));
661 mStreamListenerFunctions
.AppendElement(
662 AsVariant(OnStopRequestParams
{aRequest
, aStatusCode
}));
664 // If we're not a multi-part channel, then we're finished and we don't
665 // expect any further events. If we are, then this might be called again,
666 // so wait for OnAfterLastPart instead.
667 nsCOMPtr
<nsIMultiPartChannel
> multiPartChannel
= do_QueryInterface(aRequest
);
668 if (!multiPartChannel
) {
675 //-----------------------------------------------------------------------------
676 // EarlyHintPreloader::nsIStreamListener
677 //-----------------------------------------------------------------------------
679 // Implementation copied from DocumentLoadListener::OnDataAvailable
680 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2530-2549
682 EarlyHintPreloader::OnDataAvailable(nsIRequest
* aRequest
,
683 nsIInputStream
* aInputStream
,
684 uint64_t aOffset
, uint32_t aCount
) {
685 AssertIsOnMainThread();
686 LOG(("EarlyHintPreloader::OnDataAvailable [this=%p]\n", this));
687 // This isn't supposed to happen, since we suspended the channel, but
688 // sometimes Suspend just doesn't work. This can happen when we're routing
689 // through nsUnknownDecoder to sniff the content type, and it doesn't handle
690 // being suspended. Let's just store the data and manually forward it to our
691 // redirected channel when it's ready.
693 nsresult rv
= NS_ReadInputStreamToString(aInputStream
, data
, aCount
);
694 NS_ENSURE_SUCCESS(rv
, rv
);
696 mStreamListenerFunctions
.AppendElement(AsVariant(
697 OnDataAvailableParams
{aRequest
, std::move(data
), aOffset
, aCount
}));
702 //-----------------------------------------------------------------------------
703 // EarlyHintPreloader::nsIMultiPartChannelListener
704 //-----------------------------------------------------------------------------
707 EarlyHintPreloader::OnAfterLastPart(nsresult aStatus
) {
708 LOG(("EarlyHintPreloader::OnAfterLastPart [this=%p]", this));
709 mStreamListenerFunctions
.AppendElement(
710 AsVariant(OnAfterLastPartParams
{aStatus
}));
715 //-----------------------------------------------------------------------------
716 // EarlyHintPreloader::nsIChannelEventSink
717 //-----------------------------------------------------------------------------
720 EarlyHintPreloader::AsyncOnChannelRedirect(
721 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t aFlags
,
722 nsIAsyncVerifyRedirectCallback
* callback
) {
723 LOG(("EarlyHintPreloader::AsyncOnChannelRedirect [this=%p]", this));
724 nsCOMPtr
<nsIURI
> newURI
;
725 nsresult rv
= NS_GetFinalChannelURI(aNewChannel
, getter_AddRefs(newURI
));
726 NS_ENSURE_SUCCESS(rv
, rv
);
728 rv
= aNewChannel
->GetURI(getter_AddRefs(newURI
));
730 callback
->OnRedirectVerifyCallback(rv
);
734 // HTTP request headers are not automatically forwarded to the new channel.
735 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aNewChannel
);
736 NS_ENSURE_STATE(httpChannel
);
738 rv
= httpChannel
->SetRequestHeader("X-Moz"_ns
, "early hint"_ns
, false);
739 MOZ_ASSERT(NS_SUCCEEDED(rv
));
741 // Assign to mChannel after we get notification about success of the
742 // redirect in OnRedirectResult.
743 mRedirectChannel
= aNewChannel
;
745 callback
->OnRedirectVerifyCallback(NS_OK
);
749 //-----------------------------------------------------------------------------
750 // EarlyHintPreloader::nsIRedirectResultListener
751 //-----------------------------------------------------------------------------
754 EarlyHintPreloader::OnRedirectResult(nsresult aStatus
) {
755 LOG(("EarlyHintPreloader::OnRedirectResult [this=%p] aProceeding=0x%" PRIx32
,
756 this, static_cast<uint32_t>(aStatus
)));
757 if (NS_SUCCEEDED(aStatus
) && mRedirectChannel
) {
758 mChannel
= mRedirectChannel
;
761 mRedirectChannel
= nullptr;
766 //-----------------------------------------------------------------------------
767 // EarlyHintPreloader::nsINamed
768 //-----------------------------------------------------------------------------
771 EarlyHintPreloader::GetName(nsACString
& aName
) {
772 aName
.AssignLiteral("EarlyHintPreloader");
776 //-----------------------------------------------------------------------------
777 // EarlyHintPreloader::nsITimerCallback
778 //-----------------------------------------------------------------------------
781 EarlyHintPreloader::Notify(nsITimer
* timer
) {
782 // Death grip, because we will most likely remove the last reference when
783 // deleting us from the EarlyHintRegistrar
784 RefPtr
<EarlyHintPreloader
> deathGrip(this);
786 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
787 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
790 mRedirectChannel
= nullptr;
795 mChannel
->CancelWithReason(NS_ERROR_ABORT
, "parent-connect-timeout"_ns
);
797 glean::netwerk::parent_connect_timeout
.Add(1);
801 SetState(ePreloaderTimeout
);
806 //-----------------------------------------------------------------------------
807 // EarlyHintPreloader::nsIInterfaceRequestor
808 //-----------------------------------------------------------------------------
811 EarlyHintPreloader::GetInterface(const nsIID
& aIID
, void** aResult
) {
812 if (aIID
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
814 *aResult
= static_cast<nsIChannelEventSink
*>(this);
818 if (aIID
.Equals(NS_GET_IID(nsIRedirectResultListener
))) {
820 *aResult
= static_cast<nsIRedirectResultListener
*>(this);
824 if (aIID
.Equals(NS_GET_IID(nsILoadContext
)) && mLoadContext
!= nullptr) {
825 nsCOMPtr
<nsILoadContext
> loadContext
= mLoadContext
;
826 loadContext
.forget(aResult
);
830 return NS_ERROR_NO_INTERFACE
;
833 void EarlyHintPreloader::CollectResourcesTypeTelemetry(
834 ASDestination aASDestination
) {
835 if (aASDestination
== ASDestination::DESTINATION_FONT
) {
836 glean::netwerk::early_hints
.Get("font"_ns
).Add(1);
837 } else if (aASDestination
== ASDestination::DESTINATION_SCRIPT
) {
838 glean::netwerk::early_hints
.Get("script"_ns
).Add(1);
839 } else if (aASDestination
== ASDestination::DESTINATION_STYLE
) {
840 glean::netwerk::early_hints
.Get("stylesheet"_ns
).Add(1);
841 } else if (aASDestination
== ASDestination::DESTINATION_IMAGE
) {
842 glean::netwerk::early_hints
.Get("image"_ns
).Add(1);
843 } else if (aASDestination
== ASDestination::DESTINATION_FETCH
) {
844 glean::netwerk::early_hints
.Get("fetch"_ns
).Add(1);
846 glean::netwerk::early_hints
.Get("other"_ns
).Add(1);
849 } // namespace mozilla::net