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 "mozilla/CORSMode.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/nsCSPContext.h"
16 #include "mozilla/dom/nsMixedContentBlocker.h"
17 #include "mozilla/dom/ReferrerInfo.h"
18 #include "mozilla/glean/GleanMetrics.h"
19 #include "mozilla/ipc/BackgroundUtils.h"
20 #include "mozilla/LoadInfo.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/net/EarlyHintRegistrar.h"
23 #include "mozilla/net/NeckoChannelParams.h"
24 #include "mozilla/StaticPrefs_network.h"
25 #include "mozilla/Telemetry.h"
26 #include "nsAttrValue.h"
27 #include "nsAttrValueInlines.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 "nsILoadInfo.h"
40 #include "nsIParentChannel.h"
41 #include "nsIReferrerInfo.h"
44 #include "nsNetUtil.h"
45 #include "nsQueryObject.h"
46 #include "nsStreamUtils.h"
47 #include "nsStringStream.h"
48 #include "ParentChannelListener.h"
49 #include "nsIChannel.h"
50 #include "nsInterfaceRequestorAgg.h"
53 // To enable logging (see mozilla/Logging.h for full details):
55 // set MOZ_LOG=EarlyHint:5
56 // set MOZ_LOG_FILE=earlyhint.log
58 // this enables LogLevel::Debug level information and places all output in
59 // the file earlyhint.log
61 static mozilla::LazyLogModule
gEarlyHintLog("EarlyHint");
64 #define LOG(args) MOZ_LOG(gEarlyHintLog, mozilla::LogLevel::Debug, args)
67 #define LOG_ENABLED() MOZ_LOG_TEST(gEarlyHintLog, mozilla::LogLevel::Debug)
69 namespace mozilla::net
{
72 // This id uniquely identifies each early hint preloader in the
73 // EarlyHintRegistrar. Must only be accessed from main thread.
74 static uint64_t gEarlyHintPreloaderId
{0};
77 //=============================================================================
79 //=============================================================================
81 void OngoingEarlyHints::CancelAll(const nsACString
& aReason
) {
82 for (auto& preloader
: mPreloaders
) {
83 preloader
->CancelChannel(NS_ERROR_ABORT
, aReason
, /* aDeleteEntry */ true);
86 mStartedPreloads
.Clear();
89 bool OngoingEarlyHints::Contains(const PreloadHashKey
& aKey
) {
90 return mStartedPreloads
.Contains(aKey
);
93 bool OngoingEarlyHints::Add(const PreloadHashKey
& aKey
,
94 RefPtr
<EarlyHintPreloader
> aPreloader
) {
95 if (!mStartedPreloads
.Contains(aKey
)) {
96 mStartedPreloads
.Insert(aKey
);
97 mPreloaders
.AppendElement(aPreloader
);
103 void OngoingEarlyHints::RegisterLinksAndGetConnectArgs(
104 dom::ContentParentId aCpId
, nsTArray
<EarlyHintConnectArgs
>& aOutLinks
) {
105 // register all channels before returning
106 for (auto& preload
: mPreloaders
) {
107 EarlyHintConnectArgs args
;
108 if (preload
->Register(aCpId
, args
)) {
109 aOutLinks
.AppendElement(std::move(args
));
114 //=============================================================================
115 // EarlyHintPreloader
116 //=============================================================================
118 EarlyHintPreloader::EarlyHintPreloader() {
119 AssertIsOnMainThread();
120 mConnectArgs
.earlyHintPreloaderId() = ++gEarlyHintPreloaderId
;
123 EarlyHintPreloader::~EarlyHintPreloader() {
128 Telemetry::Accumulate(Telemetry::EH_STATE_OF_PRELOAD_REQUEST
, mState
);
132 Maybe
<PreloadHashKey
> EarlyHintPreloader::GenerateHashKey(
133 ASDestination aAs
, nsIURI
* aURI
, nsIPrincipal
* aPrincipal
,
134 CORSMode aCorsMode
, bool aIsModulepreload
) {
135 if (aIsModulepreload
) {
136 return Some(PreloadHashKey::CreateAsScript(
137 aURI
, aCorsMode
, JS::loader::ScriptKind::eModule
));
139 if (aAs
== ASDestination::DESTINATION_FONT
&& aCorsMode
!= CORS_NONE
) {
140 return Some(PreloadHashKey::CreateAsFont(aURI
, aCorsMode
));
142 if (aAs
== ASDestination::DESTINATION_IMAGE
) {
143 return Some(PreloadHashKey::CreateAsImage(aURI
, aPrincipal
, aCorsMode
));
145 if (aAs
== ASDestination::DESTINATION_SCRIPT
) {
146 return Some(PreloadHashKey::CreateAsScript(
147 aURI
, aCorsMode
, JS::loader::ScriptKind::eClassic
));
149 if (aAs
== ASDestination::DESTINATION_STYLE
) {
150 return Some(PreloadHashKey::CreateAsStyle(
151 aURI
, aPrincipal
, aCorsMode
,
152 css::SheetParsingMode::eAuthorSheetFeatures
));
154 if (aAs
== ASDestination::DESTINATION_FETCH
&& aCorsMode
!= CORS_NONE
) {
155 return Some(PreloadHashKey::CreateAsFetch(aURI
, aCorsMode
));
161 nsSecurityFlags
EarlyHintPreloader::ComputeSecurityFlags(CORSMode aCORSMode
,
163 if (aAs
== ASDestination::DESTINATION_FONT
) {
164 return nsContentSecurityManager::ComputeSecurityFlags(
166 nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS
);
168 if (aAs
== ASDestination::DESTINATION_IMAGE
) {
169 return nsContentSecurityManager::ComputeSecurityFlags(
170 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
171 CORS_NONE_MAPS_TO_INHERITED_CONTEXT
) |
172 nsILoadInfo::SEC_ALLOW_CHROME
;
174 if (aAs
== ASDestination::DESTINATION_SCRIPT
) {
175 return nsContentSecurityManager::ComputeSecurityFlags(
176 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
177 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS
) |
178 nsILoadInfo::SEC_ALLOW_CHROME
;
180 if (aAs
== ASDestination::DESTINATION_STYLE
) {
181 return nsContentSecurityManager::ComputeSecurityFlags(
182 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
183 CORS_NONE_MAPS_TO_INHERITED_CONTEXT
) |
184 nsILoadInfo::SEC_ALLOW_CHROME
;
187 if (aAs
== ASDestination::DESTINATION_FETCH
) {
188 return nsContentSecurityManager::ComputeSecurityFlags(
189 aCORSMode
, nsContentSecurityManager::CORSSecurityMapping::
190 CORS_NONE_MAPS_TO_DISABLED_CORS_CHECKS
);
192 MOZ_ASSERT(false, "Unexpected ASDestination");
193 return nsContentSecurityManager::ComputeSecurityFlags(
195 nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS
);
199 void EarlyHintPreloader::MaybeCreateAndInsertPreload(
200 OngoingEarlyHints
* aOngoingEarlyHints
, const LinkHeader
& aLinkHeader
,
201 nsIURI
* aBaseURI
, nsIPrincipal
* aPrincipal
,
202 nsICookieJarSettings
* aCookieJarSettings
,
203 const nsACString
& aResponseReferrerPolicy
, const nsACString
& aCSPHeader
,
204 uint64_t aBrowsingContextID
, nsIInterfaceRequestor
* aCallbacks
,
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 nsCOMPtr
<nsIURI
> uri
;
223 NS_ENSURE_SUCCESS_VOID(
224 NS_NewURI(getter_AddRefs(uri
), aLinkHeader
.mHref
, nullptr, aBaseURI
));
225 // The link relation may apply to a different resource, specified
226 // in the anchor parameter. For the link relations supported so far,
227 // we simply abort if the link applies to a resource different to the
229 if (!nsContentUtils::LinkContextIsURI(aLinkHeader
.mAnchor
, uri
)) {
233 // only preload secure context urls
234 if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri
)) {
238 CORSMode corsMode
= dom::Element::StringToCORSMode(aLinkHeader
.mCrossOrigin
);
240 Maybe
<PreloadHashKey
> hashKey
=
241 GenerateHashKey(destination
, uri
, aPrincipal
, corsMode
, aIsModulepreload
);
246 if (aOngoingEarlyHints
->Contains(*hashKey
)) {
250 nsContentPolicyType contentPolicyType
=
251 aIsModulepreload
? (IsScriptLikeOrInvalid(aLinkHeader
.mAs
)
252 ? nsContentPolicyType::TYPE_SCRIPT
253 : nsContentPolicyType::TYPE_INVALID
)
254 : AsValueToContentPolicy(as
);
256 if (contentPolicyType
== nsContentPolicyType::TYPE_INVALID
) {
260 dom::ReferrerPolicy linkReferrerPolicy
=
261 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
262 aLinkHeader
.mReferrerPolicy
);
264 dom::ReferrerPolicy responseReferrerPolicy
=
265 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(
266 NS_ConvertUTF8toUTF16(aResponseReferrerPolicy
));
268 // The early hint may have two referrer policies, one from the response header
269 // and one from the link element.
271 // For example, in this server response:
272 // HTTP/1.1 103 Early Hints
273 // Referrer-Policy : origin
274 // Link: </style.css>; rel=preload; as=style referrerpolicy=no-referrer
276 // The link header referrer policy, if present, will take precedence over
277 // the response referrer policy
278 dom::ReferrerPolicy finalReferrerPolicy
= responseReferrerPolicy
;
279 if (linkReferrerPolicy
!= dom::ReferrerPolicy::_empty
) {
280 finalReferrerPolicy
= linkReferrerPolicy
;
282 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
=
283 new dom::ReferrerInfo(aBaseURI
, finalReferrerPolicy
);
285 RefPtr
<EarlyHintPreloader
> earlyHintPreloader
= new EarlyHintPreloader();
287 // Security flags for modulepreload's request mode are computed here directly
288 // until full support for worker destinations can be added.
290 // Implements "To fetch a single module script,"
291 // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
292 // and the top-level module fetch flag is set, then set request's
293 // mode to "same-origin".
294 nsSecurityFlags securityFlags
=
296 ? ((aLinkHeader
.mAs
.LowerCaseEqualsASCII("worker") ||
297 aLinkHeader
.mAs
.LowerCaseEqualsASCII("sharedworker") ||
298 aLinkHeader
.mAs
.LowerCaseEqualsASCII("serviceworker"))
299 ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
300 : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
) |
301 (corsMode
== CORS_USE_CREDENTIALS
302 ? nsILoadInfo::SEC_COOKIES_INCLUDE
303 : nsILoadInfo::SEC_COOKIES_SAME_ORIGIN
) |
304 nsILoadInfo::SEC_ALLOW_CHROME
305 : EarlyHintPreloader::ComputeSecurityFlags(corsMode
, destination
);
307 // Verify that the resource should be loaded.
308 // This isn't the ideal way to test the resource against the CSP.
309 // The problem comes from the fact that at the stage of Early Hint
310 // processing we have not yet created a document where we would normally store
313 // First we will create a load info,
314 // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
315 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new LoadInfo(
316 aPrincipal
, // loading principal
317 aPrincipal
, // triggering principal
318 nullptr /* aLoadingContext node */,
319 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
, contentPolicyType
);
321 if (aCSPHeader
.Length() != 0) {
322 // If the CSP header is present then create a new CSP and apply the header
324 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= new nsCSPContext();
325 nsresult rv
= csp
->SetRequestContextWithPrincipal(
326 aPrincipal
, aBaseURI
, u
""_ns
, 0 /* aInnerWindowId */);
327 NS_ENSURE_SUCCESS_VOID(rv
);
328 rv
= CSP_AppendCSPFromHeader(csp
, NS_ConvertUTF8toUTF16(aCSPHeader
),
329 false /* report only */);
330 NS_ENSURE_SUCCESS_VOID(rv
);
332 // We create a temporary ClientInfo. This is required on the loadInfo as
333 // that is how the CSP is queried. More specificially, as a hack to be able
334 // to call NS_CheckContentLoadPolicy on nsILoadInfo which exclusively
335 // accesses the CSP from the ClientInfo, we create a synthetic ClientInfo to
336 // hold the CSP we are creating. This is not a safe thing to do in any other
337 // circumstance because ClientInfos are always describing a ClientSource
338 // that corresponds to a global or potential global, so creating an info
339 // without a source is unsound. For the purposes of doing things before a
340 // global exists, fetch has the concept of a
341 // https://fetch.spec.whatwg.org/#concept-request-reserved-client and
342 // nsILoadInfo explicity has methods around GiveReservedClientSource which
343 // are primarily used by ClientChannelHelper. If you are trying to do real
344 // CSP stuff and the ClientInfo is not there yet, please enhance the logic
345 // around ClientChannelHelper.
347 mozilla::ipc::PrincipalInfo principalInfo
;
348 rv
= PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
);
349 NS_ENSURE_SUCCESS_VOID(rv
);
350 dom::ClientInfo
clientInfo(nsID::GenerateUUID(), dom::ClientType::Window
,
351 principalInfo
, TimeStamp::Now());
353 // Our newly-created CSP is set on the ClientInfo via the indirect route of
354 // first serializing to CSPInfo
355 ipc::CSPInfo cspInfo
;
356 rv
= CSPToCSPInfo(csp
, &cspInfo
);
357 NS_ENSURE_SUCCESS_VOID(rv
);
358 clientInfo
.SetCspInfo(cspInfo
);
360 // This ClientInfo is then set on the new loadInfo.
361 // It can now be used to test the resource against the policy
362 secCheckLoadInfo
->SetClientInfo(clientInfo
);
365 int16_t shouldLoad
= nsIContentPolicy::ACCEPT
;
367 NS_CheckContentLoadPolicy(uri
, secCheckLoadInfo
, ""_ns
, &shouldLoad
,
368 nsContentUtils::GetContentPolicy());
370 if (NS_FAILED(rv
) || NS_CP_REJECTED(shouldLoad
)) {
374 NS_ENSURE_SUCCESS_VOID(earlyHintPreloader
->OpenChannel(
375 uri
, aPrincipal
, securityFlags
, contentPolicyType
, referrerInfo
,
376 aCookieJarSettings
, aBrowsingContextID
, aCallbacks
));
378 earlyHintPreloader
->SetLinkHeader(aLinkHeader
);
380 DebugOnly
<bool> result
=
381 aOngoingEarlyHints
->Add(*hashKey
, earlyHintPreloader
);
385 nsresult
EarlyHintPreloader::OpenChannel(
386 nsIURI
* aURI
, nsIPrincipal
* aPrincipal
, nsSecurityFlags aSecurityFlags
,
387 nsContentPolicyType aContentPolicyType
, nsIReferrerInfo
* aReferrerInfo
,
388 nsICookieJarSettings
* aCookieJarSettings
, uint64_t aBrowsingContextID
,
389 nsIInterfaceRequestor
* aCallbacks
) {
390 MOZ_ASSERT(aContentPolicyType
== nsContentPolicyType::TYPE_IMAGE
||
391 aContentPolicyType
==
392 nsContentPolicyType::TYPE_INTERNAL_FETCH_PRELOAD
||
393 aContentPolicyType
== nsContentPolicyType::TYPE_SCRIPT
||
394 aContentPolicyType
== nsContentPolicyType::TYPE_STYLESHEET
||
395 aContentPolicyType
== nsContentPolicyType::TYPE_FONT
);
397 nsCOMPtr
<nsIInterfaceRequestor
> wrappedCallbacks
;
398 NS_NewInterfaceRequestorAggregation(this, aCallbacks
,
399 getter_AddRefs(wrappedCallbacks
));
401 NS_NewChannel(getter_AddRefs(mChannel
), aURI
, aPrincipal
, aSecurityFlags
,
402 aContentPolicyType
, aCookieJarSettings
,
403 /* aPerformanceStorage */ nullptr,
404 /* aLoadGroup */ nullptr,
405 /* aCallbacks */ wrappedCallbacks
, nsIRequest::LOAD_NORMAL
);
407 NS_ENSURE_SUCCESS(rv
, rv
);
409 RefPtr
<nsHttpChannel
> httpChannelObject
= do_QueryObject(mChannel
);
410 if (!httpChannelObject
) {
412 return NS_ERROR_ABORT
;
415 // configure HTTP specific stuff
416 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(mChannel
);
419 return NS_ERROR_ABORT
;
421 DebugOnly
<nsresult
> success
= httpChannel
->SetReferrerInfo(aReferrerInfo
);
422 MOZ_ASSERT(NS_SUCCEEDED(success
));
423 success
= httpChannel
->SetRequestHeader("X-Moz"_ns
, "early hint"_ns
, false);
424 MOZ_ASSERT(NS_SUCCEEDED(success
));
426 mParentListener
= new ParentChannelListener(this, nullptr);
430 rv
= mChannel
->AsyncOpen(mParentListener
);
432 mParentListener
= nullptr;
436 SetState(ePreloaderOpened
);
438 // Setting the BrowsingContextID here to let Early Hint requests show up in
439 // devtools. Normally that would automatically happen if we would pass the
440 // nsILoadGroup in ns_NewChannel above, but the nsILoadGroup is inaccessible
441 // here in the ParentProcess. The nsILoadGroup only exists in ContentProcess
442 // as part of the document and nsDocShell. It is also not yet determined which
443 // ContentProcess this load belongs to.
444 nsCOMPtr
<nsILoadInfo
> loadInfo
= mChannel
->LoadInfo();
445 static_cast<LoadInfo
*>(loadInfo
.get())
446 ->UpdateBrowsingContextID(aBrowsingContextID
);
451 void EarlyHintPreloader::PriorizeAsPreload() {
452 nsLoadFlags loadFlags
= nsIRequest::LOAD_NORMAL
;
453 Unused
<< mChannel
->GetLoadFlags(&loadFlags
);
454 Unused
<< mChannel
->SetLoadFlags(loadFlags
| nsIRequest::LOAD_BACKGROUND
);
456 if (nsCOMPtr
<nsIClassOfService
> cos
= do_QueryInterface(mChannel
)) {
457 Unused
<< cos
->AddClassFlags(nsIClassOfService::Unblocked
);
461 void EarlyHintPreloader::SetLinkHeader(const LinkHeader
& aLinkHeader
) {
462 mConnectArgs
.link() = aLinkHeader
;
465 bool EarlyHintPreloader::IsFromContentParent(dom::ContentParentId aCpId
) const {
466 return aCpId
== mCpId
;
469 bool EarlyHintPreloader::Register(dom::ContentParentId aCpId
,
470 EarlyHintConnectArgs
& aOut
) {
473 // Set minimum delay of 1ms to always start the timer after the function call
475 nsresult rv
= NS_NewTimerWithCallback(
476 getter_AddRefs(mTimer
), this,
477 std::max(StaticPrefs::network_early_hints_parent_connect_timeout(),
479 nsITimer::TYPE_ONE_SHOT
);
482 CancelChannel(NS_ERROR_ABORT
, "new-timer-failed"_ns
,
483 /* aDeleteEntry */ false);
487 // Create an entry in the redirect channel registrar
488 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
489 registrar
->RegisterEarlyHint(mConnectArgs
.earlyHintPreloaderId(), this);
495 nsresult
EarlyHintPreloader::CancelChannel(nsresult aStatus
,
496 const nsACString
& aReason
,
498 LOG(("EarlyHintPreloader::CancelChannel [this=%p]\n", this));
505 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
506 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
508 // clear redirect channel in case this channel is cleared between the call of
509 // EarlyHintPreloader::AsyncOnChannelRedirect and
510 // EarlyHintPreloader::OnRedirectResult
511 mRedirectChannel
= nullptr;
516 mChannel
->CancelWithReason(aStatus
, aReason
);
517 // Clearing mChannel is safe, because this EarlyHintPreloader is not in the
518 // EarlyHintRegistrar after this function call and we won't call
519 // SetHttpChannelFromEarlyHintPreloader nor OnStartRequest on mParent.
521 SetState(ePreloaderCancelled
);
526 void EarlyHintPreloader::OnParentReady(nsIParentChannel
* aParent
) {
527 AssertIsOnMainThread();
529 LOG(("EarlyHintPreloader::OnParentReady [this=%p]\n", this));
531 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
533 obs
->NotifyObservers(mChannel
, "earlyhints-connectback", nullptr);
543 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
544 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
546 if (mOnStartRequestCalled
) {
548 InvokeStreamListenerFunctions();
552 void EarlyHintPreloader::SetParentChannel() {
553 RefPtr
<HttpBaseChannel
> channel
= do_QueryObject(mChannel
);
554 RefPtr
<HttpChannelParent
> parent
= do_QueryObject(mParent
);
555 parent
->SetHttpChannelFromEarlyHintPreloader(channel
);
559 // https://searchfox.org/mozilla-central/rev/b4150d1c6fae0c51c522df2d2c939cf5ad331d4c/netwerk/ipc/DocumentLoadListener.cpp#1311
560 void EarlyHintPreloader::InvokeStreamListenerFunctions() {
561 AssertIsOnMainThread();
562 RefPtr
<EarlyHintPreloader
> self(this);
565 "EarlyHintPreloader::InvokeStreamListenerFunctions [this=%p parent=%p]\n",
566 this, mParent
.get()));
568 // If we failed to suspend the channel, then we might have received
569 // some messages while the redirected was being handled.
570 // Manually send them on now.
572 // This is safe to do, because OnStartRequest/OnStopRequest/OnDataAvailable
573 // are all called on the main thread. They can't be called until we worked
574 // through all functions in the streamListnerFunctions array.
575 mParentListener
->SetListenerAfterRedirect(mParent
);
577 nsTArray
<StreamListenerFunction
> streamListenerFunctions
=
578 std::move(mStreamListenerFunctions
);
580 ForwardStreamListenerFunctions(streamListenerFunctions
, mParent
);
582 // We don't expect to get new stream listener functions added
583 // via re-entrancy. If this ever happens, we should understand
584 // exactly why before allowing it.
585 NS_ASSERTION(mStreamListenerFunctions
.IsEmpty(),
586 "Should not have added new stream listener function!");
588 if (mChannel
&& mSuspended
) {
593 mParentListener
= nullptr;
595 SetState(ePreloaderUsed
);
598 //-----------------------------------------------------------------------------
599 // EarlyHintPreloader::nsISupports
600 //-----------------------------------------------------------------------------
602 NS_IMPL_ISUPPORTS(EarlyHintPreloader
, nsIRequestObserver
, nsIStreamListener
,
603 nsIChannelEventSink
, nsIInterfaceRequestor
,
604 nsIRedirectResultListener
, nsIMultiPartChannelListener
,
605 nsINamed
, nsITimerCallback
);
607 //-----------------------------------------------------------------------------
608 // EarlyHintPreloader::nsIStreamListener
609 //-----------------------------------------------------------------------------
611 // Implementation copied and adapted from DocumentLoadListener::OnStartRequest
612 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2317-2508
614 EarlyHintPreloader::OnStartRequest(nsIRequest
* aRequest
) {
615 LOG(("EarlyHintPreloader::OnStartRequest [this=%p]\n", this));
616 AssertIsOnMainThread();
618 mOnStartRequestCalled
= true;
620 nsCOMPtr
<nsIMultiPartChannel
> multiPartChannel
= do_QueryInterface(aRequest
);
621 if (multiPartChannel
) {
622 multiPartChannel
->GetBaseChannel(getter_AddRefs(mChannel
));
624 mChannel
= do_QueryInterface(aRequest
);
626 MOZ_DIAGNOSTIC_ASSERT(mChannel
);
628 nsresult status
= NS_OK
;
629 Unused
<< aRequest
->GetStatus(&status
);
633 mParent
->OnStartRequest(aRequest
);
634 InvokeStreamListenerFunctions();
636 // Don't suspend the chanel when the channel got cancelled with
637 // CancelChannel, because then OnStopRequest wouldn't get called and we
638 // wouldn't clean up the channel.
639 if (NS_SUCCEEDED(status
)) {
643 mStreamListenerFunctions
.AppendElement(
644 AsVariant(OnStartRequestParams
{aRequest
}));
647 // return error after adding the OnStartRequest forward. The OnStartRequest
648 // failure has to be forwarded to listener, because they called AsyncOpen on
653 // Implementation copied from DocumentLoadListener::OnStopRequest
654 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2510-2528
656 EarlyHintPreloader::OnStopRequest(nsIRequest
* aRequest
, nsresult aStatusCode
) {
657 AssertIsOnMainThread();
658 LOG(("EarlyHintPreloader::OnStopRequest [this=%p]\n", this));
659 mStreamListenerFunctions
.AppendElement(
660 AsVariant(OnStopRequestParams
{aRequest
, aStatusCode
}));
662 // If we're not a multi-part channel, then we're finished and we don't
663 // expect any further events. If we are, then this might be called again,
664 // so wait for OnAfterLastPart instead.
665 nsCOMPtr
<nsIMultiPartChannel
> multiPartChannel
= do_QueryInterface(aRequest
);
666 if (!multiPartChannel
) {
673 //-----------------------------------------------------------------------------
674 // EarlyHintPreloader::nsIStreamListener
675 //-----------------------------------------------------------------------------
677 // Implementation copied from DocumentLoadListener::OnDataAvailable
678 // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2530-2549
680 EarlyHintPreloader::OnDataAvailable(nsIRequest
* aRequest
,
681 nsIInputStream
* aInputStream
,
682 uint64_t aOffset
, uint32_t aCount
) {
683 AssertIsOnMainThread();
684 LOG(("EarlyHintPreloader::OnDataAvailable [this=%p]\n", this));
685 // This isn't supposed to happen, since we suspended the channel, but
686 // sometimes Suspend just doesn't work. This can happen when we're routing
687 // through nsUnknownDecoder to sniff the content type, and it doesn't handle
688 // being suspended. Let's just store the data and manually forward it to our
689 // redirected channel when it's ready.
691 nsresult rv
= NS_ReadInputStreamToString(aInputStream
, data
, aCount
);
692 NS_ENSURE_SUCCESS(rv
, rv
);
694 mStreamListenerFunctions
.AppendElement(
695 AsVariant(OnDataAvailableParams
{aRequest
, data
, aOffset
, aCount
}));
700 //-----------------------------------------------------------------------------
701 // EarlyHintPreloader::nsIMultiPartChannelListener
702 //-----------------------------------------------------------------------------
705 EarlyHintPreloader::OnAfterLastPart(nsresult aStatus
) {
706 LOG(("EarlyHintPreloader::OnAfterLastPart [this=%p]", this));
707 mStreamListenerFunctions
.AppendElement(
708 AsVariant(OnAfterLastPartParams
{aStatus
}));
713 //-----------------------------------------------------------------------------
714 // EarlyHintPreloader::nsIChannelEventSink
715 //-----------------------------------------------------------------------------
718 EarlyHintPreloader::AsyncOnChannelRedirect(
719 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t aFlags
,
720 nsIAsyncVerifyRedirectCallback
* callback
) {
721 LOG(("EarlyHintPreloader::AsyncOnChannelRedirect [this=%p]", this));
722 nsCOMPtr
<nsIURI
> newURI
;
723 nsresult rv
= NS_GetFinalChannelURI(aNewChannel
, getter_AddRefs(newURI
));
724 NS_ENSURE_SUCCESS(rv
, rv
);
726 rv
= aNewChannel
->GetURI(getter_AddRefs(newURI
));
728 callback
->OnRedirectVerifyCallback(rv
);
732 // HTTP request headers are not automatically forwarded to the new channel.
733 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aNewChannel
);
734 NS_ENSURE_STATE(httpChannel
);
736 rv
= httpChannel
->SetRequestHeader("X-Moz"_ns
, "early hint"_ns
, false);
737 MOZ_ASSERT(NS_SUCCEEDED(rv
));
739 // Assign to mChannel after we get notification about success of the
740 // redirect in OnRedirectResult.
741 mRedirectChannel
= aNewChannel
;
743 callback
->OnRedirectVerifyCallback(NS_OK
);
747 //-----------------------------------------------------------------------------
748 // EarlyHintPreloader::nsIRedirectResultListener
749 //-----------------------------------------------------------------------------
752 EarlyHintPreloader::OnRedirectResult(nsresult aStatus
) {
753 LOG(("EarlyHintPreloader::OnRedirectResult [this=%p] aProceeding=0x%" PRIx32
,
754 this, static_cast<uint32_t>(aStatus
)));
755 if (NS_SUCCEEDED(aStatus
) && mRedirectChannel
) {
756 mChannel
= mRedirectChannel
;
759 mRedirectChannel
= nullptr;
764 //-----------------------------------------------------------------------------
765 // EarlyHintPreloader::nsINamed
766 //-----------------------------------------------------------------------------
769 EarlyHintPreloader::GetName(nsACString
& aName
) {
770 aName
.AssignLiteral("EarlyHintPreloader");
774 //-----------------------------------------------------------------------------
775 // EarlyHintPreloader::nsITimerCallback
776 //-----------------------------------------------------------------------------
779 EarlyHintPreloader::Notify(nsITimer
* timer
) {
780 // Death grip, because we will most likely remove the last reference when
781 // deleting us from the EarlyHintRegistrar
782 RefPtr
<EarlyHintPreloader
> deathGrip(this);
784 RefPtr
<EarlyHintRegistrar
> registrar
= EarlyHintRegistrar::GetOrCreate();
785 registrar
->DeleteEntry(mCpId
, mConnectArgs
.earlyHintPreloaderId());
788 mRedirectChannel
= nullptr;
793 mChannel
->CancelWithReason(NS_ERROR_ABORT
, "parent-connect-timeout"_ns
);
796 SetState(ePreloaderTimeout
);
801 //-----------------------------------------------------------------------------
802 // EarlyHintPreloader::nsIInterfaceRequestor
803 //-----------------------------------------------------------------------------
806 EarlyHintPreloader::GetInterface(const nsIID
& aIID
, void** aResult
) {
807 if (aIID
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
809 *aResult
= static_cast<nsIChannelEventSink
*>(this);
813 if (aIID
.Equals(NS_GET_IID(nsIRedirectResultListener
))) {
815 *aResult
= static_cast<nsIRedirectResultListener
*>(this);
819 return NS_ERROR_NO_INTERFACE
;
822 void EarlyHintPreloader::CollectResourcesTypeTelemetry(
823 ASDestination aASDestination
) {
824 if (aASDestination
== ASDestination::DESTINATION_FONT
) {
825 glean::netwerk::early_hints
.Get("font"_ns
).Add(1);
826 } else if (aASDestination
== ASDestination::DESTINATION_SCRIPT
) {
827 glean::netwerk::early_hints
.Get("script"_ns
).Add(1);
828 } else if (aASDestination
== ASDestination::DESTINATION_STYLE
) {
829 glean::netwerk::early_hints
.Get("stylesheet"_ns
).Add(1);
830 } else if (aASDestination
== ASDestination::DESTINATION_IMAGE
) {
831 glean::netwerk::early_hints
.Get("image"_ns
).Add(1);
832 } else if (aASDestination
== ASDestination::DESTINATION_FETCH
) {
833 glean::netwerk::early_hints
.Get("fetch"_ns
).Add(1);
835 glean::netwerk::early_hints
.Get("other"_ns
).Add(1);
838 } // namespace mozilla::net