/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* loading of CSS style sheets using the network APIs */
// 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")); returnfalse;
}
// 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 (mIsLinkRelPreloadOrEarlyHint != aKey.mIsLinkRelPreloadOrEarlyHint) { constauto& linkPreloadMetadata =
mIsLinkRelPreloadOrEarlyHint ? mSRIMetadata : aKey.mSRIMetadata; constauto& consumerPreloadMetadata =
mIsLinkRelPreloadOrEarlyHint ? aKey.mSRIMetadata : mSRIMetadata; if (!consumerPreloadMetadata.CanTrustBeDelegatedTo(linkPreloadMetadata)) {
LOG((" > Preload SRI metadata mismatch\n")); returnfalse;
}
}
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();
}
RefPtr<StyleSheet> SheetLoadData::ValueForCache() const { // We need to clone the sheet on insertion to the cache because otherwise the // stylesheets can keep full windows alive via either their JS wrapper, or via // StyleSheet::mRelevantGlobal. // // If this ever changes, then you also need to fix up the memory reporting in // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports. return mSheet->Clone(nullptr, nullptr);
}
Loader::Loader(Document* aDocument) : Loader() {
MOZ_ASSERT(aDocument, "We should get a valid document from the caller!");
mDocument = aDocument;
mIsDocumentAssociated = true;
mDocumentCompatMode = aDocument->GetCompatibilityMode();
mSheets = SharedStyleSheetCache::Get();
RegisterInSheetCache();
}
// 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.
Loader::~Loader() = default;
void Loader::DropDocumentReference() {
MOZ_ASSERT(NS_IsMainThread()); // Flush out pending datas just so we don't leak by accident. if (mSheets) {
DeregisterFromSheetCache();
}
mDocument = nullptr;
}
// We need to block resolution of parse promise until we receive OnStopRequest // on Main thread. This is necessary because parse promise resolution fires // OnLoad event must not be dispatched until OnStopRequest in main thread is // processed, for stuff like performance resource entries.
mSheet->BlockParsePromise();
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); if (!channel) { return;
}
if (AllLoadsCanceled(*this)) { return NS_BINDING_ABORTED;
}
if (NS_FAILED(aStatus)) { return aStatus;
}
// 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;
nsresult result = httpChannel->GetRequestSucceeded(&requestSucceeded); if (NS_SUCCEEDED(result) && !requestSucceeded) { return NS_ERROR_NOT_AVAILABLE;
}
}
// 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 + parent stylesheet // (and didn't have any cross-origin redirects). See bug 524223. constbool validType = contentType.EqualsLiteral("text/css") ||
contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
contentType.IsEmpty(); if (!validType) { constbool sameOrigin = mSheet->IsOriginClean(); constauto flag = sameOrigin && mCompatMode == eCompatibility_NavQuirks
? nsIScriptError::warningFlag
: nsIScriptError::errorFlag; constauto errorMessage = flag == nsIScriptError::errorFlag
? "MimeNotCss"_ns
: "MimeNotCssWarn"_ns;
NS_ConvertUTF8toUTF16 sheetUri(mURI->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 contentType16(contentType);
nsAutoCString referrerSpec; if (nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer()) {
referrer->GetSpec(referrerSpec);
}
mLoader->mReporter->AddConsoleReport(
flag, "CSS Loader"_ns, PropertiesFile::CSS_PROPERTIES, referrerSpec, 0, 0, errorMessage, {sheetUri, contentType16}); if (flag == nsIScriptError::errorFlag) {
LOG_WARN(
(" Ignoring sheet with improper MIME type %s", contentType.get())); return NS_ERROR_NOT_AVAILABLE;
}
}
SRIMetadata sriMetadata;
mSheet->GetIntegrity(sriMetadata); if (!sriMetadata.IsEmpty()) {
nsresult rv = VerifySheetIntegrity(sriMetadata, aChannel, mTainting,
aBytes1, aBytes2, mLoader->mReporter); if (NS_FAILED(rv)) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(gSriPRLog, LogLevel::Debug,
("css::Loader::OnStreamComplete, bad metadata")); return NS_ERROR_SRI_CORRUPT;
}
} 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;
}
}
if (!aMediaString.IsEmpty()) {
NS_ASSERTION(!aMediaList, "must not provide both aMediaString and aMediaList");
mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMediaString));
}
/** *InsertSheetInTreehandlesorderingofsheetsinthedocumentorshadowroot. * *Herewehavetwotypesofsheets--thosewithlinkingelementsand *thosewithout.ThelatterareloadedbyLink:headers,andareonlyaddedto *thedocument. * *Thefollowingconstraintsareobserved: *1)Anysheetwithalinkingelementcomesafterallsheetswithout *linkingelements *2)Sheetswithoutlinkingelementsareinsertedintheorderin *whichtheinsertingrequestscomein,sincealloftheseare *insertedduringheaderdataprocessinginthecontentsink *3)Sheetswithlinkingelementsareorderedbasedondocumentorder *asdeterminedbyCompareDocumentPosition.
*/ void Loader::InsertSheetInTree(StyleSheet& aSheet) {
LOG(("css::Loader::InsertSheetInTree"));
MOZ_ASSERT(mDocument, "Must have a document to insert into");
// If our owning node is null, we come from a link header.
nsINode* owningNode = aSheet.GetOwnerNode();
MOZ_ASSERT_IF(owningNode, owningNode->OwnerDoc() == mDocument);
DocumentOrShadowRoot* target =
owningNode ? owningNode->GetContainingDocumentOrShadowRoot() : mDocument;
MOZ_ASSERT(target, "Where should we insert it?");
// 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"));
}
nsresult Loader::NewStyleSheetChannel(SheetLoadData& aLoadData,
UsePreload aUsePreload,
UseLoadGroup aUseLoadGroup,
nsIChannel** aOutChannel) {
nsCOMPtr<nsILoadGroup> loadGroup;
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
net::ClassificationFlags triggeringClassificationFlags; if (aUseLoadGroup == UseLoadGroup::Yes && 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")); return NS_ERROR_UNEXPECTED;
}
// If the script context is tracking, we use the flags from the script // tracking flags. Otherwise, we fallback to use the flags from the // document.
triggeringClassificationFlags = mDocument->GetScriptTrackingFlags();
}
// 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 (requestingNode) { return NS_NewChannelWithTriggeringPrincipal(
aOutChannel, aLoadData.mURI, requestingNode, triggeringPrincipal,
securityFlags, contentPolicyType, /* aPerformanceStorage = */ nullptr, loadGroup);
}
if (aUsePreload == UsePreload::Yes) { auto result = URLPreloader::ReadURI(aLoadData.mURI); if (result.isOk()) {
nsCOMPtr<nsIInputStream> stream;
MOZ_TRY(
NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
// 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 (NS_FAILED(rv)) {
LOG_ERROR((" Failed to open URI synchronously"));
streamLoader->ChannelOpenFailed(rv);
channel->SetNotificationCallbacks(nullptr);
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);
}
// 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->DeferLoad(aKey, aLoadData); return true;
} returnfalse;
}
bool Loader::MaybeCoalesceLoadAndNotifyOpen(SheetLoadData& aLoadData,
SheetState aSheetState, const SheetLoadDataHashKey& aKey, const PreloadHashKey& aPreloadKey) { bool coalescedLoad = false; auto cacheState = [&aSheetState] { switch (aSheetState) { case SheetState::Complete: return CachedSubResourceState::Complete; case SheetState::Pending: return CachedSubResourceState::Pending; case SheetState::Loading: return CachedSubResourceState::Loading; case SheetState::NeedsParser: return CachedSubResourceState::Miss;
}
MOZ_ASSERT_UNREACHABLE("wat"); return CachedSubResourceState::Miss;
}();
if ((coalescedLoad = mSheets->CoalesceLoad(aKey, aLoadData, cacheState))) { if (aSheetState == SheetState::Pending) {
++mPendingLoadCount;
} else { // TODO: why not just `IsPreload()`?
aLoadData.NotifyOpen(aPreloadKey, mDocument,
aLoadData.IsLinkRelPreloadOrEarlyHint());
}
} return coalescedLoad;
}
/** *LoadSheethandlestheactualloadofasheet.Iftheloadis *supposedtobesynchronousitjustopensachannelsynchronously *usingthegivenuri,wrapstheresultingstreaminaconverter *streamandcallsParseSheet.Otherwiseittriestolookforan *existingloadforthisURIandpiggybackonit.Failingallthat, *anewloadiskickedoffasynchronously.
*/
nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState,
uint64_t aEarlyHintPreloaderId,
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()) {
IncrementOngoingLoadCountAndMaybeBlockOnload();
}
// 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;
}
}
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;
}
if (aLoadData.mSyncLoad) { return LoadSheetSyncInternal(aLoadData, aSheetState);
}
SheetLoadDataHashKey key(aLoadData);
auto preloadKey = PreloadHashKey::CreateAsStyle(aLoadData); if (mSheets) { if (MaybeDeferLoad(aLoadData, aSheetState, aPendingLoad, key)) { return NS_OK;
}
if (MaybeCoalesceLoadAndNotifyOpen(aLoadData, aSheetState, key,
preloadKey)) { // All done here; once the load completes we'll be marked complete // automatically. return NS_OK;
}
}
// Adjusting priorites is specified as implementation-defined. // See corresponding preferences in StaticPrefList.yaml for more context. const int32_t supportsPriorityDelta = [&]() { if (aLoadData.ShouldDefer()) { return FETCH_PRIORITY_ADJUSTMENT_FOR(deferred_style,
aLoadData.mFetchPriority);
} if (aLoadData.IsLinkRelPreloadOrEarlyHint()) { return FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_style,
aLoadData.mFetchPriority);
} return FETCH_PRIORITY_ADJUSTMENT_FOR(non_deferred_style,
aLoadData.mFetchPriority);
}();
if (!aLoadData.ShouldDefer()) { if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
cos->AddClassFlags(nsIClassOfService::Leader);
}
if (!aLoadData.BlocksLoadEvent()) {
SheetLoadData::AddLoadBackgroundFlag(channel);
}
}
AdjustPriority(aLoadData, channel);
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) { if (nsCOMPtr<nsIReferrerInfo> referrerInfo = aLoadData.ReferrerInfo()) {
rv = httpChannel->SetReferrerInfo(referrerInfo);
(void)NS_WARN_IF(NS_FAILED(rv));
}
// Set the initiator type if (nsCOMPtr<nsITimedChannel> timedChannel =
do_QueryInterface(httpChannel)) {
timedChannel->SetInitiatorType(aLoadData.InitiatorTypeString()); if (aLoadData.mParentData &&
!aLoadData.mParentData->mSheet->IsOriginClean()) { // This is a child sheet load of a cross-origin stylesheet. // // 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. // Mark the channel so PerformanceMainThread::AddEntry will not // report the resource.
timedChannel->SetReportResourceTiming(false);
}
}
}
// 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);
// Some cases, like inline style and UA stylesheets, need to be parsed // synchronously. The former may trigger child loads, the latter must not. if (loadData->mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
sheet->ParseSheetSync(this, aBytes, loadData);
loadData->mIsBeingParsed = false;
// This parse does not need to be synchronous. \o/ // // Note that load is already blocked from // IncrementOngoingLoadCountAndMaybeBlockOnload(), and will be unblocked from // SheetFinishedParsingAsync which will end up in NotifyObservers as needed.
sheet->ParseSheet(*this, aBytes, aLoadData)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[loadData = aLoadData](bool aDummy) {
MOZ_ASSERT(NS_IsMainThread());
loadData->get()->SheetFinishedParsingAsync();
},
[] { MOZ_CRASH("rejected parse promise"); }); return Completed::No;
}
RefPtr loadDispatcher = aData.PrepareLoadEventIfNeeded(); if (aData.mURI) {
aData.NotifyStop(aStatus); // NOTE(emilio): This needs to happen before notifying observers, as // FontFaceSet for example checks for pending sheet loads from the // StyleSheetLoaded callback. if (aData.BlocksLoadEvent()) {
DecrementOngoingLoadCountAndMaybeUnblockOnload(); if (mPendingLoadCount && mPendingLoadCount == mOngoingLoadCount) {
LOG((" No more loading sheets; starting deferred loads"));
StartDeferredLoads();
}
}
} if (!aData.mTitle.IsEmpty() && NS_SUCCEEDED(aStatus)) {
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( "Loader::NotifyObservers - Create PageStyle actor",
[doc = RefPtr{mDocument}] { // Force creating the page style actor, if available. // This will no-op if no actor with this name is registered (outside // of desktop Firefox).
nsCOMPtr<nsISupports> pageStyleActor =
do_QueryActor("PageStyle", doc);
(void)pageStyleActor;
}));
} if (aData.mMustNotify) { if (nsCOMPtr<nsICSSLoaderObserver> observer = std::move(aData.mObserver)) {
LOG((" Notifying observer %p for data %p. deferred: %d", observer.get(),
&aData, aData.ShouldDefer()));
observer->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
}
for (constauto& obsRef : mObservers.ForwardRange()) {
nsCOMPtr<nsICSSLoaderObserver> obs{obsRef};
LOG((" Notifying global observer %p for data %p. deferred: %d",
obs.get(), &aData, aData.ShouldDefer()));
obs->StyleSheetLoaded(aData.mSheet, aData.ShouldDefer(), aStatus);
}
// The intention is that this would return true for inputs like // (https://example.com/a, https://example.com/b) // but not for: // (https://example.com/a, https://example.com/b/c) // where "regular" relative URIs would resolve differently. staticbool BaseURIsArePathCompatible(nsIURI* aA, nsIURI* aB) { if (!aA || !aB) { returnfalse;
}
constexpr auto kDummyPath = "foo.css"_ns;
nsAutoCString resultA;
nsAutoCString resultB;
aA->Resolve(kDummyPath, resultA);
aB->Resolve(kDummyPath, resultB); return resultA == resultB;
}
staticbool CanReuseInlineSheet(SharedStyleSheetCache::InlineSheetEntry& aEntry,
nsIURI* aNewBaseURI, bool aIsImage) { auto dependency = aEntry.mSheet->OriginalContentsUriDependency(); if (dependency == StyleNonLocalUriDependency::No) { // If there are no non-local uris, then we can reuse. return true;
} if (aIsImage != aEntry.mWasLoadedAsImage) { // Even with the same base uri, if the document was loaded as an image on // one document, but wasn't on another, we can't cache the sheet, since // non-local URIs should not load in those, see bug 1982344. returnfalse;
} if (dependency == StyleNonLocalUriDependency::Absolute) { // If there are only absolute uris, then we don't need to worry about the // base. return true;
}
nsIURI* oldBase = aEntry.mSheet->GetBaseURI(); if (URIsEqual(oldBase, aNewBaseURI)) { return true;
} switch (dependency) { case StyleNonLocalUriDependency::Absolute: case StyleNonLocalUriDependency::No:
MOZ_ASSERT_UNREACHABLE("How?"); break; case StyleNonLocalUriDependency::Path: if (BaseURIsArePathCompatible(oldBase, aNewBaseURI)) { break;
}
[[fallthrough]]; case StyleNonLocalUriDependency::Full:
LOG((" Can't reuse due to base URI dependency")); returnfalse;
} return true;
}
RefPtr<StyleSheet> Loader::LookupInlineSheetInCache( const nsAString& aBuffer, nsIPrincipal* aSheetPrincipal, nsIURI* aBaseURI) {
MOZ_ASSERT(mDocument);
MOZ_ASSERT(mSheets, "Document associated loader should have sheet cache"); auto result = mSheets->LookupInline(LoaderPrincipal(), aBuffer); if (!result) { return nullptr;
}
MOZ_ASSERT(!result.Data().IsEmpty()); constbool asImage = mDocument->IsBeingUsedAsImage(); for (auto& candidate : result.Data()) { auto* sheet = candidate.mSheet.get();
MOZ_ASSERT(!sheet->HasModifiedRules(), "How did we end up with a dirty sheet?"); if (NS_WARN_IF(!sheet->Principal()->Equals(aSheetPrincipal))) { // If the sheet is going to have different access rights, don't return it // from the cache. XXX can this happen now that we eagerly clone? continue;
} if (!CanReuseInlineSheet(candidate, aBaseURI, asImage)) { continue;
} return sheet->Clone(nullptr, nullptr);
} return nullptr;
}
void Loader::MaybeNotifyPreloadUsed(SheetLoadData& aData) { if (!mDocument) { return;
}
auto key = PreloadHashKey::CreateAsStyle(aData);
RefPtr<PreloaderBase> preload = mDocument->Preloads().LookupPreload(key); if (!preload) { return;
}
if (!mEnabled) {
LOG_WARN((" Not enabled")); return Err(NS_ERROR_NOT_AVAILABLE);
}
if (!mDocument) { return Err(NS_ERROR_NOT_INITIALIZED);
}
MOZ_ASSERT(LinkStyle::FromNodeOrNull(aInfo.mContent), "Element is not a style linking element!");
// Since we're not planning to load a URI, no need to hand a principal to the // load data or to CreateSheet().
// Check IsAlternateSheet now, since it can mutate our document. auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
// Use the document's base URL so that @import in the inline sheet picks up // the right base.
nsIURI* baseURI = aInfo.mContent->GetBaseURI();
MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
nsIPrincipal* loadingPrincipal = LoaderPrincipal();
nsIPrincipal* principal = aInfo.mTriggeringPrincipal
? aInfo.mTriggeringPrincipal.get()
: loadingPrincipal;
nsIPrincipal* sheetPrincipal = [&] { // The triggering principal may be an expanded principal, which is safe to // use for URL security checks, but not as the loader principal for a // stylesheet. So treat this as principal inheritance, and downgrade if // necessary. // // FIXME(emilio): Why doing this for inline sheets but not for links? if (aInfo.mTriggeringPrincipal) { return BasePrincipal::Cast(aInfo.mTriggeringPrincipal)
->PrincipalToInherit();
} return LoaderPrincipal();
}();
RefPtr<StyleSheet> sheet =
LookupInlineSheetInCache(aBuffer, sheetPrincipal, baseURI); constbool isSheetFromCache = !!sheet; if (!isSheetFromCache) {
sheet = MakeRefPtr<StyleSheet>(StyleOrigin::Author, aInfo.mCORSMode,
SRIMetadata{}); // If an extension creates an inline stylesheet, we don't want to consider // it same-origin with the page. // FIXME(emilio): That's rather odd.
sheet->SetOriginClean(LoaderPrincipal()->Subsumes(sheetPrincipal));
} // We allow sharing inline sheets with e.g. different base URIs, iff there's // no dependency on that base URI. However, we still need to keep track of the // right URIs in case the sheet is then mutated. EnsureUniqueInner will make // sure the StylesheetContents get fixed up.
nsIReferrerInfo* referrerInfo =
aInfo.mContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
sheet->SetURIs(nullptr, baseURI, referrerInfo, sheetPrincipal);
auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
isAlternate, aInfo.mIsExplicitlyEnabled); if (auto* linkStyle = LinkStyle::FromNode(*aInfo.mContent)) {
linkStyle->SetStyleSheet(sheet);
}
MOZ_ASSERT(sheet->IsComplete() == isSheetFromCache);
Completed completed; auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, /* aURI = */ nullptr, sheet, SyncLoad::No,
aInfo.mContent, isAlternate, matched, StylePreloadKind::None, aObserver,
principal, aInfo.mReferrerInfo, aInfo.mNonce, aInfo.mFetchPriority,
nullptr);
MOZ_ASSERT(data->GetRequestingNode() == aInfo.mContent); if (isSheetFromCache) {
MOZ_ASSERT(sheet->IsComplete());
MOZ_ASSERT(sheet->GetOwnerNode() == aInfo.mContent);
completed = Completed::Yes;
InsertSheetInTree(*sheet);
NotifyOfCachedLoad(std::move(data));
} else { // Parse completion releases the load data. // // Note that we need to parse synchronously, since the web expects that the // effects of inline stylesheets are visible immediately (aside from // @imports).
NS_ConvertUTF16toUTF8 utf8(aBuffer);
RefPtr<SheetLoadDataHolder> holder( new nsMainThreadPtrHolder<css::SheetLoadData>(__func__, data.get(),
true));
completed = ParseSheet(utf8, holder, AllowAsyncParse::No); if (completed == Completed::Yes) {
mSheets->InsertInline(
LoaderPrincipal(), aBuffer,
{data->ValueForCache(), mDocument->IsBeingUsedAsImage()});
} else {
data->mMustNotify = true;
}
}
nsINode* requestingNode =
aInfo.mContent ? static_cast<nsINode*>(aInfo.mContent) : mDocument; constbool syncLoad = [&] { if (!aInfo.mContent) { returnfalse;
} constbool privilegedShadowTree =
aInfo.mContent->IsInShadowTree() &&
(aInfo.mContent->ChromeOnlyAccess() ||
aInfo.mContent->OwnerDoc()->ChromeRulesEnabled()); if (!privilegedShadowTree) { returnfalse;
} if (!IsPrivilegedURI(aInfo.mURI)) { returnfalse;
} // We're loading a chrome/resource URI in a chrome doc shadow tree or UA // widget. Load synchronously to avoid FOUC. return true;
}();
LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
MOZ_ASSERT_IF(syncLoad, !aObserver);
nsresult rv = CheckContentPolicy(
loadingPrincipal, principal, aInfo.mURI, requestingNode, aInfo.mNonce,
StylePreloadKind::None, aInfo.mCORSMode, aInfo.mIntegrity); if (NS_FAILED(rv)) { // Don't fire the error event if our document is loaded as data. We're // supposed to not even try to do loads in that case... Unfortunately, we // implement that via nsDataDocumentContentPolicy, which doesn't have a good // way to communicate back to us that _it_ is the thing that blocked the // load. if (aInfo.mContent && !mDocument->IsLoadedAsData()) { // Fire an async error event on it. auto loadBlockingAsyncDispatcher =
MakeRefPtr<LoadBlockingAsyncEventDispatcher>(
aInfo.mContent, u"error"_ns, CanBubble::eNo,
ChromeOnlyDispatch::eNo);
loadBlockingAsyncDispatcher->PostDOMEvent();
} return Err(rv);
}
// Check IsAlternateSheet now, since it can mutate our document and make // pending sheets go to the non-pending state. auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel); auto [sheet, state, networkMetadata] =
CreateSheet(aInfo, StyleOrigin::Author, syncLoad, StylePreloadKind::None);
LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
isAlternate, aInfo.mIsExplicitlyEnabled);
// We may get here with no content for Link: headers for example.
MOZ_ASSERT(!aInfo.mContent || LinkStyle::FromNode(*aInfo.mContent), "If there is any node, it should be a LinkStyle"); auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, aInfo.mURI, sheet, SyncLoad(syncLoad), aInfo.mContent,
isAlternate, matched, StylePreloadKind::None, aObserver, principal,
aInfo.mReferrerInfo, aInfo.mNonce, aInfo.mFetchPriority,
networkMetadata.forget());
// Now we need to actually load it. auto result = LoadSheetResult{Completed::No, isAlternate, matched};
MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(), "These should better match!");
// Load completion will free the data
rv = LoadSheet(*data, state, 0); if (NS_FAILED(rv)) { return Err(rv);
}
if (!syncLoad) {
data->mMustNotify = true;
} return result;
}
staticbool HaveAncestorDataWithURI(SheetLoadData& aData, nsIURI* aURI) { if (!aData.mURI) { // Inline style; this won't have any ancestors
MOZ_ASSERT(!aData.mParentData, "How does inline style have a parent?"); returnfalse;
}
// Datas down the mNext chain have the same URI as aData, so we // don't have to compare to them. But they might have different // parents, and we have to check all of those.
SheetLoadData* data = &aData; do { if (data->mParentData &&
HaveAncestorDataWithURI(*data->mParentData, aURI)) { return true;
}
data = data->mNext;
} while (data);
returnfalse;
}
nsresult Loader::LoadChildSheet(StyleSheet& aParentSheet,
SheetLoadData* aParentData, nsIURI* aURL,
dom::MediaList* aMedia,
LoaderReusableStyleSheets* aReusableSheets) {
LOG(("css::Loader::LoadChildSheet"));
MOZ_ASSERT(aURL, "Must have a URI to load");
if (!mEnabled) {
LOG_WARN((" Not enabled")); return NS_ERROR_NOT_AVAILABLE;
}
if (aParentData) {
LOG((" Have a parent load")); // Check for cycles if (HaveAncestorDataWithURI(*aParentData, aURL)) { // Houston, we have a loop, blow off this child and pretend this never // happened
LOG_ERROR((" @import cycle detected, dropping load")); return NS_OK;
}
NS_ASSERTION(aParentData->mSheet == &aParentSheet, "Unexpected call to LoadChildSheet");
} else {
LOG((" No parent load; must be CSSOM")); // No parent load data, so the sheet will need to be notified when // we finish, if it can be, if we do the load asynchronously.
observer = &aParentSheet;
}
// Now that we know it's safe to load this (passes security check and not a // loop) do so.
RefPtr<StyleSheet> sheet;
RefPtr<SubResourceNetworkMetadataHolder> networkMetadata;
SheetState state; bool isReusableSheet = false; if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
state = SheetState::Complete;
isReusableSheet = true;
} else { // For now, use CORS_NONE for child sheets
std::tie(sheet, state, networkMetadata) = CreateSheet(
aURL, nullptr, principal, aParentSheet.GetOrigin(), CORS_NONE,
aParentData ? aParentData->mEncoding : nullptr,
u""_ns, // integrity is only checked on main sheet
aParentData && aParentData->mSyncLoad, StylePreloadKind::None);
PrepareSheet(*sheet, u""_ns, u""_ns, aMedia, IsAlternate::No,
IsExplicitlyEnabled::No);
}
auto data = MakeRefPtr<SheetLoadData>(
this, aURL, sheet, aParentData, observer, principal,
aParentSheet.GetReferrerInfo(), networkMetadata.forget());
MOZ_ASSERT(data->GetRequestingNode() == requestingNode);
MaybeNotifyPreloadUsed(*data);
if (state == SheetState::Complete) {
LOG((" Sheet already complete")); // We're completely done. No need to notify, even, since the // @import rule addition/modification will trigger the right style // changes automatically. if (!isReusableSheet) { // Child sheets are not handled by NotifyObservers, and these need to be // performed here if the sheet comes from the SharedStyleSheetCache.
data->mSheet->PropagateUseCountersTo(mDocument); if (MaybePutIntoLoadsPerformed(*data)) {
NotifyObserversForCachedSheet(*data);
AddPerformanceEntryForCachedSheet(*data);
}
}
data->mIntentionallyDropped = true; return NS_OK;
}
bool syncLoad = data->mSyncLoad;
// Load completion will release the data
rv = LoadSheet(*data, state, 0);
NS_ENSURE_SUCCESS(rv, rv);
if (!syncLoad) {
data->mMustNotify = true;
} return rv;
}
void Loader::NotifyOfCachedLoad(RefPtr<SheetLoadData> aLoadData) {
LOG(("css::Loader::PostLoadEvent"));
MOZ_ASSERT(aLoadData->mSheet->IsComplete(), "Only expected to be used for cached sheets"); // If we get to this code, the stylesheet loaded correctly at some point, so // we can just schedule a load event and don't need to touch the data's // mLoadFailed. // Note that we do this here and not from inside our SheetComplete so that we // don't end up running the load event more async than needed.
MOZ_ASSERT(!aLoadData->mLoadFailed, "Why are we marked as failed?");
aLoadData->mSheetAlreadyComplete = true;
if (aLoadData->mURI) {
aLoadData->mShouldEmulateNotificationsForCachedLoad = true;
}
// We need to check mURI to match // DecrementOngoingLoadCountAndMaybeUnblockOnload(). if (aLoadData->mURI && aLoadData->BlocksLoadEvent()) {
IncrementOngoingLoadCountAndMaybeBlockOnload();
}
SheetComplete(*aLoadData, NS_OK);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader) if (tmp->mSheets) { if (tmp->mDocument) {
tmp->DeregisterFromSheetCache();
}
tmp->mSheets = nullptr;
}
tmp->mObservers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
// Measurement of the following members may be added later if DMD finds it is // worthwhile: // The following members aren't measured: // - mDocument, because it's a weak backpointer
return n;
}
nsIPrincipal* Loader::LoaderPrincipal() const {
MOZ_ASSERT(NS_IsMainThread()); if (mDocument) { return mDocument->NodePrincipal();
} // Loaders without a document do system loads. return nsContentUtils::GetSystemPrincipal();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.