1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* loading of CSS style sheets using the network APIs */
8
9 #include "mozilla/css/Loader.h"
10
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/dom/DocGroup.h"
13 #include "mozilla/dom/SRILogHelper.h"
14 #include "mozilla/IntegerPrintfMacros.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/LoadInfo.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/MemoryReporting.h"
19 #include "mozilla/PreloadHashKey.h"
20 #include "mozilla/ResultExtensions.h"
21 #include "mozilla/SchedulerGroup.h"
22 #include "mozilla/URLPreloader.h"
23 #include "nsIRunnable.h"
24 #include "nsISupportsPriority.h"
25 #include "nsITimedChannel.h"
26 #include "nsICachingChannel.h"
27 #include "nsSyncLoadService.h"
28 #include "nsCOMPtr.h"
29 #include "nsString.h"
30 #include "nsIContent.h"
31 #include "nsIContentInlines.h"
32 #include "nsICookieJarSettings.h"
33 #include "mozilla/dom/Document.h"
34 #include "nsIURI.h"
35 #include "nsNetUtil.h"
36 #include "nsContentUtils.h"
37 #include "nsIScriptSecurityManager.h"
38 #include "nsContentPolicyUtils.h"
39 #include "nsIHttpChannel.h"
40 #include "nsIHttpChannelInternal.h"
41 #include "nsIClassOfService.h"
42 #include "nsIScriptError.h"
43 #include "nsMimeTypes.h"
44 #include "nsICSSLoaderObserver.h"
45 #include "nsThreadUtils.h"
46 #include "nsGkAtoms.h"
47 #include "nsIThreadInternal.h"
48 #include "nsINetworkPredictor.h"
49 #include "nsStringStream.h"
50 #include "mozilla/dom/MediaList.h"
51 #include "mozilla/dom/ShadowRoot.h"
52 #include "mozilla/dom/URL.h"
53 #include "mozilla/net/UrlClassifierFeatureFactory.h"
54 #include "mozilla/AsyncEventDispatcher.h"
55 #include "mozilla/ProfilerLabels.h"
56 #include "mozilla/ServoBindings.h"
57 #include "mozilla/StyleSheet.h"
58 #include "mozilla/StyleSheetInlines.h"
59 #include "mozilla/ConsoleReportCollector.h"
60 #include "mozilla/ServoUtils.h"
61 #include "mozilla/css/StreamLoader.h"
62 #include "mozilla/SharedStyleSheetCache.h"
63 #include "mozilla/StaticPrefs_dom.h"
64 #include "ReferrerInfo.h"
65
66 #ifdef MOZ_XUL
67 # include "nsXULPrototypeCache.h"
68 #endif
69
70 #include "nsError.h"
71
72 #include "mozilla/dom/SRICheck.h"
73
74 #include "mozilla/Encoding.h"
75
76 using namespace mozilla::dom;
77
78 // 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
79 #define SNIFFING_BUFFER_SIZE 1024
80
81 /**
82 * OVERALL ARCHITECTURE
83 *
84 * The CSS Loader gets requests to load various sorts of style sheets:
85 * inline style from <style> elements, linked style, @import-ed child
86 * sheets, non-document sheets. The loader handles the following tasks:
87 * 1) Creation of the actual style sheet objects: CreateSheet()
88 * 2) setting of the right media, title, enabled state, etc on the
89 * sheet: PrepareSheet()
90 * 3) Insertion of the sheet in the proper cascade order:
91 * InsertSheetInTree() and InsertChildSheet()
92 * 4) Load of the sheet: LoadSheet() including security checks
93 * 5) Parsing of the sheet: ParseSheet()
94 * 6) Cleanup: SheetComplete()
95 *
96 * The detailed documentation for these functions is found with the
97 * function implementations.
98 *
99 * The following helper object is used:
100 * SheetLoadData -- a small class that is used to store all the
101 * information needed for the loading of a sheet;
102 * this class handles listening for the stream
103 * loader completion and also handles charset
104 * determination.
105 */
106
107 extern mozilla::LazyLogModule sCssLoaderLog;
108 mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
109
110 static mozilla::LazyLogModule gSriPRLog("SRI");
111
112 #define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
113 #define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
114 #define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
115 #define LOG(args) LOG_DEBUG(args)
116
117 #define LOG_ERROR_ENABLED() \
118 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
119 #define LOG_WARN_ENABLED() \
120 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
121 #define LOG_DEBUG_ENABLED() \
122 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
123 #define LOG_ENABLED() LOG_DEBUG_ENABLED()
124
125 #define LOG_URI(format, uri) \
126 PR_BEGIN_MACRO \
127 NS_ASSERTION(uri, "Logging null uri"); \
128 if (LOG_ENABLED()) { \
129 LOG((format, uri->GetSpecOrDefault().get())); \
130 } \
131 PR_END_MACRO
132
133 // And some convenience strings...
134 static const char* const gStateStrings[] = {"NeedsParser", "Pending", "Loading",
135 "Complete"};
136
137 namespace mozilla {
138
SheetLoadDataHashKey(const css::SheetLoadData & aLoadData)139 SheetLoadDataHashKey::SheetLoadDataHashKey(const css::SheetLoadData& aLoadData)
140 : mURI(aLoadData.mURI),
141 mPrincipal(aLoadData.mTriggeringPrincipal),
142 mLoaderPrincipal(aLoadData.mLoader->LoaderPrincipal()),
143 mPartitionPrincipal(aLoadData.mLoader->PartitionedPrincipal()),
144 mEncodingGuess(aLoadData.mGuessedEncoding),
145 mCORSMode(aLoadData.mSheet->GetCORSMode()),
146 mParsingMode(aLoadData.mSheet->ParsingMode()),
147 mCompatMode(aLoadData.mCompatMode),
148 mIsLinkRelPreload(aLoadData.IsLinkRelPreload()) {
149 MOZ_COUNT_CTOR(SheetLoadDataHashKey);
150 MOZ_ASSERT(mURI);
151 MOZ_ASSERT(mPrincipal);
152 MOZ_ASSERT(mLoaderPrincipal);
153 MOZ_ASSERT(mPartitionPrincipal);
154 aLoadData.mSheet->GetIntegrity(mSRIMetadata);
155 }
156
KeyEquals(const SheetLoadDataHashKey & aKey) const157 bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
158 {
159 bool eq;
160 if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
161 return false;
162 }
163 }
164
165 LOG_URI("KeyEquals(%s)\n", mURI);
166
167 if (!mPrincipal->Equals(aKey.mPrincipal)) {
168 LOG((" > Principal mismatch\n"));
169 return false;
170 }
171
172 // We only check for partition principal equality if any of the loads are
173 // triggered by a document rather than e.g. an extension (which have different
174 // origins than the loader principal).
175 if (mPrincipal->Equals(mLoaderPrincipal) ||
176 aKey.mPrincipal->Equals(aKey.mLoaderPrincipal)) {
177 if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
178 LOG((" > Partition principal mismatch\n"));
179 return false;
180 }
181 }
182
183 if (mCORSMode != aKey.mCORSMode) {
184 LOG((" > CORS mismatch\n"));
185 return false;
186 }
187
188 if (mParsingMode != aKey.mParsingMode) {
189 LOG((" > Parsing mode mismatch\n"));
190 return false;
191 }
192
193 if (mCompatMode != aKey.mCompatMode) {
194 LOG((" > Quirks mismatch\n"));
195 return false;
196 }
197
198 // If encoding differs, then don't reuse the cache.
199 //
200 // TODO(emilio): When the encoding is determined from the request (either
201 // BOM or Content-Length or @charset), we could do a bit better,
202 // theoretically.
203 if (mEncodingGuess != aKey.mEncodingGuess) {
204 LOG((" > Encoding guess mismatch\n"));
205 return false;
206 }
207
208 // Consuming stylesheet tags must never coalesce to <link preload> initiated
209 // speculative loads with a weaker SRI hash or its different value. This
210 // check makes sure that regular loads will never find such a weaker preload
211 // and rather start a new, independent load with new, stronger SRI checker
212 // set up, so that integrity is ensured.
213 if (mIsLinkRelPreload != aKey.mIsLinkRelPreload) {
214 const auto& linkPreloadMetadata =
215 mIsLinkRelPreload ? mSRIMetadata : aKey.mSRIMetadata;
216 const auto& consumerPreloadMetadata =
217 mIsLinkRelPreload ? aKey.mSRIMetadata : mSRIMetadata;
218
219 if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
220 LOG((" > Preload SRI metadata mismatch\n"));
221 return false;
222 }
223 }
224
225 return true;
226 }
227
228 namespace css {
229
GetFallbackEncoding(Loader & aLoader,nsINode * aOwningNode,const Encoding * aPreloadOrParentDataEncoding)230 static NotNull<const Encoding*> GetFallbackEncoding(
231 Loader& aLoader, nsINode* aOwningNode,
232 const Encoding* aPreloadOrParentDataEncoding) {
233 const Encoding* encoding;
234 // Now try the charset on the <link> or processing instruction
235 // that loaded us
236 if (aOwningNode) {
237 nsAutoString label16;
238 LinkStyle::FromNode(*aOwningNode)->GetCharset(label16);
239 encoding = Encoding::ForLabel(label16);
240 if (encoding) {
241 return WrapNotNull(encoding);
242 }
243 }
244
245 // Try preload or parent sheet encoding.
246 if (aPreloadOrParentDataEncoding) {
247 return WrapNotNull(aPreloadOrParentDataEncoding);
248 }
249
250 if (auto* doc = aLoader.GetDocument()) {
251 // Use the document charset.
252 return doc->GetDocumentCharacterSet();
253 }
254
255 return UTF_8_ENCODING;
256 }
257
258 /********************************
259 * SheetLoadData implementation *
260 ********************************/
NS_IMPL_ISUPPORTS(SheetLoadData,nsIRunnable,nsIThreadObserver)261 NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunnable, nsIThreadObserver)
262
263 SheetLoadData::SheetLoadData(
264 Loader* aLoader, const nsAString& aTitle, nsIURI* aURI, StyleSheet* aSheet,
265 bool aSyncLoad, nsINode* aOwningNode, IsAlternate aIsAlternate,
266 MediaMatched aMediaMatches, StylePreloadKind aPreloadKind,
267 nsICSSLoaderObserver* aObserver, nsIPrincipal* aTriggeringPrincipal,
268 nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode)
269 : mLoader(aLoader),
270 mTitle(aTitle),
271 mEncoding(nullptr),
272 mURI(aURI),
273 mLineNumber(1),
274 mSheet(aSheet),
275 mNext(nullptr),
276 mPendingChildren(0),
277 mSyncLoad(aSyncLoad),
278 mIsNonDocumentSheet(false),
279 mIsChildSheet(aSheet->GetParentSheet()),
280 mIsLoading(false),
281 mIsBeingParsed(false),
282 mIsCancelled(false),
283 mMustNotify(false),
284 mWasAlternate(aIsAlternate == IsAlternate::Yes),
285 mMediaMatched(aMediaMatches == MediaMatched::Yes),
286 mUseSystemPrincipal(false),
287 mSheetAlreadyComplete(false),
288 mIsCrossOriginNoCORS(false),
289 mBlockResourceTiming(false),
290 mLoadFailed(false),
291 mPreloadKind(aPreloadKind),
292 mOwningNode(aOwningNode),
293 mObserver(aObserver),
294 mTriggeringPrincipal(aTriggeringPrincipal),
295 mReferrerInfo(aReferrerInfo),
296 mRequestingNode(aRequestingNode),
297 mGuessedEncoding(GetFallbackEncoding(*aLoader, aOwningNode, nullptr)),
298 mCompatMode(aLoader->CompatMode(aPreloadKind)) {
299 MOZ_ASSERT(!mOwningNode || dom::LinkStyle::FromNode(*mOwningNode),
300 "Must implement LinkStyle");
301 MOZ_ASSERT(mTriggeringPrincipal);
302 MOZ_ASSERT(mLoader, "Must have a loader!");
303 }
304
SheetLoadData(Loader * aLoader,nsIURI * aURI,StyleSheet * aSheet,SheetLoadData * aParentData,nsICSSLoaderObserver * aObserver,nsIPrincipal * aTriggeringPrincipal,nsIReferrerInfo * aReferrerInfo,nsINode * aRequestingNode)305 SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
306 SheetLoadData* aParentData,
307 nsICSSLoaderObserver* aObserver,
308 nsIPrincipal* aTriggeringPrincipal,
309 nsIReferrerInfo* aReferrerInfo,
310 nsINode* aRequestingNode)
311 : mLoader(aLoader),
312 mEncoding(nullptr),
313 mURI(aURI),
314 mLineNumber(1),
315 mSheet(aSheet),
316 mNext(nullptr),
317 mParentData(aParentData),
318 mPendingChildren(0),
319 mSyncLoad(aParentData && aParentData->mSyncLoad),
320 mIsNonDocumentSheet(aParentData && aParentData->mIsNonDocumentSheet),
321 mIsChildSheet(aSheet->GetParentSheet()),
322 mIsLoading(false),
323 mIsBeingParsed(false),
324 mIsCancelled(false),
325 mMustNotify(false),
326 mWasAlternate(false),
327 mMediaMatched(true),
328 mUseSystemPrincipal(aParentData && aParentData->mUseSystemPrincipal),
329 mSheetAlreadyComplete(false),
330 mIsCrossOriginNoCORS(false),
331 mBlockResourceTiming(false),
332 mLoadFailed(false),
333 mPreloadKind(StylePreloadKind::None),
334 mOwningNode(nullptr),
335 mObserver(aObserver),
336 mTriggeringPrincipal(aTriggeringPrincipal),
337 mReferrerInfo(aReferrerInfo),
338 mRequestingNode(aRequestingNode),
339 mGuessedEncoding(GetFallbackEncoding(
340 *aLoader, nullptr, aParentData ? aParentData->mEncoding : nullptr)),
341 mCompatMode(aLoader->CompatMode(mPreloadKind)) {
342 MOZ_ASSERT(mLoader, "Must have a loader!");
343 MOZ_ASSERT(mTriggeringPrincipal);
344 MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
345 "Shouldn't use system principal for async loads");
346 MOZ_ASSERT_IF(aParentData, mIsChildSheet);
347 }
348
SheetLoadData(Loader * aLoader,nsIURI * aURI,StyleSheet * aSheet,bool aSyncLoad,UseSystemPrincipal aUseSystemPrincipal,StylePreloadKind aPreloadKind,const Encoding * aPreloadEncoding,nsICSSLoaderObserver * aObserver,nsIPrincipal * aTriggeringPrincipal,nsIReferrerInfo * aReferrerInfo,nsINode * aRequestingNode)349 SheetLoadData::SheetLoadData(
350 Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet, bool aSyncLoad,
351 UseSystemPrincipal aUseSystemPrincipal, StylePreloadKind aPreloadKind,
352 const Encoding* aPreloadEncoding, nsICSSLoaderObserver* aObserver,
353 nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
354 nsINode* aRequestingNode)
355 : mLoader(aLoader),
356 mEncoding(nullptr),
357 mURI(aURI),
358 mLineNumber(1),
359 mSheet(aSheet),
360 mNext(nullptr),
361 mPendingChildren(0),
362 mSyncLoad(aSyncLoad),
363 mIsNonDocumentSheet(true),
364 mIsChildSheet(false),
365 mIsLoading(false),
366 mIsBeingParsed(false),
367 mIsCancelled(false),
368 mMustNotify(false),
369 mWasAlternate(false),
370 mMediaMatched(true),
371 mUseSystemPrincipal(aUseSystemPrincipal == UseSystemPrincipal::Yes),
372 mSheetAlreadyComplete(false),
373 mIsCrossOriginNoCORS(false),
374 mBlockResourceTiming(false),
375 mLoadFailed(false),
376 mPreloadKind(aPreloadKind),
377 mOwningNode(nullptr),
378 mObserver(aObserver),
379 mTriggeringPrincipal(aTriggeringPrincipal),
380 mReferrerInfo(aReferrerInfo),
381 mRequestingNode(aRequestingNode),
382 mGuessedEncoding(
383 GetFallbackEncoding(*aLoader, nullptr, aPreloadEncoding)),
384 mCompatMode(aLoader->CompatMode(aPreloadKind)) {
385 MOZ_ASSERT(mTriggeringPrincipal);
386 MOZ_ASSERT(mLoader, "Must have a loader!");
387 MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
388 "Shouldn't use system principal for async loads");
389 MOZ_ASSERT(!aSheet->GetParentSheet(), "Shouldn't be used for child loads");
390 }
391
~SheetLoadData()392 SheetLoadData::~SheetLoadData() {
393 MOZ_DIAGNOSTIC_ASSERT(mSheetCompleteCalled || mIntentionallyDropped,
394 "Should always call SheetComplete, except when "
395 "dropping the load");
396
397 // Do this iteratively to avoid blowing up the stack.
398 RefPtr<SheetLoadData> next = std::move(mNext);
399 while (next) {
400 next = std::move(next->mNext);
401 }
402 }
403
404 NS_IMETHODIMP
Run()405 SheetLoadData::Run() {
406 mLoader->HandleLoadEvent(*this);
407 return NS_OK;
408 }
409
410 NS_IMETHODIMP
OnDispatchedEvent()411 SheetLoadData::OnDispatchedEvent() { return NS_OK; }
412
413 NS_IMETHODIMP
OnProcessNextEvent(nsIThreadInternal * aThread,bool aMayWait)414 SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
415 // XXXkhuey this is insane!
416 // We want to fire our load even before or after event processing,
417 // whichever comes first.
418 FireLoadEvent(aThread);
419 return NS_OK;
420 }
421
422 NS_IMETHODIMP
AfterProcessNextEvent(nsIThreadInternal * aThread,bool aEventWasProcessed)423 SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
424 bool aEventWasProcessed) {
425 // XXXkhuey this too!
426 // We want to fire our load even before or after event processing,
427 // whichever comes first.
428 FireLoadEvent(aThread);
429 return NS_OK;
430 }
431
PrioritizeAsPreload(nsIChannel * aChannel)432 void SheetLoadData::PrioritizeAsPreload(nsIChannel* aChannel) {
433 if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
434 sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
435 }
436 }
437
PrioritizeAsPreload()438 void SheetLoadData::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
439
FireLoadEvent(nsIThreadInternal * aThread)440 void SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread) {
441 // First remove ourselves as a thread observer. But we need to keep
442 // ourselves alive while doing that!
443 RefPtr<SheetLoadData> kungFuDeathGrip(this);
444 aThread->RemoveObserver(this);
445
446 // Now fire the event.
447 //
448 // NOTE(emilio): A bit weird that we fire the event even if the node is no
449 // longer in the tree, or the sheet that just loaded / errored is not the
450 // current node.sheet, but...
451 nsCOMPtr<nsINode> node = mOwningNode;
452 MOZ_ASSERT(node, "How did that happen???");
453
454 nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(), node,
455 mLoadFailed ? u"error"_ns : u"load"_ns,
456 CanBubble::eNo, Cancelable::eNo);
457
458 MOZ_ASSERT(BlocksLoadEvent());
459 mLoader->UnblockOnload(true);
460 }
461
ScheduleLoadEventIfNeeded()462 void SheetLoadData::ScheduleLoadEventIfNeeded() {
463 if (!mOwningNode) {
464 return;
465 }
466
467 MOZ_ASSERT(BlocksLoadEvent(), "The rel=preload load event happens elsewhere");
468
469 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
470 nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
471 if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
472 mLoader->BlockOnload();
473 }
474 }
475
476 /*********************
477 * Style sheet reuse *
478 *********************/
479
FindReusableStyleSheet(nsIURI * aURL,RefPtr<StyleSheet> & aResult)480 bool LoaderReusableStyleSheets::FindReusableStyleSheet(
481 nsIURI* aURL, RefPtr<StyleSheet>& aResult) {
482 MOZ_ASSERT(aURL);
483 for (size_t i = mReusableSheets.Length(); i > 0; --i) {
484 size_t index = i - 1;
485 bool sameURI;
486 MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
487 nsresult rv =
488 aURL->Equals(mReusableSheets[index]->GetOriginalURI(), &sameURI);
489 if (!NS_FAILED(rv) && sameURI) {
490 aResult = mReusableSheets[index];
491 mReusableSheets.RemoveElementAt(index);
492 return true;
493 }
494 }
495 return false;
496 }
497 /*************************
498 * Loader Implementation *
499 *************************/
500
Loader()501 Loader::Loader()
502 : mDocument(nullptr),
503 mDocumentCompatMode(eCompatibility_FullStandards),
504 mReporter(new ConsoleReportCollector()) {}
505
Loader(DocGroup * aDocGroup)506 Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }
507
Loader(Document * aDocument)508 Loader::Loader(Document* aDocument) : Loader() {
509 MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
510 mDocument = aDocument;
511 mDocumentCompatMode = aDocument->GetCompatibilityMode();
512 mSheets = SharedStyleSheetCache::Get();
513 RegisterInSheetCache();
514 }
515
~Loader()516 Loader::~Loader() {
517 // Note: no real need to revoke our stylesheet loaded events -- they
518 // hold strong references to us, so if we're going away that means
519 // they're all done.
520 }
521
RegisterInSheetCache()522 void Loader::RegisterInSheetCache() {
523 MOZ_ASSERT(mDocument);
524 MOZ_ASSERT(mSheets);
525
526 mSheets->RegisterLoader(*this);
527 }
528
DeregisterFromSheetCache()529 void Loader::DeregisterFromSheetCache() {
530 MOZ_ASSERT(mDocument);
531 MOZ_ASSERT(mSheets);
532
533 mSheets->CancelLoadsForLoader(*this);
534 mSheets->UnregisterLoader(*this);
535 }
536
DropDocumentReference()537 void Loader::DropDocumentReference() {
538 // Flush out pending datas just so we don't leak by accident.
539 if (mSheets) {
540 DeregisterFromSheetCache();
541 }
542 mDocument = nullptr;
543 }
544
DocumentStyleSheetSetChanged()545 void Loader::DocumentStyleSheetSetChanged() {
546 MOZ_ASSERT(mDocument);
547
548 // start any pending alternates that aren't alternates anymore
549 mSheets->StartDeferredLoadsForLoader(
550 *this, SharedStyleSheetCache::StartLoads::IfNonAlternate);
551 }
552
553 static const char kCharsetSym[] = "@charset \"";
554
GetCharsetFromData(const char * aStyleSheetData,uint32_t aDataLength,nsACString & aCharset)555 static bool GetCharsetFromData(const char* aStyleSheetData,
556 uint32_t aDataLength, nsACString& aCharset) {
557 aCharset.Truncate();
558 if (aDataLength <= sizeof(kCharsetSym) - 1) return false;
559
560 if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
561 return false;
562 }
563
564 for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
565 char c = aStyleSheetData[i];
566 if (c == '"') {
567 ++i;
568 if (i < aDataLength && aStyleSheetData[i] == ';') {
569 return true;
570 }
571 // fail
572 break;
573 }
574 aCharset.Append(c);
575 }
576
577 // Did not see end quote or semicolon
578 aCharset.Truncate();
579 return false;
580 }
581
DetermineNonBOMEncoding(const nsACString & aSegment,nsIChannel * aChannel) const582 NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
583 const nsACString& aSegment, nsIChannel* aChannel) const {
584 const Encoding* encoding;
585 nsAutoCString label;
586
587 // Check HTTP
588 if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
589 encoding = Encoding::ForLabel(label);
590 if (encoding) {
591 return WrapNotNull(encoding);
592 }
593 }
594
595 // Check @charset
596 auto sniffingLength = aSegment.Length();
597 if (sniffingLength > SNIFFING_BUFFER_SIZE) {
598 sniffingLength = SNIFFING_BUFFER_SIZE;
599 }
600 if (GetCharsetFromData(aSegment.BeginReading(), sniffingLength, label)) {
601 encoding = Encoding::ForLabel(label);
602 if (encoding == UTF_16BE_ENCODING || encoding == UTF_16LE_ENCODING) {
603 return UTF_8_ENCODING;
604 }
605 if (encoding) {
606 return WrapNotNull(encoding);
607 }
608 }
609 return mGuessedEncoding;
610 }
611
VerifySheetIntegrity(const SRIMetadata & aMetadata,nsIChannel * aChannel,const nsACString & aFirst,const nsACString & aSecond,const nsACString & aSourceFileURI,nsIConsoleReportCollector * aReporter)612 static nsresult VerifySheetIntegrity(const SRIMetadata& aMetadata,
613 nsIChannel* aChannel,
614 const nsACString& aFirst,
615 const nsACString& aSecond,
616 const nsACString& aSourceFileURI,
617 nsIConsoleReportCollector* aReporter) {
618 NS_ENSURE_ARG_POINTER(aReporter);
619
620 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), LogLevel::Debug)) {
621 nsAutoCString requestURL;
622 nsCOMPtr<nsIURI> originalURI;
623 if (aChannel &&
624 NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
625 originalURI) {
626 originalURI->GetAsciiSpec(requestURL);
627 }
628 MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
629 ("VerifySheetIntegrity (unichar stream)"));
630 }
631
632 SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
633 nsresult rv =
634 verifier.Update(aFirst.Length(), (const uint8_t*)aFirst.BeginReading());
635 NS_ENSURE_SUCCESS(rv, rv);
636 rv =
637 verifier.Update(aSecond.Length(), (const uint8_t*)aSecond.BeginReading());
638 NS_ENSURE_SUCCESS(rv, rv);
639
640 return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
641 }
642
AllLoadsCanceled(const SheetLoadData & aData)643 static bool AllLoadsCanceled(const SheetLoadData& aData) {
644 const SheetLoadData* data = &aData;
645 do {
646 if (!data->mIsCancelled) {
647 return false;
648 }
649 } while ((data = data->mNext));
650 return true;
651 }
652
653 /*
654 * Stream completion code shared by Stylo and the old style system.
655 *
656 * Here we need to check that the load did not give us an http error
657 * page and check the mimetype on the channel to make sure we're not
658 * loading non-text/css data in standards mode.
659 */
VerifySheetReadyToParse(nsresult aStatus,const nsACString & aBytes1,const nsACString & aBytes2,nsIChannel * aChannel)660 nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
661 const nsACString& aBytes1,
662 const nsACString& aBytes2,
663 nsIChannel* aChannel) {
664 LOG(("SheetLoadData::VerifySheetReadyToParse"));
665 NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
666
667 if (AllLoadsCanceled(*this)) {
668 LOG_WARN((" All loads are canceled, dropping"));
669 mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
670 return NS_OK;
671 }
672
673 if (NS_FAILED(aStatus)) {
674 LOG_WARN(
675 (" Load failed: status 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
676 // Handle sheet not loading error because source was a tracking URL (or
677 // fingerprinting, cryptomining, etc).
678 // We make a note of this sheet node by including it in a dedicated
679 // array of blocked tracking nodes under its parent document.
680 //
681 // Multiple sheet load instances might be tied to this request,
682 // we annotate each one linked to a valid owning element (node).
683 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
684 aStatus)) {
685 if (Document* doc = mLoader->GetDocument()) {
686 for (SheetLoadData* data = this; data; data = data->mNext) {
687 // mOwningNode may be null but AddBlockTrackingNode can cope
688 doc->AddBlockedNodeByClassifier(
689 nsIContent::FromNodeOrNull(data->mOwningNode));
690 }
691 }
692 }
693 mLoader->SheetComplete(*this, aStatus);
694 return NS_OK;
695 }
696
697 if (!aChannel) {
698 mLoader->SheetComplete(*this, NS_OK);
699 return NS_OK;
700 }
701
702 nsCOMPtr<nsIURI> originalURI;
703 aChannel->GetOriginalURI(getter_AddRefs(originalURI));
704
705 // If the channel's original URI is "chrome:", we want that, since
706 // the observer code in nsXULPrototypeCache depends on chrome stylesheets
707 // having a chrome URI. (Whether or not chrome stylesheets come through
708 // this codepath seems nondeterministic.)
709 // Otherwise we want the potentially-HTTP-redirected URI.
710 nsCOMPtr<nsIURI> channelURI;
711 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
712
713 if (!channelURI || !originalURI) {
714 NS_ERROR("Someone just violated the nsIRequest contract");
715 LOG_WARN((" Channel without a URI. Bad!"));
716 mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
717 return NS_OK;
718 }
719
720 nsCOMPtr<nsIPrincipal> principal;
721 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
722 nsresult result = NS_ERROR_NOT_AVAILABLE;
723 if (secMan) { // Could be null if we already shut down
724 if (mUseSystemPrincipal) {
725 result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
726 } else {
727 result = secMan->GetChannelResultPrincipal(aChannel,
728 getter_AddRefs(principal));
729 }
730 }
731
732 if (NS_FAILED(result)) {
733 LOG_WARN((" Couldn't get principal"));
734 mLoader->SheetComplete(*this, result);
735 return NS_OK;
736 }
737
738 mSheet->SetPrincipal(principal);
739
740 if (mSheet->GetCORSMode() == CORS_NONE &&
741 !mTriggeringPrincipal->Subsumes(principal)) {
742 mIsCrossOriginNoCORS = true;
743 }
744
745 // If it's an HTTP channel, we want to make sure this is not an
746 // error document we got.
747 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
748 bool requestSucceeded;
749 result = httpChannel->GetRequestSucceeded(&requestSucceeded);
750 if (NS_SUCCEEDED(result) && !requestSucceeded) {
751 LOG((" Load returned an error page"));
752 mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
753 return NS_OK;
754 }
755
756 nsAutoCString sourceMapURL;
757 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
758 mSheet->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL));
759 }
760 }
761
762 nsAutoCString contentType;
763 aChannel->GetContentType(contentType);
764
765 // In standards mode, a style sheet must have one of these MIME
766 // types to be processed at all. In quirks mode, we accept any
767 // MIME type, but only if the style sheet is same-origin with the
768 // requesting document or parent sheet. See bug 524223.
769
770 bool validType = contentType.EqualsLiteral("text/css") ||
771 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
772 contentType.IsEmpty();
773
774 if (!validType) {
775 const char* errorMessage;
776 uint32_t errorFlag;
777 bool sameOrigin = true;
778
779 bool subsumed;
780 result = mTriggeringPrincipal->Subsumes(principal, &subsumed);
781 if (NS_FAILED(result) || !subsumed) {
782 sameOrigin = false;
783 }
784
785 if (sameOrigin && mCompatMode == eCompatibility_NavQuirks) {
786 errorMessage = "MimeNotCssWarn";
787 errorFlag = nsIScriptError::warningFlag;
788 } else {
789 errorMessage = "MimeNotCss";
790 errorFlag = nsIScriptError::errorFlag;
791 }
792
793 AutoTArray<nsString, 2> strings;
794 CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
795 CopyASCIItoUTF16(contentType, *strings.AppendElement());
796
797 nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
798 nsContentUtils::ReportToConsole(
799 errorFlag, "CSS Loader"_ns, mLoader->mDocument,
800 nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);
801
802 if (errorFlag == nsIScriptError::errorFlag) {
803 LOG_WARN(
804 (" Ignoring sheet with improper MIME type %s", contentType.get()));
805 mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
806 return NS_OK;
807 }
808 }
809
810 SRIMetadata sriMetadata;
811 mSheet->GetIntegrity(sriMetadata);
812 if (!sriMetadata.IsEmpty()) {
813 nsAutoCString sourceUri;
814 if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
815 mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
816 }
817 nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
818 sourceUri, mLoader->mReporter);
819
820 nsCOMPtr<nsILoadGroup> loadGroup;
821 aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
822 if (loadGroup) {
823 mLoader->mReporter->FlushConsoleReports(loadGroup);
824 } else {
825 mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
826 }
827
828 if (NS_FAILED(rv)) {
829 LOG((" Load was blocked by SRI"));
830 MOZ_LOG(gSriPRLog, LogLevel::Debug,
831 ("css::Loader::OnStreamComplete, bad metadata"));
832 mLoader->SheetComplete(*this, NS_ERROR_SRI_CORRUPT);
833 return NS_OK;
834 }
835 }
836
837 // Enough to set the URIs on mSheet, since any sibling datas we have share
838 // the same mInner as mSheet and will thus get the same URI.
839 mSheet->SetURIs(channelURI, originalURI, channelURI);
840
841 ReferrerPolicy policy =
842 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
843 nsCOMPtr<nsIReferrerInfo> referrerInfo =
844 ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);
845
846 mSheet->SetReferrerInfo(referrerInfo);
847 return NS_OK_PARSE_SHEET;
848 }
849
IsAlternateSheet(const nsAString & aTitle,bool aHasAlternateRel)850 Loader::IsAlternate Loader::IsAlternateSheet(const nsAString& aTitle,
851 bool aHasAlternateRel) {
852 // A sheet is alternate if it has a nonempty title that doesn't match the
853 // currently selected style set. But if there _is_ no currently selected
854 // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
855 // is nonempty, we should select the style set corresponding to aTitle, since
856 // that's a preferred sheet.
857 if (aTitle.IsEmpty()) {
858 return IsAlternate::No;
859 }
860
861 if (mDocument) {
862 const nsString& currentSheetSet = mDocument->GetCurrentStyleSheetSet();
863 if (!aHasAlternateRel && currentSheetSet.IsEmpty()) {
864 // There's no preferred set yet, and we now have a sheet with a title.
865 // Make that be the preferred set.
866 // FIXME(emilio): This is kinda wild, can we do it somewhere else?
867 mDocument->SetPreferredStyleSheetSet(aTitle);
868 // We're definitely not an alternate. Also, beware that at this point
869 // currentSheetSet may dangle.
870 return IsAlternate::No;
871 }
872
873 if (aTitle.Equals(currentSheetSet)) {
874 return IsAlternate::No;
875 }
876 }
877
878 return IsAlternate::Yes;
879 }
880
CheckContentPolicy(nsIPrincipal * aLoadingPrincipal,nsIPrincipal * aTriggeringPrincipal,nsIURI * aTargetURI,nsINode * aRequestingNode,const nsAString & aNonce,StylePreloadKind aPreloadKind)881 nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
882 nsIPrincipal* aTriggeringPrincipal,
883 nsIURI* aTargetURI,
884 nsINode* aRequestingNode,
885 const nsAString& aNonce,
886 StylePreloadKind aPreloadKind) {
887 // When performing a system load don't consult content policies.
888 if (!mDocument) {
889 return NS_OK;
890 }
891
892 nsContentPolicyType contentPolicyType =
893 aPreloadKind == StylePreloadKind::None
894 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
895 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
896
897 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
898 aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
899 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
900
901 // snapshot the nonce at load start time for performing CSP checks
902 if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
903 secCheckLoadInfo->SetCspNonce(aNonce);
904 MOZ_ASSERT_IF(aPreloadKind != StylePreloadKind::None, aNonce.IsEmpty());
905 }
906
907 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
908 nsresult rv = NS_CheckContentLoadPolicy(aTargetURI, secCheckLoadInfo,
909 "text/css"_ns, &shouldLoad,
910 nsContentUtils::GetContentPolicy());
911 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
912 // Asynchronously notify observers (e.g devtools) of CSP failure.
913 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
914 "Loader::NotifyOnFailedCheckPolicy",
915 [targetURI = RefPtr<nsIURI>(aTargetURI),
916 requestingNode = RefPtr<nsINode>(aRequestingNode),
917 contentPolicyType]() {
918 nsCOMPtr<nsIChannel> channel;
919 NS_NewChannel(
920 getter_AddRefs(channel), targetURI, requestingNode,
921 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
922 contentPolicyType);
923 NS_SetRequestBlockingReason(
924 channel, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
925 nsCOMPtr<nsIObserverService> obsService =
926 services::GetObserverService();
927 if (obsService) {
928 obsService->NotifyObservers(
929 channel, "http-on-failed-opening-request", nullptr);
930 }
931 }));
932 return NS_ERROR_CONTENT_BLOCKED;
933 }
934 return NS_OK;
935 }
936
RecordUseCountersIfNeeded(Document * aDoc,const StyleUseCounters * aCounters)937 static void RecordUseCountersIfNeeded(Document* aDoc,
938 const StyleUseCounters* aCounters) {
939 if (!aDoc || !aCounters) {
940 return;
941 }
942 const StyleUseCounters* docCounters = aDoc->GetStyleUseCounters();
943 if (!docCounters) {
944 return;
945 }
946 Servo_UseCounters_Merge(docCounters, aCounters);
947 aDoc->MaybeWarnAboutZoom();
948 }
949
DidHitCompleteSheetCache(const SheetLoadDataHashKey & aKey,const StyleUseCounters * aCounters)950 void Loader::DidHitCompleteSheetCache(const SheetLoadDataHashKey& aKey,
951 const StyleUseCounters* aCounters) {
952 MOZ_ASSERT(mDocument);
953 if (mLoadsPerformed.EnsureInserted(aKey)) {
954 RecordUseCountersIfNeeded(mDocument, aCounters);
955 }
956 }
957
958 /**
959 * CreateSheet() creates a StyleSheet object for the given URI.
960 *
961 * We check for an existing style sheet object for that uri in various caches
962 * and clone it if we find it. Cloned sheets will have the title/media/enabled
963 * state of the sheet they are clones off; make sure to call PrepareSheet() on
964 * the result of CreateSheet().
965 */
CreateSheet(nsIURI * aURI,nsIContent * aLinkingContent,nsIPrincipal * aTriggeringPrincipal,css::SheetParsingMode aParsingMode,CORSMode aCORSMode,const Encoding * aPreloadOrParentDataEncoding,const nsAString & aIntegrity,bool aSyncLoad,StylePreloadKind aPreloadKind)966 std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
967 nsIURI* aURI, nsIContent* aLinkingContent,
968 nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode aParsingMode,
969 CORSMode aCORSMode, const Encoding* aPreloadOrParentDataEncoding,
970 const nsAString& aIntegrity, bool aSyncLoad,
971 StylePreloadKind aPreloadKind) {
972 MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
973 LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
974
975 SRIMetadata sriMetadata;
976 if (!aIntegrity.IsEmpty()) {
977 MOZ_LOG(gSriPRLog, LogLevel::Debug,
978 ("css::Loader::CreateSheet, integrity=%s",
979 NS_ConvertUTF16toUTF8(aIntegrity).get()));
980 nsAutoCString sourceUri;
981 if (mDocument && mDocument->GetDocumentURI()) {
982 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
983 }
984 SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
985 }
986
987 if (mSheets) {
988 SheetLoadDataHashKey key(aURI, aTriggeringPrincipal, LoaderPrincipal(),
989 PartitionedPrincipal(),
990 GetFallbackEncoding(*this, aLinkingContent,
991 aPreloadOrParentDataEncoding),
992 aCORSMode, aParsingMode, CompatMode(aPreloadKind),
993 sriMetadata, aPreloadKind);
994 auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
995 if (const auto& [styleSheet, sheetState] = cacheResult; styleSheet) {
996 LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
997 return cacheResult;
998 }
999 }
1000
1001 nsIURI* sheetURI = aURI;
1002 nsIURI* baseURI = aURI;
1003 nsIURI* originalURI = aURI;
1004
1005 auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
1006 sheet->SetURIs(sheetURI, originalURI, baseURI);
1007 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1008 ReferrerInfo::CreateForExternalCSSResources(sheet);
1009 sheet->SetReferrerInfo(referrerInfo);
1010 LOG((" Needs parser"));
1011 return {std::move(sheet), SheetState::NeedsParser};
1012 }
1013
MediaListMatches(const MediaList * aMediaList,const Document * aDocument)1014 static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
1015 const Document* aDocument) {
1016 if (!aMediaList || !aDocument) {
1017 return Loader::MediaMatched::Yes;
1018 }
1019
1020 if (aMediaList->Matches(*aDocument)) {
1021 return Loader::MediaMatched::Yes;
1022 }
1023
1024 return Loader::MediaMatched::No;
1025 }
1026
1027 /**
1028 * PrepareSheet() handles setting the media and title on the sheet, as
1029 * well as setting the enabled state based on the title and whether
1030 * the sheet had "alternate" in its rel.
1031 */
PrepareSheet(StyleSheet & aSheet,const nsAString & aTitle,const nsAString & aMediaString,MediaList * aMediaList,IsAlternate aIsAlternate,IsExplicitlyEnabled aIsExplicitlyEnabled)1032 Loader::MediaMatched Loader::PrepareSheet(
1033 StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
1034 MediaList* aMediaList, IsAlternate aIsAlternate,
1035 IsExplicitlyEnabled aIsExplicitlyEnabled) {
1036 RefPtr<MediaList> mediaList(aMediaList);
1037
1038 if (!aMediaString.IsEmpty()) {
1039 NS_ASSERTION(!aMediaList,
1040 "must not provide both aMediaString and aMediaList");
1041 mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
1042 }
1043
1044 aSheet.SetMedia(do_AddRef(mediaList));
1045
1046 aSheet.SetTitle(aTitle);
1047 aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
1048 aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
1049 return MediaListMatches(mediaList, mDocument);
1050 }
1051
1052 /**
1053 * InsertSheetInTree handles ordering of sheets in the document or shadow root.
1054 *
1055 * Here we have two types of sheets -- those with linking elements and
1056 * those without. The latter are loaded by Link: headers, and are only added to
1057 * the document.
1058 *
1059 * The following constraints are observed:
1060 * 1) Any sheet with a linking element comes after all sheets without
1061 * linking elements
1062 * 2) Sheets without linking elements are inserted in the order in
1063 * which the inserting requests come in, since all of these are
1064 * inserted during header data processing in the content sink
1065 * 3) Sheets with linking elements are ordered based on document order
1066 * as determined by CompareDocumentPosition.
1067 */
InsertSheetInTree(StyleSheet & aSheet,nsINode * aOwningNode)1068 void Loader::InsertSheetInTree(StyleSheet& aSheet, nsINode* aOwningNode) {
1069 LOG(("css::Loader::InsertSheetInTree"));
1070 MOZ_ASSERT(mDocument, "Must have a document to insert into");
1071 MOZ_ASSERT(!aOwningNode || aOwningNode->IsInUncomposedDoc() ||
1072 aOwningNode->IsInShadowTree(),
1073 "Why would we insert it anywhere?");
1074 ShadowRoot* shadow =
1075 aOwningNode ? aOwningNode->GetContainingShadow() : nullptr;
1076
1077 auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
1078 : static_cast<DocumentOrShadowRoot&>(*mDocument);
1079
1080 // XXX Need to cancel pending sheet loads for this element, if any
1081
1082 int32_t sheetCount = target.SheetCount();
1083
1084 /*
1085 * Start the walk at the _end_ of the list, since in the typical
1086 * case we'll just want to append anyway. We want to break out of
1087 * the loop when insertionPoint points to just before the index we
1088 * want to insert at. In other words, when we leave the loop
1089 * insertionPoint is the index of the stylesheet that immediately
1090 * precedes the one we're inserting.
1091 */
1092 int32_t insertionPoint = sheetCount - 1;
1093 for (; insertionPoint >= 0; --insertionPoint) {
1094 nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
1095 if (sheetOwner && !aOwningNode) {
1096 // Keep moving; all sheets with a sheetOwner come after all
1097 // sheets without a linkingNode
1098 continue;
1099 }
1100
1101 if (!sheetOwner) {
1102 // Aha! The current sheet has no sheet owner, so we want to insert after
1103 // it no matter whether we have a linking content or not.
1104 break;
1105 }
1106
1107 MOZ_ASSERT(aOwningNode != sheetOwner,
1108 "Why do we still have our old sheet?");
1109
1110 // Have to compare
1111 if (nsContentUtils::PositionIsBefore(sheetOwner, aOwningNode)) {
1112 // The current sheet comes before us, and it better be the first
1113 // such, because now we break
1114 break;
1115 }
1116 }
1117
1118 ++insertionPoint;
1119
1120 if (shadow) {
1121 shadow->InsertSheetAt(insertionPoint, aSheet);
1122 } else {
1123 mDocument->InsertSheetAt(insertionPoint, aSheet);
1124 }
1125
1126 LOG((" Inserting into target (doc: %d) at position %d",
1127 target.AsNode().IsDocument(), insertionPoint));
1128 }
1129
1130 /**
1131 * InsertChildSheet handles ordering of @import-ed sheet in their
1132 * parent sheets. Here we want to just insert based on order of the
1133 * @import rules that imported the sheets. In theory we can't just
1134 * append to the end because the CSSOM can insert @import rules. In
1135 * practice, we get the call to load the child sheet before the CSSOM
1136 * has finished inserting the @import rule, so we have no idea where
1137 * to put it anyway. So just append for now. (In the future if we
1138 * want to insert the sheet at the correct position, we'll need to
1139 * restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
1140 * bug 1220506.)
1141 */
InsertChildSheet(StyleSheet & aSheet,StyleSheet & aParentSheet)1142 void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
1143 LOG(("css::Loader::InsertChildSheet"));
1144
1145 // child sheets should always start out enabled, even if they got
1146 // cloned off of top-level sheets which were disabled
1147 aSheet.SetEnabled(true);
1148 aParentSheet.AppendStyleSheet(aSheet);
1149
1150 LOG((" Inserting into parent sheet"));
1151 }
1152
1153 /**
1154 * LoadSheet handles the actual load of a sheet. If the load is
1155 * supposed to be synchronous it just opens a channel synchronously
1156 * using the given uri, wraps the resulting stream in a converter
1157 * stream and calls ParseSheet. Otherwise it tries to look for an
1158 * existing load for this URI and piggyback on it. Failing all that,
1159 * a new load is kicked off asynchronously.
1160 */
LoadSheet(SheetLoadData & aLoadData,SheetState aSheetState,PendingLoad aPendingLoad)1161 nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
1162 PendingLoad aPendingLoad) {
1163 LOG(("css::Loader::LoadSheet"));
1164 MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
1165 MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
1166 MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
1167 MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
1168 "Shouldn't use system principal for async loads");
1169
1170 LOG_URI(" Load from: '%s'", aLoadData.mURI);
1171
1172 // If we're firing a pending load, this load is already accounted for the
1173 // first time it went through this function.
1174 if (aPendingLoad == PendingLoad::No) {
1175 if (aLoadData.BlocksLoadEvent()) {
1176 IncrementOngoingLoadCount();
1177 }
1178
1179 // We technically never defer non-top-level sheets, so this condition could
1180 // be outside the branch, but conceptually it should be here.
1181 if (aLoadData.mParentData) {
1182 ++aLoadData.mParentData->mPendingChildren;
1183 }
1184 }
1185
1186 nsresult rv = NS_OK;
1187
1188 if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
1189 // No point starting the load; just release all the data and such.
1190 LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
1191 SheetComplete(aLoadData, NS_BINDING_ABORTED);
1192 return NS_BINDING_ABORTED;
1193 }
1194
1195 SRIMetadata sriMetadata;
1196 aLoadData.mSheet->GetIntegrity(sriMetadata);
1197
1198 if (aLoadData.mSyncLoad) {
1199 LOG((" Synchronous load"));
1200 MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
1201 MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
1202 "Sync loads can't reuse existing async loads");
1203
1204 // Create a StreamLoader instance to which we will feed
1205 // the data from the sync load. Do this before creating the
1206 // channel to make error recovery simpler.
1207 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1208
1209 if (mDocument) {
1210 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1211 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1212 mDocument);
1213 }
1214
1215 nsSecurityFlags securityFlags =
1216 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
1217 nsILoadInfo::SEC_ALLOW_CHROME;
1218
1219 nsContentPolicyType contentPolicyType =
1220 aLoadData.mPreloadKind == StylePreloadKind::None
1221 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1222 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1223
1224 // Just load it
1225 nsCOMPtr<nsIChannel> channel;
1226 // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
1227 // a node and a principal.
1228 // This is because of a case where the node is the document being styled and
1229 // the principal is the stylesheet (perhaps from a different origin) that is
1230 // applying the styles.
1231 if (aLoadData.mRequestingNode) {
1232 rv = NS_NewChannelWithTriggeringPrincipal(
1233 getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
1234 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1235 } else {
1236 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1237 auto result = URLPreloader::ReadURI(aLoadData.mURI);
1238 if (result.isOk()) {
1239 nsCOMPtr<nsIInputStream> stream;
1240 MOZ_TRY(
1241 NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
1242
1243 rv = NS_NewInputStreamChannel(
1244 getter_AddRefs(channel), aLoadData.mURI, stream.forget(),
1245 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1246 } else {
1247 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1248 aLoadData.mTriggeringPrincipal, securityFlags,
1249 contentPolicyType);
1250 }
1251 }
1252 if (NS_FAILED(rv)) {
1253 LOG_ERROR((" Failed to create channel"));
1254 streamLoader->ChannelOpenFailed(rv);
1255 SheetComplete(aLoadData, rv);
1256 return rv;
1257 }
1258
1259 // snapshot the nonce at load start time for performing CSP checks
1260 if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
1261 if (aLoadData.mRequestingNode) {
1262 // TODO(bug 1607009) move to SheetLoadData
1263 nsString* cspNonce = static_cast<nsString*>(
1264 aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
1265 if (cspNonce) {
1266 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1267 loadInfo->SetCspNonce(*cspNonce);
1268 }
1269 }
1270 }
1271
1272 nsCOMPtr<nsIInputStream> stream;
1273 rv = channel->Open(getter_AddRefs(stream));
1274
1275 if (NS_FAILED(rv)) {
1276 LOG_ERROR((" Failed to open URI synchronously"));
1277 streamLoader->ChannelOpenFailed(rv);
1278 SheetComplete(aLoadData, rv);
1279 return rv;
1280 }
1281
1282 // Force UA sheets to be UTF-8.
1283 // XXX this is only necessary because the default in
1284 // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
1285 channel->SetContentCharset("UTF-8"_ns);
1286
1287 // Manually feed the streamloader the contents of the stream.
1288 // This will call back into OnStreamComplete
1289 // and thence to ParseSheet. Regardless of whether this fails,
1290 // SheetComplete has been called.
1291 return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
1292 streamLoader, channel);
1293 }
1294
1295 SheetLoadDataHashKey key(aLoadData);
1296
1297 auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData);
1298 bool coalescedLoad = false;
1299 if (mSheets) {
1300 // If we have at least one other load ongoing, then we can defer it until
1301 // all non-pending loads are done.
1302 if (aSheetState == SheetState::NeedsParser &&
1303 aPendingLoad == PendingLoad::No && aLoadData.ShouldDefer() &&
1304 mOngoingLoadCount > mPendingLoadCount + 1) {
1305 LOG((" Deferring sheet load"));
1306 ++mPendingLoadCount;
1307 mSheets->DeferSheetLoad(key, aLoadData);
1308 return NS_OK;
1309 }
1310
1311 if ((coalescedLoad = mSheets->CoalesceLoad(key, aLoadData, aSheetState))) {
1312 if (aSheetState == SheetState::Pending) {
1313 ++mPendingLoadCount;
1314 return NS_OK;
1315 }
1316 }
1317 }
1318
1319 aLoadData.NotifyOpen(preloadKey, mDocument, aLoadData.IsLinkRelPreload());
1320 if (coalescedLoad) {
1321 // All done here; once the load completes we'll be marked complete
1322 // automatically.
1323 return NS_OK;
1324 }
1325
1326 nsCOMPtr<nsILoadGroup> loadGroup;
1327 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1328 if (mDocument) {
1329 loadGroup = mDocument->GetDocumentLoadGroup();
1330 // load for a document with no loadgrup indicates that something is
1331 // completely bogus, let's bail out early.
1332 if (!loadGroup) {
1333 LOG_ERROR((" Failed to query loadGroup from document"));
1334 SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
1335 return NS_ERROR_UNEXPECTED;
1336 }
1337
1338 cookieJarSettings = mDocument->CookieJarSettings();
1339 }
1340
1341 #ifdef DEBUG
1342 AutoRestore<bool> syncCallbackGuard(mSyncCallback);
1343 mSyncCallback = true;
1344 #endif
1345
1346 CORSMode ourCORSMode = aLoadData.mSheet->GetCORSMode();
1347 nsSecurityFlags securityFlags =
1348 ourCORSMode == CORS_NONE
1349 ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
1350 : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1351 if (ourCORSMode == CORS_ANONYMOUS) {
1352 securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
1353 } else if (ourCORSMode == CORS_USE_CREDENTIALS) {
1354 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1355 }
1356 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
1357
1358 nsContentPolicyType contentPolicyType =
1359 aLoadData.mPreloadKind == StylePreloadKind::None
1360 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1361 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1362
1363 nsCOMPtr<nsIChannel> channel;
1364 // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
1365 // and a principal. This is because of a case where the node is the document
1366 // being styled and the principal is the stylesheet (perhaps from a different
1367 // origin) that is applying the styles.
1368 if (aLoadData.mRequestingNode) {
1369 rv = NS_NewChannelWithTriggeringPrincipal(
1370 getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
1371 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType,
1372 /* PerformanceStorage */ nullptr, loadGroup);
1373 } else {
1374 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1375 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1376 aLoadData.mTriggeringPrincipal, securityFlags,
1377 contentPolicyType, cookieJarSettings,
1378 /* aPerformanceStorage */ nullptr, loadGroup);
1379 }
1380
1381 if (NS_FAILED(rv)) {
1382 LOG_ERROR((" Failed to create channel"));
1383 SheetComplete(aLoadData, rv);
1384 return rv;
1385 }
1386
1387 // snapshot the nonce at load start time for performing CSP checks
1388 if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
1389 if (aLoadData.mRequestingNode) {
1390 // TODO(bug 1607009) move to SheetLoadData
1391 nsString* cspNonce = static_cast<nsString*>(
1392 aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
1393 if (cspNonce) {
1394 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1395 loadInfo->SetCspNonce(*cspNonce);
1396 }
1397 }
1398 }
1399
1400 if (!aLoadData.ShouldDefer()) {
1401 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
1402 cos->AddClassFlags(nsIClassOfService::Leader);
1403 }
1404 if (aLoadData.IsLinkRelPreload()) {
1405 SheetLoadData::PrioritizeAsPreload(channel);
1406 SheetLoadData::AddLoadBackgroundFlag(channel);
1407 }
1408 }
1409
1410 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
1411 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
1412 rv = httpChannel->SetReferrerInfo(referrerInfo);
1413 Unused << NS_WARN_IF(NS_FAILED(rv));
1414 }
1415
1416 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
1417 do_QueryInterface(httpChannel);
1418 if (internalChannel) {
1419 rv = internalChannel->SetIntegrityMetadata(
1420 sriMetadata.GetIntegrityString());
1421 NS_ENSURE_SUCCESS(rv, rv);
1422 }
1423
1424 // Set the initiator type
1425 if (nsCOMPtr<nsITimedChannel> timedChannel =
1426 do_QueryInterface(httpChannel)) {
1427 if (aLoadData.mParentData) {
1428 timedChannel->SetInitiatorType(u"css"_ns);
1429
1430 // This is a child sheet load.
1431 //
1432 // The resource timing of the sub-resources that a document loads
1433 // should normally be reported to the document. One exception is any
1434 // sub-resources of any cross-origin resources that are loaded. We
1435 // don't mind reporting timing data for a direct child cross-origin
1436 // resource since the resource that linked to it (and hence potentially
1437 // anything in that parent origin) is aware that the cross-origin
1438 // resources is to be loaded. However, we do not want to report
1439 // timings for any sub-resources that a cross-origin resource may load
1440 // since that obviously leaks information about what the cross-origin
1441 // resource loads, which is bad.
1442 //
1443 // In addition to checking whether we're an immediate child resource of
1444 // a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
1445 // to true on our parent), we also check our parent to see whether it
1446 // itself is a sub-resource of a cross-origin resource by checking
1447 // mBlockResourceTiming. If that is set then we too are such a
1448 // sub-resource and so we set the flag on ourself too to propagate it
1449 // on down.
1450 if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
1451 aLoadData.mParentData->mBlockResourceTiming) {
1452 // Set a flag so any other stylesheet triggered by this one will
1453 // not be reported
1454 aLoadData.mBlockResourceTiming = true;
1455
1456 // Mark the channel so PerformanceMainThread::AddEntry will not
1457 // report the resource.
1458 timedChannel->SetReportResourceTiming(false);
1459 }
1460
1461 } else {
1462 timedChannel->SetInitiatorType(u"link"_ns);
1463 }
1464 }
1465 }
1466
1467 // Now tell the channel we expect text/css data back.... We do
1468 // this before opening it, so it's only treated as a hint.
1469 channel->SetContentType("text/css"_ns);
1470
1471 // We don't have to hold on to the stream loader. The ownership
1472 // model is: Necko owns the stream loader, which owns the load data,
1473 // which owns us
1474 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1475 if (mDocument) {
1476 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1477 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
1478 }
1479
1480 rv = channel->AsyncOpen(streamLoader);
1481 if (NS_FAILED(rv)) {
1482 LOG_ERROR((" Failed to create stream loader"));
1483 streamLoader->ChannelOpenFailed(rv);
1484 // NOTE: NotifyStop will be done in SheetComplete -> NotifyObservers.
1485 aLoadData.NotifyStart(channel);
1486 SheetComplete(aLoadData, rv);
1487 return rv;
1488 }
1489
1490 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1491 if (nsCOMPtr<nsIHttpChannelInternal> hci = do_QueryInterface(channel)) {
1492 hci->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
1493 }
1494 #endif
1495
1496 if (mSheets) {
1497 mSheets->LoadStarted(key, aLoadData);
1498 }
1499 return NS_OK;
1500 }
1501
1502 /**
1503 * ParseSheet handles parsing the data stream.
1504 */
ParseSheet(const nsACString & aBytes,SheetLoadData & aLoadData,AllowAsyncParse aAllowAsync)1505 Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
1506 SheetLoadData& aLoadData,
1507 AllowAsyncParse aAllowAsync) {
1508 LOG(("css::Loader::ParseSheet"));
1509 if (aLoadData.mURI) {
1510 LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
1511 }
1512 AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
1513
1514 ++mParsedSheetCount;
1515
1516 aLoadData.mIsBeingParsed = true;
1517
1518 StyleSheet* sheet = aLoadData.mSheet;
1519 MOZ_ASSERT(sheet);
1520
1521 // Some cases, like inline style and UA stylesheets, need to be parsed
1522 // synchronously. The former may trigger child loads, the latter must not.
1523 if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
1524 sheet->ParseSheetSync(this, aBytes, &aLoadData, aLoadData.mLineNumber);
1525 aLoadData.mIsBeingParsed = false;
1526
1527 bool noPendingChildren = aLoadData.mPendingChildren == 0;
1528 MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
1529 if (noPendingChildren) {
1530 SheetComplete(aLoadData, NS_OK);
1531 return Completed::Yes;
1532 }
1533 return Completed::No;
1534 }
1535
1536 // This parse does not need to be synchronous. \o/
1537 //
1538 // Note that load is already blocked from IncrementOngoingLoadCount(), and
1539 // will be unblocked from SheetFinishedParsingAsync which will end up in
1540 // NotifyObservers as needed.
1541 nsCOMPtr<nsISerialEventTarget> target = DispatchTarget();
1542 sheet->ParseSheet(*this, aBytes, aLoadData)
1543 ->Then(
1544 target, __func__,
1545 [loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
1546 MOZ_ASSERT(NS_IsMainThread());
1547 loadData->SheetFinishedParsingAsync();
1548 },
1549 [] { MOZ_CRASH("rejected parse promise"); });
1550 return Completed::No;
1551 }
1552
NotifyObservers(SheetLoadData & aData,nsresult aStatus)1553 void Loader::NotifyObservers(SheetLoadData& aData, nsresult aStatus) {
1554 RecordUseCountersIfNeeded(mDocument, aData.mUseCounters.get());
1555 if (aData.mURI) {
1556 mLoadsPerformed.PutEntry(SheetLoadDataHashKey(aData));
1557 aData.NotifyStop(aStatus);
1558 // NOTE(emilio): This needs to happen before notifying observers, as
1559 // FontFaceSet for example checks for pending sheet loads from the
1560 // StyleSheetLoaded callback.
1561 if (aData.BlocksLoadEvent()) {
1562 DecrementOngoingLoadCount();
1563 }
1564 }
1565
1566 if (aData.mMustNotify) {
1567 if (aData.mObserver) {
1568 LOG((" Notifying observer %p for data %p. deferred: %d",
1569 aData.mObserver.get(), &aData, aData.ShouldDefer()));
1570 aData.mObserver->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(),
1571 aStatus);
1572 }
1573
1574 for (nsCOMPtr<nsICSSLoaderObserver> obs : mObservers.ForwardRange()) {
1575 LOG((" Notifying global observer %p for data %p. deferred: %d",
1576 obs.get(), &aData, aData.ShouldDefer()));
1577 obs->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
1578 }
1579 }
1580
1581 if (mPendingLoadCount && mPendingLoadCount == mOngoingLoadCount) {
1582 LOG((" No more loading sheets; starting deferred loads"));
1583 StartDeferredLoads();
1584 }
1585 }
1586
1587 /**
1588 * SheetComplete is the do-it-all cleanup function. It removes the
1589 * load data from the "loading" hashtable, adds the sheet to the
1590 * "completed" hashtable, massages the XUL cache, handles siblings of
1591 * the load data (other loads for the same URI), handles unblocking
1592 * blocked parent loads as needed, and most importantly calls
1593 * NS_RELEASE on the load data to destroy the whole mess.
1594 */
SheetComplete(SheetLoadData & aLoadData,nsresult aStatus)1595 void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
1596 LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
1597 static_cast<uint32_t>(aStatus)));
1598 SharedStyleSheetCache::LoadCompleted(mSheets.get(), aLoadData, aStatus);
1599 }
1600
1601 // static
MarkLoadTreeFailed(SheetLoadData & aLoadData,Loader * aOnlyForLoader)1602 void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData,
1603 Loader* aOnlyForLoader) {
1604 if (aLoadData.mURI) {
1605 LOG_URI(" Load failed: '%s'", aLoadData.mURI);
1606 }
1607
1608 SheetLoadData* data = &aLoadData;
1609 do {
1610 if (!aOnlyForLoader || aOnlyForLoader == data->mLoader) {
1611 data->mLoadFailed = true;
1612 data->mSheet->MaybeRejectReplacePromise();
1613 }
1614
1615 if (data->mParentData) {
1616 MarkLoadTreeFailed(*data->mParentData, aOnlyForLoader);
1617 }
1618
1619 data = data->mNext;
1620 } while (data);
1621 }
1622
LookupInlineSheetInCache(const nsAString & aBuffer)1623 RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(const nsAString& aBuffer) {
1624 auto result = mInlineSheets.Lookup(aBuffer);
1625 if (!result) {
1626 return nullptr;
1627 }
1628 if (result.Data()->HasModifiedRules()) {
1629 // Remove it now that we know that we're never going to use this stylesheet
1630 // again.
1631 result.Remove();
1632 return nullptr;
1633 }
1634 return result.Data()->Clone(nullptr, nullptr, nullptr, nullptr);
1635 }
1636
MaybeNotifyPreloadUsed(SheetLoadData & aData)1637 void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) {
1638 if (!mDocument) {
1639 return;
1640 }
1641
1642 auto key = PreloadHashKey::CreateAsStyle(aData);
1643 RefPtr<PreloaderBase> preload = mDocument->Preloads().LookupPreload(key);
1644 if (!preload) {
1645 return;
1646 }
1647
1648 preload->NotifyUsage();
1649 }
1650
LoadInlineStyle(const SheetInfo & aInfo,const nsAString & aBuffer,uint32_t aLineNumber,nsICSSLoaderObserver * aObserver)1651 Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
1652 const SheetInfo& aInfo, const nsAString& aBuffer, uint32_t aLineNumber,
1653 nsICSSLoaderObserver* aObserver) {
1654 LOG(("css::Loader::LoadInlineStyle"));
1655 MOZ_ASSERT(aInfo.mContent);
1656
1657 if (!mEnabled) {
1658 LOG_WARN((" Not enabled"));
1659 return Err(NS_ERROR_NOT_AVAILABLE);
1660 }
1661
1662 if (!mDocument) {
1663 return Err(NS_ERROR_NOT_INITIALIZED);
1664 }
1665
1666 MOZ_ASSERT(LinkStyle::FromNodeOrNull(aInfo.mContent),
1667 "Element is not a style linking element!");
1668
1669 // Since we're not planning to load a URI, no need to hand a principal to the
1670 // load data or to CreateSheet().
1671
1672 // Check IsAlternateSheet now, since it can mutate our document.
1673 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1674 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1675
1676 // Use the document's base URL so that @import in the inline sheet picks up
1677 // the right base.
1678 nsIURI* baseURI = aInfo.mContent->GetBaseURI();
1679 nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
1680 nsIURI* originalURI = nullptr;
1681
1682 MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
1683
1684 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1685 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1686 ? aInfo.mTriggeringPrincipal.get()
1687 : loadingPrincipal;
1688
1689 // We only cache sheets if in shadow trees, since regular document sheets are
1690 // likely to be unique.
1691 const bool isWorthCaching = aInfo.mContent->IsInShadowTree();
1692 RefPtr<StyleSheet> sheet;
1693 if (isWorthCaching) {
1694 sheet = LookupInlineSheetInCache(aBuffer);
1695 }
1696 const bool sheetFromCache = !!sheet;
1697 if (!sheet) {
1698 sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
1699 SRIMetadata{});
1700 sheet->SetURIs(sheetURI, originalURI, baseURI);
1701 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1702 ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
1703 sheet->SetReferrerInfo(referrerInfo);
1704
1705 nsIPrincipal* sheetPrincipal = principal;
1706 if (aInfo.mTriggeringPrincipal) {
1707 // The triggering principal may be an expanded principal, which is safe to
1708 // use for URL security checks, but not as the loader principal for a
1709 // stylesheet. So treat this as principal inheritance, and downgrade if
1710 // necessary.
1711 sheetPrincipal =
1712 BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
1713 }
1714
1715 // We never actually load this, so just set its principal directly
1716 sheet->SetPrincipal(sheetPrincipal);
1717 }
1718
1719 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1720 isAlternate, aInfo.mIsExplicitlyEnabled);
1721
1722 if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
1723 linkStyle->SetStyleSheet(sheet);
1724 }
1725 if (sheet->IsComplete()) {
1726 InsertSheetInTree(*sheet, aInfo.mContent);
1727 }
1728
1729 Completed completed;
1730 if (sheetFromCache) {
1731 MOZ_ASSERT(sheet->IsComplete());
1732 completed = Completed::Yes;
1733 } else {
1734 auto data = MakeRefPtr<SheetLoadData>(
1735 this, aInfo.mTitle, nullptr, sheet, false, aInfo.mContent, isAlternate,
1736 matched, StylePreloadKind::None, aObserver, principal,
1737 aInfo.mReferrerInfo, aInfo.mContent);
1738 data->mLineNumber = aLineNumber;
1739
1740 // Parse completion releases the load data.
1741 //
1742 // Note that we need to parse synchronously, since the web expects that the
1743 // effects of inline stylesheets are visible immediately (aside from
1744 // @imports).
1745 NS_ConvertUTF16toUTF8 utf8(aBuffer);
1746 completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
1747 if (completed == Completed::Yes) {
1748 // TODO(emilio): Try to cache sheets with @import rules, maybe?
1749 if (isWorthCaching) {
1750 mInlineSheets.InsertOrUpdate(aBuffer, std::move(sheet));
1751 }
1752 } else {
1753 data->mMustNotify = true;
1754 }
1755 }
1756
1757 return LoadSheetResult{completed, isAlternate, matched};
1758 }
1759
LoadStyleLink(const SheetInfo & aInfo,nsICSSLoaderObserver * aObserver)1760 Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
1761 const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
1762 MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
1763 LOG(("css::Loader::LoadStyleLink"));
1764 LOG_URI(" Link uri: '%s'", aInfo.mURI);
1765 LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
1766 LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
1767 LOG((" Link alternate rel: %d", aInfo.mHasAlternateRel));
1768
1769 if (!mEnabled) {
1770 LOG_WARN((" Not enabled"));
1771 return Err(NS_ERROR_NOT_AVAILABLE);
1772 }
1773
1774 if (!mDocument) {
1775 return Err(NS_ERROR_NOT_INITIALIZED);
1776 }
1777
1778 MOZ_ASSERT_IF(aInfo.mContent,
1779 aInfo.mContent->NodePrincipal() == mDocument->NodePrincipal());
1780 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1781 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1782 ? aInfo.mTriggeringPrincipal.get()
1783 : loadingPrincipal;
1784
1785 nsINode* context = aInfo.mContent;
1786 if (!context) {
1787 context = mDocument;
1788 }
1789
1790 bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
1791 IsChromeURI(aInfo.mURI);
1792 LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
1793 MOZ_ASSERT_IF(syncLoad, !aObserver);
1794
1795 nsresult rv =
1796 CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI, context,
1797 aInfo.mNonce, StylePreloadKind::None);
1798 if (NS_WARN_IF(NS_FAILED(rv))) {
1799 // Don't fire the error event if our document is loaded as data. We're
1800 // supposed to not even try to do loads in that case... Unfortunately, we
1801 // implement that via nsDataDocumentContentPolicy, which doesn't have a good
1802 // way to communicate back to us that _it_ is the thing that blocked the
1803 // load.
1804 if (aInfo.mContent && !mDocument->IsLoadedAsData()) {
1805 // Fire an async error event on it.
1806 RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
1807 new LoadBlockingAsyncEventDispatcher(aInfo.mContent, u"error"_ns,
1808 CanBubble::eNo,
1809 ChromeOnlyDispatch::eNo);
1810 loadBlockingAsyncDispatcher->PostDOMEvent();
1811 }
1812 return Err(rv);
1813 }
1814
1815 // Check IsAlternateSheet now, since it can mutate our document and make
1816 // pending sheets go to the non-pending state.
1817 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1818 auto [sheet, state] = CreateSheet(aInfo, eAuthorSheetFeatures, syncLoad,
1819 StylePreloadKind::None);
1820
1821 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1822
1823 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1824 isAlternate, aInfo.mIsExplicitlyEnabled);
1825
1826 if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
1827 linkStyle->SetStyleSheet(sheet);
1828 }
1829 if (sheet->IsComplete()) {
1830 InsertSheetInTree(*sheet, aInfo.mContent);
1831 }
1832
1833 // We may get here with no content for Link: headers for example.
1834 MOZ_ASSERT(!aInfo.mContent || LinkStyle::FromNode(*aInfo.mContent),
1835 "If there is any node, it should be a LinkStyle");
1836 auto data = MakeRefPtr<SheetLoadData>(
1837 this, aInfo.mTitle, aInfo.mURI, sheet, syncLoad, aInfo.mContent,
1838 isAlternate, matched, StylePreloadKind::None, aObserver, principal,
1839 aInfo.mReferrerInfo, context);
1840
1841 MaybeNotifyPreloadUsed(*data);
1842
1843 if (state == SheetState::Complete) {
1844 LOG((" Sheet already complete: 0x%p", sheet.get()));
1845 if (aObserver || !mObservers.IsEmpty() || aInfo.mContent) {
1846 rv = PostLoadEvent(std::move(data));
1847 if (NS_FAILED(rv)) {
1848 return Err(rv);
1849 }
1850 } else {
1851 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1852 // We don't have to notify anyone of this load, as it was complete, so
1853 // drop it intentionally.
1854 data->mIntentionallyDropped = true;
1855 #endif
1856 }
1857
1858 // The load hasn't been completed yet, will be done in PostLoadEvent.
1859 return LoadSheetResult{Completed::No, isAlternate, matched};
1860 }
1861
1862 // Now we need to actually load it.
1863 auto result = LoadSheetResult{Completed::No, isAlternate, matched};
1864
1865 MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
1866 "These should better match!");
1867
1868 // Load completion will free the data
1869 rv = LoadSheet(*data, state);
1870 if (NS_FAILED(rv)) {
1871 return Err(rv);
1872 }
1873
1874 if (!syncLoad) {
1875 data->mMustNotify = true;
1876 }
1877 return result;
1878 }
1879
HaveAncestorDataWithURI(SheetLoadData & aData,nsIURI * aURI)1880 static bool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) {
1881 if (!aData.mURI) {
1882 // Inline style; this won't have any ancestors
1883 MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?");
1884 return false;
1885 }
1886
1887 bool equal;
1888 if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
1889 return true;
1890 }
1891
1892 // Datas down the mNext chain have the same URI as aData, so we
1893 // don't have to compare to them. But they might have different
1894 // parents, and we have to check all of those.
1895 SheetLoadData* data = &aData;
1896 do {
1897 if (data->mParentData &&
1898 HaveAncestorDataWithURI(*data->mParentData, aURI)) {
1899 return true;
1900 }
1901
1902 data = data->mNext;
1903 } while (data);
1904
1905 return false;
1906 }
1907
LoadChildSheet(StyleSheet & aParentSheet,SheetLoadData * aParentData,nsIURI * aURL,dom::MediaList * aMedia,LoaderReusableStyleSheets * aReusableSheets)1908 nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
1909 SheetLoadData* aParentData, nsIURI* aURL,
1910 dom::MediaList* aMedia,
1911 LoaderReusableStyleSheets* aReusableSheets) {
1912 LOG(("css::Loader::LoadChildSheet"));
1913 MOZ_ASSERT(aURL, "Must have a URI to load");
1914
1915 if (!mEnabled) {
1916 LOG_WARN((" Not enabled"));
1917 return NS_ERROR_NOT_AVAILABLE;
1918 }
1919
1920 LOG_URI(" Child uri: '%s'", aURL);
1921
1922 nsCOMPtr<nsINode> owningNode;
1923
1924 // Check for an associated document or shadow root: if none, don't bother
1925 // walking up the parent sheets.
1926 if (aParentSheet.GetAssociatedDocumentOrShadowRoot()) {
1927 StyleSheet* topSheet = &aParentSheet;
1928 while (StyleSheet* parent = topSheet->GetParentSheet()) {
1929 topSheet = parent;
1930 }
1931 owningNode = topSheet->GetOwnerNode();
1932 }
1933
1934 nsINode* context = nullptr;
1935 if (owningNode) {
1936 context = owningNode;
1937 MOZ_ASSERT(LoaderPrincipal() == owningNode->NodePrincipal());
1938 } else if (mDocument) {
1939 context = mDocument;
1940 }
1941
1942 nsIPrincipal* principal = aParentSheet.Principal();
1943 nsresult rv = CheckContentPolicy(LoaderPrincipal(), principal, aURL, context,
1944 u""_ns, StylePreloadKind::None);
1945 if (NS_WARN_IF(NS_FAILED(rv))) {
1946 if (aParentData) {
1947 MarkLoadTreeFailed(*aParentData);
1948 }
1949 return rv;
1950 }
1951
1952 nsCOMPtr<nsICSSLoaderObserver> observer;
1953
1954 if (aParentData) {
1955 LOG((" Have a parent load"));
1956 // Check for cycles
1957 if (HaveAncestorDataWithURI(*aParentData, aURL)) {
1958 // Houston, we have a loop, blow off this child and pretend this never
1959 // happened
1960 LOG_ERROR((" @import cycle detected, dropping load"));
1961 return NS_OK;
1962 }
1963
1964 NS_ASSERTION(aParentData->mSheet == &aParentSheet,
1965 "Unexpected call to LoadChildSheet");
1966 } else {
1967 LOG((" No parent load; must be CSSOM"));
1968 // No parent load data, so the sheet will need to be notified when
1969 // we finish, if it can be, if we do the load asynchronously.
1970 observer = &aParentSheet;
1971 }
1972
1973 // Now that we know it's safe to load this (passes security check and not a
1974 // loop) do so.
1975 RefPtr<StyleSheet> sheet;
1976 SheetState state;
1977 if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
1978 state = SheetState::Complete;
1979 } else {
1980 // For now, use CORS_NONE for child sheets
1981 std::tie(sheet, state) = CreateSheet(
1982 aURL, nullptr, principal, aParentSheet.ParsingMode(), CORS_NONE,
1983 aParentData ? aParentData->mEncoding : nullptr,
1984 u""_ns, // integrity is only checked on main sheet
1985 aParentData && aParentData->mSyncLoad, StylePreloadKind::None);
1986 PrepareSheet(*sheet, u""_ns, u""_ns, aMedia, IsAlternate::No,
1987 IsExplicitlyEnabled::No);
1988 }
1989
1990 MOZ_ASSERT(sheet);
1991 InsertChildSheet(*sheet, aParentSheet);
1992
1993 auto data = MakeRefPtr<SheetLoadData>(
1994 this, aURL, sheet, aParentData, observer, principal,
1995 aParentSheet.GetReferrerInfo(), context);
1996
1997 MaybeNotifyPreloadUsed(*data);
1998
1999 if (state == SheetState::Complete) {
2000 LOG((" Sheet already complete"));
2001 // We're completely done. No need to notify, even, since the
2002 // @import rule addition/modification will trigger the right style
2003 // changes automatically.
2004 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2005 data->mIntentionallyDropped = true;
2006 #endif
2007 return NS_OK;
2008 }
2009
2010 bool syncLoad = data->mSyncLoad;
2011
2012 // Load completion will release the data
2013 rv = LoadSheet(*data, state);
2014 NS_ENSURE_SUCCESS(rv, rv);
2015
2016 if (!syncLoad) {
2017 data->mMustNotify = true;
2018 }
2019 return rv;
2020 }
2021
LoadSheetSync(nsIURI * aURL,SheetParsingMode aParsingMode,UseSystemPrincipal aUseSystemPrincipal)2022 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
2023 nsIURI* aURL, SheetParsingMode aParsingMode,
2024 UseSystemPrincipal aUseSystemPrincipal) {
2025 LOG(("css::Loader::LoadSheetSync"));
2026 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2027 return InternalLoadNonDocumentSheet(
2028 aURL, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2029 referrerInfo, nullptr, CORS_NONE, u""_ns);
2030 }
2031
LoadSheet(nsIURI * aURI,SheetParsingMode aParsingMode,UseSystemPrincipal aUseSystemPrincipal,nsICSSLoaderObserver * aObserver)2032 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2033 nsIURI* aURI, SheetParsingMode aParsingMode,
2034 UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
2035 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2036 return InternalLoadNonDocumentSheet(
2037 aURI, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2038 referrerInfo, aObserver, CORS_NONE, u""_ns);
2039 }
2040
LoadSheet(nsIURI * aURL,StylePreloadKind aPreloadKind,const Encoding * aPreloadEncoding,nsIReferrerInfo * aReferrerInfo,nsICSSLoaderObserver * aObserver,CORSMode aCORSMode,const nsAString & aIntegrity)2041 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2042 nsIURI* aURL, StylePreloadKind aPreloadKind,
2043 const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
2044 nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
2045 const nsAString& aIntegrity) {
2046 LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
2047 return InternalLoadNonDocumentSheet(
2048 aURL, aPreloadKind, eAuthorSheetFeatures, UseSystemPrincipal::No,
2049 aPreloadEncoding, aReferrerInfo, aObserver, aCORSMode, aIntegrity);
2050 }
2051
InternalLoadNonDocumentSheet(nsIURI * aURL,StylePreloadKind aPreloadKind,SheetParsingMode aParsingMode,UseSystemPrincipal aUseSystemPrincipal,const Encoding * aPreloadEncoding,nsIReferrerInfo * aReferrerInfo,nsICSSLoaderObserver * aObserver,CORSMode aCORSMode,const nsAString & aIntegrity)2052 Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
2053 nsIURI* aURL, StylePreloadKind aPreloadKind, SheetParsingMode aParsingMode,
2054 UseSystemPrincipal aUseSystemPrincipal, const Encoding* aPreloadEncoding,
2055 nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
2056 CORSMode aCORSMode, const nsAString& aIntegrity) {
2057 MOZ_ASSERT(aURL, "Must have a URI to load");
2058 MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
2059 "Shouldn't load system-principal sheets async");
2060 MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");
2061
2062 LOG_URI(" Non-document sheet uri: '%s'", aURL);
2063
2064 if (!mEnabled) {
2065 LOG_WARN((" Not enabled"));
2066 return Err(NS_ERROR_NOT_AVAILABLE);
2067 }
2068
2069 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
2070 nsIPrincipal* triggeringPrincipal = loadingPrincipal;
2071 nsresult rv = CheckContentPolicy(loadingPrincipal, triggeringPrincipal, aURL,
2072 mDocument, u""_ns, aPreloadKind);
2073 if (NS_FAILED(rv)) {
2074 return Err(rv);
2075 }
2076
2077 bool syncLoad = !aObserver;
2078 auto [sheet, state] =
2079 CreateSheet(aURL, nullptr, triggeringPrincipal, aParsingMode, aCORSMode,
2080 aPreloadEncoding, aIntegrity, syncLoad, aPreloadKind);
2081
2082 PrepareSheet(*sheet, u""_ns, u""_ns, nullptr, IsAlternate::No,
2083 IsExplicitlyEnabled::No);
2084
2085 auto data = MakeRefPtr<SheetLoadData>(
2086 this, aURL, sheet, syncLoad, aUseSystemPrincipal, aPreloadKind,
2087 aPreloadEncoding, aObserver, triggeringPrincipal, aReferrerInfo,
2088 mDocument);
2089 if (state == SheetState::Complete) {
2090 LOG((" Sheet already complete"));
2091 if (aObserver || !mObservers.IsEmpty()) {
2092 rv = PostLoadEvent(std::move(data));
2093 if (NS_FAILED(rv)) {
2094 return Err(rv);
2095 }
2096 } else {
2097 // We don't have to notify anyone of this load, as it was complete, so
2098 // drop it intentionally.
2099 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2100 data->mIntentionallyDropped = true;
2101 #endif
2102 }
2103 return sheet;
2104 }
2105
2106 rv = LoadSheet(*data, state);
2107 if (NS_FAILED(rv)) {
2108 return Err(rv);
2109 }
2110 if (aObserver) {
2111 data->mMustNotify = true;
2112 }
2113 return sheet;
2114 }
2115
PostLoadEvent(RefPtr<SheetLoadData> aLoadData)2116 nsresult Loader::PostLoadEvent(RefPtr<SheetLoadData> aLoadData) {
2117 LOG(("css::Loader::PostLoadEvent"));
2118 mPostedEvents.AppendElement(aLoadData);
2119
2120 nsresult rv;
2121 RefPtr<SheetLoadData> runnable(aLoadData);
2122 if (mDocument) {
2123 rv = mDocument->Dispatch(TaskCategory::Other, runnable.forget());
2124 } else if (mDocGroup) {
2125 rv = mDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
2126 } else {
2127 rv = SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
2128 }
2129
2130 if (NS_FAILED(rv)) {
2131 NS_WARNING("failed to dispatch stylesheet load event");
2132 mPostedEvents.RemoveElement(aLoadData);
2133 } else {
2134 if (aLoadData->BlocksLoadEvent()) {
2135 IncrementOngoingLoadCount();
2136 }
2137
2138 // We want to notify the observer for this data.
2139 aLoadData->mMustNotify = true;
2140 aLoadData->mSheetAlreadyComplete = true;
2141
2142 // If we get to this code, aSheet loaded correctly at some point, so
2143 // we can just schedule a load event and don't need to touch the
2144 // data's mLoadFailed. Note that we do this here and not from
2145 // inside our SheetComplete so that we don't end up running the load
2146 // event async.
2147 MOZ_ASSERT(!aLoadData->mLoadFailed, "Why are we marked as failed?");
2148 aLoadData->ScheduleLoadEventIfNeeded();
2149 }
2150
2151 return rv;
2152 }
2153
HandleLoadEvent(SheetLoadData & aEvent)2154 void Loader::HandleLoadEvent(SheetLoadData& aEvent) {
2155 // XXXbz can't assert this yet.... May not have an observer because
2156 // we're unblocking the parser
2157 // NS_ASSERTION(aEvent->mObserver, "Must have observer");
2158 NS_ASSERTION(aEvent.mSheet, "Must have sheet");
2159
2160 mPostedEvents.RemoveElement(&aEvent);
2161 SheetComplete(aEvent, NS_OK);
2162 }
2163
Stop()2164 void Loader::Stop() {
2165 if (mSheets) {
2166 mSheets->CancelLoadsForLoader(*this);
2167 }
2168
2169 auto arr = std::move(mPostedEvents);
2170 for (auto& data : arr) {
2171 data->mIsCancelled = true;
2172 }
2173 }
2174
HasPendingLoads()2175 bool Loader::HasPendingLoads() { return mOngoingLoadCount; }
2176
AddObserver(nsICSSLoaderObserver * aObserver)2177 void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
2178 MOZ_ASSERT(aObserver, "Must have observer");
2179 mObservers.AppendElementUnlessExists(aObserver);
2180 }
2181
RemoveObserver(nsICSSLoaderObserver * aObserver)2182 void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
2183 mObservers.RemoveElement(aObserver);
2184 }
2185
StartDeferredLoads()2186 void Loader::StartDeferredLoads() {
2187 if (mSheets && mPendingLoadCount) {
2188 mSheets->StartDeferredLoadsForLoader(
2189 *this, SharedStyleSheetCache::StartLoads::Always);
2190 }
2191 }
2192
2193 NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
2194
2195 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
2196 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets);
2197 for (const auto& data : tmp->mInlineSheets.Values()) {
2198 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
2199 cb.NoteXPCOMChild(data);
2200 }
2201 for (nsCOMPtr<nsICSSLoaderObserver>& obs : tmp->mObservers.ForwardRange()) {
2202 ImplCycleCollectionTraverse(cb, obs, "mozilla::css::Loader.mObservers");
2203 }
2204 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2205 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2206
2207 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
2208 if (tmp->mSheets) {
2209 if (tmp->mDocument) {
2210 tmp->DeregisterFromSheetCache();
2211 }
2212 tmp->mSheets = nullptr;
2213 }
2214 tmp->mInlineSheets.Clear();
2215 tmp->mObservers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)2216 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)
2217 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2218
2219 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
2220 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
2221
2222 size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2223 size_t n = aMallocSizeOf(this);
2224
2225 n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
2226
2227 n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
2228 for (const auto& entry : mInlineSheets) {
2229 n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2230 // If the sheet has a parent, then its parent will report it so we don't
2231 // have to worry about it here.
2232 const StyleSheet* sheet = entry.GetWeak();
2233 MOZ_ASSERT(!sheet->GetParentSheet(),
2234 "How did an @import rule end up here?");
2235 if (!sheet->GetOwnerNode()) {
2236 n += sheet->SizeOfIncludingThis(aMallocSizeOf);
2237 }
2238 }
2239
2240 // Measurement of the following members may be added later if DMD finds it is
2241 // worthwhile:
2242 // - mPostedEvents: transient, and should be small
2243 //
2244 // The following members aren't measured:
2245 // - mDocument, because it's a weak backpointer
2246
2247 return n;
2248 }
2249
LoaderPrincipal() const2250 nsIPrincipal* Loader::LoaderPrincipal() const {
2251 if (mDocument) {
2252 return mDocument->NodePrincipal();
2253 }
2254 // Loaders without a document do system loads.
2255 return nsContentUtils::GetSystemPrincipal();
2256 }
2257
PartitionedPrincipal() const2258 nsIPrincipal* Loader::PartitionedPrincipal() const {
2259 if (mDocument && StaticPrefs::privacy_partition_network_state()) {
2260 return mDocument->PartitionedPrincipal();
2261 }
2262 return LoaderPrincipal();
2263 }
2264
ShouldBypassCache() const2265 bool Loader::ShouldBypassCache() const {
2266 if (!mDocument) {
2267 return false;
2268 }
2269 RefPtr<nsILoadGroup> lg = mDocument->GetDocumentLoadGroup();
2270 if (!lg) {
2271 return false;
2272 }
2273 nsLoadFlags flags;
2274 if (NS_FAILED(lg->GetLoadFlags(&flags))) {
2275 return false;
2276 }
2277 return flags & (nsIRequest::LOAD_BYPASS_CACHE |
2278 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE);
2279 }
2280
BlockOnload()2281 void Loader::BlockOnload() {
2282 if (mDocument) {
2283 mDocument->BlockOnload();
2284 }
2285 }
2286
UnblockOnload(bool aFireSync)2287 void Loader::UnblockOnload(bool aFireSync) {
2288 if (mDocument) {
2289 mDocument->UnblockOnload(aFireSync);
2290 }
2291 }
2292
DispatchTarget()2293 already_AddRefed<nsISerialEventTarget> Loader::DispatchTarget() {
2294 nsCOMPtr<nsISerialEventTarget> target;
2295 if (mDocument) {
2296 // If you change this, you may want to change StyleSheet::Replace
2297 target = mDocument->EventTargetFor(TaskCategory::Other);
2298 } else if (mDocGroup) {
2299 target = mDocGroup->EventTargetFor(TaskCategory::Other);
2300 } else {
2301 target = GetMainThreadSerialEventTarget();
2302 }
2303
2304 return target.forget();
2305 }
2306
2307 } // namespace css
2308 } // namespace mozilla
2309