Bug 1698786: part 2) Change some compile-time dependent `printf`s to `MOZ_LOG` in...
[gecko.git] / layout / style / Loader.cpp
blobf74585ea6380d6dc30f969d6272be29bc797cec2
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/. */
7 /* loading of CSS style sheets using the network APIs */
9 #include "mozilla/css/Loader.h"
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"
66 #ifdef MOZ_XUL
67 # include "nsXULPrototypeCache.h"
68 #endif
70 #include "nsError.h"
72 #include "mozilla/dom/SRICheck.h"
74 #include "mozilla/Encoding.h"
76 using namespace mozilla::dom;
78 // 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
79 #define SNIFFING_BUFFER_SIZE 1024
81 /**
82 * OVERALL ARCHITECTURE
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()
96 * The detailed documentation for these functions is found with the
97 * function implementations.
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.
107 extern mozilla::LazyLogModule sCssLoaderLog;
108 mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
110 static mozilla::LazyLogModule gSriPRLog("SRI");
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)
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()
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())); \
131 PR_END_MACRO
133 // And some convenience strings...
134 static const char* const gStateStrings[] = {"NeedsParser", "Pending", "Loading",
135 "Complete"};
137 namespace mozilla {
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);
157 bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
159 bool eq;
160 if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
161 return false;
165 LOG_URI("KeyEquals(%s)\n", mURI);
167 if (!mPrincipal->Equals(aKey.mPrincipal)) {
168 LOG((" > Principal mismatch\n"));
169 return false;
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;
183 if (mCORSMode != aKey.mCORSMode) {
184 LOG((" > CORS mismatch\n"));
185 return false;
188 if (mParsingMode != aKey.mParsingMode) {
189 LOG((" > Parsing mode mismatch\n"));
190 return false;
193 if (mCompatMode != aKey.mCompatMode) {
194 LOG((" > Quirks mismatch\n"));
195 return false;
198 // If encoding differs, then don't reuse the cache.
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;
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;
219 if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
220 LOG((" > Preload SRI metadata mismatch\n"));
221 return false;
225 return true;
228 namespace css {
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);
245 // Try preload or parent sheet encoding.
246 if (aPreloadOrParentDataEncoding) {
247 return WrapNotNull(aPreloadOrParentDataEncoding);
250 if (auto* doc = aLoader.GetDocument()) {
251 // Use the document charset.
252 return doc->GetDocumentCharacterSet();
255 return UTF_8_ENCODING;
258 /********************************
259 * SheetLoadData implementation *
260 ********************************/
261 NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunnable, nsIThreadObserver)
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!");
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);
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");
392 SheetLoadData::~SheetLoadData() {
393 MOZ_DIAGNOSTIC_ASSERT(mSheetCompleteCalled || mIntentionallyDropped,
394 "Should always call SheetComplete, except when "
395 "dropping the load");
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);
404 NS_IMETHODIMP
405 SheetLoadData::Run() {
406 mLoader->HandleLoadEvent(*this);
407 return NS_OK;
410 NS_IMETHODIMP
411 SheetLoadData::OnDispatchedEvent() { return NS_OK; }
413 NS_IMETHODIMP
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;
422 NS_IMETHODIMP
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;
432 void SheetLoadData::PrioritizeAsPreload(nsIChannel* aChannel) {
433 if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
434 sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
438 void SheetLoadData::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
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);
446 // Now fire the event.
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???");
454 nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(), node,
455 mLoadFailed ? u"error"_ns : u"load"_ns,
456 CanBubble::eNo, Cancelable::eNo);
458 MOZ_ASSERT(BlocksLoadEvent());
459 mLoader->UnblockOnload(true);
462 void SheetLoadData::ScheduleLoadEventIfNeeded() {
463 if (!mOwningNode) {
464 return;
467 MOZ_ASSERT(BlocksLoadEvent(), "The rel=preload load event happens elsewhere");
469 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
470 nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
471 if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
472 mLoader->BlockOnload();
476 /*********************
477 * Style sheet reuse *
478 *********************/
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;
495 return false;
497 /*************************
498 * Loader Implementation *
499 *************************/
501 Loader::Loader()
502 : mDocument(nullptr),
503 mDocumentCompatMode(eCompatibility_FullStandards),
504 mReporter(new ConsoleReportCollector()) {}
506 Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }
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();
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.
522 void Loader::RegisterInSheetCache() {
523 MOZ_ASSERT(mDocument);
524 MOZ_ASSERT(mSheets);
526 mSheets->RegisterLoader(*this);
529 void Loader::DeregisterFromSheetCache() {
530 MOZ_ASSERT(mDocument);
531 MOZ_ASSERT(mSheets);
533 mSheets->CancelLoadsForLoader(*this);
534 mSheets->UnregisterLoader(*this);
537 void Loader::DropDocumentReference() {
538 // Flush out pending datas just so we don't leak by accident.
539 if (mSheets) {
540 DeregisterFromSheetCache();
542 mDocument = nullptr;
545 void Loader::DocumentStyleSheetSetChanged() {
546 MOZ_ASSERT(mDocument);
548 // start any pending alternates that aren't alternates anymore
549 mSheets->StartDeferredLoadsForLoader(
550 *this, SharedStyleSheetCache::StartLoads::IfNonAlternate);
553 static const char kCharsetSym[] = "@charset \"";
555 static bool GetCharsetFromData(const char* aStyleSheetData,
556 uint32_t aDataLength, nsACString& aCharset) {
557 aCharset.Truncate();
558 if (aDataLength <= sizeof(kCharsetSym) - 1) return false;
560 if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
561 return false;
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;
571 // fail
572 break;
574 aCharset.Append(c);
577 // Did not see end quote or semicolon
578 aCharset.Truncate();
579 return false;
582 NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
583 const nsACString& aSegment, nsIChannel* aChannel) const {
584 const Encoding* encoding;
585 nsAutoCString label;
587 // Check HTTP
588 if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
589 encoding = Encoding::ForLabel(label);
590 if (encoding) {
591 return WrapNotNull(encoding);
595 // Check @charset
596 auto sniffingLength = aSegment.Length();
597 if (sniffingLength > SNIFFING_BUFFER_SIZE) {
598 sniffingLength = SNIFFING_BUFFER_SIZE;
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;
605 if (encoding) {
606 return WrapNotNull(encoding);
609 return mGuessedEncoding;
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);
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);
628 MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
629 ("VerifySheetIntegrity (unichar stream)"));
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);
640 return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
643 static bool AllLoadsCanceled(const SheetLoadData& aData) {
644 const SheetLoadData* data = &aData;
645 do {
646 if (!data->mIsCancelled) {
647 return false;
649 } while ((data = data->mNext));
650 return true;
654 * Stream completion code shared by Stylo and the old style system.
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.
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");
667 if (AllLoadsCanceled(*this)) {
668 LOG_WARN((" All loads are canceled, dropping"));
669 mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
670 return NS_OK;
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.
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));
693 mLoader->SheetComplete(*this, aStatus);
694 return NS_OK;
697 if (!aChannel) {
698 mLoader->SheetComplete(*this, NS_OK);
699 return NS_OK;
702 nsCOMPtr<nsIURI> originalURI;
703 aChannel->GetOriginalURI(getter_AddRefs(originalURI));
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));
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;
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));
732 if (NS_FAILED(result)) {
733 LOG_WARN((" Couldn't get principal"));
734 mLoader->SheetComplete(*this, result);
735 return NS_OK;
738 mSheet->SetPrincipal(principal);
740 if (mSheet->GetCORSMode() == CORS_NONE &&
741 !mTriggeringPrincipal->Subsumes(principal)) {
742 mIsCrossOriginNoCORS = true;
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;
756 nsAutoCString sourceMapURL;
757 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
758 mSheet->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL));
762 nsAutoCString contentType;
763 aChannel->GetContentType(contentType);
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.
770 bool validType = contentType.EqualsLiteral("text/css") ||
771 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
772 contentType.IsEmpty();
774 if (!validType) {
775 const char* errorMessage;
776 uint32_t errorFlag;
777 bool sameOrigin = true;
779 bool subsumed;
780 result = mTriggeringPrincipal->Subsumes(principal, &subsumed);
781 if (NS_FAILED(result) || !subsumed) {
782 sameOrigin = false;
785 if (sameOrigin && mCompatMode == eCompatibility_NavQuirks) {
786 errorMessage = "MimeNotCssWarn";
787 errorFlag = nsIScriptError::warningFlag;
788 } else {
789 errorMessage = "MimeNotCss";
790 errorFlag = nsIScriptError::errorFlag;
793 AutoTArray<nsString, 2> strings;
794 CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
795 CopyASCIItoUTF16(contentType, *strings.AppendElement());
797 nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
798 nsContentUtils::ReportToConsole(
799 errorFlag, "CSS Loader"_ns, mLoader->mDocument,
800 nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);
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;
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);
817 nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
818 sourceUri, mLoader->mReporter);
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);
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;
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);
841 ReferrerPolicy policy =
842 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
843 nsCOMPtr<nsIReferrerInfo> referrerInfo =
844 ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);
846 mSheet->SetReferrerInfo(referrerInfo);
847 return NS_OK_PARSE_SHEET;
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;
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;
873 if (aTitle.Equals(currentSheetSet)) {
874 return IsAlternate::No;
878 return IsAlternate::Yes;
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;
892 nsContentPolicyType contentPolicyType =
893 aPreloadKind == StylePreloadKind::None
894 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
895 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
897 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
898 aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
899 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
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());
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 return NS_ERROR_CONTENT_BLOCKED;
914 return NS_OK;
917 static void RecordUseCountersIfNeeded(Document* aDoc,
918 const StyleUseCounters* aCounters) {
919 if (!aDoc || !aCounters) {
920 return;
922 const StyleUseCounters* docCounters = aDoc->GetStyleUseCounters();
923 if (!docCounters) {
924 return;
926 Servo_UseCounters_Merge(docCounters, aCounters);
927 aDoc->MaybeWarnAboutZoom();
930 void Loader::DidHitCompleteSheetCache(const SheetLoadDataHashKey& aKey,
931 const StyleUseCounters* aCounters) {
932 MOZ_ASSERT(mDocument);
933 if (mLoadsPerformed.EnsureInserted(aKey)) {
934 RecordUseCountersIfNeeded(mDocument, aCounters);
939 * CreateSheet() creates a StyleSheet object for the given URI.
941 * We check for an existing style sheet object for that uri in various caches
942 * and clone it if we find it. Cloned sheets will have the title/media/enabled
943 * state of the sheet they are clones off; make sure to call PrepareSheet() on
944 * the result of CreateSheet().
946 std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
947 nsIURI* aURI, nsIContent* aLinkingContent,
948 nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode aParsingMode,
949 CORSMode aCORSMode, const Encoding* aPreloadOrParentDataEncoding,
950 const nsAString& aIntegrity, bool aSyncLoad,
951 StylePreloadKind aPreloadKind) {
952 MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
953 LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
955 SRIMetadata sriMetadata;
956 if (!aIntegrity.IsEmpty()) {
957 MOZ_LOG(gSriPRLog, LogLevel::Debug,
958 ("css::Loader::CreateSheet, integrity=%s",
959 NS_ConvertUTF16toUTF8(aIntegrity).get()));
960 nsAutoCString sourceUri;
961 if (mDocument && mDocument->GetDocumentURI()) {
962 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
964 SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
967 if (mSheets) {
968 SheetLoadDataHashKey key(aURI, aTriggeringPrincipal, LoaderPrincipal(),
969 PartitionedPrincipal(),
970 GetFallbackEncoding(*this, aLinkingContent,
971 aPreloadOrParentDataEncoding),
972 aCORSMode, aParsingMode, CompatMode(aPreloadKind),
973 sriMetadata, aPreloadKind);
974 auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
975 if (const auto& [styleSheet, sheetState] = cacheResult; styleSheet) {
976 LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
977 return cacheResult;
981 nsIURI* sheetURI = aURI;
982 nsIURI* baseURI = aURI;
983 nsIURI* originalURI = aURI;
985 auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
986 sheet->SetURIs(sheetURI, originalURI, baseURI);
987 nsCOMPtr<nsIReferrerInfo> referrerInfo =
988 ReferrerInfo::CreateForExternalCSSResources(sheet);
989 sheet->SetReferrerInfo(referrerInfo);
990 LOG((" Needs parser"));
991 return {std::move(sheet), SheetState::NeedsParser};
994 static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
995 const Document* aDocument) {
996 if (!aMediaList || !aDocument) {
997 return Loader::MediaMatched::Yes;
1000 if (aMediaList->Matches(*aDocument)) {
1001 return Loader::MediaMatched::Yes;
1004 return Loader::MediaMatched::No;
1008 * PrepareSheet() handles setting the media and title on the sheet, as
1009 * well as setting the enabled state based on the title and whether
1010 * the sheet had "alternate" in its rel.
1012 Loader::MediaMatched Loader::PrepareSheet(
1013 StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
1014 MediaList* aMediaList, IsAlternate aIsAlternate,
1015 IsExplicitlyEnabled aIsExplicitlyEnabled) {
1016 RefPtr<MediaList> mediaList(aMediaList);
1018 if (!aMediaString.IsEmpty()) {
1019 NS_ASSERTION(!aMediaList,
1020 "must not provide both aMediaString and aMediaList");
1021 mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
1024 aSheet.SetMedia(do_AddRef(mediaList));
1026 aSheet.SetTitle(aTitle);
1027 aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
1028 aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
1029 return MediaListMatches(mediaList, mDocument);
1033 * InsertSheetInTree handles ordering of sheets in the document or shadow root.
1035 * Here we have two types of sheets -- those with linking elements and
1036 * those without. The latter are loaded by Link: headers, and are only added to
1037 * the document.
1039 * The following constraints are observed:
1040 * 1) Any sheet with a linking element comes after all sheets without
1041 * linking elements
1042 * 2) Sheets without linking elements are inserted in the order in
1043 * which the inserting requests come in, since all of these are
1044 * inserted during header data processing in the content sink
1045 * 3) Sheets with linking elements are ordered based on document order
1046 * as determined by CompareDocumentPosition.
1048 void Loader::InsertSheetInTree(StyleSheet& aSheet, nsINode* aOwningNode) {
1049 LOG(("css::Loader::InsertSheetInTree"));
1050 MOZ_ASSERT(mDocument, "Must have a document to insert into");
1051 MOZ_ASSERT(!aOwningNode || aOwningNode->IsInUncomposedDoc() ||
1052 aOwningNode->IsInShadowTree(),
1053 "Why would we insert it anywhere?");
1054 ShadowRoot* shadow =
1055 aOwningNode ? aOwningNode->GetContainingShadow() : nullptr;
1057 auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
1058 : static_cast<DocumentOrShadowRoot&>(*mDocument);
1060 // XXX Need to cancel pending sheet loads for this element, if any
1062 int32_t sheetCount = target.SheetCount();
1065 * Start the walk at the _end_ of the list, since in the typical
1066 * case we'll just want to append anyway. We want to break out of
1067 * the loop when insertionPoint points to just before the index we
1068 * want to insert at. In other words, when we leave the loop
1069 * insertionPoint is the index of the stylesheet that immediately
1070 * precedes the one we're inserting.
1072 int32_t insertionPoint = sheetCount - 1;
1073 for (; insertionPoint >= 0; --insertionPoint) {
1074 nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
1075 if (sheetOwner && !aOwningNode) {
1076 // Keep moving; all sheets with a sheetOwner come after all
1077 // sheets without a linkingNode
1078 continue;
1081 if (!sheetOwner) {
1082 // Aha! The current sheet has no sheet owner, so we want to insert after
1083 // it no matter whether we have a linking content or not.
1084 break;
1087 MOZ_ASSERT(aOwningNode != sheetOwner,
1088 "Why do we still have our old sheet?");
1090 // Have to compare
1091 if (nsContentUtils::PositionIsBefore(sheetOwner, aOwningNode)) {
1092 // The current sheet comes before us, and it better be the first
1093 // such, because now we break
1094 break;
1098 ++insertionPoint;
1100 if (shadow) {
1101 shadow->InsertSheetAt(insertionPoint, aSheet);
1102 } else {
1103 mDocument->InsertSheetAt(insertionPoint, aSheet);
1106 LOG((" Inserting into target (doc: %d) at position %d",
1107 target.AsNode().IsDocument(), insertionPoint));
1111 * InsertChildSheet handles ordering of @import-ed sheet in their
1112 * parent sheets. Here we want to just insert based on order of the
1113 * @import rules that imported the sheets. In theory we can't just
1114 * append to the end because the CSSOM can insert @import rules. In
1115 * practice, we get the call to load the child sheet before the CSSOM
1116 * has finished inserting the @import rule, so we have no idea where
1117 * to put it anyway. So just append for now. (In the future if we
1118 * want to insert the sheet at the correct position, we'll need to
1119 * restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
1120 * bug 1220506.)
1122 void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
1123 LOG(("css::Loader::InsertChildSheet"));
1125 // child sheets should always start out enabled, even if they got
1126 // cloned off of top-level sheets which were disabled
1127 aSheet.SetEnabled(true);
1128 aParentSheet.AppendStyleSheet(aSheet);
1130 LOG((" Inserting into parent sheet"));
1134 * LoadSheet handles the actual load of a sheet. If the load is
1135 * supposed to be synchronous it just opens a channel synchronously
1136 * using the given uri, wraps the resulting stream in a converter
1137 * stream and calls ParseSheet. Otherwise it tries to look for an
1138 * existing load for this URI and piggyback on it. Failing all that,
1139 * a new load is kicked off asynchronously.
1141 nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
1142 PendingLoad aPendingLoad) {
1143 LOG(("css::Loader::LoadSheet"));
1144 MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
1145 MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
1146 MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
1147 MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
1148 "Shouldn't use system principal for async loads");
1150 LOG_URI(" Load from: '%s'", aLoadData.mURI);
1152 // If we're firing a pending load, this load is already accounted for the
1153 // first time it went through this function.
1154 if (aPendingLoad == PendingLoad::No) {
1155 if (aLoadData.BlocksLoadEvent()) {
1156 IncrementOngoingLoadCount();
1159 // We technically never defer non-top-level sheets, so this condition could
1160 // be outside the branch, but conceptually it should be here.
1161 if (aLoadData.mParentData) {
1162 ++aLoadData.mParentData->mPendingChildren;
1166 nsresult rv = NS_OK;
1168 if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
1169 // No point starting the load; just release all the data and such.
1170 LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
1171 SheetComplete(aLoadData, NS_BINDING_ABORTED);
1172 return NS_BINDING_ABORTED;
1175 SRIMetadata sriMetadata;
1176 aLoadData.mSheet->GetIntegrity(sriMetadata);
1178 if (aLoadData.mSyncLoad) {
1179 LOG((" Synchronous load"));
1180 MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
1181 MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
1182 "Sync loads can't reuse existing async loads");
1184 // Create a StreamLoader instance to which we will feed
1185 // the data from the sync load. Do this before creating the
1186 // channel to make error recovery simpler.
1187 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1189 if (mDocument) {
1190 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1191 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1192 mDocument);
1195 nsSecurityFlags securityFlags =
1196 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
1197 nsILoadInfo::SEC_ALLOW_CHROME;
1199 nsContentPolicyType contentPolicyType =
1200 aLoadData.mPreloadKind == StylePreloadKind::None
1201 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1202 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1204 // Just load it
1205 nsCOMPtr<nsIChannel> channel;
1206 // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
1207 // a node and a principal.
1208 // This is because of a case where the node is the document being styled and
1209 // the principal is the stylesheet (perhaps from a different origin) that is
1210 // applying the styles.
1211 if (aLoadData.mRequestingNode) {
1212 rv = NS_NewChannelWithTriggeringPrincipal(
1213 getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
1214 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1215 } else {
1216 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1217 auto result = URLPreloader::ReadURI(aLoadData.mURI);
1218 if (result.isOk()) {
1219 nsCOMPtr<nsIInputStream> stream;
1220 MOZ_TRY(
1221 NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
1223 rv = NS_NewInputStreamChannel(
1224 getter_AddRefs(channel), aLoadData.mURI, stream.forget(),
1225 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1226 } else {
1227 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1228 aLoadData.mTriggeringPrincipal, securityFlags,
1229 contentPolicyType);
1232 if (NS_FAILED(rv)) {
1233 LOG_ERROR((" Failed to create channel"));
1234 streamLoader->ChannelOpenFailed(rv);
1235 SheetComplete(aLoadData, rv);
1236 return rv;
1239 // snapshot the nonce at load start time for performing CSP checks
1240 if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
1241 if (aLoadData.mRequestingNode) {
1242 // TODO(bug 1607009) move to SheetLoadData
1243 nsString* cspNonce = static_cast<nsString*>(
1244 aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
1245 if (cspNonce) {
1246 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1247 loadInfo->SetCspNonce(*cspNonce);
1252 nsCOMPtr<nsIInputStream> stream;
1253 rv = channel->Open(getter_AddRefs(stream));
1255 if (NS_FAILED(rv)) {
1256 LOG_ERROR((" Failed to open URI synchronously"));
1257 streamLoader->ChannelOpenFailed(rv);
1258 SheetComplete(aLoadData, rv);
1259 return rv;
1262 // Force UA sheets to be UTF-8.
1263 // XXX this is only necessary because the default in
1264 // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
1265 channel->SetContentCharset("UTF-8"_ns);
1267 // Manually feed the streamloader the contents of the stream.
1268 // This will call back into OnStreamComplete
1269 // and thence to ParseSheet. Regardless of whether this fails,
1270 // SheetComplete has been called.
1271 return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
1272 streamLoader, channel);
1275 SheetLoadDataHashKey key(aLoadData);
1277 auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData);
1278 bool coalescedLoad = false;
1279 if (mSheets) {
1280 // If we have at least one other load ongoing, then we can defer it until
1281 // all non-pending loads are done.
1282 if (aSheetState == SheetState::NeedsParser &&
1283 aPendingLoad == PendingLoad::No && aLoadData.ShouldDefer() &&
1284 mOngoingLoadCount > mPendingLoadCount + 1) {
1285 LOG((" Deferring sheet load"));
1286 ++mPendingLoadCount;
1287 mSheets->DeferSheetLoad(key, aLoadData);
1288 return NS_OK;
1291 if ((coalescedLoad = mSheets->CoalesceLoad(key, aLoadData, aSheetState))) {
1292 if (aSheetState == SheetState::Pending) {
1293 ++mPendingLoadCount;
1294 return NS_OK;
1299 aLoadData.NotifyOpen(preloadKey, mDocument, aLoadData.IsLinkRelPreload());
1300 if (coalescedLoad) {
1301 // All done here; once the load completes we'll be marked complete
1302 // automatically.
1303 return NS_OK;
1306 nsCOMPtr<nsILoadGroup> loadGroup;
1307 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1308 if (mDocument) {
1309 loadGroup = mDocument->GetDocumentLoadGroup();
1310 // load for a document with no loadgrup indicates that something is
1311 // completely bogus, let's bail out early.
1312 if (!loadGroup) {
1313 LOG_ERROR((" Failed to query loadGroup from document"));
1314 SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
1315 return NS_ERROR_UNEXPECTED;
1318 cookieJarSettings = mDocument->CookieJarSettings();
1321 #ifdef DEBUG
1322 AutoRestore<bool> syncCallbackGuard(mSyncCallback);
1323 mSyncCallback = true;
1324 #endif
1326 CORSMode ourCORSMode = aLoadData.mSheet->GetCORSMode();
1327 nsSecurityFlags securityFlags =
1328 ourCORSMode == CORS_NONE
1329 ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
1330 : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1331 if (ourCORSMode == CORS_ANONYMOUS) {
1332 securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
1333 } else if (ourCORSMode == CORS_USE_CREDENTIALS) {
1334 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1336 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
1338 nsContentPolicyType contentPolicyType =
1339 aLoadData.mPreloadKind == StylePreloadKind::None
1340 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1341 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1343 nsCOMPtr<nsIChannel> channel;
1344 // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
1345 // and a principal. This is because of a case where the node is the document
1346 // being styled and the principal is the stylesheet (perhaps from a different
1347 // origin) that is applying the styles.
1348 if (aLoadData.mRequestingNode) {
1349 rv = NS_NewChannelWithTriggeringPrincipal(
1350 getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
1351 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType,
1352 /* PerformanceStorage */ nullptr, loadGroup);
1353 } else {
1354 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1355 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1356 aLoadData.mTriggeringPrincipal, securityFlags,
1357 contentPolicyType, cookieJarSettings,
1358 /* aPerformanceStorage */ nullptr, loadGroup);
1361 if (NS_FAILED(rv)) {
1362 LOG_ERROR((" Failed to create channel"));
1363 SheetComplete(aLoadData, rv);
1364 return rv;
1367 // snapshot the nonce at load start time for performing CSP checks
1368 if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
1369 if (aLoadData.mRequestingNode) {
1370 // TODO(bug 1607009) move to SheetLoadData
1371 nsString* cspNonce = static_cast<nsString*>(
1372 aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
1373 if (cspNonce) {
1374 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1375 loadInfo->SetCspNonce(*cspNonce);
1380 if (!aLoadData.ShouldDefer()) {
1381 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
1382 cos->AddClassFlags(nsIClassOfService::Leader);
1384 if (aLoadData.IsLinkRelPreload()) {
1385 SheetLoadData::PrioritizeAsPreload(channel);
1386 SheetLoadData::AddLoadBackgroundFlag(channel);
1390 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
1391 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
1392 rv = httpChannel->SetReferrerInfo(referrerInfo);
1393 Unused << NS_WARN_IF(NS_FAILED(rv));
1396 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
1397 do_QueryInterface(httpChannel);
1398 if (internalChannel) {
1399 rv = internalChannel->SetIntegrityMetadata(
1400 sriMetadata.GetIntegrityString());
1401 NS_ENSURE_SUCCESS(rv, rv);
1404 // Set the initiator type
1405 if (nsCOMPtr<nsITimedChannel> timedChannel =
1406 do_QueryInterface(httpChannel)) {
1407 if (aLoadData.mParentData) {
1408 timedChannel->SetInitiatorType(u"css"_ns);
1410 // This is a child sheet load.
1412 // The resource timing of the sub-resources that a document loads
1413 // should normally be reported to the document. One exception is any
1414 // sub-resources of any cross-origin resources that are loaded. We
1415 // don't mind reporting timing data for a direct child cross-origin
1416 // resource since the resource that linked to it (and hence potentially
1417 // anything in that parent origin) is aware that the cross-origin
1418 // resources is to be loaded. However, we do not want to report
1419 // timings for any sub-resources that a cross-origin resource may load
1420 // since that obviously leaks information about what the cross-origin
1421 // resource loads, which is bad.
1423 // In addition to checking whether we're an immediate child resource of
1424 // a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
1425 // to true on our parent), we also check our parent to see whether it
1426 // itself is a sub-resource of a cross-origin resource by checking
1427 // mBlockResourceTiming. If that is set then we too are such a
1428 // sub-resource and so we set the flag on ourself too to propagate it
1429 // on down.
1430 if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
1431 aLoadData.mParentData->mBlockResourceTiming) {
1432 // Set a flag so any other stylesheet triggered by this one will
1433 // not be reported
1434 aLoadData.mBlockResourceTiming = true;
1436 // Mark the channel so PerformanceMainThread::AddEntry will not
1437 // report the resource.
1438 timedChannel->SetReportResourceTiming(false);
1441 } else {
1442 timedChannel->SetInitiatorType(u"link"_ns);
1447 // Now tell the channel we expect text/css data back.... We do
1448 // this before opening it, so it's only treated as a hint.
1449 channel->SetContentType("text/css"_ns);
1451 // We don't have to hold on to the stream loader. The ownership
1452 // model is: Necko owns the stream loader, which owns the load data,
1453 // which owns us
1454 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1455 if (mDocument) {
1456 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1457 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
1460 rv = channel->AsyncOpen(streamLoader);
1461 if (NS_FAILED(rv)) {
1462 LOG_ERROR((" Failed to create stream loader"));
1463 streamLoader->ChannelOpenFailed(rv);
1464 // NOTE: NotifyStop will be done in SheetComplete -> NotifyObservers.
1465 aLoadData.NotifyStart(channel);
1466 SheetComplete(aLoadData, rv);
1467 return rv;
1470 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1471 if (nsCOMPtr<nsIHttpChannelInternal> hci = do_QueryInterface(channel)) {
1472 hci->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
1474 #endif
1476 if (mSheets) {
1477 mSheets->LoadStarted(key, aLoadData);
1479 return NS_OK;
1483 * ParseSheet handles parsing the data stream.
1485 Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
1486 SheetLoadData& aLoadData,
1487 AllowAsyncParse aAllowAsync) {
1488 LOG(("css::Loader::ParseSheet"));
1489 if (aLoadData.mURI) {
1490 LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
1492 AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
1494 ++mParsedSheetCount;
1496 aLoadData.mIsBeingParsed = true;
1498 StyleSheet* sheet = aLoadData.mSheet;
1499 MOZ_ASSERT(sheet);
1501 // Some cases, like inline style and UA stylesheets, need to be parsed
1502 // synchronously. The former may trigger child loads, the latter must not.
1503 if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
1504 sheet->ParseSheetSync(this, aBytes, &aLoadData, aLoadData.mLineNumber);
1505 aLoadData.mIsBeingParsed = false;
1507 bool noPendingChildren = aLoadData.mPendingChildren == 0;
1508 MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
1509 if (noPendingChildren) {
1510 SheetComplete(aLoadData, NS_OK);
1511 return Completed::Yes;
1513 return Completed::No;
1516 // This parse does not need to be synchronous. \o/
1518 // Note that load is already blocked from IncrementOngoingLoadCount(), and
1519 // will be unblocked from SheetFinishedParsingAsync which will end up in
1520 // NotifyObservers as needed.
1521 nsCOMPtr<nsISerialEventTarget> target = DispatchTarget();
1522 sheet->ParseSheet(*this, aBytes, aLoadData)
1523 ->Then(
1524 target, __func__,
1525 [loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
1526 MOZ_ASSERT(NS_IsMainThread());
1527 loadData->SheetFinishedParsingAsync();
1529 [] { MOZ_CRASH("rejected parse promise"); });
1530 return Completed::No;
1533 void Loader::NotifyObservers(SheetLoadData& aData, nsresult aStatus) {
1534 RecordUseCountersIfNeeded(mDocument, aData.mUseCounters.get());
1535 if (aData.mURI) {
1536 mLoadsPerformed.PutEntry(SheetLoadDataHashKey(aData));
1537 aData.NotifyStop(aStatus);
1538 // NOTE(emilio): This needs to happen before notifying observers, as
1539 // FontFaceSet for example checks for pending sheet loads from the
1540 // StyleSheetLoaded callback.
1541 if (aData.BlocksLoadEvent()) {
1542 DecrementOngoingLoadCount();
1546 if (aData.mMustNotify) {
1547 if (aData.mObserver) {
1548 LOG((" Notifying observer %p for data %p. deferred: %d",
1549 aData.mObserver.get(), &aData, aData.ShouldDefer()));
1550 aData.mObserver->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(),
1551 aStatus);
1554 for (nsCOMPtr<nsICSSLoaderObserver> obs : mObservers.ForwardRange()) {
1555 LOG((" Notifying global observer %p for data %p. deferred: %d",
1556 obs.get(), &aData, aData.ShouldDefer()));
1557 obs->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
1561 if (mPendingLoadCount && mPendingLoadCount == mOngoingLoadCount) {
1562 LOG((" No more loading sheets; starting deferred loads"));
1563 StartDeferredLoads();
1568 * SheetComplete is the do-it-all cleanup function. It removes the
1569 * load data from the "loading" hashtable, adds the sheet to the
1570 * "completed" hashtable, massages the XUL cache, handles siblings of
1571 * the load data (other loads for the same URI), handles unblocking
1572 * blocked parent loads as needed, and most importantly calls
1573 * NS_RELEASE on the load data to destroy the whole mess.
1575 void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
1576 LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
1577 static_cast<uint32_t>(aStatus)));
1578 SharedStyleSheetCache::LoadCompleted(mSheets.get(), aLoadData, aStatus);
1581 // static
1582 void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData,
1583 Loader* aOnlyForLoader) {
1584 if (aLoadData.mURI) {
1585 LOG_URI(" Load failed: '%s'", aLoadData.mURI);
1588 SheetLoadData* data = &aLoadData;
1589 do {
1590 if (!aOnlyForLoader || aOnlyForLoader == data->mLoader) {
1591 data->mLoadFailed = true;
1592 data->mSheet->MaybeRejectReplacePromise();
1595 if (data->mParentData) {
1596 MarkLoadTreeFailed(*data->mParentData, aOnlyForLoader);
1599 data = data->mNext;
1600 } while (data);
1603 RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(const nsAString& aBuffer) {
1604 auto result = mInlineSheets.Lookup(aBuffer);
1605 if (!result) {
1606 return nullptr;
1608 if (result.Data()->HasModifiedRules()) {
1609 // Remove it now that we know that we're never going to use this stylesheet
1610 // again.
1611 result.Remove();
1612 return nullptr;
1614 return result.Data()->Clone(nullptr, nullptr, nullptr, nullptr);
1617 void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) {
1618 if (!mDocument) {
1619 return;
1622 auto key = PreloadHashKey::CreateAsStyle(aData);
1623 RefPtr<PreloaderBase> preload = mDocument->Preloads().LookupPreload(key);
1624 if (!preload) {
1625 return;
1628 preload->NotifyUsage();
1631 Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
1632 const SheetInfo& aInfo, const nsAString& aBuffer, uint32_t aLineNumber,
1633 nsICSSLoaderObserver* aObserver) {
1634 LOG(("css::Loader::LoadInlineStyle"));
1635 MOZ_ASSERT(aInfo.mContent);
1637 if (!mEnabled) {
1638 LOG_WARN((" Not enabled"));
1639 return Err(NS_ERROR_NOT_AVAILABLE);
1642 if (!mDocument) {
1643 return Err(NS_ERROR_NOT_INITIALIZED);
1646 MOZ_ASSERT(LinkStyle::FromNodeOrNull(aInfo.mContent),
1647 "Element is not a style linking element!");
1649 // Since we're not planning to load a URI, no need to hand a principal to the
1650 // load data or to CreateSheet().
1652 // Check IsAlternateSheet now, since it can mutate our document.
1653 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1654 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1656 // Use the document's base URL so that @import in the inline sheet picks up
1657 // the right base.
1658 nsIURI* baseURI = aInfo.mContent->GetBaseURI();
1659 nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
1660 nsIURI* originalURI = nullptr;
1662 MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
1664 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1665 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1666 ? aInfo.mTriggeringPrincipal.get()
1667 : loadingPrincipal;
1669 // We only cache sheets if in shadow trees, since regular document sheets are
1670 // likely to be unique.
1671 const bool isWorthCaching = aInfo.mContent->IsInShadowTree();
1672 RefPtr<StyleSheet> sheet;
1673 if (isWorthCaching) {
1674 sheet = LookupInlineSheetInCache(aBuffer);
1676 const bool sheetFromCache = !!sheet;
1677 if (!sheet) {
1678 sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
1679 SRIMetadata{});
1680 sheet->SetURIs(sheetURI, originalURI, baseURI);
1681 nsCOMPtr<nsIReferrerInfo> referrerInfo =
1682 ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
1683 sheet->SetReferrerInfo(referrerInfo);
1685 nsIPrincipal* sheetPrincipal = principal;
1686 if (aInfo.mTriggeringPrincipal) {
1687 // The triggering principal may be an expanded principal, which is safe to
1688 // use for URL security checks, but not as the loader principal for a
1689 // stylesheet. So treat this as principal inheritance, and downgrade if
1690 // necessary.
1691 sheetPrincipal =
1692 BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
1695 // We never actually load this, so just set its principal directly
1696 sheet->SetPrincipal(sheetPrincipal);
1699 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1700 isAlternate, aInfo.mIsExplicitlyEnabled);
1702 if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
1703 linkStyle->SetStyleSheet(sheet);
1705 if (sheet->IsComplete()) {
1706 InsertSheetInTree(*sheet, aInfo.mContent);
1709 Completed completed;
1710 if (sheetFromCache) {
1711 MOZ_ASSERT(sheet->IsComplete());
1712 completed = Completed::Yes;
1713 } else {
1714 auto data = MakeRefPtr<SheetLoadData>(
1715 this, aInfo.mTitle, nullptr, sheet, false, aInfo.mContent, isAlternate,
1716 matched, StylePreloadKind::None, aObserver, principal,
1717 aInfo.mReferrerInfo, aInfo.mContent);
1718 data->mLineNumber = aLineNumber;
1720 // Parse completion releases the load data.
1722 // Note that we need to parse synchronously, since the web expects that the
1723 // effects of inline stylesheets are visible immediately (aside from
1724 // @imports).
1725 NS_ConvertUTF16toUTF8 utf8(aBuffer);
1726 completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
1727 if (completed == Completed::Yes) {
1728 // TODO(emilio): Try to cache sheets with @import rules, maybe?
1729 if (isWorthCaching) {
1730 mInlineSheets.InsertOrUpdate(aBuffer, std::move(sheet));
1732 } else {
1733 data->mMustNotify = true;
1737 return LoadSheetResult{completed, isAlternate, matched};
1740 Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
1741 const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
1742 MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
1743 LOG(("css::Loader::LoadStyleLink"));
1744 LOG_URI(" Link uri: '%s'", aInfo.mURI);
1745 LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
1746 LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
1747 LOG((" Link alternate rel: %d", aInfo.mHasAlternateRel));
1749 if (!mEnabled) {
1750 LOG_WARN((" Not enabled"));
1751 return Err(NS_ERROR_NOT_AVAILABLE);
1754 if (!mDocument) {
1755 return Err(NS_ERROR_NOT_INITIALIZED);
1758 MOZ_ASSERT_IF(aInfo.mContent,
1759 aInfo.mContent->NodePrincipal() == mDocument->NodePrincipal());
1760 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1761 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1762 ? aInfo.mTriggeringPrincipal.get()
1763 : loadingPrincipal;
1765 nsINode* context = aInfo.mContent;
1766 if (!context) {
1767 context = mDocument;
1770 bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
1771 IsChromeURI(aInfo.mURI);
1772 LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
1773 MOZ_ASSERT_IF(syncLoad, !aObserver);
1775 nsresult rv =
1776 CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI, context,
1777 aInfo.mNonce, StylePreloadKind::None);
1778 if (NS_WARN_IF(NS_FAILED(rv))) {
1779 // Don't fire the error event if our document is loaded as data. We're
1780 // supposed to not even try to do loads in that case... Unfortunately, we
1781 // implement that via nsDataDocumentContentPolicy, which doesn't have a good
1782 // way to communicate back to us that _it_ is the thing that blocked the
1783 // load.
1784 if (aInfo.mContent && !mDocument->IsLoadedAsData()) {
1785 // Fire an async error event on it.
1786 RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
1787 new LoadBlockingAsyncEventDispatcher(aInfo.mContent, u"error"_ns,
1788 CanBubble::eNo,
1789 ChromeOnlyDispatch::eNo);
1790 loadBlockingAsyncDispatcher->PostDOMEvent();
1792 return Err(rv);
1795 // Check IsAlternateSheet now, since it can mutate our document and make
1796 // pending sheets go to the non-pending state.
1797 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1798 auto [sheet, state] = CreateSheet(aInfo, eAuthorSheetFeatures, syncLoad,
1799 StylePreloadKind::None);
1801 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1803 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1804 isAlternate, aInfo.mIsExplicitlyEnabled);
1806 if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
1807 linkStyle->SetStyleSheet(sheet);
1809 if (sheet->IsComplete()) {
1810 InsertSheetInTree(*sheet, aInfo.mContent);
1813 // We may get here with no content for Link: headers for example.
1814 MOZ_ASSERT(!aInfo.mContent || LinkStyle::FromNode(*aInfo.mContent),
1815 "If there is any node, it should be a LinkStyle");
1816 auto data = MakeRefPtr<SheetLoadData>(
1817 this, aInfo.mTitle, aInfo.mURI, sheet, syncLoad, aInfo.mContent,
1818 isAlternate, matched, StylePreloadKind::None, aObserver, principal,
1819 aInfo.mReferrerInfo, context);
1821 MaybeNotifyPreloadUsed(*data);
1823 if (state == SheetState::Complete) {
1824 LOG((" Sheet already complete: 0x%p", sheet.get()));
1825 if (aObserver || !mObservers.IsEmpty() || aInfo.mContent) {
1826 rv = PostLoadEvent(std::move(data));
1827 if (NS_FAILED(rv)) {
1828 return Err(rv);
1830 } else {
1831 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1832 // We don't have to notify anyone of this load, as it was complete, so
1833 // drop it intentionally.
1834 data->mIntentionallyDropped = true;
1835 #endif
1838 // The load hasn't been completed yet, will be done in PostLoadEvent.
1839 return LoadSheetResult{Completed::No, isAlternate, matched};
1842 // Now we need to actually load it.
1843 auto result = LoadSheetResult{Completed::No, isAlternate, matched};
1845 MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
1846 "These should better match!");
1848 // Load completion will free the data
1849 rv = LoadSheet(*data, state);
1850 if (NS_FAILED(rv)) {
1851 return Err(rv);
1854 if (!syncLoad) {
1855 data->mMustNotify = true;
1857 return result;
1860 static bool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) {
1861 if (!aData.mURI) {
1862 // Inline style; this won't have any ancestors
1863 MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?");
1864 return false;
1867 bool equal;
1868 if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
1869 return true;
1872 // Datas down the mNext chain have the same URI as aData, so we
1873 // don't have to compare to them. But they might have different
1874 // parents, and we have to check all of those.
1875 SheetLoadData* data = &aData;
1876 do {
1877 if (data->mParentData &&
1878 HaveAncestorDataWithURI(*data->mParentData, aURI)) {
1879 return true;
1882 data = data->mNext;
1883 } while (data);
1885 return false;
1888 nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
1889 SheetLoadData* aParentData, nsIURI* aURL,
1890 dom::MediaList* aMedia,
1891 LoaderReusableStyleSheets* aReusableSheets) {
1892 LOG(("css::Loader::LoadChildSheet"));
1893 MOZ_ASSERT(aURL, "Must have a URI to load");
1895 if (!mEnabled) {
1896 LOG_WARN((" Not enabled"));
1897 return NS_ERROR_NOT_AVAILABLE;
1900 LOG_URI(" Child uri: '%s'", aURL);
1902 nsCOMPtr<nsINode> owningNode;
1904 // Check for an associated document or shadow root: if none, don't bother
1905 // walking up the parent sheets.
1906 if (aParentSheet.GetAssociatedDocumentOrShadowRoot()) {
1907 StyleSheet* topSheet = &aParentSheet;
1908 while (StyleSheet* parent = topSheet->GetParentSheet()) {
1909 topSheet = parent;
1911 owningNode = topSheet->GetOwnerNode();
1914 nsINode* context = nullptr;
1915 if (owningNode) {
1916 context = owningNode;
1917 MOZ_ASSERT(LoaderPrincipal() == owningNode->NodePrincipal());
1918 } else if (mDocument) {
1919 context = mDocument;
1922 nsIPrincipal* principal = aParentSheet.Principal();
1923 nsresult rv = CheckContentPolicy(LoaderPrincipal(), principal, aURL, context,
1924 u""_ns, StylePreloadKind::None);
1925 if (NS_WARN_IF(NS_FAILED(rv))) {
1926 if (aParentData) {
1927 MarkLoadTreeFailed(*aParentData);
1929 return rv;
1932 nsCOMPtr<nsICSSLoaderObserver> observer;
1934 if (aParentData) {
1935 LOG((" Have a parent load"));
1936 // Check for cycles
1937 if (HaveAncestorDataWithURI(*aParentData, aURL)) {
1938 // Houston, we have a loop, blow off this child and pretend this never
1939 // happened
1940 LOG_ERROR((" @import cycle detected, dropping load"));
1941 return NS_OK;
1944 NS_ASSERTION(aParentData->mSheet == &aParentSheet,
1945 "Unexpected call to LoadChildSheet");
1946 } else {
1947 LOG((" No parent load; must be CSSOM"));
1948 // No parent load data, so the sheet will need to be notified when
1949 // we finish, if it can be, if we do the load asynchronously.
1950 observer = &aParentSheet;
1953 // Now that we know it's safe to load this (passes security check and not a
1954 // loop) do so.
1955 RefPtr<StyleSheet> sheet;
1956 SheetState state;
1957 if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
1958 state = SheetState::Complete;
1959 } else {
1960 // For now, use CORS_NONE for child sheets
1961 std::tie(sheet, state) = CreateSheet(
1962 aURL, nullptr, principal, aParentSheet.ParsingMode(), CORS_NONE,
1963 aParentData ? aParentData->mEncoding : nullptr,
1964 u""_ns, // integrity is only checked on main sheet
1965 aParentData && aParentData->mSyncLoad, StylePreloadKind::None);
1966 PrepareSheet(*sheet, u""_ns, u""_ns, aMedia, IsAlternate::No,
1967 IsExplicitlyEnabled::No);
1970 MOZ_ASSERT(sheet);
1971 InsertChildSheet(*sheet, aParentSheet);
1973 auto data = MakeRefPtr<SheetLoadData>(
1974 this, aURL, sheet, aParentData, observer, principal,
1975 aParentSheet.GetReferrerInfo(), context);
1977 MaybeNotifyPreloadUsed(*data);
1979 if (state == SheetState::Complete) {
1980 LOG((" Sheet already complete"));
1981 // We're completely done. No need to notify, even, since the
1982 // @import rule addition/modification will trigger the right style
1983 // changes automatically.
1984 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1985 data->mIntentionallyDropped = true;
1986 #endif
1987 return NS_OK;
1990 bool syncLoad = data->mSyncLoad;
1992 // Load completion will release the data
1993 rv = LoadSheet(*data, state);
1994 NS_ENSURE_SUCCESS(rv, rv);
1996 if (!syncLoad) {
1997 data->mMustNotify = true;
1999 return rv;
2002 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
2003 nsIURI* aURL, SheetParsingMode aParsingMode,
2004 UseSystemPrincipal aUseSystemPrincipal) {
2005 LOG(("css::Loader::LoadSheetSync"));
2006 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2007 return InternalLoadNonDocumentSheet(
2008 aURL, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2009 referrerInfo, nullptr, CORS_NONE, u""_ns);
2012 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2013 nsIURI* aURI, SheetParsingMode aParsingMode,
2014 UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
2015 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2016 return InternalLoadNonDocumentSheet(
2017 aURI, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2018 referrerInfo, aObserver, CORS_NONE, u""_ns);
2021 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2022 nsIURI* aURL, StylePreloadKind aPreloadKind,
2023 const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
2024 nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
2025 const nsAString& aIntegrity) {
2026 LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
2027 return InternalLoadNonDocumentSheet(
2028 aURL, aPreloadKind, eAuthorSheetFeatures, UseSystemPrincipal::No,
2029 aPreloadEncoding, aReferrerInfo, aObserver, aCORSMode, aIntegrity);
2032 Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
2033 nsIURI* aURL, StylePreloadKind aPreloadKind, SheetParsingMode aParsingMode,
2034 UseSystemPrincipal aUseSystemPrincipal, const Encoding* aPreloadEncoding,
2035 nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
2036 CORSMode aCORSMode, const nsAString& aIntegrity) {
2037 MOZ_ASSERT(aURL, "Must have a URI to load");
2038 MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
2039 "Shouldn't load system-principal sheets async");
2040 MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");
2042 LOG_URI(" Non-document sheet uri: '%s'", aURL);
2044 if (!mEnabled) {
2045 LOG_WARN((" Not enabled"));
2046 return Err(NS_ERROR_NOT_AVAILABLE);
2049 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
2050 nsIPrincipal* triggeringPrincipal = loadingPrincipal;
2051 nsresult rv = CheckContentPolicy(loadingPrincipal, triggeringPrincipal, aURL,
2052 mDocument, u""_ns, aPreloadKind);
2053 if (NS_FAILED(rv)) {
2054 return Err(rv);
2057 bool syncLoad = !aObserver;
2058 auto [sheet, state] =
2059 CreateSheet(aURL, nullptr, triggeringPrincipal, aParsingMode, aCORSMode,
2060 aPreloadEncoding, aIntegrity, syncLoad, aPreloadKind);
2062 PrepareSheet(*sheet, u""_ns, u""_ns, nullptr, IsAlternate::No,
2063 IsExplicitlyEnabled::No);
2065 auto data = MakeRefPtr<SheetLoadData>(
2066 this, aURL, sheet, syncLoad, aUseSystemPrincipal, aPreloadKind,
2067 aPreloadEncoding, aObserver, triggeringPrincipal, aReferrerInfo,
2068 mDocument);
2069 if (state == SheetState::Complete) {
2070 LOG((" Sheet already complete"));
2071 if (aObserver || !mObservers.IsEmpty()) {
2072 rv = PostLoadEvent(std::move(data));
2073 if (NS_FAILED(rv)) {
2074 return Err(rv);
2076 } else {
2077 // We don't have to notify anyone of this load, as it was complete, so
2078 // drop it intentionally.
2079 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
2080 data->mIntentionallyDropped = true;
2081 #endif
2083 return sheet;
2086 rv = LoadSheet(*data, state);
2087 if (NS_FAILED(rv)) {
2088 return Err(rv);
2090 if (aObserver) {
2091 data->mMustNotify = true;
2093 return sheet;
2096 nsresult Loader::PostLoadEvent(RefPtr<SheetLoadData> aLoadData) {
2097 LOG(("css::Loader::PostLoadEvent"));
2098 mPostedEvents.AppendElement(aLoadData);
2100 nsresult rv;
2101 RefPtr<SheetLoadData> runnable(aLoadData);
2102 if (mDocument) {
2103 rv = mDocument->Dispatch(TaskCategory::Other, runnable.forget());
2104 } else if (mDocGroup) {
2105 rv = mDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
2106 } else {
2107 rv = SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
2110 if (NS_FAILED(rv)) {
2111 NS_WARNING("failed to dispatch stylesheet load event");
2112 mPostedEvents.RemoveElement(aLoadData);
2113 } else {
2114 if (aLoadData->BlocksLoadEvent()) {
2115 IncrementOngoingLoadCount();
2118 // We want to notify the observer for this data.
2119 aLoadData->mMustNotify = true;
2120 aLoadData->mSheetAlreadyComplete = true;
2122 // If we get to this code, aSheet loaded correctly at some point, so
2123 // we can just schedule a load event and don't need to touch the
2124 // data's mLoadFailed. Note that we do this here and not from
2125 // inside our SheetComplete so that we don't end up running the load
2126 // event async.
2127 MOZ_ASSERT(!aLoadData->mLoadFailed, "Why are we marked as failed?");
2128 aLoadData->ScheduleLoadEventIfNeeded();
2131 return rv;
2134 void Loader::HandleLoadEvent(SheetLoadData& aEvent) {
2135 // XXXbz can't assert this yet.... May not have an observer because
2136 // we're unblocking the parser
2137 // NS_ASSERTION(aEvent->mObserver, "Must have observer");
2138 NS_ASSERTION(aEvent.mSheet, "Must have sheet");
2140 mPostedEvents.RemoveElement(&aEvent);
2141 SheetComplete(aEvent, NS_OK);
2144 void Loader::Stop() {
2145 if (mSheets) {
2146 mSheets->CancelLoadsForLoader(*this);
2149 auto arr = std::move(mPostedEvents);
2150 for (auto& data : arr) {
2151 data->mIsCancelled = true;
2155 bool Loader::HasPendingLoads() { return mOngoingLoadCount; }
2157 void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
2158 MOZ_ASSERT(aObserver, "Must have observer");
2159 mObservers.AppendElementUnlessExists(aObserver);
2162 void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
2163 mObservers.RemoveElement(aObserver);
2166 void Loader::StartDeferredLoads() {
2167 if (mSheets && mPendingLoadCount) {
2168 mSheets->StartDeferredLoadsForLoader(
2169 *this, SharedStyleSheetCache::StartLoads::Always);
2173 NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
2175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
2176 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets);
2177 for (auto iter = tmp->mInlineSheets.Iter(); !iter.Done(); iter.Next()) {
2178 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
2179 cb.NoteXPCOMChild(iter.UserData());
2181 for (nsCOMPtr<nsICSSLoaderObserver>& obs : tmp->mObservers.ForwardRange()) {
2182 ImplCycleCollectionTraverse(cb, obs, "mozilla::css::Loader.mObservers");
2184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2186 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
2187 if (tmp->mSheets) {
2188 if (tmp->mDocument) {
2189 tmp->DeregisterFromSheetCache();
2191 tmp->mSheets = nullptr;
2193 tmp->mInlineSheets.Clear();
2194 tmp->mObservers.Clear();
2195 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2197 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
2198 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
2200 size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2201 size_t n = aMallocSizeOf(this);
2203 n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
2205 n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
2206 for (const auto& entry : mInlineSheets) {
2207 n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2208 // If the sheet has a parent, then its parent will report it so we don't
2209 // have to worry about it here.
2210 const StyleSheet* sheet = entry.GetWeak();
2211 MOZ_ASSERT(!sheet->GetParentSheet(),
2212 "How did an @import rule end up here?");
2213 if (!sheet->GetOwnerNode()) {
2214 n += sheet->SizeOfIncludingThis(aMallocSizeOf);
2218 // Measurement of the following members may be added later if DMD finds it is
2219 // worthwhile:
2220 // - mPostedEvents: transient, and should be small
2222 // The following members aren't measured:
2223 // - mDocument, because it's a weak backpointer
2225 return n;
2228 nsIPrincipal* Loader::LoaderPrincipal() const {
2229 if (mDocument) {
2230 return mDocument->NodePrincipal();
2232 // Loaders without a document do system loads.
2233 return nsContentUtils::GetSystemPrincipal();
2236 nsIPrincipal* Loader::PartitionedPrincipal() const {
2237 if (mDocument && StaticPrefs::privacy_partition_network_state()) {
2238 return mDocument->PartitionedPrincipal();
2240 return LoaderPrincipal();
2243 bool Loader::ShouldBypassCache() const {
2244 if (!mDocument) {
2245 return false;
2247 RefPtr<nsILoadGroup> lg = mDocument->GetDocumentLoadGroup();
2248 if (!lg) {
2249 return false;
2251 nsLoadFlags flags;
2252 if (NS_FAILED(lg->GetLoadFlags(&flags))) {
2253 return false;
2255 return flags & (nsIRequest::LOAD_BYPASS_CACHE |
2256 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE);
2259 void Loader::BlockOnload() {
2260 if (mDocument) {
2261 mDocument->BlockOnload();
2265 void Loader::UnblockOnload(bool aFireSync) {
2266 if (mDocument) {
2267 mDocument->UnblockOnload(aFireSync);
2271 already_AddRefed<nsISerialEventTarget> Loader::DispatchTarget() {
2272 nsCOMPtr<nsISerialEventTarget> target;
2273 if (mDocument) {
2274 // If you change this, you may want to change StyleSheet::Replace
2275 target = mDocument->EventTargetFor(TaskCategory::Other);
2276 } else if (mDocGroup) {
2277 target = mDocGroup->EventTargetFor(TaskCategory::Other);
2278 } else {
2279 target = GetMainThreadSerialEventTarget();
2282 return target.forget();
2285 } // namespace css
2286 } // namespace mozilla