EarlyHintsService.cpp (5732B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 3 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "EarlyHintsService.h" 9 #include "EarlyHintPreconnect.h" 10 #include "EarlyHintPreloader.h" 11 #include "mozilla/dom/LinkStyle.h" 12 #include "mozilla/PreloadHashKey.h" 13 #include "mozilla/StoragePrincipalHelper.h" 14 #include "nsContentUtils.h" 15 #include "nsIChannel.h" 16 #include "nsICookieJarSettings.h" 17 #include "nsILoadInfo.h" 18 #include "nsIPrincipal.h" 19 #include "nsNetUtil.h" 20 #include "nsString.h" 21 22 namespace mozilla::net { 23 24 EarlyHintsService::EarlyHintsService() 25 : mOngoingEarlyHints(new OngoingEarlyHints()) {} 26 27 // implementing the destructor in the .cpp file to allow EarlyHintsService.h 28 // not to include EarlyHintPreloader.h, decoupling the two files and hopefully 29 // allow faster compile times 30 EarlyHintsService::~EarlyHintsService() = default; 31 32 void EarlyHintsService::EarlyHint( 33 const nsACString& aLinkHeader, nsIURI* aBaseURI, nsIChannel* aChannel, 34 const nsACString& aReferrerPolicy, const nsACString& aCSPHeader, 35 dom::CanonicalBrowsingContext* aLoadingBrowsingContext) { 36 mEarlyHintsCount++; 37 if (mFirstEarlyHint.isNothing()) { 38 mFirstEarlyHint.emplace(TimeStamp::NowLoRes()); 39 } else { 40 // Only allow one early hint response with link headers. See 41 // https://html.spec.whatwg.org/multipage/semantics.html#early-hints 42 // > Note: Only the first early hint response served during the navigation 43 // > is handled, and it is discarded if it is succeeded by a cross-origin 44 // > redirect. 45 return; 46 } 47 48 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 49 // We only follow Early Hints sent on the main document. Make sure that we got 50 // the main document channel here. 51 if (loadInfo->GetExternalContentPolicyType() != 52 ExtContentPolicy::TYPE_DOCUMENT) { 53 MOZ_ASSERT(false, "Early Hint on non-document channel"); 54 return; 55 } 56 nsCOMPtr<nsIPrincipal> principal; 57 // We want to set the top-level document as the triggeringPrincipal for the 58 // load of the sub-resources (image, font, fetch, script, style, fetch and in 59 // the future maybe more). We can't use the `triggeringPrincipal` of the main 60 // document channel, because it is the `systemPrincipal` for user initiated 61 // loads. Same for the `LoadInfo::FindPrincipalToInherit(aChannel)`. 62 // 63 // On 3xx redirects of the main document to cross site locations, all Early 64 // Hint preloads get cancelled as specified in the whatwg spec: 65 // 66 // Note: Only the first early hint response served during the navigation is 67 // handled, and it is discarded if it is succeeded by a cross-origin 68 // redirect. [1] 69 // 70 // Therefore the channel doesn't need to change the principal for any reason 71 // and has the correct principal for the whole lifetime. 72 // 73 // [1]: https://html.spec.whatwg.org/multipage/semantics.html#early-hints 74 nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 75 aChannel, getter_AddRefs(principal)); 76 NS_ENSURE_SUCCESS_VOID(rv); 77 78 nsCOMPtr<nsICookieJarSettings> cookieJarSettings; 79 if (NS_FAILED( 80 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)))) { 81 return; 82 } 83 84 // TODO: find out why LinkHeaderParser uses utf16 and check if it can be 85 // changed to utf8 86 auto linkHeaders = ParseLinkHeader(NS_ConvertUTF8toUTF16(aLinkHeader)); 87 88 for (auto& linkHeader : linkHeaders) { 89 if (linkHeader.mRel.LowerCaseEqualsLiteral("preconnect")) { 90 mLinkType |= dom::LinkStyle::ePRECONNECT; 91 OriginAttributes originAttributes; 92 StoragePrincipalHelper::GetOriginAttributesForNetworkState( 93 aChannel, originAttributes); 94 EarlyHintPreconnect::MaybePreconnect(linkHeader, aBaseURI, 95 std::move(originAttributes)); 96 } else if (linkHeader.mRel.LowerCaseEqualsLiteral("preload")) { 97 mLinkType |= dom::LinkStyle::ePRELOAD; 98 EarlyHintPreloader::MaybeCreateAndInsertPreload( 99 mOngoingEarlyHints, linkHeader, aBaseURI, principal, 100 cookieJarSettings, aReferrerPolicy, aCSPHeader, 101 loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, false); 102 } else if (linkHeader.mRel.LowerCaseEqualsLiteral("modulepreload")) { 103 mLinkType |= dom::LinkStyle::eMODULE_PRELOAD; 104 EarlyHintPreloader::MaybeCreateAndInsertPreload( 105 mOngoingEarlyHints, linkHeader, aBaseURI, principal, 106 cookieJarSettings, aReferrerPolicy, aCSPHeader, 107 loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, true); 108 } else if (linkHeader.mRel.LowerCaseEqualsLiteral( 109 "compression-dictionary")) { 110 mLinkType |= dom::LinkStyle::eCOMPRESSION_DICTIONARY; 111 EarlyHintPreloader::MaybeCreateAndInsertPreload( 112 mOngoingEarlyHints, linkHeader, aBaseURI, principal, 113 cookieJarSettings, aReferrerPolicy, aCSPHeader, 114 loadInfo->GetBrowsingContextID(), aLoadingBrowsingContext, true); 115 } 116 } 117 } 118 119 void EarlyHintsService::Cancel(const nsACString& aReason) { 120 Reset(); 121 mOngoingEarlyHints->CancelAll(aReason); 122 } 123 124 void EarlyHintsService::RegisterLinksAndGetConnectArgs( 125 dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) { 126 mOngoingEarlyHints->RegisterLinksAndGetConnectArgs(aCpId, aOutLinks); 127 } 128 129 void EarlyHintsService::Reset() { 130 if (mEarlyHintsCount == 0) { 131 return; 132 } 133 // Reset telemetry counters and timestamps. 134 mEarlyHintsCount = 0; 135 mFirstEarlyHint = Nothing(); 136 } 137 138 } // namespace mozilla::net