Bug 1864419 - Part 1: Factor out ArgumentsData array into container class r=jandem
[gecko.git] / layout / style / Loader.cpp
blobfa5ce7fbd866d33776ef34aeed98af78d557cc29
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 "nsIChildChannel.h"
24 #include "nsISupportsPriority.h"
25 #include "nsITimedChannel.h"
26 #include "nsICachingChannel.h"
27 #include "nsSyncLoadService.h"
28 #include "nsContentSecurityManager.h"
29 #include "nsCOMPtr.h"
30 #include "nsString.h"
31 #include "nsIContent.h"
32 #include "nsIContentInlines.h"
33 #include "nsICookieJarSettings.h"
34 #include "mozilla/dom/Document.h"
35 #include "nsIURI.h"
36 #include "nsNetUtil.h"
37 #include "nsContentUtils.h"
38 #include "nsIScriptSecurityManager.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsIHttpChannel.h"
41 #include "nsIHttpChannelInternal.h"
42 #include "nsIClassOfService.h"
43 #include "nsIScriptError.h"
44 #include "nsMimeTypes.h"
45 #include "nsICSSLoaderObserver.h"
46 #include "nsThreadUtils.h"
47 #include "nsGkAtoms.h"
48 #include "nsIThreadInternal.h"
49 #include "nsINetworkPredictor.h"
50 #include "nsQueryActor.h"
51 #include "nsStringStream.h"
52 #include "mozilla/dom/MediaList.h"
53 #include "mozilla/dom/ShadowRoot.h"
54 #include "mozilla/dom/URL.h"
55 #include "mozilla/net/UrlClassifierFeatureFactory.h"
56 #include "mozilla/AsyncEventDispatcher.h"
57 #include "mozilla/ProfilerLabels.h"
58 #include "mozilla/ServoBindings.h"
59 #include "mozilla/StyleSheet.h"
60 #include "mozilla/StyleSheetInlines.h"
61 #include "mozilla/ConsoleReportCollector.h"
62 #include "mozilla/ServoUtils.h"
63 #include "mozilla/css/StreamLoader.h"
64 #include "mozilla/SharedStyleSheetCache.h"
65 #include "mozilla/StaticPrefs_layout.h"
66 #include "mozilla/StaticPrefs_dom.h"
67 #include "mozilla/Try.h"
68 #include "ReferrerInfo.h"
70 #include "nsXULPrototypeCache.h"
72 #include "nsError.h"
74 #include "mozilla/dom/SRICheck.h"
76 #include "mozilla/Encoding.h"
78 using namespace mozilla::dom;
80 // 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
81 #define SNIFFING_BUFFER_SIZE 1024
83 /**
84 * OVERALL ARCHITECTURE
86 * The CSS Loader gets requests to load various sorts of style sheets:
87 * inline style from <style> elements, linked style, @import-ed child
88 * sheets, non-document sheets. The loader handles the following tasks:
89 * 1) Creation of the actual style sheet objects: CreateSheet()
90 * 2) setting of the right media, title, enabled state, etc on the
91 * sheet: PrepareSheet()
92 * 3) Insertion of the sheet in the proper cascade order:
93 * InsertSheetInTree() and InsertChildSheet()
94 * 4) Load of the sheet: LoadSheet() including security checks
95 * 5) Parsing of the sheet: ParseSheet()
96 * 6) Cleanup: SheetComplete()
98 * The detailed documentation for these functions is found with the
99 * function implementations.
101 * The following helper object is used:
102 * SheetLoadData -- a small class that is used to store all the
103 * information needed for the loading of a sheet;
104 * this class handles listening for the stream
105 * loader completion and also handles charset
106 * determination.
109 extern mozilla::LazyLogModule sCssLoaderLog;
110 mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
112 static mozilla::LazyLogModule gSriPRLog("SRI");
114 static bool IsPrivilegedURI(nsIURI* aURI) {
115 return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource");
118 #define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
119 #define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
120 #define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
121 #define LOG(args) LOG_DEBUG(args)
123 #define LOG_ERROR_ENABLED() \
124 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
125 #define LOG_WARN_ENABLED() \
126 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
127 #define LOG_DEBUG_ENABLED() \
128 MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
129 #define LOG_ENABLED() LOG_DEBUG_ENABLED()
131 #define LOG_URI(format, uri) \
132 PR_BEGIN_MACRO \
133 NS_ASSERTION(uri, "Logging null uri"); \
134 if (LOG_ENABLED()) { \
135 LOG((format, uri->GetSpecOrDefault().get())); \
137 PR_END_MACRO
139 // And some convenience strings...
140 static const char* const gStateStrings[] = {"NeedsParser", "Pending", "Loading",
141 "Complete"};
143 namespace mozilla {
145 SheetLoadDataHashKey::SheetLoadDataHashKey(const css::SheetLoadData& aLoadData)
146 : mURI(aLoadData.mURI),
147 mPrincipal(aLoadData.mTriggeringPrincipal),
148 mLoaderPrincipal(aLoadData.mLoader->LoaderPrincipal()),
149 mPartitionPrincipal(aLoadData.mLoader->PartitionedPrincipal()),
150 mEncodingGuess(aLoadData.mGuessedEncoding),
151 mCORSMode(aLoadData.mSheet->GetCORSMode()),
152 mParsingMode(aLoadData.mSheet->ParsingMode()),
153 mCompatMode(aLoadData.mCompatMode),
154 mIsLinkRelPreload(aLoadData.IsLinkRelPreload()) {
155 MOZ_COUNT_CTOR(SheetLoadDataHashKey);
156 MOZ_ASSERT(mURI);
157 MOZ_ASSERT(mPrincipal);
158 MOZ_ASSERT(mLoaderPrincipal);
159 MOZ_ASSERT(mPartitionPrincipal);
160 aLoadData.mSheet->GetIntegrity(mSRIMetadata);
163 bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
165 bool eq;
166 if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
167 return false;
171 LOG_URI("KeyEquals(%s)\n", mURI);
173 if (mParsingMode != aKey.mParsingMode) {
174 LOG((" > Parsing mode mismatch\n"));
175 return false;
178 // Chrome URIs ignore everything else.
179 if (IsPrivilegedURI(mURI)) {
180 return true;
183 if (!mPrincipal->Equals(aKey.mPrincipal)) {
184 LOG((" > Principal mismatch\n"));
185 return false;
188 // We only check for partition principal equality if any of the loads are
189 // triggered by a document rather than e.g. an extension (which have different
190 // origins than the loader principal).
191 if (mPrincipal->Equals(mLoaderPrincipal) ||
192 aKey.mPrincipal->Equals(aKey.mLoaderPrincipal)) {
193 if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
194 LOG((" > Partition principal mismatch\n"));
195 return false;
199 if (mCORSMode != aKey.mCORSMode) {
200 LOG((" > CORS mismatch\n"));
201 return false;
204 if (mCompatMode != aKey.mCompatMode) {
205 LOG((" > Quirks mismatch\n"));
206 return false;
209 // If encoding differs, then don't reuse the cache.
211 // TODO(emilio): When the encoding is determined from the request (either
212 // BOM or Content-Length or @charset), we could do a bit better,
213 // theoretically.
214 if (mEncodingGuess != aKey.mEncodingGuess) {
215 LOG((" > Encoding guess mismatch\n"));
216 return false;
219 // Consuming stylesheet tags must never coalesce to <link preload> initiated
220 // speculative loads with a weaker SRI hash or its different value. This
221 // check makes sure that regular loads will never find such a weaker preload
222 // and rather start a new, independent load with new, stronger SRI checker
223 // set up, so that integrity is ensured.
224 if (mIsLinkRelPreload != aKey.mIsLinkRelPreload) {
225 const auto& linkPreloadMetadata =
226 mIsLinkRelPreload ? mSRIMetadata : aKey.mSRIMetadata;
227 const auto& consumerPreloadMetadata =
228 mIsLinkRelPreload ? aKey.mSRIMetadata : mSRIMetadata;
230 if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
231 LOG((" > Preload SRI metadata mismatch\n"));
232 return false;
236 return true;
239 namespace css {
241 static NotNull<const Encoding*> GetFallbackEncoding(
242 Loader& aLoader, nsINode* aOwningNode,
243 const Encoding* aPreloadOrParentDataEncoding) {
244 const Encoding* encoding;
245 // Now try the charset on the <link> or processing instruction
246 // that loaded us
247 if (aOwningNode) {
248 nsAutoString label16;
249 LinkStyle::FromNode(*aOwningNode)->GetCharset(label16);
250 encoding = Encoding::ForLabel(label16);
251 if (encoding) {
252 return WrapNotNull(encoding);
256 // Try preload or parent sheet encoding.
257 if (aPreloadOrParentDataEncoding) {
258 return WrapNotNull(aPreloadOrParentDataEncoding);
261 if (auto* doc = aLoader.GetDocument()) {
262 // Use the document charset.
263 return doc->GetDocumentCharacterSet();
266 return UTF_8_ENCODING;
269 /********************************
270 * SheetLoadData implementation *
271 ********************************/
272 NS_IMPL_ISUPPORTS(SheetLoadData, nsISupports)
274 SheetLoadData::SheetLoadData(
275 css::Loader* aLoader, const nsAString& aTitle, nsIURI* aURI,
276 StyleSheet* aSheet, SyncLoad aSyncLoad, nsINode* aOwningNode,
277 IsAlternate aIsAlternate, MediaMatched aMediaMatches,
278 StylePreloadKind aPreloadKind, nsICSSLoaderObserver* aObserver,
279 nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
280 const nsAString& aNonce)
281 : mLoader(aLoader),
282 mTitle(aTitle),
283 mEncoding(nullptr),
284 mURI(aURI),
285 mSheet(aSheet),
286 mPendingChildren(0),
287 mSyncLoad(aSyncLoad == SyncLoad::Yes),
288 mIsNonDocumentSheet(false),
289 mIsChildSheet(aSheet->GetParentSheet()),
290 mIsBeingParsed(false),
291 mIsLoading(false),
292 mIsCancelled(false),
293 mMustNotify(false),
294 mHadOwnerNode(!!aOwningNode),
295 mWasAlternate(aIsAlternate == IsAlternate::Yes),
296 mMediaMatched(aMediaMatches == MediaMatched::Yes),
297 mUseSystemPrincipal(false),
298 mSheetAlreadyComplete(false),
299 mIsCrossOriginNoCORS(false),
300 mBlockResourceTiming(false),
301 mLoadFailed(false),
302 mPreloadKind(aPreloadKind),
303 mObserver(aObserver),
304 mTriggeringPrincipal(aTriggeringPrincipal),
305 mReferrerInfo(aReferrerInfo),
306 mNonce(aNonce),
307 mGuessedEncoding(GetFallbackEncoding(*aLoader, aOwningNode, nullptr)),
308 mCompatMode(aLoader->CompatMode(aPreloadKind)) {
309 MOZ_ASSERT(!aOwningNode || dom::LinkStyle::FromNode(*aOwningNode),
310 "Must implement LinkStyle");
311 MOZ_ASSERT(mTriggeringPrincipal);
312 MOZ_ASSERT(mLoader, "Must have a loader!");
315 SheetLoadData::SheetLoadData(css::Loader* aLoader, nsIURI* aURI,
316 StyleSheet* aSheet, SheetLoadData* aParentData,
317 nsICSSLoaderObserver* aObserver,
318 nsIPrincipal* aTriggeringPrincipal,
319 nsIReferrerInfo* aReferrerInfo)
320 : mLoader(aLoader),
321 mEncoding(nullptr),
322 mURI(aURI),
323 mSheet(aSheet),
324 mParentData(aParentData),
325 mPendingChildren(0),
326 mSyncLoad(aParentData && aParentData->mSyncLoad),
327 mIsNonDocumentSheet(aParentData && aParentData->mIsNonDocumentSheet),
328 mIsChildSheet(aSheet->GetParentSheet()),
329 mIsBeingParsed(false),
330 mIsLoading(false),
331 mIsCancelled(false),
332 mMustNotify(false),
333 mHadOwnerNode(false),
334 mWasAlternate(false),
335 mMediaMatched(true),
336 mUseSystemPrincipal(aParentData && aParentData->mUseSystemPrincipal),
337 mSheetAlreadyComplete(false),
338 mIsCrossOriginNoCORS(false),
339 mBlockResourceTiming(false),
340 mLoadFailed(false),
341 mPreloadKind(StylePreloadKind::None),
342 mObserver(aObserver),
343 mTriggeringPrincipal(aTriggeringPrincipal),
344 mReferrerInfo(aReferrerInfo),
345 mNonce(u""_ns),
346 mGuessedEncoding(GetFallbackEncoding(
347 *aLoader, nullptr, aParentData ? aParentData->mEncoding : nullptr)),
348 mCompatMode(aLoader->CompatMode(mPreloadKind)) {
349 MOZ_ASSERT(mLoader, "Must have a loader!");
350 MOZ_ASSERT(mTriggeringPrincipal);
351 MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
352 "Shouldn't use system principal for async loads");
353 MOZ_ASSERT_IF(aParentData, mIsChildSheet);
356 SheetLoadData::SheetLoadData(
357 css::Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet, SyncLoad aSyncLoad,
358 UseSystemPrincipal aUseSystemPrincipal, StylePreloadKind aPreloadKind,
359 const Encoding* aPreloadEncoding, nsICSSLoaderObserver* aObserver,
360 nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
361 const nsAString& aNonce)
362 : mLoader(aLoader),
363 mEncoding(nullptr),
364 mURI(aURI),
365 mSheet(aSheet),
366 mPendingChildren(0),
367 mSyncLoad(aSyncLoad == SyncLoad::Yes),
368 mIsNonDocumentSheet(true),
369 mIsChildSheet(false),
370 mIsBeingParsed(false),
371 mIsLoading(false),
372 mIsCancelled(false),
373 mMustNotify(false),
374 mHadOwnerNode(false),
375 mWasAlternate(false),
376 mMediaMatched(true),
377 mUseSystemPrincipal(aUseSystemPrincipal == UseSystemPrincipal::Yes),
378 mSheetAlreadyComplete(false),
379 mIsCrossOriginNoCORS(false),
380 mBlockResourceTiming(false),
381 mLoadFailed(false),
382 mPreloadKind(aPreloadKind),
383 mObserver(aObserver),
384 mTriggeringPrincipal(aTriggeringPrincipal),
385 mReferrerInfo(aReferrerInfo),
386 mNonce(aNonce),
387 mGuessedEncoding(
388 GetFallbackEncoding(*aLoader, nullptr, aPreloadEncoding)),
389 mCompatMode(aLoader->CompatMode(aPreloadKind)) {
390 MOZ_ASSERT(mTriggeringPrincipal);
391 MOZ_ASSERT(mLoader, "Must have a loader!");
392 MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
393 "Shouldn't use system principal for async loads");
394 MOZ_ASSERT(!aSheet->GetParentSheet(), "Shouldn't be used for child loads");
397 SheetLoadData::~SheetLoadData() {
398 MOZ_RELEASE_ASSERT(mSheetCompleteCalled || mIntentionallyDropped,
399 "Should always call SheetComplete, except when "
400 "dropping the load");
403 RefPtr<StyleSheet> SheetLoadData::ValueForCache() const {
404 // We need to clone the sheet on insertion to the cache because otherwise the
405 // stylesheets can keep full windows alive via either their JS wrapper, or via
406 // StyleSheet::mRelevantGlobal.
408 // If this ever changes, then you also need to fix up the memory reporting in
409 // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports.
410 return mSheet->Clone(nullptr, nullptr);
413 void SheetLoadData::PrioritizeAsPreload(nsIChannel* aChannel) {
414 if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
415 sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
419 void SheetLoadData::StartPendingLoad() {
420 mLoader->LoadSheet(*this, Loader::SheetState::NeedsParser, 0,
421 Loader::PendingLoad::Yes);
424 already_AddRefed<AsyncEventDispatcher>
425 SheetLoadData::PrepareLoadEventIfNeeded() {
426 nsCOMPtr<nsINode> node = mSheet->GetOwnerNode();
427 if (!node) {
428 return nullptr;
430 MOZ_ASSERT(!RootLoadData().IsLinkRelPreload(),
431 "rel=preload handled elsewhere");
432 RefPtr<AsyncEventDispatcher> dispatcher;
433 if (BlocksLoadEvent()) {
434 dispatcher = new LoadBlockingAsyncEventDispatcher(
435 node, mLoadFailed ? u"error"_ns : u"load"_ns, CanBubble::eNo,
436 ChromeOnlyDispatch::eNo);
437 } else {
438 // Fire the load event on the link, but don't block the document load.
439 dispatcher =
440 new AsyncEventDispatcher(node, mLoadFailed ? u"error"_ns : u"load"_ns,
441 CanBubble::eNo, ChromeOnlyDispatch::eNo);
443 return dispatcher.forget();
446 nsINode* SheetLoadData::GetRequestingNode() const {
447 if (nsINode* node = mSheet->GetOwnerNodeOfOutermostSheet()) {
448 return node;
450 return mLoader->GetDocument();
453 /*********************
454 * Style sheet reuse *
455 *********************/
457 bool LoaderReusableStyleSheets::FindReusableStyleSheet(
458 nsIURI* aURL, RefPtr<StyleSheet>& aResult) {
459 MOZ_ASSERT(aURL);
460 for (size_t i = mReusableSheets.Length(); i > 0; --i) {
461 size_t index = i - 1;
462 bool sameURI;
463 MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
464 nsresult rv =
465 aURL->Equals(mReusableSheets[index]->GetOriginalURI(), &sameURI);
466 if (!NS_FAILED(rv) && sameURI) {
467 aResult = mReusableSheets[index];
468 mReusableSheets.RemoveElementAt(index);
469 return true;
472 return false;
474 /*************************
475 * Loader Implementation *
476 *************************/
478 Loader::Loader()
479 : mDocument(nullptr),
480 mDocumentCompatMode(eCompatibility_FullStandards),
481 mReporter(new ConsoleReportCollector()) {}
483 Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }
485 Loader::Loader(Document* aDocument) : Loader() {
486 MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
487 mDocument = aDocument;
488 mIsDocumentAssociated = true;
489 mDocumentCompatMode = aDocument->GetCompatibilityMode();
490 mSheets = SharedStyleSheetCache::Get();
491 RegisterInSheetCache();
494 // Note: no real need to revoke our stylesheet loaded events -- they hold strong
495 // references to us, so if we're going away that means they're all done.
496 Loader::~Loader() = default;
498 void Loader::RegisterInSheetCache() {
499 MOZ_ASSERT(mDocument);
500 MOZ_ASSERT(mSheets);
502 mSheets->RegisterLoader(*this);
505 void Loader::DeregisterFromSheetCache() {
506 MOZ_ASSERT(mDocument);
507 MOZ_ASSERT(mSheets);
509 mSheets->CancelLoadsForLoader(*this);
510 mSheets->UnregisterLoader(*this);
513 void Loader::DropDocumentReference() {
514 // Flush out pending datas just so we don't leak by accident.
515 if (mSheets) {
516 DeregisterFromSheetCache();
518 mDocument = nullptr;
521 void Loader::DocumentStyleSheetSetChanged() {
522 MOZ_ASSERT(mDocument);
524 // start any pending alternates that aren't alternates anymore
525 mSheets->StartPendingLoadsForLoader(*this, [&](const SheetLoadData& aData) {
526 return IsAlternateSheet(aData.mTitle, true) != IsAlternate::Yes;
530 static const char kCharsetSym[] = "@charset \"";
532 static bool GetCharsetFromData(const char* aStyleSheetData,
533 uint32_t aDataLength, nsACString& aCharset) {
534 aCharset.Truncate();
535 if (aDataLength <= sizeof(kCharsetSym) - 1) return false;
537 if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
538 return false;
541 for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
542 char c = aStyleSheetData[i];
543 if (c == '"') {
544 ++i;
545 if (i < aDataLength && aStyleSheetData[i] == ';') {
546 return true;
548 // fail
549 break;
551 aCharset.Append(c);
554 // Did not see end quote or semicolon
555 aCharset.Truncate();
556 return false;
559 NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
560 const nsACString& aSegment, nsIChannel* aChannel) const {
561 const Encoding* encoding;
562 nsAutoCString label;
564 // Check HTTP
565 if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
566 encoding = Encoding::ForLabel(label);
567 if (encoding) {
568 return WrapNotNull(encoding);
572 // Check @charset
573 auto sniffingLength = aSegment.Length();
574 if (sniffingLength > SNIFFING_BUFFER_SIZE) {
575 sniffingLength = SNIFFING_BUFFER_SIZE;
577 if (GetCharsetFromData(aSegment.BeginReading(), sniffingLength, label)) {
578 encoding = Encoding::ForLabel(label);
579 if (encoding == UTF_16BE_ENCODING || encoding == UTF_16LE_ENCODING) {
580 return UTF_8_ENCODING;
582 if (encoding) {
583 return WrapNotNull(encoding);
586 return mGuessedEncoding;
589 static nsresult VerifySheetIntegrity(const SRIMetadata& aMetadata,
590 nsIChannel* aChannel,
591 const nsACString& aFirst,
592 const nsACString& aSecond,
593 const nsACString& aSourceFileURI,
594 nsIConsoleReportCollector* aReporter) {
595 NS_ENSURE_ARG_POINTER(aReporter);
597 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), LogLevel::Debug)) {
598 nsAutoCString requestURL;
599 nsCOMPtr<nsIURI> originalURI;
600 if (aChannel &&
601 NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
602 originalURI) {
603 originalURI->GetAsciiSpec(requestURL);
605 MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
606 ("VerifySheetIntegrity (unichar stream)"));
609 SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
610 nsresult rv =
611 verifier.Update(aFirst.Length(), (const uint8_t*)aFirst.BeginReading());
612 NS_ENSURE_SUCCESS(rv, rv);
613 rv =
614 verifier.Update(aSecond.Length(), (const uint8_t*)aSecond.BeginReading());
615 NS_ENSURE_SUCCESS(rv, rv);
617 return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
620 static bool AllLoadsCanceled(const SheetLoadData& aData) {
621 const SheetLoadData* data = &aData;
622 do {
623 if (!data->IsCancelled()) {
624 return false;
626 } while ((data = data->mNext));
627 return true;
631 * Stream completion code shared by Stylo and the old style system.
633 * Here we need to check that the load did not give us an http error
634 * page and check the mimetype on the channel to make sure we're not
635 * loading non-text/css data in standards mode.
637 nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
638 const nsACString& aBytes1,
639 const nsACString& aBytes2,
640 nsIChannel* aChannel) {
641 LOG(("SheetLoadData::VerifySheetReadyToParse"));
642 NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
644 if (AllLoadsCanceled(*this)) {
645 LOG_WARN((" All loads are canceled, dropping"));
646 mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
647 return NS_OK;
650 if (NS_FAILED(aStatus)) {
651 LOG_WARN(
652 (" Load failed: status 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
653 // Handle sheet not loading error because source was a tracking URL (or
654 // fingerprinting, cryptomining, etc).
655 // We make a note of this sheet node by including it in a dedicated
656 // array of blocked tracking nodes under its parent document.
658 // Multiple sheet load instances might be tied to this request,
659 // we annotate each one linked to a valid owning element (node).
660 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
661 aStatus)) {
662 if (Document* doc = mLoader->GetDocument()) {
663 for (SheetLoadData* data = this; data; data = data->mNext) {
664 // owner node may be null but AddBlockTrackingNode can cope
665 doc->AddBlockedNodeByClassifier(data->mSheet->GetOwnerNode());
669 mLoader->SheetComplete(*this, aStatus);
670 return NS_OK;
673 if (!aChannel) {
674 mLoader->SheetComplete(*this, NS_OK);
675 return NS_OK;
678 nsCOMPtr<nsIURI> originalURI;
679 aChannel->GetOriginalURI(getter_AddRefs(originalURI));
681 // If the channel's original URI is "chrome:", we want that, since
682 // the observer code in nsXULPrototypeCache depends on chrome stylesheets
683 // having a chrome URI. (Whether or not chrome stylesheets come through
684 // this codepath seems nondeterministic.)
685 // Otherwise we want the potentially-HTTP-redirected URI.
686 nsCOMPtr<nsIURI> channelURI;
687 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
689 if (!channelURI || !originalURI) {
690 NS_ERROR("Someone just violated the nsIRequest contract");
691 LOG_WARN((" Channel without a URI. Bad!"));
692 mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
693 return NS_OK;
696 nsCOMPtr<nsIPrincipal> principal;
697 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
698 nsresult result = NS_ERROR_NOT_AVAILABLE;
699 if (secMan) { // Could be null if we already shut down
700 if (mUseSystemPrincipal) {
701 result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
702 } else {
703 result = secMan->GetChannelResultPrincipal(aChannel,
704 getter_AddRefs(principal));
708 if (NS_FAILED(result)) {
709 LOG_WARN((" Couldn't get principal"));
710 mLoader->SheetComplete(*this, result);
711 return NS_OK;
714 mSheet->SetPrincipal(principal);
716 if (mSheet->GetCORSMode() == CORS_NONE &&
717 !mTriggeringPrincipal->Subsumes(principal)) {
718 mIsCrossOriginNoCORS = true;
721 // If it's an HTTP channel, we want to make sure this is not an
722 // error document we got.
723 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
724 bool requestSucceeded;
725 result = httpChannel->GetRequestSucceeded(&requestSucceeded);
726 if (NS_SUCCEEDED(result) && !requestSucceeded) {
727 LOG((" Load returned an error page"));
728 mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
729 return NS_OK;
732 nsCString sourceMapURL;
733 if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
734 mSheet->SetSourceMapURL(std::move(sourceMapURL));
738 nsAutoCString contentType;
739 aChannel->GetContentType(contentType);
741 // In standards mode, a style sheet must have one of these MIME
742 // types to be processed at all. In quirks mode, we accept any
743 // MIME type, but only if the style sheet is same-origin with the
744 // requesting document or parent sheet. See bug 524223.
746 bool validType = contentType.EqualsLiteral("text/css") ||
747 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
748 contentType.IsEmpty();
750 if (!validType) {
751 const char* errorMessage;
752 uint32_t errorFlag;
753 bool sameOrigin = true;
755 bool subsumed;
756 result = mTriggeringPrincipal->Subsumes(principal, &subsumed);
757 if (NS_FAILED(result) || !subsumed) {
758 sameOrigin = false;
761 if (sameOrigin && mCompatMode == eCompatibility_NavQuirks) {
762 errorMessage = "MimeNotCssWarn";
763 errorFlag = nsIScriptError::warningFlag;
764 } else {
765 errorMessage = "MimeNotCss";
766 errorFlag = nsIScriptError::errorFlag;
769 AutoTArray<nsString, 2> strings;
770 CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
771 CopyASCIItoUTF16(contentType, *strings.AppendElement());
773 nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
774 nsContentUtils::ReportToConsole(
775 errorFlag, "CSS Loader"_ns, mLoader->mDocument,
776 nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);
778 if (errorFlag == nsIScriptError::errorFlag) {
779 LOG_WARN(
780 (" Ignoring sheet with improper MIME type %s", contentType.get()));
781 mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
782 return NS_OK;
786 SRIMetadata sriMetadata;
787 mSheet->GetIntegrity(sriMetadata);
788 if (!sriMetadata.IsEmpty()) {
789 nsAutoCString sourceUri;
790 if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
791 mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
793 nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
794 sourceUri, mLoader->mReporter);
796 nsCOMPtr<nsILoadGroup> loadGroup;
797 aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
798 if (loadGroup) {
799 mLoader->mReporter->FlushConsoleReports(loadGroup);
800 } else {
801 mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
804 if (NS_FAILED(rv)) {
805 LOG((" Load was blocked by SRI"));
806 MOZ_LOG(gSriPRLog, LogLevel::Debug,
807 ("css::Loader::OnStreamComplete, bad metadata"));
808 mLoader->SheetComplete(*this, NS_ERROR_SRI_CORRUPT);
809 return NS_OK;
813 // Enough to set the URIs on mSheet, since any sibling datas we have share
814 // the same mInner as mSheet and will thus get the same URI.
815 mSheet->SetURIs(channelURI, originalURI, channelURI);
817 ReferrerPolicy policy =
818 nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
819 nsCOMPtr<nsIReferrerInfo> referrerInfo =
820 ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);
822 mSheet->SetReferrerInfo(referrerInfo);
823 return NS_OK_PARSE_SHEET;
826 Loader::IsAlternate Loader::IsAlternateSheet(const nsAString& aTitle,
827 bool aHasAlternateRel) {
828 // A sheet is alternate if it has a nonempty title that doesn't match the
829 // currently selected style set. But if there _is_ no currently selected
830 // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
831 // is nonempty, we should select the style set corresponding to aTitle, since
832 // that's a preferred sheet.
833 if (aTitle.IsEmpty()) {
834 return IsAlternate::No;
837 if (mDocument) {
838 const nsString& currentSheetSet = mDocument->GetCurrentStyleSheetSet();
839 if (!aHasAlternateRel && currentSheetSet.IsEmpty()) {
840 // There's no preferred set yet, and we now have a sheet with a title.
841 // Make that be the preferred set.
842 // FIXME(emilio): This is kinda wild, can we do it somewhere else?
843 mDocument->SetPreferredStyleSheetSet(aTitle);
844 // We're definitely not an alternate. Also, beware that at this point
845 // currentSheetSet may dangle.
846 return IsAlternate::No;
849 if (aTitle.Equals(currentSheetSet)) {
850 return IsAlternate::No;
854 return IsAlternate::Yes;
857 nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
858 nsIPrincipal* aTriggeringPrincipal,
859 nsIURI* aTargetURI,
860 nsINode* aRequestingNode,
861 const nsAString& aNonce,
862 StylePreloadKind aPreloadKind) {
863 // When performing a system load don't consult content policies.
864 if (!mDocument) {
865 return NS_OK;
868 nsContentPolicyType contentPolicyType =
869 aPreloadKind == StylePreloadKind::None
870 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
871 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
873 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
874 aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
875 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
876 secCheckLoadInfo->SetCspNonce(aNonce);
878 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
879 nsresult rv = NS_CheckContentLoadPolicy(aTargetURI, secCheckLoadInfo,
880 "text/css"_ns, &shouldLoad,
881 nsContentUtils::GetContentPolicy());
882 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
883 // Asynchronously notify observers (e.g devtools) of CSP failure.
884 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
885 "Loader::NotifyOnFailedCheckPolicy",
886 [targetURI = RefPtr<nsIURI>(aTargetURI),
887 requestingNode = RefPtr<nsINode>(aRequestingNode),
888 contentPolicyType]() {
889 nsCOMPtr<nsIChannel> channel;
890 NS_NewChannel(
891 getter_AddRefs(channel), targetURI, requestingNode,
892 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
893 contentPolicyType);
894 NS_SetRequestBlockingReason(
895 channel, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
896 nsCOMPtr<nsIObserverService> obsService =
897 services::GetObserverService();
898 if (obsService) {
899 obsService->NotifyObservers(
900 channel, "http-on-failed-opening-request", nullptr);
902 }));
903 return NS_ERROR_CONTENT_BLOCKED;
905 return NS_OK;
908 static void RecordUseCountersIfNeeded(Document* aDoc,
909 const StyleSheet& aSheet) {
910 if (!aDoc) {
911 return;
913 const StyleUseCounters* docCounters = aDoc->GetStyleUseCounters();
914 if (!docCounters) {
915 return;
917 if (aSheet.URLData()->ChromeRulesEnabled()) {
918 return;
920 const auto* sheetCounters = aSheet.GetStyleUseCounters();
921 if (!sheetCounters) {
922 return;
924 Servo_UseCounters_Merge(docCounters, sheetCounters);
925 aDoc->MaybeWarnAboutZoom();
929 * CreateSheet() creates a StyleSheet object for the given URI.
931 * We check for an existing style sheet object for that uri in various caches
932 * and clone it if we find it. Cloned sheets will have the title/media/enabled
933 * state of the sheet they are clones off; make sure to call PrepareSheet() on
934 * the result of CreateSheet().
936 std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
937 nsIURI* aURI, nsIContent* aLinkingContent,
938 nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode aParsingMode,
939 CORSMode aCORSMode, const Encoding* aPreloadOrParentDataEncoding,
940 const nsAString& aIntegrity, bool aSyncLoad,
941 StylePreloadKind aPreloadKind) {
942 MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
943 LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
945 SRIMetadata sriMetadata;
946 if (!aIntegrity.IsEmpty()) {
947 MOZ_LOG(gSriPRLog, LogLevel::Debug,
948 ("css::Loader::CreateSheet, integrity=%s",
949 NS_ConvertUTF16toUTF8(aIntegrity).get()));
950 nsAutoCString sourceUri;
951 if (mDocument && mDocument->GetDocumentURI()) {
952 mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
954 SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
957 if (mSheets) {
958 SheetLoadDataHashKey key(aURI, aTriggeringPrincipal, LoaderPrincipal(),
959 PartitionedPrincipal(),
960 GetFallbackEncoding(*this, aLinkingContent,
961 aPreloadOrParentDataEncoding),
962 aCORSMode, aParsingMode, CompatMode(aPreloadKind),
963 sriMetadata, aPreloadKind);
964 auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
965 if (cacheResult.mState != CachedSubResourceState::Miss) {
966 SheetState sheetState = SheetState::Complete;
967 RefPtr<StyleSheet> sheet;
968 if (cacheResult.mCompleteValue) {
969 sheet = cacheResult.mCompleteValue->Clone(nullptr, nullptr);
970 mDocument->SetDidHitCompleteSheetCache();
971 RecordUseCountersIfNeeded(mDocument, *sheet);
972 mLoadsPerformed.PutEntry(key);
973 } else {
974 MOZ_ASSERT(cacheResult.mLoadingOrPendingValue);
975 sheet = cacheResult.mLoadingOrPendingValue->ValueForCache();
976 sheetState = cacheResult.mState == CachedSubResourceState::Loading
977 ? SheetState::Loading
978 : SheetState::Pending;
980 LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
981 return {std::move(sheet), sheetState};
985 nsIURI* sheetURI = aURI;
986 nsIURI* baseURI = aURI;
987 nsIURI* originalURI = aURI;
989 auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
990 sheet->SetURIs(sheetURI, originalURI, baseURI);
991 nsCOMPtr<nsIReferrerInfo> referrerInfo =
992 ReferrerInfo::CreateForExternalCSSResources(sheet);
993 sheet->SetReferrerInfo(referrerInfo);
994 LOG((" Needs parser"));
995 return {std::move(sheet), SheetState::NeedsParser};
998 static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
999 const Document* aDocument) {
1000 if (!aMediaList || !aDocument) {
1001 return Loader::MediaMatched::Yes;
1004 if (aMediaList->Matches(*aDocument)) {
1005 return Loader::MediaMatched::Yes;
1008 return Loader::MediaMatched::No;
1012 * PrepareSheet() handles setting the media and title on the sheet, as
1013 * well as setting the enabled state based on the title and whether
1014 * the sheet had "alternate" in its rel.
1016 Loader::MediaMatched Loader::PrepareSheet(
1017 StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
1018 MediaList* aMediaList, IsAlternate aIsAlternate,
1019 IsExplicitlyEnabled aIsExplicitlyEnabled) {
1020 RefPtr<MediaList> mediaList(aMediaList);
1022 if (!aMediaString.IsEmpty()) {
1023 NS_ASSERTION(!aMediaList,
1024 "must not provide both aMediaString and aMediaList");
1025 mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
1028 aSheet.SetMedia(do_AddRef(mediaList));
1030 aSheet.SetTitle(aTitle);
1031 aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
1032 aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
1033 return MediaListMatches(mediaList, mDocument);
1037 * InsertSheetInTree handles ordering of sheets in the document or shadow root.
1039 * Here we have two types of sheets -- those with linking elements and
1040 * those without. The latter are loaded by Link: headers, and are only added to
1041 * the document.
1043 * The following constraints are observed:
1044 * 1) Any sheet with a linking element comes after all sheets without
1045 * linking elements
1046 * 2) Sheets without linking elements are inserted in the order in
1047 * which the inserting requests come in, since all of these are
1048 * inserted during header data processing in the content sink
1049 * 3) Sheets with linking elements are ordered based on document order
1050 * as determined by CompareDocumentPosition.
1052 void Loader::InsertSheetInTree(StyleSheet& aSheet) {
1053 LOG(("css::Loader::InsertSheetInTree"));
1054 MOZ_ASSERT(mDocument, "Must have a document to insert into");
1056 nsINode* owningNode = aSheet.GetOwnerNode();
1057 MOZ_ASSERT(!owningNode || owningNode->IsInUncomposedDoc() ||
1058 owningNode->IsInShadowTree(),
1059 "Why would we insert it anywhere?");
1060 ShadowRoot* shadow = owningNode ? owningNode->GetContainingShadow() : nullptr;
1062 auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
1063 : static_cast<DocumentOrShadowRoot&>(*mDocument);
1065 // XXX Need to cancel pending sheet loads for this element, if any
1067 int32_t sheetCount = target.SheetCount();
1070 * Start the walk at the _end_ of the list, since in the typical
1071 * case we'll just want to append anyway. We want to break out of
1072 * the loop when insertionPoint points to just before the index we
1073 * want to insert at. In other words, when we leave the loop
1074 * insertionPoint is the index of the stylesheet that immediately
1075 * precedes the one we're inserting.
1077 int32_t insertionPoint = sheetCount - 1;
1078 for (; insertionPoint >= 0; --insertionPoint) {
1079 nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
1080 if (sheetOwner && !owningNode) {
1081 // Keep moving; all sheets with a sheetOwner come after all
1082 // sheets without a linkingNode
1083 continue;
1086 if (!sheetOwner) {
1087 // Aha! The current sheet has no sheet owner, so we want to insert after
1088 // it no matter whether we have a linking content or not.
1089 break;
1092 MOZ_ASSERT(owningNode != sheetOwner, "Why do we still have our old sheet?");
1094 // Have to compare
1095 if (nsContentUtils::PositionIsBefore(sheetOwner, owningNode)) {
1096 // The current sheet comes before us, and it better be the first
1097 // such, because now we break
1098 break;
1102 ++insertionPoint;
1104 if (shadow) {
1105 shadow->InsertSheetAt(insertionPoint, aSheet);
1106 } else {
1107 mDocument->InsertSheetAt(insertionPoint, aSheet);
1110 LOG((" Inserting into target (doc: %d) at position %d",
1111 target.AsNode().IsDocument(), insertionPoint));
1115 * InsertChildSheet handles ordering of @import-ed sheet in their
1116 * parent sheets. Here we want to just insert based on order of the
1117 * @import rules that imported the sheets. In theory we can't just
1118 * append to the end because the CSSOM can insert @import rules. In
1119 * practice, we get the call to load the child sheet before the CSSOM
1120 * has finished inserting the @import rule, so we have no idea where
1121 * to put it anyway. So just append for now. (In the future if we
1122 * want to insert the sheet at the correct position, we'll need to
1123 * restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
1124 * bug 1220506.)
1126 void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
1127 LOG(("css::Loader::InsertChildSheet"));
1129 // child sheets should always start out enabled, even if they got
1130 // cloned off of top-level sheets which were disabled
1131 aSheet.SetEnabled(true);
1132 aParentSheet.AppendStyleSheet(aSheet);
1134 LOG((" Inserting into parent sheet"));
1137 nsresult Loader::LoadSheetSyncInternal(SheetLoadData& aLoadData,
1138 SheetState aSheetState) {
1139 LOG((" Synchronous load"));
1140 MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
1141 MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
1142 "Sync loads can't reuse existing async loads");
1144 nsINode* requestingNode = aLoadData.GetRequestingNode();
1146 nsresult rv = NS_OK;
1148 // Create a StreamLoader instance to which we will feed
1149 // the data from the sync load. Do this before creating the
1150 // channel to make error recovery simpler.
1151 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1153 if (mDocument) {
1154 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1155 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
1158 // Synchronous loads should only be used internally. Therefore no CORS
1159 // policy is needed.
1160 nsSecurityFlags securityFlags =
1161 nsContentSecurityManager::ComputeSecurityFlags(
1162 CORSMode::CORS_NONE, nsContentSecurityManager::CORSSecurityMapping::
1163 CORS_NONE_MAPS_TO_INHERITED_CONTEXT);
1165 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
1167 nsContentPolicyType contentPolicyType =
1168 aLoadData.mPreloadKind == StylePreloadKind::None
1169 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1170 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1172 // Just load it
1173 nsCOMPtr<nsIChannel> channel;
1174 // Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
1175 // a node and a principal.
1176 // This is because of a case where the node is the document being styled and
1177 // the principal is the stylesheet (perhaps from a different origin) that is
1178 // applying the styles.
1179 if (requestingNode) {
1180 rv = NS_NewChannelWithTriggeringPrincipal(
1181 getter_AddRefs(channel), aLoadData.mURI, requestingNode,
1182 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1183 } else {
1184 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1185 auto result = URLPreloader::ReadURI(aLoadData.mURI);
1186 if (result.isOk()) {
1187 nsCOMPtr<nsIInputStream> stream;
1188 MOZ_TRY(
1189 NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
1191 rv = NS_NewInputStreamChannel(
1192 getter_AddRefs(channel), aLoadData.mURI, stream.forget(),
1193 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
1194 } else {
1195 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1196 aLoadData.mTriggeringPrincipal, securityFlags,
1197 contentPolicyType);
1200 if (NS_FAILED(rv)) {
1201 LOG_ERROR((" Failed to create channel"));
1202 streamLoader->ChannelOpenFailed(rv);
1203 SheetComplete(aLoadData, rv);
1204 return rv;
1207 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1208 loadInfo->SetCspNonce(aLoadData.Nonce());
1210 nsCOMPtr<nsIInputStream> stream;
1211 rv = channel->Open(getter_AddRefs(stream));
1213 if (NS_FAILED(rv)) {
1214 LOG_ERROR((" Failed to open URI synchronously"));
1215 streamLoader->ChannelOpenFailed(rv);
1216 SheetComplete(aLoadData, rv);
1217 return rv;
1220 // Force UA sheets to be UTF-8.
1221 // XXX this is only necessary because the default in
1222 // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
1223 channel->SetContentCharset("UTF-8"_ns);
1225 // Manually feed the streamloader the contents of the stream.
1226 // This will call back into OnStreamComplete
1227 // and thence to ParseSheet. Regardless of whether this fails,
1228 // SheetComplete has been called.
1229 return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
1230 streamLoader, channel);
1233 bool Loader::MaybeDeferLoad(SheetLoadData& aLoadData, SheetState aSheetState,
1234 PendingLoad aPendingLoad,
1235 const SheetLoadDataHashKey& aKey) {
1236 MOZ_ASSERT(mSheets);
1238 // If we have at least one other load ongoing, then we can defer it until
1239 // all non-pending loads are done.
1240 if (aSheetState == SheetState::NeedsParser &&
1241 aPendingLoad == PendingLoad::No && aLoadData.ShouldDefer() &&
1242 mOngoingLoadCount > mPendingLoadCount + 1) {
1243 LOG((" Deferring sheet load"));
1244 ++mPendingLoadCount;
1245 mSheets->DeferLoad(aKey, aLoadData);
1246 return true;
1248 return false;
1251 bool Loader::MaybeCoalesceLoadAndNotifyOpen(SheetLoadData& aLoadData,
1252 SheetState aSheetState,
1253 const SheetLoadDataHashKey& aKey,
1254 const PreloadHashKey& aPreloadKey) {
1255 bool coalescedLoad = false;
1256 auto cacheState = [&aSheetState] {
1257 switch (aSheetState) {
1258 case SheetState::Complete:
1259 return CachedSubResourceState::Complete;
1260 case SheetState::Pending:
1261 return CachedSubResourceState::Pending;
1262 case SheetState::Loading:
1263 return CachedSubResourceState::Loading;
1264 case SheetState::NeedsParser:
1265 return CachedSubResourceState::Miss;
1267 MOZ_ASSERT_UNREACHABLE("wat");
1268 return CachedSubResourceState::Miss;
1269 }();
1271 if ((coalescedLoad = mSheets->CoalesceLoad(aKey, aLoadData, cacheState))) {
1272 if (aSheetState == SheetState::Pending) {
1273 ++mPendingLoadCount;
1274 } else {
1275 aLoadData.NotifyOpen(aPreloadKey, mDocument,
1276 aLoadData.IsLinkRelPreload());
1279 return coalescedLoad;
1283 * LoadSheet handles the actual load of a sheet. If the load is
1284 * supposed to be synchronous it just opens a channel synchronously
1285 * using the given uri, wraps the resulting stream in a converter
1286 * stream and calls ParseSheet. Otherwise it tries to look for an
1287 * existing load for this URI and piggyback on it. Failing all that,
1288 * a new load is kicked off asynchronously.
1290 nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
1291 uint64_t aEarlyHintPreloaderId,
1292 PendingLoad aPendingLoad) {
1293 LOG(("css::Loader::LoadSheet"));
1294 MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
1295 MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
1296 MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
1297 MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
1298 "Shouldn't use system principal for async loads");
1300 LOG_URI(" Load from: '%s'", aLoadData.mURI);
1302 // If we're firing a pending load, this load is already accounted for the
1303 // first time it went through this function.
1304 if (aPendingLoad == PendingLoad::No) {
1305 if (aLoadData.BlocksLoadEvent()) {
1306 IncrementOngoingLoadCountAndMaybeBlockOnload();
1309 // We technically never defer non-top-level sheets, so this condition could
1310 // be outside the branch, but conceptually it should be here.
1311 if (aLoadData.mParentData) {
1312 ++aLoadData.mParentData->mPendingChildren;
1316 if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
1317 // No point starting the load; just release all the data and such.
1318 LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
1319 SheetComplete(aLoadData, NS_BINDING_ABORTED);
1320 return NS_BINDING_ABORTED;
1323 if (aLoadData.mSyncLoad) {
1324 return LoadSheetSyncInternal(aLoadData, aSheetState);
1327 SheetLoadDataHashKey key(aLoadData);
1329 auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData);
1330 if (mSheets) {
1331 if (MaybeDeferLoad(aLoadData, aSheetState, aPendingLoad, key)) {
1332 return NS_OK;
1335 if (MaybeCoalesceLoadAndNotifyOpen(aLoadData, aSheetState, key,
1336 preloadKey)) {
1337 // All done here; once the load completes we'll be marked complete
1338 // automatically.
1339 return NS_OK;
1343 aLoadData.NotifyOpen(preloadKey, mDocument, aLoadData.IsLinkRelPreload());
1345 return LoadSheetAsyncInternal(aLoadData, aEarlyHintPreloaderId, key);
1348 nsresult Loader::LoadSheetAsyncInternal(SheetLoadData& aLoadData,
1349 uint64_t aEarlyHintPreloaderId,
1350 const SheetLoadDataHashKey& aKey) {
1351 nsresult rv = NS_OK;
1353 SRIMetadata sriMetadata;
1354 aLoadData.mSheet->GetIntegrity(sriMetadata);
1356 nsINode* requestingNode = aLoadData.GetRequestingNode();
1358 nsCOMPtr<nsILoadGroup> loadGroup;
1359 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1360 if (mDocument) {
1361 loadGroup = mDocument->GetDocumentLoadGroup();
1362 // load for a document with no loadgrup indicates that something is
1363 // completely bogus, let's bail out early.
1364 if (!loadGroup) {
1365 LOG_ERROR((" Failed to query loadGroup from document"));
1366 SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
1367 return NS_ERROR_UNEXPECTED;
1370 cookieJarSettings = mDocument->CookieJarSettings();
1373 #ifdef DEBUG
1374 AutoRestore<bool> syncCallbackGuard(mSyncCallback);
1375 mSyncCallback = true;
1376 #endif
1378 nsSecurityFlags securityFlags =
1379 nsContentSecurityManager::ComputeSecurityFlags(
1380 aLoadData.mSheet->GetCORSMode(),
1381 nsContentSecurityManager::CORSSecurityMapping::
1382 CORS_NONE_MAPS_TO_INHERITED_CONTEXT);
1384 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
1386 nsContentPolicyType contentPolicyType =
1387 aLoadData.mPreloadKind == StylePreloadKind::None
1388 ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
1389 : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
1391 nsCOMPtr<nsIChannel> channel;
1392 // Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
1393 // and a principal. This is because of a case where the node is the document
1394 // being styled and the principal is the stylesheet (perhaps from a different
1395 // origin) that is applying the styles.
1396 if (requestingNode) {
1397 rv = NS_NewChannelWithTriggeringPrincipal(
1398 getter_AddRefs(channel), aLoadData.mURI, requestingNode,
1399 aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType,
1400 /* PerformanceStorage */ nullptr, loadGroup);
1401 } else {
1402 MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
1403 rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
1404 aLoadData.mTriggeringPrincipal, securityFlags,
1405 contentPolicyType, cookieJarSettings,
1406 /* aPerformanceStorage */ nullptr, loadGroup);
1409 if (NS_FAILED(rv)) {
1410 LOG_ERROR((" Failed to create channel"));
1411 SheetComplete(aLoadData, rv);
1412 return rv;
1415 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1416 loadInfo->SetCspNonce(aLoadData.Nonce());
1418 if (!aLoadData.ShouldDefer()) {
1419 if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
1420 cos->AddClassFlags(nsIClassOfService::Leader);
1422 if (aLoadData.IsLinkRelPreload()) {
1423 SheetLoadData::PrioritizeAsPreload(channel);
1424 SheetLoadData::AddLoadBackgroundFlag(channel);
1428 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
1429 if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
1430 rv = httpChannel->SetReferrerInfo(referrerInfo);
1431 Unused << NS_WARN_IF(NS_FAILED(rv));
1434 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
1435 do_QueryInterface(httpChannel);
1436 if (internalChannel) {
1437 rv = internalChannel->SetIntegrityMetadata(
1438 sriMetadata.GetIntegrityString());
1439 NS_ENSURE_SUCCESS(rv, rv);
1442 // Set the initiator type
1443 if (nsCOMPtr<nsITimedChannel> timedChannel =
1444 do_QueryInterface(httpChannel)) {
1445 if (aLoadData.mParentData) {
1446 timedChannel->SetInitiatorType(u"css"_ns);
1448 // This is a child sheet load.
1450 // The resource timing of the sub-resources that a document loads
1451 // should normally be reported to the document. One exception is any
1452 // sub-resources of any cross-origin resources that are loaded. We
1453 // don't mind reporting timing data for a direct child cross-origin
1454 // resource since the resource that linked to it (and hence potentially
1455 // anything in that parent origin) is aware that the cross-origin
1456 // resources is to be loaded. However, we do not want to report
1457 // timings for any sub-resources that a cross-origin resource may load
1458 // since that obviously leaks information about what the cross-origin
1459 // resource loads, which is bad.
1461 // In addition to checking whether we're an immediate child resource of
1462 // a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
1463 // to true on our parent), we also check our parent to see whether it
1464 // itself is a sub-resource of a cross-origin resource by checking
1465 // mBlockResourceTiming. If that is set then we too are such a
1466 // sub-resource and so we set the flag on ourself too to propagate it
1467 // on down.
1468 if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
1469 aLoadData.mParentData->mBlockResourceTiming) {
1470 // Set a flag so any other stylesheet triggered by this one will
1471 // not be reported
1472 aLoadData.mBlockResourceTiming = true;
1474 // Mark the channel so PerformanceMainThread::AddEntry will not
1475 // report the resource.
1476 timedChannel->SetReportResourceTiming(false);
1479 } else if (aEarlyHintPreloaderId) {
1480 timedChannel->SetInitiatorType(u"early-hints"_ns);
1481 } else {
1482 timedChannel->SetInitiatorType(u"link"_ns);
1487 // Now tell the channel we expect text/css data back.... We do
1488 // this before opening it, so it's only treated as a hint.
1489 channel->SetContentType("text/css"_ns);
1491 // We don't have to hold on to the stream loader. The ownership
1492 // model is: Necko owns the stream loader, which owns the load data,
1493 // which owns us
1494 auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
1495 if (mDocument) {
1496 net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
1497 nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
1500 if (aEarlyHintPreloaderId) {
1501 nsCOMPtr<nsIHttpChannelInternal> channelInternal =
1502 do_QueryInterface(channel);
1503 NS_ENSURE_TRUE(channelInternal != nullptr, NS_ERROR_FAILURE);
1505 rv = channelInternal->SetEarlyHintPreloaderId(aEarlyHintPreloaderId);
1506 NS_ENSURE_SUCCESS(rv, rv);
1508 rv = channel->AsyncOpen(streamLoader);
1509 if (NS_FAILED(rv)) {
1510 LOG_ERROR((" Failed to create stream loader"));
1511 streamLoader->ChannelOpenFailed(rv);
1512 // NOTE: NotifyStop will be done in SheetComplete -> NotifyObservers.
1513 aLoadData.NotifyStart(channel);
1514 SheetComplete(aLoadData, rv);
1515 return rv;
1518 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1519 if (nsCOMPtr<nsIHttpChannelInternal> hci = do_QueryInterface(channel)) {
1520 hci->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
1522 #endif
1524 if (mSheets) {
1525 mSheets->LoadStarted(aKey, aLoadData);
1527 return NS_OK;
1531 * ParseSheet handles parsing the data stream.
1533 Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
1534 SheetLoadData& aLoadData,
1535 AllowAsyncParse aAllowAsync) {
1536 LOG(("css::Loader::ParseSheet"));
1537 if (aLoadData.mURI) {
1538 LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
1540 AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_CSSParsing);
1542 ++mParsedSheetCount;
1544 aLoadData.mIsBeingParsed = true;
1546 StyleSheet* sheet = aLoadData.mSheet;
1547 MOZ_ASSERT(sheet);
1549 // Some cases, like inline style and UA stylesheets, need to be parsed
1550 // synchronously. The former may trigger child loads, the latter must not.
1551 if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
1552 sheet->ParseSheetSync(this, aBytes, &aLoadData);
1553 aLoadData.mIsBeingParsed = false;
1555 bool noPendingChildren = aLoadData.mPendingChildren == 0;
1556 MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
1557 if (noPendingChildren) {
1558 SheetComplete(aLoadData, NS_OK);
1559 return Completed::Yes;
1561 return Completed::No;
1564 // This parse does not need to be synchronous. \o/
1566 // Note that load is already blocked from
1567 // IncrementOngoingLoadCountAndMaybeBlockOnload(), and will be unblocked from
1568 // SheetFinishedParsingAsync which will end up in NotifyObservers as needed.
1569 sheet->ParseSheet(*this, aBytes, aLoadData)
1570 ->Then(
1571 GetMainThreadSerialEventTarget(), __func__,
1572 [loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
1573 MOZ_ASSERT(NS_IsMainThread());
1574 loadData->SheetFinishedParsingAsync();
1576 [] { MOZ_CRASH("rejected parse promise"); });
1577 return Completed::No;
1580 void Loader::NotifyObservers(SheetLoadData& aData, nsresult aStatus) {
1581 RecordUseCountersIfNeeded(mDocument, *aData.mSheet);
1582 RefPtr loadDispatcher = aData.PrepareLoadEventIfNeeded();
1583 if (aData.mURI) {
1584 mLoadsPerformed.PutEntry(SheetLoadDataHashKey(aData));
1585 aData.NotifyStop(aStatus);
1586 // NOTE(emilio): This needs to happen before notifying observers, as
1587 // FontFaceSet for example checks for pending sheet loads from the
1588 // StyleSheetLoaded callback.
1589 if (aData.BlocksLoadEvent()) {
1590 DecrementOngoingLoadCountAndMaybeUnblockOnload();
1591 if (mPendingLoadCount && mPendingLoadCount == mOngoingLoadCount) {
1592 LOG((" No more loading sheets; starting deferred loads"));
1593 StartDeferredLoads();
1597 if (!aData.mTitle.IsEmpty() && NS_SUCCEEDED(aStatus)) {
1598 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
1599 "Loader::NotifyObservers - Create PageStyle actor",
1600 [doc = RefPtr{mDocument}] {
1601 // Force creating the page style actor, if available.
1602 // This will no-op if no actor with this name is registered (outside
1603 // of desktop Firefox).
1604 nsCOMPtr<nsISupports> pageStyleActor =
1605 do_QueryActor("PageStyle", doc);
1606 Unused << pageStyleActor;
1607 }));
1609 if (aData.mMustNotify) {
1610 if (nsCOMPtr<nsICSSLoaderObserver> observer = std::move(aData.mObserver)) {
1611 LOG((" Notifying observer %p for data %p. deferred: %d", observer.get(),
1612 &aData, aData.ShouldDefer()));
1613 observer->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
1616 for (nsCOMPtr<nsICSSLoaderObserver> obs : mObservers.ForwardRange()) {
1617 LOG((" Notifying global observer %p for data %p. deferred: %d",
1618 obs.get(), &aData, aData.ShouldDefer()));
1619 obs->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
1622 if (loadDispatcher) {
1623 loadDispatcher->RunDOMEventWhenSafe();
1625 } else if (loadDispatcher) {
1626 loadDispatcher->PostDOMEvent();
1631 * SheetComplete is the do-it-all cleanup function. It removes the
1632 * load data from the "loading" hashtable, adds the sheet to the
1633 * "completed" hashtable, massages the XUL cache, handles siblings of
1634 * the load data (other loads for the same URI), handles unblocking
1635 * blocked parent loads as needed, and most importantly calls
1636 * NS_RELEASE on the load data to destroy the whole mess.
1638 void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
1639 LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
1640 static_cast<uint32_t>(aStatus)));
1641 SharedStyleSheetCache::LoadCompleted(mSheets.get(), aLoadData, aStatus);
1644 // static
1645 void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData,
1646 Loader* aOnlyForLoader) {
1647 if (aLoadData.mURI) {
1648 LOG_URI(" Load failed: '%s'", aLoadData.mURI);
1651 SheetLoadData* data = &aLoadData;
1652 do {
1653 if (!aOnlyForLoader || aOnlyForLoader == data->mLoader) {
1654 data->mLoadFailed = true;
1655 data->mSheet->MaybeRejectReplacePromise();
1658 if (data->mParentData) {
1659 MarkLoadTreeFailed(*data->mParentData, aOnlyForLoader);
1662 data = data->mNext;
1663 } while (data);
1666 RefPtr<StyleSheet> Loader::LookupInlineSheetInCache(
1667 const nsAString& aBuffer, nsIPrincipal* aSheetPrincipal) {
1668 auto result = mInlineSheets.Lookup(aBuffer);
1669 if (!result) {
1670 return nullptr;
1672 StyleSheet* sheet = result.Data();
1673 if (NS_WARN_IF(sheet->HasModifiedRules())) {
1674 // Remove it now that we know that we're never going to use this stylesheet
1675 // again.
1676 result.Remove();
1677 return nullptr;
1679 if (NS_WARN_IF(!sheet->Principal()->Equals(aSheetPrincipal))) {
1680 // If the sheet is going to have different access rights, don't return it
1681 // from the cache.
1682 return nullptr;
1684 return sheet->Clone(nullptr, nullptr);
1687 void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) {
1688 if (!mDocument) {
1689 return;
1692 auto key = PreloadHashKey::CreateAsStyle(aData);
1693 RefPtr<PreloaderBase> preload = mDocument->Preloads().LookupPreload(key);
1694 if (!preload) {
1695 return;
1698 preload->NotifyUsage(mDocument);
1701 Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
1702 const SheetInfo& aInfo, const nsAString& aBuffer,
1703 nsICSSLoaderObserver* aObserver) {
1704 LOG(("css::Loader::LoadInlineStyle"));
1705 MOZ_ASSERT(aInfo.mContent);
1707 if (!mEnabled) {
1708 LOG_WARN((" Not enabled"));
1709 return Err(NS_ERROR_NOT_AVAILABLE);
1712 if (!mDocument) {
1713 return Err(NS_ERROR_NOT_INITIALIZED);
1716 MOZ_ASSERT(LinkStyle::FromNodeOrNull(aInfo.mContent),
1717 "Element is not a style linking element!");
1719 // Since we're not planning to load a URI, no need to hand a principal to the
1720 // load data or to CreateSheet().
1722 // Check IsAlternateSheet now, since it can mutate our document.
1723 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1724 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1726 // Use the document's base URL so that @import in the inline sheet picks up
1727 // the right base.
1728 nsIURI* baseURI = aInfo.mContent->GetBaseURI();
1729 nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
1730 nsIURI* originalURI = nullptr;
1732 MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
1733 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1734 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1735 ? aInfo.mTriggeringPrincipal.get()
1736 : loadingPrincipal;
1737 nsIPrincipal* sheetPrincipal = [&] {
1738 // The triggering principal may be an expanded principal, which is safe to
1739 // use for URL security checks, but not as the loader principal for a
1740 // stylesheet. So treat this as principal inheritance, and downgrade if
1741 // necessary.
1743 // FIXME(emilio): Why doing this for inline sheets but not for links?
1744 if (aInfo.mTriggeringPrincipal) {
1745 return BasePrincipal::Cast(aInfo.mTriggeringPrincipal)
1746 ->PrincipalToInherit();
1748 return LoaderPrincipal();
1749 }();
1751 // We only cache sheets if in shadow trees, since regular document sheets are
1752 // likely to be unique.
1753 const bool isWorthCaching =
1754 StaticPrefs::layout_css_inline_style_caching_always_enabled() ||
1755 aInfo.mContent->IsInShadowTree();
1756 RefPtr<StyleSheet> sheet;
1757 if (isWorthCaching) {
1758 sheet = LookupInlineSheetInCache(aBuffer, sheetPrincipal);
1760 const bool isSheetFromCache = !!sheet;
1761 if (!isSheetFromCache) {
1762 sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
1763 SRIMetadata{});
1764 sheet->SetURIs(sheetURI, originalURI, baseURI);
1765 nsIReferrerInfo* referrerInfo =
1766 aInfo.mContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
1767 sheet->SetReferrerInfo(referrerInfo);
1768 // We never actually load this, so just set its principal directly.
1769 sheet->SetPrincipal(sheetPrincipal);
1772 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1773 isAlternate, aInfo.mIsExplicitlyEnabled);
1774 if (auto* linkStyle = LinkStyle::FromNode(*aInfo.mContent)) {
1775 linkStyle->SetStyleSheet(sheet);
1777 MOZ_ASSERT(sheet->IsComplete() == isSheetFromCache);
1779 Completed completed;
1780 auto data = MakeRefPtr<SheetLoadData>(
1781 this, aInfo.mTitle, /* aURI = */ nullptr, sheet, SyncLoad::No,
1782 aInfo.mContent, isAlternate, matched, StylePreloadKind::None, aObserver,
1783 principal, aInfo.mReferrerInfo, aInfo.mNonce);
1784 MOZ_ASSERT(data->GetRequestingNode() == aInfo.mContent);
1785 if (isSheetFromCache) {
1786 MOZ_ASSERT(sheet->IsComplete());
1787 MOZ_ASSERT(sheet->GetOwnerNode() == aInfo.mContent);
1788 completed = Completed::Yes;
1789 InsertSheetInTree(*sheet);
1790 NotifyOfCachedLoad(std::move(data));
1791 } else {
1792 // Parse completion releases the load data.
1794 // Note that we need to parse synchronously, since the web expects that the
1795 // effects of inline stylesheets are visible immediately (aside from
1796 // @imports).
1797 NS_ConvertUTF16toUTF8 utf8(aBuffer);
1798 completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
1799 if (completed == Completed::Yes) {
1800 if (isWorthCaching) {
1801 mInlineSheets.InsertOrUpdate(aBuffer, std::move(sheet));
1803 } else {
1804 data->mMustNotify = true;
1808 return LoadSheetResult{completed, isAlternate, matched};
1811 Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
1812 const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
1813 MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
1814 LOG(("css::Loader::LoadStyleLink"));
1815 LOG_URI(" Link uri: '%s'", aInfo.mURI);
1816 LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
1817 LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
1818 LOG((" Link alternate rel: %d", aInfo.mHasAlternateRel));
1820 if (!mEnabled) {
1821 LOG_WARN((" Not enabled"));
1822 return Err(NS_ERROR_NOT_AVAILABLE);
1825 if (!mDocument) {
1826 return Err(NS_ERROR_NOT_INITIALIZED);
1829 MOZ_ASSERT_IF(aInfo.mContent,
1830 aInfo.mContent->NodePrincipal() == mDocument->NodePrincipal());
1831 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
1832 nsIPrincipal* principal = aInfo.mTriggeringPrincipal
1833 ? aInfo.mTriggeringPrincipal.get()
1834 : loadingPrincipal;
1836 nsINode* requestingNode =
1837 aInfo.mContent ? static_cast<nsINode*>(aInfo.mContent) : mDocument;
1838 const bool syncLoad = [&] {
1839 if (!aInfo.mContent) {
1840 return false;
1842 const bool privilegedShadowTree =
1843 aInfo.mContent->IsInShadowTree() &&
1844 (aInfo.mContent->ChromeOnlyAccess() ||
1845 aInfo.mContent->OwnerDoc()->ChromeRulesEnabled());
1846 if (!privilegedShadowTree) {
1847 return false;
1849 if (!IsPrivilegedURI(aInfo.mURI)) {
1850 return false;
1852 // We're loading a chrome/resource URI in a chrome doc shadow tree or UA
1853 // widget. Load synchronously to avoid FOUC.
1854 return true;
1855 }();
1856 LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
1857 MOZ_ASSERT_IF(syncLoad, !aObserver);
1859 nsresult rv =
1860 CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI,
1861 requestingNode, aInfo.mNonce, StylePreloadKind::None);
1862 if (NS_WARN_IF(NS_FAILED(rv))) {
1863 // Don't fire the error event if our document is loaded as data. We're
1864 // supposed to not even try to do loads in that case... Unfortunately, we
1865 // implement that via nsDataDocumentContentPolicy, which doesn't have a good
1866 // way to communicate back to us that _it_ is the thing that blocked the
1867 // load.
1868 if (aInfo.mContent && !mDocument->IsLoadedAsData()) {
1869 // Fire an async error event on it.
1870 RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
1871 new LoadBlockingAsyncEventDispatcher(aInfo.mContent, u"error"_ns,
1872 CanBubble::eNo,
1873 ChromeOnlyDispatch::eNo);
1874 loadBlockingAsyncDispatcher->PostDOMEvent();
1876 return Err(rv);
1879 // Check IsAlternateSheet now, since it can mutate our document and make
1880 // pending sheets go to the non-pending state.
1881 auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
1882 auto [sheet, state] = CreateSheet(aInfo, eAuthorSheetFeatures, syncLoad,
1883 StylePreloadKind::None);
1885 LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
1887 auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
1888 isAlternate, aInfo.mIsExplicitlyEnabled);
1890 if (auto* linkStyle = LinkStyle::FromNodeOrNull(aInfo.mContent)) {
1891 linkStyle->SetStyleSheet(sheet);
1893 MOZ_ASSERT(sheet->IsComplete() == (state == SheetState::Complete));
1895 // We may get here with no content for Link: headers for example.
1896 MOZ_ASSERT(!aInfo.mContent || LinkStyle::FromNode(*aInfo.mContent),
1897 "If there is any node, it should be a LinkStyle");
1898 auto data = MakeRefPtr<SheetLoadData>(
1899 this, aInfo.mTitle, aInfo.mURI, sheet, SyncLoad(syncLoad), aInfo.mContent,
1900 isAlternate, matched, StylePreloadKind::None, aObserver, principal,
1901 aInfo.mReferrerInfo, aInfo.mNonce);
1903 MOZ_ASSERT(data->GetRequestingNode() == requestingNode);
1905 MaybeNotifyPreloadUsed(*data);
1907 if (state == SheetState::Complete) {
1908 LOG((" Sheet already complete: 0x%p", sheet.get()));
1909 MOZ_ASSERT(sheet->GetOwnerNode() == aInfo.mContent);
1910 InsertSheetInTree(*sheet);
1911 NotifyOfCachedLoad(std::move(data));
1912 return LoadSheetResult{Completed::Yes, isAlternate, matched};
1915 // Now we need to actually load it.
1916 auto result = LoadSheetResult{Completed::No, isAlternate, matched};
1918 MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
1919 "These should better match!");
1921 // Load completion will free the data
1922 rv = LoadSheet(*data, state, 0);
1923 if (NS_FAILED(rv)) {
1924 return Err(rv);
1927 if (!syncLoad) {
1928 data->mMustNotify = true;
1930 return result;
1933 static bool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) {
1934 if (!aData.mURI) {
1935 // Inline style; this won't have any ancestors
1936 MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?");
1937 return false;
1940 bool equal;
1941 if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
1942 return true;
1945 // Datas down the mNext chain have the same URI as aData, so we
1946 // don't have to compare to them. But they might have different
1947 // parents, and we have to check all of those.
1948 SheetLoadData* data = &aData;
1949 do {
1950 if (data->mParentData &&
1951 HaveAncestorDataWithURI(*data->mParentData, aURI)) {
1952 return true;
1955 data = data->mNext;
1956 } while (data);
1958 return false;
1961 nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
1962 SheetLoadData* aParentData, nsIURI* aURL,
1963 dom::MediaList* aMedia,
1964 LoaderReusableStyleSheets* aReusableSheets) {
1965 LOG(("css::Loader::LoadChildSheet"));
1966 MOZ_ASSERT(aURL, "Must have a URI to load");
1968 if (!mEnabled) {
1969 LOG_WARN((" Not enabled"));
1970 return NS_ERROR_NOT_AVAILABLE;
1973 LOG_URI(" Child uri: '%s'", aURL);
1975 nsCOMPtr<nsINode> owningNode;
1977 nsINode* requestingNode = aParentSheet.GetOwnerNodeOfOutermostSheet();
1978 if (requestingNode) {
1979 MOZ_ASSERT(LoaderPrincipal() == requestingNode->NodePrincipal());
1980 } else {
1981 requestingNode = mDocument;
1984 nsIPrincipal* principal = aParentSheet.Principal();
1985 nsresult rv =
1986 CheckContentPolicy(LoaderPrincipal(), principal, aURL, requestingNode,
1987 /* aNonce = */ u""_ns, StylePreloadKind::None);
1988 if (NS_WARN_IF(NS_FAILED(rv))) {
1989 if (aParentData) {
1990 MarkLoadTreeFailed(*aParentData);
1992 return rv;
1995 nsCOMPtr<nsICSSLoaderObserver> observer;
1997 if (aParentData) {
1998 LOG((" Have a parent load"));
1999 // Check for cycles
2000 if (HaveAncestorDataWithURI(*aParentData, aURL)) {
2001 // Houston, we have a loop, blow off this child and pretend this never
2002 // happened
2003 LOG_ERROR((" @import cycle detected, dropping load"));
2004 return NS_OK;
2007 NS_ASSERTION(aParentData->mSheet == &aParentSheet,
2008 "Unexpected call to LoadChildSheet");
2009 } else {
2010 LOG((" No parent load; must be CSSOM"));
2011 // No parent load data, so the sheet will need to be notified when
2012 // we finish, if it can be, if we do the load asynchronously.
2013 observer = &aParentSheet;
2016 // Now that we know it's safe to load this (passes security check and not a
2017 // loop) do so.
2018 RefPtr<StyleSheet> sheet;
2019 SheetState state;
2020 if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
2021 state = SheetState::Complete;
2022 } else {
2023 // For now, use CORS_NONE for child sheets
2024 std::tie(sheet, state) = CreateSheet(
2025 aURL, nullptr, principal, aParentSheet.ParsingMode(), CORS_NONE,
2026 aParentData ? aParentData->mEncoding : nullptr,
2027 u""_ns, // integrity is only checked on main sheet
2028 aParentData && aParentData->mSyncLoad, StylePreloadKind::None);
2029 PrepareSheet(*sheet, u""_ns, u""_ns, aMedia, IsAlternate::No,
2030 IsExplicitlyEnabled::No);
2033 MOZ_ASSERT(sheet);
2034 InsertChildSheet(*sheet, aParentSheet);
2036 auto data =
2037 MakeRefPtr<SheetLoadData>(this, aURL, sheet, aParentData, observer,
2038 principal, aParentSheet.GetReferrerInfo());
2039 MOZ_ASSERT(data->GetRequestingNode() == requestingNode);
2041 MaybeNotifyPreloadUsed(*data);
2043 if (state == SheetState::Complete) {
2044 LOG((" Sheet already complete"));
2045 // We're completely done. No need to notify, even, since the
2046 // @import rule addition/modification will trigger the right style
2047 // changes automatically.
2048 data->mIntentionallyDropped = true;
2049 return NS_OK;
2052 bool syncLoad = data->mSyncLoad;
2054 // Load completion will release the data
2055 rv = LoadSheet(*data, state, 0);
2056 NS_ENSURE_SUCCESS(rv, rv);
2058 if (!syncLoad) {
2059 data->mMustNotify = true;
2061 return rv;
2064 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
2065 nsIURI* aURL, SheetParsingMode aParsingMode,
2066 UseSystemPrincipal aUseSystemPrincipal) {
2067 LOG(("css::Loader::LoadSheetSync"));
2068 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2069 return InternalLoadNonDocumentSheet(
2070 aURL, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2071 referrerInfo, nullptr, CORS_NONE, u""_ns, u""_ns, 0);
2074 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2075 nsIURI* aURI, SheetParsingMode aParsingMode,
2076 UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
2077 nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
2078 return InternalLoadNonDocumentSheet(
2079 aURI, StylePreloadKind::None, aParsingMode, aUseSystemPrincipal, nullptr,
2080 referrerInfo, aObserver, CORS_NONE, u""_ns, u""_ns, 0);
2083 Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
2084 nsIURI* aURL, StylePreloadKind aPreloadKind,
2085 const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
2086 nsICSSLoaderObserver* aObserver, uint64_t aEarlyHintPreloaderId,
2087 CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity) {
2088 LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
2089 return InternalLoadNonDocumentSheet(
2090 aURL, aPreloadKind, eAuthorSheetFeatures, UseSystemPrincipal::No,
2091 aPreloadEncoding, aReferrerInfo, aObserver, aCORSMode, aNonce, aIntegrity,
2092 aEarlyHintPreloaderId);
2095 Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
2096 nsIURI* aURL, StylePreloadKind aPreloadKind, SheetParsingMode aParsingMode,
2097 UseSystemPrincipal aUseSystemPrincipal, const Encoding* aPreloadEncoding,
2098 nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver,
2099 CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity,
2100 uint64_t aEarlyHintPreloaderId) {
2101 MOZ_ASSERT(aURL, "Must have a URI to load");
2102 MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
2103 "Shouldn't load system-principal sheets async");
2104 MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");
2106 LOG_URI(" Non-document sheet uri: '%s'", aURL);
2108 if (!mEnabled) {
2109 LOG_WARN((" Not enabled"));
2110 return Err(NS_ERROR_NOT_AVAILABLE);
2113 nsIPrincipal* loadingPrincipal = LoaderPrincipal();
2114 nsIPrincipal* triggeringPrincipal = loadingPrincipal;
2115 nsresult rv = CheckContentPolicy(loadingPrincipal, triggeringPrincipal, aURL,
2116 mDocument, aNonce, aPreloadKind);
2117 if (NS_FAILED(rv)) {
2118 return Err(rv);
2121 bool syncLoad = !aObserver;
2122 auto [sheet, state] =
2123 CreateSheet(aURL, nullptr, triggeringPrincipal, aParsingMode, aCORSMode,
2124 aPreloadEncoding, aIntegrity, syncLoad, aPreloadKind);
2126 PrepareSheet(*sheet, u""_ns, u""_ns, nullptr, IsAlternate::No,
2127 IsExplicitlyEnabled::No);
2129 auto data = MakeRefPtr<SheetLoadData>(
2130 this, aURL, sheet, SyncLoad(syncLoad), aUseSystemPrincipal, aPreloadKind,
2131 aPreloadEncoding, aObserver, triggeringPrincipal, aReferrerInfo, aNonce);
2132 MOZ_ASSERT(data->GetRequestingNode() == mDocument);
2133 if (state == SheetState::Complete) {
2134 LOG((" Sheet already complete"));
2135 NotifyOfCachedLoad(std::move(data));
2136 return sheet;
2139 rv = LoadSheet(*data, state, aEarlyHintPreloaderId);
2140 if (NS_FAILED(rv)) {
2141 return Err(rv);
2143 if (aObserver) {
2144 data->mMustNotify = true;
2146 return sheet;
2149 void Loader::NotifyOfCachedLoad(RefPtr<SheetLoadData> aLoadData) {
2150 LOG(("css::Loader::PostLoadEvent"));
2151 MOZ_ASSERT(aLoadData->mSheet->IsComplete(),
2152 "Only expected to be used for cached sheets");
2153 // If we get to this code, the stylesheet loaded correctly at some point, so
2154 // we can just schedule a load event and don't need to touch the data's
2155 // mLoadFailed.
2156 // Note that we do this here and not from inside our SheetComplete so that we
2157 // don't end up running the load event more async than needed.
2158 MOZ_ASSERT(!aLoadData->mLoadFailed, "Why are we marked as failed?");
2159 aLoadData->mSheetAlreadyComplete = true;
2161 // We need to check mURI to match
2162 // DecrementOngoingLoadCountAndMaybeUnblockOnload().
2163 if (aLoadData->mURI && aLoadData->BlocksLoadEvent()) {
2164 IncrementOngoingLoadCountAndMaybeBlockOnload();
2166 SheetComplete(*aLoadData, NS_OK);
2169 void Loader::Stop() {
2170 if (mSheets) {
2171 mSheets->CancelLoadsForLoader(*this);
2175 bool Loader::HasPendingLoads() { return mOngoingLoadCount; }
2177 void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
2178 MOZ_ASSERT(aObserver, "Must have observer");
2179 mObservers.AppendElementUnlessExists(aObserver);
2182 void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
2183 mObservers.RemoveElement(aObserver);
2186 void Loader::StartDeferredLoads() {
2187 if (mSheets && mPendingLoadCount) {
2188 mSheets->StartPendingLoadsForLoader(
2189 *this, [](const SheetLoadData&) { return true; });
2193 NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
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);
2201 for (nsCOMPtr<nsICSSLoaderObserver>& obs : tmp->mObservers.ForwardRange()) {
2202 ImplCycleCollectionTraverse(cb, obs, "mozilla::css::Loader.mObservers");
2204 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
2205 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2207 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
2208 if (tmp->mSheets) {
2209 if (tmp->mDocument) {
2210 tmp->DeregisterFromSheetCache();
2212 tmp->mSheets = nullptr;
2214 tmp->mInlineSheets.Clear();
2215 tmp->mObservers.Clear();
2216 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)
2217 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2219 size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
2220 size_t n = aMallocSizeOf(this);
2222 n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
2224 n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
2225 for (const auto& entry : mInlineSheets) {
2226 n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
2227 // If the sheet has a parent, then its parent will report it so we don't
2228 // have to worry about it here.
2229 const StyleSheet* sheet = entry.GetWeak();
2230 MOZ_ASSERT(!sheet->GetParentSheet(),
2231 "How did an @import rule end up here?");
2232 if (!sheet->GetOwnerNode()) {
2233 n += sheet->SizeOfIncludingThis(aMallocSizeOf);
2237 // Measurement of the following members may be added later if DMD finds it is
2238 // worthwhile:
2239 // The following members aren't measured:
2240 // - mDocument, because it's a weak backpointer
2242 return n;
2245 nsIPrincipal* Loader::LoaderPrincipal() const {
2246 if (mDocument) {
2247 return mDocument->NodePrincipal();
2249 // Loaders without a document do system loads.
2250 return nsContentUtils::GetSystemPrincipal();
2253 nsIPrincipal* Loader::PartitionedPrincipal() const {
2254 if (mDocument && StaticPrefs::privacy_partition_network_state()) {
2255 return mDocument->PartitionedPrincipal();
2257 return LoaderPrincipal();
2260 bool Loader::ShouldBypassCache() const {
2261 if (!mDocument) {
2262 return false;
2264 RefPtr<nsILoadGroup> lg = mDocument->GetDocumentLoadGroup();
2265 if (!lg) {
2266 return false;
2268 nsLoadFlags flags;
2269 if (NS_FAILED(lg->GetLoadFlags(&flags))) {
2270 return false;
2272 return flags & (nsIRequest::LOAD_BYPASS_CACHE |
2273 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE);
2276 void Loader::BlockOnload() {
2277 if (mDocument) {
2278 mDocument->BlockOnload();
2282 void Loader::UnblockOnload(bool aFireSync) {
2283 if (mDocument) {
2284 mDocument->UnblockOnload(aFireSync);
2288 } // namespace css
2289 } // namespace mozilla