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"
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
) {
36 lookup
.Data() = aPreload
;
39 lookup
.Insert(aPreload
);
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();
64 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aURL
, encoding
, base
);
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");
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
);
94 if (nsString
* cspNonce
=
95 static_cast<nsString
*>(aLinkElement
->GetProperty(nsGkAtoms::nonce
))) {
99 if (rel
.LowerCaseEqualsASCII("modulepreload")) {
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
);
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");
133 PreloadOrCoalesce(aURI
, aURL
, aPolicyType
, aAs
, aType
, u
""_ns
, aSrcset
,
134 aSizes
, aNonce
, aIntegrity
, aCORS
, aReferrerPolicy
,
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
{
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
) {
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
,
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
));
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
);
222 case dom::SheetPreloadStatus::AlreadyComplete
:
223 return {nullptr, /* already_complete = */ true};
224 case dom::SheetPreloadStatus::Errored
:
225 case dom::SheetPreloadStatus::InProgress
:
228 } else if (aAs
.LowerCaseEqualsASCII("image")) {
229 PreloadImage(uri
, aCORS
, aReferrerPolicy
, isImgSet
, aEarlyHintPreloaderId
,
231 } else if (aAs
.LowerCaseEqualsASCII("font")) {
232 PreloadFont(uri
, aCORS
, aReferrerPolicy
, aEarlyHintPreloaderId
,
234 } else if (aAs
.LowerCaseEqualsASCII("fetch")) {
235 PreloadFetch(uri
, aCORS
, aReferrerPolicy
, aEarlyHintPreloaderId
,
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
,
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
)) {
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
)) {
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
);
320 void PreloadService::NotifyNodeEvent(nsINode
* aNode
, bool aSuccess
) {
321 if (!aNode
->IsInComposedDoc()) {
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
)
355 } // namespace mozilla