Backed out 9 changesets (bug 1901851, bug 1728331) for causing remote worker crashes...
[gecko.git] / uriloader / preload / PreloadService.cpp
blob48cf9a364cee9fc7156055c4e819b21bde956381
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "PreloadService.h"
8 #include "FetchPreloader.h"
9 #include "PreloaderBase.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/AsyncEventDispatcher.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/dom/FetchPriority.h"
14 #include "mozilla/dom/HTMLLinkElement.h"
15 #include "mozilla/dom/ScriptLoader.h"
16 #include "mozilla/dom/ReferrerInfo.h"
17 #include "mozilla/Encoding.h"
18 #include "mozilla/FontPreloader.h"
19 #include "mozilla/StaticPrefs_network.h"
20 #include "nsGenericHTMLElement.h"
21 #include "nsNetUtil.h"
23 namespace mozilla {
25 using namespace dom;
27 static LazyLogModule sPreloadServiceLog{"PreloadService"};
29 PreloadService::PreloadService(dom::Document* aDoc) : mDocument(aDoc) {}
30 PreloadService::~PreloadService() = default;
32 bool PreloadService::RegisterPreload(const PreloadHashKey& aKey,
33 PreloaderBase* aPreload) {
34 return mPreloads.WithEntryHandle(aKey, [&](auto&& lookup) {
35 if (lookup) {
36 lookup.Data() = aPreload;
37 return true;
39 lookup.Insert(aPreload);
40 return false;
41 });
44 void PreloadService::DeregisterPreload(const PreloadHashKey& aKey) {
45 mPreloads.Remove(aKey);
48 void PreloadService::ClearAllPreloads() { mPreloads.Clear(); }
50 bool PreloadService::PreloadExists(const PreloadHashKey& aKey) {
51 return mPreloads.Contains(aKey);
54 already_AddRefed<PreloaderBase> PreloadService::LookupPreload(
55 const PreloadHashKey& aKey) const {
56 return mPreloads.Get(aKey);
59 already_AddRefed<nsIURI> PreloadService::GetPreloadURI(const nsAString& aURL) {
60 nsIURI* base = BaseURIForPreload();
61 auto encoding = mDocument->GetDocumentCharacterSet();
63 nsCOMPtr<nsIURI> uri;
64 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
65 if (NS_FAILED(rv)) {
66 return nullptr;
69 return uri.forget();
72 already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
73 dom::HTMLLinkElement* aLinkElement, nsContentPolicyType aPolicyType) {
74 if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
75 MOZ_ASSERT_UNREACHABLE("Caller should check");
76 return nullptr;
79 nsAutoString as, charset, crossOrigin, integrity, referrerPolicy,
80 fetchPriority, rel, srcset, sizes, type, url;
82 nsCOMPtr<nsIURI> uri = aLinkElement->GetURI();
83 aLinkElement->GetCharset(charset);
84 aLinkElement->GetImageSrcset(srcset);
85 aLinkElement->GetImageSizes(sizes);
86 aLinkElement->GetHref(url);
87 aLinkElement->GetCrossOrigin(crossOrigin);
88 aLinkElement->GetIntegrity(integrity);
89 aLinkElement->GetReferrerPolicy(referrerPolicy);
90 aLinkElement->GetFetchPriority(fetchPriority);
91 aLinkElement->GetRel(rel);
93 nsAutoString nonce;
94 if (nsString* cspNonce =
95 static_cast<nsString*>(aLinkElement->GetProperty(nsGkAtoms::nonce))) {
96 nonce = *cspNonce;
99 if (rel.LowerCaseEqualsASCII("modulepreload")) {
100 as = u"script"_ns;
101 type = u"module"_ns;
102 } else {
103 aLinkElement->GetAs(as);
104 aLinkElement->GetType(type);
107 auto result = PreloadOrCoalesce(uri, url, aPolicyType, as, type, charset,
108 srcset, sizes, nonce, integrity, crossOrigin,
109 referrerPolicy, fetchPriority,
110 /* aFromHeader = */ false, 0);
112 if (!result.mPreloader) {
113 NotifyNodeEvent(aLinkElement, result.mAlreadyComplete);
114 return nullptr;
117 result.mPreloader->AddLinkPreloadNode(aLinkElement);
118 return result.mPreloader.forget();
121 void PreloadService::PreloadLinkHeader(
122 nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
123 const nsAString& aAs, const nsAString& aType, const nsAString& aNonce,
124 const nsAString& aIntegrity, const nsAString& aSrcset,
125 const nsAString& aSizes, const nsAString& aCORS,
126 const nsAString& aReferrerPolicy, uint64_t aEarlyHintPreloaderId,
127 const nsAString& aFetchPriority) {
128 if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
129 MOZ_ASSERT_UNREACHABLE("Caller should check");
130 return;
133 PreloadOrCoalesce(aURI, aURL, aPolicyType, aAs, aType, u""_ns, aSrcset,
134 aSizes, aNonce, aIntegrity, aCORS, aReferrerPolicy,
135 aFetchPriority,
136 /* aFromHeader = */ true, aEarlyHintPreloaderId);
139 // The mapping is specified as implementation-defined, see step 15 of
140 // <https://fetch.spec.whatwg.org/#concept-fetch>.
141 // See corresponding preferences in StaticPrefList.yaml for more context.
142 class SupportsPriorityValueFor {
143 public:
144 static int32_t LinkRelPreloadFont(const FetchPriority aFetchPriority) {
145 int32_t priorityValue = nsISupportsPriority::PRIORITY_HIGH;
146 if (!StaticPrefs::network_fetchpriority_enabled()) {
147 return priorityValue;
150 return priorityValue +
151 FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_font, aFetchPriority);
154 static int32_t LinkRelPreloadFetch(const FetchPriority aFetchPriority) {
155 int32_t priorityValue = nsISupportsPriority::PRIORITY_NORMAL;
156 if (!StaticPrefs::network_fetchpriority_enabled()) {
157 return priorityValue;
160 return priorityValue +
161 FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_fetch, aFetchPriority);
165 PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce(
166 nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
167 const nsAString& aAs, const nsAString& aType, const nsAString& aCharset,
168 const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aNonce,
169 const nsAString& aIntegrity, const nsAString& aCORS,
170 const nsAString& aReferrerPolicy, const nsAString& aFetchPriority,
171 bool aFromHeader, uint64_t aEarlyHintPreloaderId) {
172 if (!aURI) {
173 MOZ_ASSERT_UNREACHABLE("Should not pass null nsIURI");
174 return {nullptr, false};
177 bool isImgSet = false;
178 PreloadHashKey preloadKey;
179 nsCOMPtr<nsIURI> uri = aURI;
181 if (aAs.LowerCaseEqualsASCII("script")) {
182 preloadKey = PreloadHashKey::CreateAsScript(uri, aCORS, aType);
183 } else if (aAs.LowerCaseEqualsASCII("style")) {
184 preloadKey = PreloadHashKey::CreateAsStyle(
185 uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS),
186 css::eAuthorSheetFeatures /* see Loader::LoadSheet */);
187 } else if (aAs.LowerCaseEqualsASCII("image")) {
188 uri = mDocument->ResolvePreloadImage(BaseURIForPreload(), aURL, aSrcset,
189 aSizes, &isImgSet);
190 if (!uri) {
191 return {nullptr, false};
194 preloadKey = PreloadHashKey::CreateAsImage(
195 uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS));
196 } else if (aAs.LowerCaseEqualsASCII("font")) {
197 preloadKey = PreloadHashKey::CreateAsFont(
198 uri, dom::Element::StringToCORSMode(aCORS));
199 } else if (aAs.LowerCaseEqualsASCII("fetch")) {
200 preloadKey = PreloadHashKey::CreateAsFetch(
201 uri, dom::Element::StringToCORSMode(aCORS));
202 } else {
203 return {nullptr, false};
206 if (RefPtr<PreloaderBase> preload = LookupPreload(preloadKey)) {
207 return {std::move(preload), false};
210 if (aAs.LowerCaseEqualsASCII("script")) {
211 PreloadScript(uri, aType, aCharset, aCORS, aReferrerPolicy, aNonce,
212 aFetchPriority, aIntegrity, true /* isInHead - TODO */,
213 aEarlyHintPreloaderId);
214 } else if (aAs.LowerCaseEqualsASCII("style")) {
215 auto status = mDocument->PreloadStyle(
216 aURI, Encoding::ForLabel(aCharset), aCORS,
217 PreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity,
218 aFromHeader ? css::StylePreloadKind::FromLinkRelPreloadHeader
219 : css::StylePreloadKind::FromLinkRelPreloadElement,
220 aEarlyHintPreloaderId, aFetchPriority);
221 switch (status) {
222 case dom::SheetPreloadStatus::AlreadyComplete:
223 return {nullptr, /* already_complete = */ true};
224 case dom::SheetPreloadStatus::Errored:
225 case dom::SheetPreloadStatus::InProgress:
226 break;
228 } else if (aAs.LowerCaseEqualsASCII("image")) {
229 PreloadImage(uri, aCORS, aReferrerPolicy, isImgSet, aEarlyHintPreloaderId,
230 aFetchPriority);
231 } else if (aAs.LowerCaseEqualsASCII("font")) {
232 PreloadFont(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId,
233 aFetchPriority);
234 } else if (aAs.LowerCaseEqualsASCII("fetch")) {
235 PreloadFetch(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId,
236 aFetchPriority);
239 RefPtr<PreloaderBase> preload = LookupPreload(preloadKey);
240 if (preload && aEarlyHintPreloaderId) {
241 preload->SetForEarlyHints();
244 return {preload, false};
247 void PreloadService::PreloadScript(
248 nsIURI* aURI, const nsAString& aType, const nsAString& aCharset,
249 const nsAString& aCrossOrigin, const nsAString& aReferrerPolicy,
250 const nsAString& aNonce, const nsAString& aFetchPriority,
251 const nsAString& aIntegrity, bool aScriptFromHead,
252 uint64_t aEarlyHintPreloaderId) {
253 mDocument->ScriptLoader()->PreloadURI(
254 aURI, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority, aIntegrity,
255 aScriptFromHead, false, false, true,
256 PreloadReferrerPolicy(aReferrerPolicy), aEarlyHintPreloaderId);
259 void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
260 const nsAString& aImageReferrerPolicy,
261 bool aIsImgSet,
262 uint64_t aEarlyHintPreloaderId,
263 const nsAString& aFetchPriority) {
264 mDocument->PreLoadImage(
265 aURI, aCrossOrigin, PreloadReferrerPolicy(aImageReferrerPolicy),
266 aIsImgSet, true, aEarlyHintPreloaderId, aFetchPriority);
269 void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
270 const nsAString& aReferrerPolicy,
271 uint64_t aEarlyHintPreloaderId,
272 const nsAString& aFetchPriority) {
273 CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
274 auto key = PreloadHashKey::CreateAsFont(aURI, cors);
276 if (PreloadExists(key)) {
277 return;
280 const auto fetchPriority =
281 nsGenericHTMLElement::ToFetchPriority(aFetchPriority);
282 const auto supportsPriorityValue =
283 SupportsPriorityValueFor::LinkRelPreloadFont(fetchPriority);
284 LogPriorityMapping(sPreloadServiceLog, fetchPriority, supportsPriorityValue);
286 RefPtr<FontPreloader> preloader = new FontPreloader();
287 dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
288 preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument,
289 aEarlyHintPreloaderId, supportsPriorityValue);
292 void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
293 const nsAString& aReferrerPolicy,
294 uint64_t aEarlyHintPreloaderId,
295 const nsAString& aFetchPriority) {
296 CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
297 auto key = PreloadHashKey::CreateAsFetch(aURI, cors);
299 if (PreloadExists(key)) {
300 return;
303 RefPtr<FetchPreloader> preloader = new FetchPreloader();
304 dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
306 const auto fetchPriority =
307 nsGenericHTMLElement::ToFetchPriority(aFetchPriority);
308 const int32_t supportsPriorityValue =
309 SupportsPriorityValueFor::LinkRelPreloadFetch(fetchPriority);
310 if (supportsPriorityValue) {
311 LogPriorityMapping(sPreloadServiceLog, fetchPriority,
312 supportsPriorityValue);
315 preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument,
316 aEarlyHintPreloaderId, supportsPriorityValue);
319 // static
320 void PreloadService::NotifyNodeEvent(nsINode* aNode, bool aSuccess) {
321 if (!aNode->IsInComposedDoc()) {
322 return;
325 // We don't dispatch synchronously since |node| might be in a DocGroup
326 // that we're not allowed to touch. (Our network request happens in the
327 // DocGroup of one of the mSources nodes--not necessarily this one).
329 RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
330 aNode, aSuccess ? u"load"_ns : u"error"_ns, CanBubble::eNo);
332 dispatcher->RequireNodeInDocument();
333 dispatcher->PostDOMEvent();
336 dom::ReferrerPolicy PreloadService::PreloadReferrerPolicy(
337 const nsAString& aReferrerPolicy) {
338 dom::ReferrerPolicy referrerPolicy =
339 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
340 if (referrerPolicy == dom::ReferrerPolicy::_empty) {
341 referrerPolicy = mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
344 return referrerPolicy;
347 nsIURI* PreloadService::BaseURIForPreload() {
348 nsIURI* documentURI = mDocument->GetDocumentURI();
349 nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
350 return (documentURI == documentBaseURI)
351 ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
352 : documentBaseURI;
355 } // namespace mozilla