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