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/AsyncEventDispatcher.h"
11 #include "mozilla/dom/HTMLLinkElement.h"
12 #include "mozilla/dom/ScriptLoader.h"
13 #include "mozilla/dom/ReferrerInfo.h"
14 #include "mozilla/Encoding.h"
15 #include "mozilla/FontPreloader.h"
16 #include "mozilla/StaticPrefs_network.h"
17 #include "nsNetUtil.h"
21 PreloadService::PreloadService(dom::Document
* aDoc
) : mDocument(aDoc
) {}
22 PreloadService::~PreloadService() = default;
24 bool PreloadService::RegisterPreload(const PreloadHashKey
& aKey
,
25 PreloaderBase
* aPreload
) {
26 return mPreloads
.WithEntryHandle(aKey
, [&](auto&& lookup
) {
28 lookup
.Data() = aPreload
;
31 lookup
.Insert(aPreload
);
36 void PreloadService::DeregisterPreload(const PreloadHashKey
& aKey
) {
37 mPreloads
.Remove(aKey
);
40 void PreloadService::ClearAllPreloads() { mPreloads
.Clear(); }
42 bool PreloadService::PreloadExists(const PreloadHashKey
& aKey
) {
43 return mPreloads
.Contains(aKey
);
46 already_AddRefed
<PreloaderBase
> PreloadService::LookupPreload(
47 const PreloadHashKey
& aKey
) const {
48 return mPreloads
.Get(aKey
);
51 already_AddRefed
<nsIURI
> PreloadService::GetPreloadURI(const nsAString
& aURL
) {
52 nsIURI
* base
= BaseURIForPreload();
53 auto encoding
= mDocument
->GetDocumentCharacterSet();
56 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aURL
, encoding
, base
);
64 already_AddRefed
<PreloaderBase
> PreloadService::PreloadLinkElement(
65 dom::HTMLLinkElement
* aLinkElement
, nsContentPolicyType aPolicyType
) {
66 if (aPolicyType
== nsIContentPolicy::TYPE_INVALID
) {
67 MOZ_ASSERT_UNREACHABLE("Caller should check");
71 if (!StaticPrefs::network_preload()) {
75 nsAutoString as
, charset
, crossOrigin
, integrity
, referrerPolicy
, rel
, srcset
,
78 nsCOMPtr
<nsIURI
> uri
= aLinkElement
->GetURI();
79 aLinkElement
->GetCharset(charset
);
80 aLinkElement
->GetImageSrcset(srcset
);
81 aLinkElement
->GetImageSizes(sizes
);
82 aLinkElement
->GetHref(url
);
83 aLinkElement
->GetCrossOrigin(crossOrigin
);
84 aLinkElement
->GetIntegrity(integrity
);
85 aLinkElement
->GetReferrerPolicy(referrerPolicy
);
86 aLinkElement
->GetRel(rel
);
88 if (rel
.LowerCaseEqualsASCII("modulepreload")) {
92 aLinkElement
->GetAs(as
);
93 aLinkElement
->GetType(type
);
96 auto result
= PreloadOrCoalesce(uri
, url
, aPolicyType
, as
, type
, charset
,
97 srcset
, sizes
, integrity
, crossOrigin
,
98 referrerPolicy
, /* aFromHeader = */ false, 0);
100 if (!result
.mPreloader
) {
101 NotifyNodeEvent(aLinkElement
, result
.mAlreadyComplete
);
105 result
.mPreloader
->AddLinkPreloadNode(aLinkElement
);
106 return result
.mPreloader
.forget();
109 void PreloadService::PreloadLinkHeader(
110 nsIURI
* aURI
, const nsAString
& aURL
, nsContentPolicyType aPolicyType
,
111 const nsAString
& aAs
, const nsAString
& aType
, const nsAString
& aIntegrity
,
112 const nsAString
& aSrcset
, const nsAString
& aSizes
, const nsAString
& aCORS
,
113 const nsAString
& aReferrerPolicy
, uint64_t aEarlyHintPreloaderId
) {
114 if (aPolicyType
== nsIContentPolicy::TYPE_INVALID
) {
115 MOZ_ASSERT_UNREACHABLE("Caller should check");
119 if (!StaticPrefs::network_preload()) {
123 PreloadOrCoalesce(aURI
, aURL
, aPolicyType
, aAs
, aType
, u
""_ns
, aSrcset
,
124 aSizes
, aIntegrity
, aCORS
, aReferrerPolicy
,
125 /* aFromHeader = */ true, aEarlyHintPreloaderId
);
128 PreloadService::PreloadOrCoalesceResult
PreloadService::PreloadOrCoalesce(
129 nsIURI
* aURI
, const nsAString
& aURL
, nsContentPolicyType aPolicyType
,
130 const nsAString
& aAs
, const nsAString
& aType
, const nsAString
& aCharset
,
131 const nsAString
& aSrcset
, const nsAString
& aSizes
,
132 const nsAString
& aIntegrity
, const nsAString
& aCORS
,
133 const nsAString
& aReferrerPolicy
, bool aFromHeader
,
134 uint64_t aEarlyHintPreloaderId
) {
136 MOZ_ASSERT_UNREACHABLE("Should not pass null nsIURI");
137 return {nullptr, false};
140 bool isImgSet
= false;
141 PreloadHashKey preloadKey
;
142 nsCOMPtr
<nsIURI
> uri
= aURI
;
144 if (aAs
.LowerCaseEqualsASCII("script")) {
145 preloadKey
= PreloadHashKey::CreateAsScript(uri
, aCORS
, aType
);
146 } else if (aAs
.LowerCaseEqualsASCII("style")) {
147 preloadKey
= PreloadHashKey::CreateAsStyle(
148 uri
, mDocument
->NodePrincipal(), dom::Element::StringToCORSMode(aCORS
),
149 css::eAuthorSheetFeatures
/* see Loader::LoadSheet */);
150 } else if (aAs
.LowerCaseEqualsASCII("image")) {
151 uri
= mDocument
->ResolvePreloadImage(BaseURIForPreload(), aURL
, aSrcset
,
154 return {nullptr, false};
157 preloadKey
= PreloadHashKey::CreateAsImage(
158 uri
, mDocument
->NodePrincipal(), dom::Element::StringToCORSMode(aCORS
));
159 } else if (aAs
.LowerCaseEqualsASCII("font")) {
160 preloadKey
= PreloadHashKey::CreateAsFont(
161 uri
, dom::Element::StringToCORSMode(aCORS
));
162 } else if (aAs
.LowerCaseEqualsASCII("fetch")) {
163 preloadKey
= PreloadHashKey::CreateAsFetch(
164 uri
, dom::Element::StringToCORSMode(aCORS
));
166 return {nullptr, false};
169 if (RefPtr
<PreloaderBase
> preload
= LookupPreload(preloadKey
)) {
170 return {std::move(preload
), false};
173 if (aAs
.LowerCaseEqualsASCII("script")) {
174 PreloadScript(uri
, aType
, aCharset
, aCORS
, aReferrerPolicy
, aIntegrity
,
175 true /* isInHead - TODO */, aEarlyHintPreloaderId
);
176 } else if (aAs
.LowerCaseEqualsASCII("style")) {
177 auto status
= mDocument
->PreloadStyle(
178 aURI
, Encoding::ForLabel(aCharset
), aCORS
,
179 PreloadReferrerPolicy(aReferrerPolicy
), aIntegrity
,
180 aFromHeader
? css::StylePreloadKind::FromLinkRelPreloadHeader
181 : css::StylePreloadKind::FromLinkRelPreloadElement
,
182 aEarlyHintPreloaderId
);
184 case dom::SheetPreloadStatus::AlreadyComplete
:
185 return {nullptr, /* already_complete = */ true};
186 case dom::SheetPreloadStatus::Errored
:
187 case dom::SheetPreloadStatus::InProgress
:
190 } else if (aAs
.LowerCaseEqualsASCII("image")) {
191 PreloadImage(uri
, aCORS
, aReferrerPolicy
, isImgSet
, aEarlyHintPreloaderId
);
192 } else if (aAs
.LowerCaseEqualsASCII("font")) {
193 PreloadFont(uri
, aCORS
, aReferrerPolicy
, aEarlyHintPreloaderId
);
194 } else if (aAs
.LowerCaseEqualsASCII("fetch")) {
195 PreloadFetch(uri
, aCORS
, aReferrerPolicy
, aEarlyHintPreloaderId
);
198 RefPtr
<PreloaderBase
> preload
= LookupPreload(preloadKey
);
199 if (preload
&& aEarlyHintPreloaderId
) {
200 preload
->SetForEarlyHints();
203 return {preload
, false};
206 void PreloadService::PreloadScript(nsIURI
* aURI
, const nsAString
& aType
,
207 const nsAString
& aCharset
,
208 const nsAString
& aCrossOrigin
,
209 const nsAString
& aReferrerPolicy
,
210 const nsAString
& aIntegrity
,
211 bool aScriptFromHead
,
212 uint64_t aEarlyHintPreloaderId
) {
213 mDocument
->ScriptLoader()->PreloadURI(
214 aURI
, aCharset
, aType
, aCrossOrigin
, aIntegrity
, aScriptFromHead
, false,
215 false, false, true, PreloadReferrerPolicy(aReferrerPolicy
),
216 aEarlyHintPreloaderId
);
219 void PreloadService::PreloadImage(nsIURI
* aURI
, const nsAString
& aCrossOrigin
,
220 const nsAString
& aImageReferrerPolicy
,
222 uint64_t aEarlyHintPreloaderId
) {
223 mDocument
->PreLoadImage(aURI
, aCrossOrigin
,
224 PreloadReferrerPolicy(aImageReferrerPolicy
),
225 aIsImgSet
, true, aEarlyHintPreloaderId
);
228 void PreloadService::PreloadFont(nsIURI
* aURI
, const nsAString
& aCrossOrigin
,
229 const nsAString
& aReferrerPolicy
,
230 uint64_t aEarlyHintPreloaderId
) {
231 CORSMode cors
= dom::Element::StringToCORSMode(aCrossOrigin
);
232 auto key
= PreloadHashKey::CreateAsFont(aURI
, cors
);
234 if (PreloadExists(key
)) {
238 RefPtr
<FontPreloader
> preloader
= new FontPreloader();
239 dom::ReferrerPolicy referrerPolicy
= PreloadReferrerPolicy(aReferrerPolicy
);
240 preloader
->OpenChannel(key
, aURI
, cors
, referrerPolicy
, mDocument
,
241 aEarlyHintPreloaderId
);
244 void PreloadService::PreloadFetch(nsIURI
* aURI
, const nsAString
& aCrossOrigin
,
245 const nsAString
& aReferrerPolicy
,
246 uint64_t aEarlyHintPreloaderId
) {
247 CORSMode cors
= dom::Element::StringToCORSMode(aCrossOrigin
);
248 auto key
= PreloadHashKey::CreateAsFetch(aURI
, cors
);
250 if (PreloadExists(key
)) {
254 RefPtr
<FetchPreloader
> preloader
= new FetchPreloader();
255 dom::ReferrerPolicy referrerPolicy
= PreloadReferrerPolicy(aReferrerPolicy
);
256 preloader
->OpenChannel(key
, aURI
, cors
, referrerPolicy
, mDocument
,
257 aEarlyHintPreloaderId
);
261 void PreloadService::NotifyNodeEvent(nsINode
* aNode
, bool aSuccess
) {
262 if (!aNode
->IsInComposedDoc()) {
266 // We don't dispatch synchronously since |node| might be in a DocGroup
267 // that we're not allowed to touch. (Our network request happens in the
268 // DocGroup of one of the mSources nodes--not necessarily this one).
270 RefPtr
<AsyncEventDispatcher
> dispatcher
= new AsyncEventDispatcher(
271 aNode
, aSuccess
? u
"load"_ns
: u
"error"_ns
, CanBubble::eNo
);
273 dispatcher
->RequireNodeInDocument();
274 dispatcher
->PostDOMEvent();
277 dom::ReferrerPolicy
PreloadService::PreloadReferrerPolicy(
278 const nsAString
& aReferrerPolicy
) {
279 dom::ReferrerPolicy referrerPolicy
=
280 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy
);
281 if (referrerPolicy
== dom::ReferrerPolicy::_empty
) {
282 referrerPolicy
= mDocument
->GetPreloadReferrerInfo()->ReferrerPolicy();
285 return referrerPolicy
;
288 nsIURI
* PreloadService::BaseURIForPreload() {
289 nsIURI
* documentURI
= mDocument
->GetDocumentURI();
290 nsIURI
* documentBaseURI
= mDocument
->GetDocBaseURI();
291 return (documentURI
== documentBaseURI
)
292 ? (mSpeculationBaseURI
? mSpeculationBaseURI
.get() : documentURI
)
296 } // namespace mozilla