Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* loading of CSS style sheets using the network APIs */
#include "mozilla/css/Loader.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Logging.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PreloadHashKey.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/URLPreloader.h"
#include "nsIRunnable.h"
#include "nsISupportsPriority.h"
#include "nsITimedChannel.h"
#include "nsICachingChannel.h"
#include "nsSyncLoadService.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsICookieJarSettings.h"
#include "mozilla/dom/Document.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentPolicyUtils.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIClassOfService.h"
#include "nsIScriptError.h"
#include "nsMimeTypes.h"
#include "nsICSSLoaderObserver.h"
#include "nsThreadUtils.h"
#include "nsGkAtoms.h"
#include "nsIThreadInternal.h"
#include "nsINetworkPredictor.h"
#include "nsStringStream.h"
#include "mozilla/dom/MediaList.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/URL.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/css/StreamLoader.h"
#include "mozilla/SharedStyleSheetCache.h"
#include "mozilla/StaticPrefs_dom.h"
#include "ReferrerInfo.h"
#ifdef MOZ_XUL
# include "nsXULPrototypeCache.h"
#endif
#include "nsError.h"
#include "mozilla/dom/SRICheck.h"
#include "mozilla/Encoding.h"
using namespace mozilla::dom;
// 1024 bytes is specified in https://drafts.csswg.org/css-syntax/
#define SNIFFING_BUFFER_SIZE 1024
/**
* OVERALL ARCHITECTURE
*
* The CSS Loader gets requests to load various sorts of style sheets:
* inline style from <style> elements, linked style, @import-ed child
* sheets, non-document sheets. The loader handles the following tasks:
* 1) Creation of the actual style sheet objects: CreateSheet()
* 2) setting of the right media, title, enabled state, etc on the
* sheet: PrepareSheet()
* 3) Insertion of the sheet in the proper cascade order:
* InsertSheetInTree() and InsertChildSheet()
* 4) Load of the sheet: LoadSheet() including security checks
* 5) Parsing of the sheet: ParseSheet()
* 6) Cleanup: SheetComplete()
*
* The detailed documentation for these functions is found with the
* function implementations.
*
* The following helper object is used:
* SheetLoadData -- a small class that is used to store all the
* information needed for the loading of a sheet;
* this class handles listening for the stream
* loader completion and also handles charset
* determination.
*/
extern mozilla::LazyLogModule sCssLoaderLog;
mozilla::LazyLogModule sCssLoaderLog("nsCSSLoader");
static mozilla::LazyLogModule gSriPRLog("SRI");
#define LOG_ERROR(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG(args) LOG_DEBUG(args)
#define LOG_ERROR_ENABLED() \
MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Error)
#define LOG_WARN_ENABLED() \
MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Warning)
#define LOG_DEBUG_ENABLED() \
MOZ_LOG_TEST(sCssLoaderLog, mozilla::LogLevel::Debug)
#define LOG_ENABLED() LOG_DEBUG_ENABLED()
#define LOG_URI(format, uri) \
PR_BEGIN_MACRO \
NS_ASSERTION(uri, "Logging null uri"); \
if (LOG_ENABLED()) { \
LOG((format, uri->GetSpecOrDefault().get())); \
} \
PR_END_MACRO
// And some convenience strings...
static const char* const gStateStrings[] = {"NeedsParser", "Pending", "Loading",
"Complete"};
namespace mozilla {
SheetLoadDataHashKey::SheetLoadDataHashKey(const css::SheetLoadData& aLoadData)
: mURI(aLoadData.mURI),
mPrincipal(aLoadData.mTriggeringPrincipal),
mLoaderPrincipal(aLoadData.mLoader->LoaderPrincipal()),
mPartitionPrincipal(aLoadData.mLoader->PartitionedPrincipal()),
mEncodingGuess(aLoadData.mGuessedEncoding),
mCORSMode(aLoadData.mSheet->GetCORSMode()),
mParsingMode(aLoadData.mSheet->ParsingMode()),
mCompatMode(aLoadData.mCompatMode),
mIsLinkRelPreload(aLoadData.IsLinkRelPreload()) {
MOZ_COUNT_CTOR(SheetLoadDataHashKey);
MOZ_ASSERT(mURI);
MOZ_ASSERT(mPrincipal);
MOZ_ASSERT(mLoaderPrincipal);
MOZ_ASSERT(mPartitionPrincipal);
aLoadData.mSheet->GetIntegrity(mSRIMetadata);
}
bool SheetLoadDataHashKey::KeyEquals(const SheetLoadDataHashKey& aKey) const {
{
bool eq;
if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
return false;
}
}
LOG_URI("KeyEquals(%s)\n", mURI);
if (!mPrincipal->Equals(aKey.mPrincipal)) {
LOG((" > Principal mismatch\n"));
return false;
}
// We only check for partition principal equality if any of the loads are
// triggered by a document rather than e.g. an extension (which have different
// origins than the loader principal).
if (mPrincipal->Equals(mLoaderPrincipal) ||
aKey.mPrincipal->Equals(aKey.mLoaderPrincipal)) {
if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
LOG((" > Partition principal mismatch\n"));
return false;
}
}
if (mCORSMode != aKey.mCORSMode) {
LOG((" > CORS mismatch\n"));
return false;
}
if (mParsingMode != aKey.mParsingMode) {
LOG((" > Parsing mode mismatch\n"));
return false;
}
if (mCompatMode != aKey.mCompatMode) {
LOG((" > Quirks mismatch\n"));
return false;
}
// If encoding differs, then don't reuse the cache.
//
// TODO(emilio): When the encoding is determined from the request (either
// BOM or Content-Length or @charset), we could do a bit better,
// theoretically.
if (mEncodingGuess != aKey.mEncodingGuess) {
LOG((" > Encoding guess mismatch\n"));
return false;
}
// Consuming stylesheet tags must never coalesce to <link preload> initiated
// speculative loads with a weaker SRI hash or its different value. This
// check makes sure that regular loads will never find such a weaker preload
// and rather start a new, independent load with new, stronger SRI checker
// set up, so that integrity is ensured.
if (mIsLinkRelPreload != aKey.mIsLinkRelPreload) {
const auto& linkPreloadMetadata =
mIsLinkRelPreload ? mSRIMetadata : aKey.mSRIMetadata;
const auto& consumerPreloadMetadata =
mIsLinkRelPreload ? aKey.mSRIMetadata : mSRIMetadata;
if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
LOG((" > Preload SRI metadata mismatch\n"));
return false;
}
}
return true;
}
namespace css {
static NotNull<const Encoding*> GetFallbackEncoding(
Loader& aLoader, nsINode* aOwningNode,
const Encoding* aPreloadOrParentDataEncoding) {
const Encoding* encoding;
// Now try the charset on the <link> or processing instruction
// that loaded us
if (aOwningNode) {
nsAutoString label16;
LinkStyle::FromNode(*aOwningNode)->GetCharset(label16);
encoding = Encoding::ForLabel(label16);
if (encoding) {
return WrapNotNull(encoding);
}
}
// Try preload or parent sheet encoding.
if (aPreloadOrParentDataEncoding) {
return WrapNotNull(aPreloadOrParentDataEncoding);
}
if (auto* doc = aLoader.GetDocument()) {
// Use the document charset.
return doc->GetDocumentCharacterSet();
}
return UTF_8_ENCODING;
}
/********************************
* SheetLoadData implementation *
********************************/
NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunnable, nsIThreadObserver)
SheetLoadData::SheetLoadData(
Loader* aLoader, const nsAString& aTitle, nsIURI* aURI, StyleSheet* aSheet,
bool aSyncLoad, nsINode* aOwningNode, IsAlternate aIsAlternate,
MediaMatched aMediaMatches, StylePreloadKind aPreloadKind,
nsICSSLoaderObserver* aObserver, nsIPrincipal* aTriggeringPrincipal,
nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode)
: mLoader(aLoader),
mTitle(aTitle),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(aSyncLoad),
mIsNonDocumentSheet(false),
mIsChildSheet(aSheet->GetParentSheet()),
mIsLoading(false),
mIsBeingParsed(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(aIsAlternate == IsAlternate::Yes),
mMediaMatched(aMediaMatches == MediaMatched::Yes),
mUseSystemPrincipal(false),
mSheetAlreadyComplete(false),
mIsCrossOriginNoCORS(false),
mBlockResourceTiming(false),
mLoadFailed(false),
mPreloadKind(aPreloadKind),
mOwningNode(aOwningNode),
mObserver(aObserver),
mTriggeringPrincipal(aTriggeringPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mGuessedEncoding(GetFallbackEncoding(*aLoader, aOwningNode, nullptr)),
mCompatMode(aLoader->CompatMode(aPreloadKind)) {
MOZ_ASSERT(!mOwningNode || dom::LinkStyle::FromNode(*mOwningNode),
"Must implement LinkStyle");
MOZ_ASSERT(mTriggeringPrincipal);
MOZ_ASSERT(mLoader, "Must have a loader!");
}
SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
SheetLoadData* aParentData,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aTriggeringPrincipal,
nsIReferrerInfo* aReferrerInfo,
nsINode* aRequestingNode)
: mLoader(aLoader),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mParentData(aParentData),
mPendingChildren(0),
mSyncLoad(aParentData && aParentData->mSyncLoad),
mIsNonDocumentSheet(aParentData && aParentData->mIsNonDocumentSheet),
mIsChildSheet(aSheet->GetParentSheet()),
mIsLoading(false),
mIsBeingParsed(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mMediaMatched(true),
mUseSystemPrincipal(aParentData && aParentData->mUseSystemPrincipal),
mSheetAlreadyComplete(false),
mIsCrossOriginNoCORS(false),
mBlockResourceTiming(false),
mLoadFailed(false),
mPreloadKind(StylePreloadKind::None),
mOwningNode(nullptr),
mObserver(aObserver),
mTriggeringPrincipal(aTriggeringPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mGuessedEncoding(GetFallbackEncoding(
*aLoader, nullptr, aParentData ? aParentData->mEncoding : nullptr)),
mCompatMode(aLoader->CompatMode(mPreloadKind)) {
MOZ_ASSERT(mLoader, "Must have a loader!");
MOZ_ASSERT(mTriggeringPrincipal);
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
MOZ_ASSERT_IF(aParentData, mIsChildSheet);
}
SheetLoadData::SheetLoadData(
Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet, bool aSyncLoad,
UseSystemPrincipal aUseSystemPrincipal, StylePreloadKind aPreloadKind,
const Encoding* aPreloadEncoding, nsICSSLoaderObserver* aObserver,
nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
nsINode* aRequestingNode)
: mLoader(aLoader),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(aSyncLoad),
mIsNonDocumentSheet(true),
mIsChildSheet(false),
mIsLoading(false),
mIsBeingParsed(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mMediaMatched(true),
mUseSystemPrincipal(aUseSystemPrincipal == UseSystemPrincipal::Yes),
mSheetAlreadyComplete(false),
mIsCrossOriginNoCORS(false),
mBlockResourceTiming(false),
mLoadFailed(false),
mPreloadKind(aPreloadKind),
mOwningNode(nullptr),
mObserver(aObserver),
mTriggeringPrincipal(aTriggeringPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mGuessedEncoding(
GetFallbackEncoding(*aLoader, nullptr, aPreloadEncoding)),
mCompatMode(aLoader->CompatMode(aPreloadKind)) {
MOZ_ASSERT(mTriggeringPrincipal);
MOZ_ASSERT(mLoader, "Must have a loader!");
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
MOZ_ASSERT(!aSheet->GetParentSheet(), "Shouldn't be used for child loads");
}
SheetLoadData::~SheetLoadData() {
MOZ_DIAGNOSTIC_ASSERT(mSheetCompleteCalled || mIntentionallyDropped,
"Should always call SheetComplete, except when "
"dropping the load");
// Do this iteratively to avoid blowing up the stack.
RefPtr<SheetLoadData> next = std::move(mNext);
while (next) {
next = std::move(next->mNext);
}
}
NS_IMETHODIMP
SheetLoadData::Run() {
mLoader->HandleLoadEvent(*this);
return NS_OK;
}
NS_IMETHODIMP
SheetLoadData::OnDispatchedEvent() { return NS_OK; }
NS_IMETHODIMP
SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread, bool aMayWait) {
// XXXkhuey this is insane!
// We want to fire our load even before or after event processing,
// whichever comes first.
FireLoadEvent(aThread);
return NS_OK;
}
NS_IMETHODIMP
SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
bool aEventWasProcessed) {
// XXXkhuey this too!
// We want to fire our load even before or after event processing,
// whichever comes first.
FireLoadEvent(aThread);
return NS_OK;
}
void SheetLoadData::PrioritizeAsPreload(nsIChannel* aChannel) {
if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
}
}
void SheetLoadData::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
void SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread) {
// First remove ourselves as a thread observer. But we need to keep
// ourselves alive while doing that!
RefPtr<SheetLoadData> kungFuDeathGrip(this);
aThread->RemoveObserver(this);
// Now fire the event.
//
// NOTE(emilio): A bit weird that we fire the event even if the node is no
// longer in the tree, or the sheet that just loaded / errored is not the
// current node.sheet, but...
nsCOMPtr<nsINode> node = mOwningNode;
MOZ_ASSERT(node, "How did that happen???");
nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(), node,
mLoadFailed ? u"error"_ns : u"load"_ns,
CanBubble::eNo, Cancelable::eNo);
MOZ_ASSERT(BlocksLoadEvent());
mLoader->UnblockOnload(true);
}
void SheetLoadData::ScheduleLoadEventIfNeeded() {
if (!mOwningNode) {
return;
}
MOZ_ASSERT(BlocksLoadEvent(), "The rel=preload load event happens elsewhere");
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
mLoader->BlockOnload();
}
}
/*********************
* Style sheet reuse *
*********************/
bool LoaderReusableStyleSheets::FindReusableStyleSheet(
nsIURI* aURL, RefPtr<StyleSheet>& aResult) {
MOZ_ASSERT(aURL);
for (size_t i = mReusableSheets.Length(); i > 0; --i) {
size_t index = i - 1;
bool sameURI;
MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
nsresult rv =
aURL->Equals(mReusableSheets[index]->GetOriginalURI(), &sameURI);
if (!NS_FAILED(rv) && sameURI) {
aResult = mReusableSheets[index];
mReusableSheets.RemoveElementAt(index);
return true;
}
}
return false;
}
/*************************
* Loader Implementation *
*************************/
Loader::Loader()
: mDocument(nullptr),
mDocumentCompatMode(eCompatibility_FullStandards),
mReporter(new ConsoleReportCollector()) {}
Loader::Loader(DocGroup* aDocGroup) : Loader() { mDocGroup = aDocGroup; }
Loader::Loader(Document* aDocument) : Loader() {
MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
mDocument = aDocument;
mDocumentCompatMode = aDocument->GetCompatibilityMode();
mSheets = SharedStyleSheetCache::Get();
RegisterInSheetCache();
}
Loader::~Loader() {
// Note: no real need to revoke our stylesheet loaded events -- they
// hold strong references to us, so if we're going away that means
// they're all done.
}
void Loader::RegisterInSheetCache() {
MOZ_ASSERT(mDocument);
MOZ_ASSERT(mSheets);
mSheets->RegisterLoader(*this);
}
void Loader::DeregisterFromSheetCache() {
MOZ_ASSERT(mDocument);
MOZ_ASSERT(mSheets);
mSheets->CancelLoadsForLoader(*this);
mSheets->UnregisterLoader(*this);
}
void Loader::DropDocumentReference() {
// Flush out pending datas just so we don't leak by accident.
if (mSheets) {
DeregisterFromSheetCache();
}
mDocument = nullptr;
}
void Loader::DocumentStyleSheetSetChanged() {
MOZ_ASSERT(mDocument);
// start any pending alternates that aren't alternates anymore
mSheets->StartDeferredLoadsForLoader(
*this, SharedStyleSheetCache::StartLoads::IfNonAlternate);
}
static const char kCharsetSym[] = "@charset \"";
static bool GetCharsetFromData(const char* aStyleSheetData,
uint32_t aDataLength, nsACString& aCharset) {
aCharset.Truncate();
if (aDataLength <= sizeof(kCharsetSym) - 1) return false;
if (strncmp(aStyleSheetData, kCharsetSym, sizeof(kCharsetSym) - 1)) {
return false;
}
for (uint32_t i = sizeof(kCharsetSym) - 1; i < aDataLength; ++i) {
char c = aStyleSheetData[i];
if (c == '"') {
++i;
if (i < aDataLength && aStyleSheetData[i] == ';') {
return true;
}
// fail
break;
}
aCharset.Append(c);
}
// Did not see end quote or semicolon
aCharset.Truncate();
return false;
}
NotNull<const Encoding*> SheetLoadData::DetermineNonBOMEncoding(
const nsACString& aSegment, nsIChannel* aChannel) const {
const Encoding* encoding;
nsAutoCString label;
// Check HTTP
if (aChannel && NS_SUCCEEDED(aChannel->GetContentCharset(label))) {
encoding = Encoding::ForLabel(label);
if (encoding) {
return WrapNotNull(encoding);
}
}
// Check @charset
auto sniffingLength = aSegment.Length();
if (sniffingLength > SNIFFING_BUFFER_SIZE) {
sniffingLength = SNIFFING_BUFFER_SIZE;
}
if (GetCharsetFromData(aSegment.BeginReading(), sniffingLength, label)) {
encoding = Encoding::ForLabel(label);
if (encoding == UTF_16BE_ENCODING || encoding == UTF_16LE_ENCODING) {
return UTF_8_ENCODING;
}
if (encoding) {
return WrapNotNull(encoding);
}
}
return mGuessedEncoding;
}
static nsresult VerifySheetIntegrity(const SRIMetadata& aMetadata,
nsIChannel* aChannel,
const nsACString& aFirst,
const nsACString& aSecond,
const nsACString& aSourceFileURI,
nsIConsoleReportCollector* aReporter) {
NS_ENSURE_ARG_POINTER(aReporter);
if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), LogLevel::Debug)) {
nsAutoCString requestURL;
nsCOMPtr<nsIURI> originalURI;
if (aChannel &&
NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
originalURI) {
originalURI->GetAsciiSpec(requestURL);
}
MOZ_LOG(SRILogHelper::GetSriLog(), LogLevel::Debug,
("VerifySheetIntegrity (unichar stream)"));
}
SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
nsresult rv =
verifier.Update(aFirst.Length(), (const uint8_t*)aFirst.BeginReading());
NS_ENSURE_SUCCESS(rv, rv);
rv =
verifier.Update(aSecond.Length(), (const uint8_t*)aSecond.BeginReading());
NS_ENSURE_SUCCESS(rv, rv);
return verifier.Verify(aMetadata, aChannel, aSourceFileURI, aReporter);
}
static bool AllLoadsCanceled(const SheetLoadData& aData) {
const SheetLoadData* data = &aData;
do {
if (!data->mIsCancelled) {
return false;
}
} while ((data = data->mNext));
return true;
}
/*
* Stream completion code shared by Stylo and the old style system.
*
* Here we need to check that the load did not give us an http error
* page and check the mimetype on the channel to make sure we're not
* loading non-text/css data in standards mode.
*/
nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
const nsACString& aBytes1,
const nsACString& aBytes2,
nsIChannel* aChannel) {
LOG(("SheetLoadData::VerifySheetReadyToParse"));
NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
if (AllLoadsCanceled(*this)) {
LOG_WARN((" All loads are canceled, dropping"));
mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
return NS_OK;
}
if (NS_FAILED(aStatus)) {
LOG_WARN(
(" Load failed: status 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
// Handle sheet not loading error because source was a tracking URL (or
// fingerprinting, cryptomining, etc).
// We make a note of this sheet node by including it in a dedicated
// array of blocked tracking nodes under its parent document.
//
// Multiple sheet load instances might be tied to this request,
// we annotate each one linked to a valid owning element (node).
if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
aStatus)) {
if (Document* doc = mLoader->GetDocument()) {
for (SheetLoadData* data = this; data; data = data->mNext) {
// mOwningNode may be null but AddBlockTrackingNode can cope
doc->AddBlockedNodeByClassifier(
nsIContent::FromNodeOrNull(data->mOwningNode));
}
}
}
mLoader->SheetComplete(*this, aStatus);
return NS_OK;
}
if (!aChannel) {
mLoader->SheetComplete(*this, NS_OK);
return NS_OK;
}
nsCOMPtr<nsIURI> originalURI;
aChannel->GetOriginalURI(getter_AddRefs(originalURI));
// If the channel's original URI is "chrome:", we want that, since
// the observer code in nsXULPrototypeCache depends on chrome stylesheets
// having a chrome URI. (Whether or not chrome stylesheets come through
// this codepath seems nondeterministic.)
// Otherwise we want the potentially-HTTP-redirected URI.
nsCOMPtr<nsIURI> channelURI;
NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
if (!channelURI || !originalURI) {
NS_ERROR("Someone just violated the nsIRequest contract");
LOG_WARN((" Channel without a URI. Bad!"));
mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
nsresult result = NS_ERROR_NOT_AVAILABLE;
if (secMan) { // Could be null if we already shut down
if (mUseSystemPrincipal) {
result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
} else {
result = secMan->GetChannelResultPrincipal(aChannel,
getter_AddRefs(principal));
}
}
if (NS_FAILED(result)) {
LOG_WARN((" Couldn't get principal"));
mLoader->SheetComplete(*this, result);
return NS_OK;
}
mSheet->SetPrincipal(principal);
if (mSheet->GetCORSMode() == CORS_NONE &&
!mTriggeringPrincipal->Subsumes(principal)) {
mIsCrossOriginNoCORS = true;
}
// If it's an HTTP channel, we want to make sure this is not an
// error document we got.
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
bool requestSucceeded;
result = httpChannel->GetRequestSucceeded(&requestSucceeded);
if (NS_SUCCEEDED(result) && !requestSucceeded) {
LOG((" Load returned an error page"));
mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
return NS_OK;
}
nsAutoCString sourceMapURL;
if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
mSheet->SetSourceMapURL(NS_ConvertUTF8toUTF16(sourceMapURL));
}
}
nsAutoCString contentType;
aChannel->GetContentType(contentType);
// In standards mode, a style sheet must have one of these MIME
// types to be processed at all. In quirks mode, we accept any
// MIME type, but only if the style sheet is same-origin with the
// requesting document or parent sheet. See bug 524223.
bool validType = contentType.EqualsLiteral("text/css") ||
contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
contentType.IsEmpty();
if (!validType) {
const char* errorMessage;
uint32_t errorFlag;
bool sameOrigin = true;
bool subsumed;
result = mTriggeringPrincipal->Subsumes(principal, &subsumed);
if (NS_FAILED(result) || !subsumed) {
sameOrigin = false;
}
if (sameOrigin && mCompatMode == eCompatibility_NavQuirks) {
errorMessage = "MimeNotCssWarn";
errorFlag = nsIScriptError::warningFlag;
} else {
errorMessage = "MimeNotCss";
errorFlag = nsIScriptError::errorFlag;
}
AutoTArray<nsString, 2> strings;
CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
CopyASCIItoUTF16(contentType, *strings.AppendElement());
nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
nsContentUtils::ReportToConsole(
errorFlag, "CSS Loader"_ns, mLoader->mDocument,
nsContentUtils::eCSS_PROPERTIES, errorMessage, strings, referrer);
if (errorFlag == nsIScriptError::errorFlag) {
LOG_WARN(
(" Ignoring sheet with improper MIME type %s", contentType.get()));
mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
return NS_OK;
}
}
SRIMetadata sriMetadata;
mSheet->GetIntegrity(sriMetadata);
if (!sriMetadata.IsEmpty()) {
nsAutoCString sourceUri;
if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, aBytes1, aBytes2,
sourceUri, mLoader->mReporter);
nsCOMPtr<nsILoadGroup> loadGroup;
aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
if (loadGroup) {
mLoader->mReporter->FlushConsoleReports(loadGroup);
} else {
mLoader->mReporter->FlushConsoleReports(mLoader->mDocument);
}
if (NS_FAILED(rv)) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(gSriPRLog, LogLevel::Debug,
("css::Loader::OnStreamComplete, bad metadata"));
mLoader->SheetComplete(*this, NS_ERROR_SRI_CORRUPT);
return NS_OK;
}
}
// Enough to set the URIs on mSheet, since any sibling datas we have share
// the same mInner as mSheet and will thus get the same URI.
mSheet->SetURIs(channelURI, originalURI, channelURI);
ReferrerPolicy policy =
nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateForExternalCSSResources(mSheet, policy);
mSheet->SetReferrerInfo(referrerInfo);
return NS_OK_PARSE_SHEET;
}
Loader::IsAlternate Loader::IsAlternateSheet(const nsAString& aTitle,
bool aHasAlternateRel) {
// A sheet is alternate if it has a nonempty title that doesn't match the
// currently selected style set. But if there _is_ no currently selected
// style set, the sheet wasn't marked as an alternate explicitly, and aTitle
// is nonempty, we should select the style set corresponding to aTitle, since
// that's a preferred sheet.
if (aTitle.IsEmpty()) {
return IsAlternate::No;
}
if (mDocument) {
const nsString& currentSheetSet = mDocument->GetCurrentStyleSheetSet();
if (!aHasAlternateRel && currentSheetSet.IsEmpty()) {
// There's no preferred set yet, and we now have a sheet with a title.
// Make that be the preferred set.
// FIXME(emilio): This is kinda wild, can we do it somewhere else?
mDocument->SetPreferredStyleSheetSet(aTitle);
// We're definitely not an alternate. Also, beware that at this point
// currentSheetSet may dangle.
return IsAlternate::No;
}
if (aTitle.Equals(currentSheetSet)) {
return IsAlternate::No;
}
}
return IsAlternate::Yes;
}
nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
nsIURI* aTargetURI,
nsINode* aRequestingNode,
const nsAString& aNonce,
StylePreloadKind aPreloadKind) {
// When performing a system load don't consult content policies.
if (!mDocument) {
return NS_OK;
}
nsContentPolicyType contentPolicyType =
aPreloadKind == StylePreloadKind::None
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
aLoadingPrincipal, aTriggeringPrincipal, aRequestingNode,
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
// snapshot the nonce at load start time for performing CSP checks
if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
secCheckLoadInfo->SetCspNonce(aNonce);
MOZ_ASSERT_IF(aPreloadKind != StylePreloadKind::None, aNonce.IsEmpty());
}
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentLoadPolicy(aTargetURI, secCheckLoadInfo,
"text/css"_ns, &shouldLoad,
nsContentUtils::GetContentPolicy());
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
// Asynchronously notify observers (e.g devtools) of CSP failure.
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"Loader::NotifyOnFailedCheckPolicy",
[targetURI = RefPtr<nsIURI>(aTargetURI),
requestingNode = RefPtr<nsINode>(aRequestingNode),
contentPolicyType]() {
nsCOMPtr<nsIChannel> channel;
NS_NewChannel(
getter_AddRefs(channel), targetURI, requestingNode,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
contentPolicyType);
NS_SetRequestBlockingReason(
channel, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
nsCOMPtr<nsIObserverService> obsService =
services::GetObserverService();
if (obsService) {
obsService->NotifyObservers(
channel, "http-on-failed-opening-request", nullptr);
}
}));
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_OK;
}
static void RecordUseCountersIfNeeded(Document* aDoc,
const StyleUseCounters* aCounters) {
if (!aDoc || !aCounters) {
return;
}
const StyleUseCounters* docCounters = aDoc->GetStyleUseCounters();
if (!docCounters) {
return;
}
Servo_UseCounters_Merge(docCounters, aCounters);
aDoc->MaybeWarnAboutZoom();
}
void Loader::DidHitCompleteSheetCache(const SheetLoadDataHashKey& aKey,
const StyleUseCounters* aCounters) {
MOZ_ASSERT(mDocument);
if (mLoadsPerformed.EnsureInserted(aKey)) {
RecordUseCountersIfNeeded(mDocument, aCounters);
}
}
/**
* CreateSheet() creates a StyleSheet object for the given URI.
*
* We check for an existing style sheet object for that uri in various caches
* and clone it if we find it. Cloned sheets will have the title/media/enabled
* state of the sheet they are clones off; make sure to call PrepareSheet() on
* the result of CreateSheet().
*/
std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
nsIURI* aURI, nsIContent* aLinkingContent,
nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode aParsingMode,
CORSMode aCORSMode, const Encoding* aPreloadOrParentDataEncoding,
const nsAString& aIntegrity, bool aSyncLoad,
StylePreloadKind aPreloadKind) {
MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(gSriPRLog, LogLevel::Debug,
("css::Loader::CreateSheet, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
nsAutoCString sourceUri;
if (mDocument && mDocument->GetDocumentURI()) {
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
}
SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
}
if (mSheets) {
SheetLoadDataHashKey key(aURI, aTriggeringPrincipal, LoaderPrincipal(),
PartitionedPrincipal(),
GetFallbackEncoding(*this, aLinkingContent,
aPreloadOrParentDataEncoding),
aCORSMode, aParsingMode, CompatMode(aPreloadKind),
sriMetadata, aPreloadKind);
auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
if (const auto& [styleSheet, sheetState] = cacheResult; styleSheet) {
LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
return cacheResult;
}
}
nsIURI* sheetURI = aURI;
nsIURI* baseURI = aURI;
nsIURI* originalURI = aURI;
auto sheet = MakeRefPtr<StyleSheet>(aParsingMode, aCORSMode, sriMetadata);
sheet->SetURIs(sheetURI, originalURI, baseURI);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateForExternalCSSResources(sheet);
sheet->SetReferrerInfo(referrerInfo);
LOG((" Needs parser"));
return {std::move(sheet), SheetState::NeedsParser};
}
static Loader::MediaMatched MediaListMatches(const MediaList* aMediaList,
const Document* aDocument) {
if (!aMediaList || !aDocument) {
return Loader::MediaMatched::Yes;
}
if (aMediaList->Matches(*aDocument)) {
return Loader::MediaMatched::Yes;
}
return Loader::MediaMatched::No;
}
/**
* PrepareSheet() handles setting the media and title on the sheet, as
* well as setting the enabled state based on the title and whether
* the sheet had "alternate" in its rel.
*/
Loader::MediaMatched Loader::PrepareSheet(
StyleSheet& aSheet, const nsAString& aTitle, const nsAString& aMediaString,
MediaList* aMediaList, IsAlternate aIsAlternate,
IsExplicitlyEnabled aIsExplicitlyEnabled) {
RefPtr<MediaList> mediaList(aMediaList);
if (!aMediaString.IsEmpty()) {
NS_ASSERTION(!aMediaList,
"must not provide both aMediaString and aMediaList");
mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
}
aSheet.SetMedia(do_AddRef(mediaList));
aSheet.SetTitle(aTitle);
aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
return MediaListMatches(mediaList, mDocument);
}
/**
* InsertSheetInTree handles ordering of sheets in the document or shadow root.
*
* Here we have two types of sheets -- those with linking elements and
* those without. The latter are loaded by Link: headers, and are only added to
* the document.
*
* The following constraints are observed:
* 1) Any sheet with a linking element comes after all sheets without
* linking elements
* 2) Sheets without linking elements are inserted in the order in
* which the inserting requests come in, since all of these are
* inserted during header data processing in the content sink
* 3) Sheets with linking elements are ordered based on document order
* as determined by CompareDocumentPosition.
*/
void Loader::InsertSheetInTree(StyleSheet& aSheet, nsINode* aOwningNode) {
LOG(("css::Loader::InsertSheetInTree"));
MOZ_ASSERT(mDocument, "Must have a document to insert into");
MOZ_ASSERT(!aOwningNode || aOwningNode->IsInUncomposedDoc() ||
aOwningNode->IsInShadowTree(),
"Why would we insert it anywhere?");
ShadowRoot* shadow =
aOwningNode ? aOwningNode->GetContainingShadow() : nullptr;
auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
: static_cast<DocumentOrShadowRoot&>(*mDocument);
// XXX Need to cancel pending sheet loads for this element, if any
int32_t sheetCount = target.SheetCount();
/*
* Start the walk at the _end_ of the list, since in the typical
* case we'll just want to append anyway. We want to break out of
* the loop when insertionPoint points to just before the index we
* want to insert at. In other words, when we leave the loop
* insertionPoint is the index of the stylesheet that immediately
* precedes the one we're inserting.
*/
int32_t insertionPoint = sheetCount - 1;
for (; insertionPoint >= 0; --insertionPoint) {
nsINode* sheetOwner = target.SheetAt(insertionPoint)->GetOwnerNode();
if (sheetOwner && !aOwningNode) {
// Keep moving; all sheets with a sheetOwner come after all
// sheets without a linkingNode
continue;
}
if (!sheetOwner) {
// Aha! The current sheet has no sheet owner, so we want to insert after
// it no matter whether we have a linking content or not.
break;
}
MOZ_ASSERT(aOwningNode != sheetOwner,
"Why do we still have our old sheet?");
// Have to compare
if (nsContentUtils::PositionIsBefore(sheetOwner, aOwningNode)) {
// The current sheet comes before us, and it better be the first
// such, because now we break
break;
}
}
++insertionPoint;
if (shadow) {
shadow->InsertSheetAt(insertionPoint, aSheet);
} else {
mDocument->InsertSheetAt(insertionPoint, aSheet);
}
LOG((" Inserting into target (doc: %d) at position %d",
target.AsNode().IsDocument(), insertionPoint));
}
/**
* InsertChildSheet handles ordering of @import-ed sheet in their
* parent sheets. Here we want to just insert based on order of the
* @import rules that imported the sheets. In theory we can't just
* append to the end because the CSSOM can insert @import rules. In
* practice, we get the call to load the child sheet before the CSSOM
* has finished inserting the @import rule, so we have no idea where
* to put it anyway. So just append for now. (In the future if we
* want to insert the sheet at the correct position, we'll need to
* restore CSSStyleSheet::InsertStyleSheetAt, which was removed in
*/
void Loader::InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet) {
LOG(("css::Loader::InsertChildSheet"));
// child sheets should always start out enabled, even if they got
// cloned off of top-level sheets which were disabled
aSheet.SetEnabled(true);
aParentSheet.AppendStyleSheet(aSheet);
LOG((" Inserting into parent sheet"));
}
/**
* LoadSheet handles the actual load of a sheet. If the load is
* supposed to be synchronous it just opens a channel synchronously
* using the given uri, wraps the resulting stream in a converter
* stream and calls ParseSheet. Otherwise it tries to look for an
* existing load for this URI and piggyback on it. Failing all that,
* a new load is kicked off asynchronously.
*/
nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
PendingLoad aPendingLoad) {
LOG(("css::Loader::LoadSheet"));
MOZ_ASSERT(aLoadData.mURI, "Need a URI to load");
MOZ_ASSERT(aLoadData.mSheet, "Need a sheet to load into");
MOZ_ASSERT(aSheetState != SheetState::Complete, "Why bother?");
MOZ_ASSERT(!aLoadData.mUseSystemPrincipal || aLoadData.mSyncLoad,
"Shouldn't use system principal for async loads");
LOG_URI(" Load from: '%s'", aLoadData.mURI);
// If we're firing a pending load, this load is already accounted for the
// first time it went through this function.
if (aPendingLoad == PendingLoad::No) {
if (aLoadData.BlocksLoadEvent()) {
IncrementOngoingLoadCount();
}
// We technically never defer non-top-level sheets, so this condition could
// be outside the branch, but conceptually it should be here.
if (aLoadData.mParentData) {
++aLoadData.mParentData->mPendingChildren;
}
}
nsresult rv = NS_OK;
if (!mDocument && !aLoadData.mIsNonDocumentSheet) {
// No point starting the load; just release all the data and such.
LOG_WARN((" No document and not non-document sheet; pre-dropping load"));
SheetComplete(aLoadData, NS_BINDING_ABORTED);
return NS_BINDING_ABORTED;
}
SRIMetadata sriMetadata;
aLoadData.mSheet->GetIntegrity(sriMetadata);
if (aLoadData.mSyncLoad) {
LOG((" Synchronous load"));
MOZ_ASSERT(!aLoadData.mObserver, "Observer for a sync load?");
MOZ_ASSERT(aSheetState == SheetState::NeedsParser,
"Sync loads can't reuse existing async loads");
// Create a StreamLoader instance to which we will feed
// the data from the sync load. Do this before creating the
// channel to make error recovery simpler.
auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
if (mDocument) {
net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
mDocument);
}
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aLoadData.mPreloadKind == StylePreloadKind::None
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
// Just load it
nsCOMPtr<nsIChannel> channel;
// Note that we are calling NS_NewChannelWithTriggeringPrincipal() with both
// a node and a principal.
// This is because of a case where the node is the document being styled and
// the principal is the stylesheet (perhaps from a different origin) that is
// applying the styles.
if (aLoadData.mRequestingNode) {
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
} else {
MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
auto result = URLPreloader::ReadURI(aLoadData.mURI);
if (result.isOk()) {
nsCOMPtr<nsIInputStream> stream;
MOZ_TRY(
NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
rv = NS_NewInputStreamChannel(
getter_AddRefs(channel), aLoadData.mURI, stream.forget(),
aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType);
} else {
rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
aLoadData.mTriggeringPrincipal, securityFlags,
contentPolicyType);
}
}
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create channel"));
streamLoader->ChannelOpenFailed(rv);
SheetComplete(aLoadData, rv);
return rv;
}
// snapshot the nonce at load start time for performing CSP checks
if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
if (aLoadData.mRequestingNode) {
// TODO(bug 1607009) move to SheetLoadData
nsString* cspNonce = static_cast<nsString*>(
aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(*cspNonce);
}
}
}
nsCOMPtr<nsIInputStream> stream;
rv = channel->Open(getter_AddRefs(stream));
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to open URI synchronously"));
streamLoader->ChannelOpenFailed(rv);
SheetComplete(aLoadData, rv);
return rv;
}
// Force UA sheets to be UTF-8.
// XXX this is only necessary because the default in
// SheetLoadData::OnDetermineCharset is wrong (bug 521039).
channel->SetContentCharset("UTF-8"_ns);
// Manually feed the streamloader the contents of the stream.
// This will call back into OnStreamComplete
// and thence to ParseSheet. Regardless of whether this fails,
// SheetComplete has been called.
return nsSyncLoadService::PushSyncStreamToListener(stream.forget(),
streamLoader, channel);
}
SheetLoadDataHashKey key(aLoadData);
auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData);
bool coalescedLoad = false;
if (mSheets) {
// If we have at least one other load ongoing, then we can defer it until
// all non-pending loads are done.
if (aSheetState == SheetState::NeedsParser &&
aPendingLoad == PendingLoad::No && aLoadData.ShouldDefer() &&
mOngoingLoadCount > mPendingLoadCount + 1) {
LOG((" Deferring sheet load"));
++mPendingLoadCount;
mSheets->DeferSheetLoad(key, aLoadData);
return NS_OK;
}
if ((coalescedLoad = mSheets->CoalesceLoad(key, aLoadData, aSheetState))) {
if (aSheetState == SheetState::Pending) {
++mPendingLoadCount;
return NS_OK;
}
}
}
aLoadData.NotifyOpen(preloadKey, mDocument, aLoadData.IsLinkRelPreload());
if (coalescedLoad) {
// All done here; once the load completes we'll be marked complete
// automatically.
return NS_OK;
}
nsCOMPtr<nsILoadGroup> loadGroup;
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
if (mDocument) {
loadGroup = mDocument->GetDocumentLoadGroup();
// load for a document with no loadgrup indicates that something is
// completely bogus, let's bail out early.
if (!loadGroup) {
LOG_ERROR((" Failed to query loadGroup from document"));
SheetComplete(aLoadData, NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
cookieJarSettings = mDocument->CookieJarSettings();
}
#ifdef DEBUG
AutoRestore<bool> syncCallbackGuard(mSyncCallback);
mSyncCallback = true;
#endif
CORSMode ourCORSMode = aLoadData.mSheet->GetCORSMode();
nsSecurityFlags securityFlags =
ourCORSMode == CORS_NONE
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
: nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
if (ourCORSMode == CORS_ANONYMOUS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
} else if (ourCORSMode == CORS_USE_CREDENTIALS) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aLoadData.mPreloadKind == StylePreloadKind::None
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD;
nsCOMPtr<nsIChannel> channel;
// Note we are calling NS_NewChannelWithTriggeringPrincipal here with a node
// and a principal. This is because of a case where the node is the document
// being styled and the principal is the stylesheet (perhaps from a different
// origin) that is applying the styles.
if (aLoadData.mRequestingNode) {
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
aLoadData.mTriggeringPrincipal, securityFlags, contentPolicyType,
/* PerformanceStorage */ nullptr, loadGroup);
} else {
MOZ_ASSERT(aLoadData.mTriggeringPrincipal->Equals(LoaderPrincipal()));
rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
aLoadData.mTriggeringPrincipal, securityFlags,
contentPolicyType, cookieJarSettings,
/* aPerformanceStorage */ nullptr, loadGroup);
}
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create channel"));
SheetComplete(aLoadData, rv);
return rv;
}
// snapshot the nonce at load start time for performing CSP checks
if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) {
if (aLoadData.mRequestingNode) {
// TODO(bug 1607009) move to SheetLoadData
nsString* cspNonce = static_cast<nsString*>(
aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(*cspNonce);
}
}
}
if (!aLoadData.ShouldDefer()) {
if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
cos->AddClassFlags(nsIClassOfService::Leader);
}
if (aLoadData.IsLinkRelPreload()) {
SheetLoadData::PrioritizeAsPreload(channel);
SheetLoadData::AddLoadBackgroundFlag(channel);
}
}
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
rv = httpChannel->SetReferrerInfo(referrerInfo);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
do_QueryInterface(httpChannel);
if (internalChannel) {
rv = internalChannel->SetIntegrityMetadata(
sriMetadata.GetIntegrityString());
NS_ENSURE_SUCCESS(rv, rv);
}
// Set the initiator type
if (nsCOMPtr<nsITimedChannel> timedChannel =
do_QueryInterface(httpChannel)) {
if (aLoadData.mParentData) {
timedChannel->SetInitiatorType(u"css"_ns);
// This is a child sheet load.
//
// The resource timing of the sub-resources that a document loads
// should normally be reported to the document. One exception is any
// sub-resources of any cross-origin resources that are loaded. We
// don't mind reporting timing data for a direct child cross-origin
// resource since the resource that linked to it (and hence potentially
// anything in that parent origin) is aware that the cross-origin
// resources is to be loaded. However, we do not want to report
// timings for any sub-resources that a cross-origin resource may load
// since that obviously leaks information about what the cross-origin
// resource loads, which is bad.
//
// In addition to checking whether we're an immediate child resource of
// a cross-origin resource (by checking if mIsCrossOriginNoCORS is set
// to true on our parent), we also check our parent to see whether it
// itself is a sub-resource of a cross-origin resource by checking
// mBlockResourceTiming. If that is set then we too are such a
// sub-resource and so we set the flag on ourself too to propagate it
// on down.
if (aLoadData.mParentData->mIsCrossOriginNoCORS ||
aLoadData.mParentData->mBlockResourceTiming) {
// Set a flag so any other stylesheet triggered by this one will
// not be reported
aLoadData.mBlockResourceTiming = true;
// Mark the channel so PerformanceMainThread::AddEntry will not
// report the resource.
timedChannel->SetReportResourceTiming(false);
}
} else {
timedChannel->SetInitiatorType(u"link"_ns);
}
}
}
// Now tell the channel we expect text/css data back.... We do
// this before opening it, so it's only treated as a hint.
channel->SetContentType("text/css"_ns);
// We don't have to hold on to the stream loader. The ownership
// model is: Necko owns the stream loader, which owns the load data,
// which owns us
auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
if (mDocument) {
net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
}
rv = channel->AsyncOpen(streamLoader);
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create stream loader"));
streamLoader->ChannelOpenFailed(rv);
// NOTE: NotifyStop will be done in SheetComplete -> NotifyObservers.
aLoadData.NotifyStart(channel);
SheetComplete(aLoadData, rv);
return rv;
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
if (nsCOMPtr<nsIHttpChannelInternal> hci = do_QueryInterface(channel)) {
hci->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
}
#endif
if (mSheets) {
mSheets->LoadStarted(key, aLoadData);
}
return NS_OK;
}
/**
* ParseSheet handles parsing the data stream.
*/
Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
SheetLoadData& aLoadData,
AllowAsyncParse aAllowAsync) {
LOG(("css::Loader::ParseSheet"));
if (aLoadData.mURI) {
LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
}
AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
++mParsedSheetCount;
aLoadData.mIsBeingParsed = true;