Bug 1864817 - implement OnDataFinished for CSSLoader. r=jesup,necko-reviewers,emilio

Differential Revision: https://phabricator.services.mozilla.com/D189403
This commit is contained in:
sunil mayya 2024-03-08 20:58:37 +00:00
parent 693d0e730b
commit b72114e181
8 changed files with 269 additions and 114 deletions

View File

@ -8,7 +8,9 @@
#include "mozilla/css/Loader.h"
#include "MainThreadUtils.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/css/ErrorReporter.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/FetchPriority.h"
#include "mozilla/dom/SRILogHelper.h"
@ -22,6 +24,7 @@
#include "mozilla/SchedulerGroup.h"
#include "mozilla/URLPreloader.h"
#include "nsIChildChannel.h"
#include "nsIPrincipal.h"
#include "nsISupportsPriority.h"
#include "nsITimedChannel.h"
#include "nsICachingChannel.h"
@ -34,7 +37,6 @@
#include "nsICookieJarSettings.h"
#include "mozilla/dom/Document.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentPolicyUtils.h"
@ -64,6 +66,7 @@
#include "mozilla/css/StreamLoader.h"
#include "mozilla/SharedStyleSheetCache.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Try.h"
@ -308,7 +311,10 @@ SheetLoadData::SheetLoadData(
mNonce(aNonce),
mFetchPriority{aFetchPriority},
mGuessedEncoding(GetFallbackEncoding(*aLoader, aOwningNode, nullptr)),
mCompatMode(aLoader->CompatMode(aPreloadKind)) {
mCompatMode(aLoader->CompatMode(aPreloadKind)),
mRecordErrors(
aLoader && aLoader->GetDocument() &&
css::ErrorReporter::ShouldReportErrors(*aLoader->GetDocument())) {
MOZ_ASSERT(!aOwningNode || dom::LinkStyle::FromNode(*aOwningNode),
"Must implement LinkStyle");
MOZ_ASSERT(mTriggeringPrincipal);
@ -349,7 +355,10 @@ SheetLoadData::SheetLoadData(css::Loader* aLoader, nsIURI* aURI,
mFetchPriority(FetchPriority::Auto),
mGuessedEncoding(GetFallbackEncoding(
*aLoader, nullptr, aParentData ? aParentData->mEncoding : nullptr)),
mCompatMode(aLoader->CompatMode(mPreloadKind)) {
mCompatMode(aLoader->CompatMode(mPreloadKind)),
mRecordErrors(
aLoader && aLoader->GetDocument() &&
css::ErrorReporter::ShouldReportErrors(*aLoader->GetDocument())) {
MOZ_ASSERT(mLoader, "Must have a loader!");
MOZ_ASSERT(mTriggeringPrincipal);
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
@ -391,7 +400,10 @@ SheetLoadData::SheetLoadData(
mFetchPriority(aFetchPriority),
mGuessedEncoding(
GetFallbackEncoding(*aLoader, nullptr, aPreloadEncoding)),
mCompatMode(aLoader->CompatMode(aPreloadKind)) {
mCompatMode(aLoader->CompatMode(aPreloadKind)),
mRecordErrors(
aLoader && aLoader->GetDocument() &&
css::ErrorReporter::ShouldReportErrors(*aLoader->GetDocument())) {
MOZ_ASSERT(mTriggeringPrincipal);
MOZ_ASSERT(mLoader, "Must have a loader!");
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
@ -639,43 +651,54 @@ static bool AllLoadsCanceled(const SheetLoadData& aData) {
* 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) {
nsresult SheetLoadData::VerifySheetReadyToParse(
nsresult aStatus, const nsACString& aBytes1, const nsACString& aBytes2,
nsIChannel* aChannel, nsIURI* aFinalChannelURI,
nsIPrincipal* aChannelResultPrincipal) {
LOG(("SheetLoadData::VerifySheetReadyToParse"));
NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");
NS_ASSERTION((!NS_IsMainThread() || !mLoader->mSyncCallback),
"Synchronous callback from necko");
if (AllLoadsCanceled(*this)) {
LOG_WARN((" All loads are canceled, dropping"));
mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
if (NS_IsMainThread()) {
LOG_WARN((" All loads are canceled, dropping"));
mLoader->SheetComplete(*this, NS_BINDING_ABORTED);
}
return NS_OK;
}
if (!NS_IsMainThread() && mRecordErrors) {
// we cannot parse sheet OMT if we need to record errors
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) {
// owner node may be null but AddBlockTrackingNode can cope
doc->AddBlockedNodeByClassifier(data->mSheet->GetOwnerNode());
if (NS_IsMainThread()) {
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) {
// owner node may be null but AddBlockTrackingNode can cope
doc->AddBlockedNodeByClassifier(data->mSheet->GetOwnerNode());
}
}
}
mLoader->SheetComplete(*this, aStatus);
}
mLoader->SheetComplete(*this, aStatus);
return NS_OK;
}
if (!aChannel) {
MOZ_ASSERT(NS_IsMainThread());
mLoader->SheetComplete(*this, NS_OK);
return NS_OK;
}
@ -688,10 +711,8 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
// 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) {
if (!aFinalChannelURI || !originalURI) {
MOZ_ASSERT(NS_IsMainThread());
NS_ERROR("Someone just violated the nsIRequest contract");
LOG_WARN((" Channel without a URI. Bad!"));
mLoader->SheetComplete(*this, NS_ERROR_UNEXPECTED);
@ -705,32 +726,33 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
if (mUseSystemPrincipal) {
result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
} else {
result = secMan->GetChannelResultPrincipal(aChannel,
getter_AddRefs(principal));
if (aChannelResultPrincipal) {
principal = aChannelResultPrincipal;
result = NS_OK;
}
}
}
if (NS_FAILED(result)) {
LOG_WARN((" Couldn't get principal"));
mLoader->SheetComplete(*this, result);
if (NS_IsMainThread()) {
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);
if (NS_IsMainThread()) {
LOG((" Load returned an error page"));
mLoader->SheetComplete(*this, NS_ERROR_NOT_AVAILABLE);
}
return NS_OK;
}
@ -753,6 +775,9 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
contentType.IsEmpty();
if (!validType) {
if (!NS_IsMainThread()) {
return NS_OK;
}
const char* errorMessage;
uint32_t errorFlag;
bool sameOrigin = true;
@ -772,7 +797,8 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
}
AutoTArray<nsString, 2> strings;
CopyUTF8toUTF16(channelURI->GetSpecOrDefault(), *strings.AppendElement());
CopyUTF8toUTF16(aFinalChannelURI->GetSpecOrDefault(),
*strings.AppendElement());
CopyASCIItoUTF16(contentType, *strings.AppendElement());
nsCOMPtr<nsIURI> referrer = ReferrerInfo()->GetOriginalReferrer();
@ -791,6 +817,13 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
SRIMetadata sriMetadata;
mSheet->GetIntegrity(sriMetadata);
if (!sriMetadata.IsEmpty()) {
if (!NS_IsMainThread()) {
// We dont process any further in OMT.
// This is because we have some main-thread only accesses below.
// We need to find a way to optimize this handling.
// See Bug 1882046.
return NS_OK;
}
nsAutoCString sourceUri;
if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
@ -815,9 +848,13 @@ nsresult SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
}
}
if (mSheet->GetCORSMode() == CORS_NONE &&
!mTriggeringPrincipal->Subsumes(principal)) {
mIsCrossOriginNoCORS = true;
}
// 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);
mSheet->SetURIs(aFinalChannelURI, originalURI, aFinalChannelURI);
ReferrerPolicy policy =
nsContentUtils::GetReferrerPolicyFromChannel(aChannel);
@ -1577,32 +1614,35 @@ nsresult Loader::LoadSheetAsyncInternal(SheetLoadData& aLoadData,
/**
* ParseSheet handles parsing the data stream.
*/
Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
SheetLoadData& aLoadData,
AllowAsyncParse aAllowAsync) {
Loader::Completed Loader::ParseSheet(
const nsACString& aBytes, const RefPtr<SheetLoadDataHolder>& aLoadData,
AllowAsyncParse aAllowAsync) {
LOG(("css::Loader::ParseSheet"));
if (aLoadData.mURI) {
LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
SheetLoadData* loadData = aLoadData->get();
MOZ_ASSERT(loadData);
if (loadData->mURI) {
LOG_URI(" Load succeeded for URI: '%s', parsing", loadData->mURI);
}
AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(LAYOUT_CSSParsing);
++mParsedSheetCount;
aLoadData.mIsBeingParsed = true;
loadData->mIsBeingParsed = true;
StyleSheet* sheet = aLoadData.mSheet;
StyleSheet* sheet = loadData->mSheet;
MOZ_ASSERT(sheet);
// Some cases, like inline style and UA stylesheets, need to be parsed
// synchronously. The former may trigger child loads, the latter must not.
if (aLoadData.mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
sheet->ParseSheetSync(this, aBytes, &aLoadData);
aLoadData.mIsBeingParsed = false;
if (loadData->mSyncLoad || aAllowAsync == AllowAsyncParse::No) {
sheet->ParseSheetSync(this, aBytes, loadData);
loadData->mIsBeingParsed = false;
bool noPendingChildren = aLoadData.mPendingChildren == 0;
MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
bool noPendingChildren = loadData->mPendingChildren == 0;
MOZ_ASSERT_IF(loadData->mSyncLoad, noPendingChildren);
if (noPendingChildren) {
SheetComplete(aLoadData, NS_OK);
SheetComplete(*loadData, NS_OK);
return Completed::Yes;
}
return Completed::No;
@ -1616,9 +1656,9 @@ Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
sheet->ParseSheet(*this, aBytes, aLoadData)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
[loadData = aLoadData](bool aDummy) {
MOZ_ASSERT(NS_IsMainThread());
loadData->SheetFinishedParsingAsync();
loadData->get()->SheetFinishedParsingAsync();
},
[] { MOZ_CRASH("rejected parse promise"); });
return Completed::No;
@ -1842,7 +1882,10 @@ Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
// effects of inline stylesheets are visible immediately (aside from
// @imports).
NS_ConvertUTF16toUTF8 utf8(aBuffer);
completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
RefPtr<SheetLoadDataHolder> holder(
new nsMainThreadPtrHolder<css::SheetLoadData>(__func__, data.get(),
true));
completed = ParseSheet(utf8, holder, AllowAsyncParse::No);
if (completed == Completed::Yes) {
if (isWorthCaching) {
mInlineSheets.InsertOrUpdate(aBuffer, std::move(sheet));

View File

@ -590,7 +590,8 @@ class Loader final {
//
// If this function returns Completed::Yes, then ParseSheet also called
// SheetComplete on aLoadData.
Completed ParseSheet(const nsACString&, SheetLoadData&, AllowAsyncParse);
Completed ParseSheet(const nsACString&, const RefPtr<SheetLoadDataHolder>&,
AllowAsyncParse);
// The load of the sheet in the load data is done, one way or another.
// Do final cleanup.
@ -646,7 +647,7 @@ class Loader final {
uint32_t mPendingLoadCount = 0;
// The number of stylesheets that we have parsed, for testing purposes.
uint32_t mParsedSheetCount = 0;
Atomic<uint32_t, MemoryOrdering::Relaxed> mParsedSheetCount{0};
bool mEnabled = true;

View File

@ -90,7 +90,9 @@ class SheetLoadData final
// so aBytes1 and aBytes2 refer to those pieces.
nsresult VerifySheetReadyToParse(nsresult aStatus, const nsACString& aBytes1,
const nsACString& aBytes2,
nsIChannel* aChannel);
nsIChannel* aChannel,
nsIURI* aFinalChannelURI,
nsIPrincipal* aPrincipal);
NS_DECL_ISUPPORTS
@ -237,6 +239,8 @@ class SheetLoadData final
// listening for the load.
bool mIntentionallyDropped = false;
const bool mRecordErrors;
bool ShouldDefer() const { return mWasAlternate || !mMediaMatched; }
RefPtr<StyleSheet> ValueForCache() const;

View File

@ -5,7 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/css/StreamLoader.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Encoding.h"
#include "mozilla/TaskQueue.h"
#include "nsContentUtils.h"
@ -14,16 +14,21 @@
#include "nsIThreadRetargetableRequest.h"
#include "nsIStreamTransportService.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
namespace mozilla::css {
StreamLoader::StreamLoader(SheetLoadData& aSheetLoadData)
: mSheetLoadData(&aSheetLoadData), mStatus(NS_OK) {}
: mSheetLoadData(&aSheetLoadData),
mStatus(NS_OK),
mMainThreadSheetLoadData(new nsMainThreadPtrHolder<SheetLoadData>(
"StreamLoader::SheetLoadData", mSheetLoadData, false)) {}
StreamLoader::~StreamLoader() {
#ifdef NIGHTLY_BUILD
MOZ_RELEASE_ASSERT(mOnStopRequestCalled || mChannelOpenFailed);
MOZ_RELEASE_ASSERT(mOnStopProcessingDone || mChannelOpenFailed);
#endif
}
@ -34,6 +39,7 @@ NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener,
NS_IMETHODIMP
StreamLoader::OnStartRequest(nsIRequest* aRequest) {
MOZ_ASSERT(aRequest);
mRequest = aRequest;
mSheetLoadData->NotifyStart(aRequest);
// It's kinda bad to let Web content send a number that results
@ -52,6 +58,12 @@ StreamLoader::OnStartRequest(nsIRequest* aRequest) {
return (mStatus = NS_ERROR_OUT_OF_MEMORY);
}
}
NS_GetFinalChannelURI(channel, getter_AddRefs(mFinalChannelURI));
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
// we dont return on error here as the error is handled in
// SheetLoadData::VerifySheetReadyToParse
Unused << secMan->GetChannelResultPrincipal(
channel, getter_AddRefs(mChannelResultPrincipal));
}
if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
nsCOMPtr<nsIEventTarget> sts =
@ -73,6 +85,12 @@ StreamLoader::OnStartRequest(nsIRequest* aRequest) {
return *info.mExpirationTime;
}();
// we need to block block resolution of parse promise until we receive
// OnStopRequest on Main thread. This is necessary because, parse promise
// resolution fires OnLoad event OnLoad event must not be dispatched until
// OnStopRequest in main thread is dispatched.
mSheetLoadData->mSheet->BlockOrUnblockParsePromise(true);
return NS_OK;
}
@ -81,39 +99,73 @@ StreamLoader::CheckListenerChain() { return NS_OK; }
NS_IMETHODIMP
StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
#ifdef NIGHTLY_BUILD
MOZ_RELEASE_ASSERT(!mOnStopRequestCalled);
mOnStopRequestCalled = true;
#endif
MOZ_ASSERT_IF(!StaticPrefs::network_send_OnDataFinished_cssLoader(),
!mOnStopProcessingDone);
// StreamLoader::OnStopRequest can get triggered twice for a request.
// Once from the path
// nsIThreadRetargetableStreamListener::OnDataFinished->StreamLoader::OnDataFinished
// (non-main thread) and
// once from nsIRequestObserver::OnStopRequest path (main thread). It is
// guaranteed that we will always get
// nsIThreadRetargetableStreamListener::OnDataFinished trigger first and this
// is always followed by nsIRequestObserver::OnStopRequest
// If we are executing OnStopRequest OMT, we need to block resolution of parse
// promise and unblock again if we are executing this in main thread.
// Resolution of parse promise fires onLoadEvent and this should not happen
// before main thread OnStopRequest is dispatched.
if (NS_IsMainThread()) {
mSheetLoadData->mSheet->BlockOrUnblockParsePromise(false);
}
if (mOnStopProcessingDone) {
return NS_OK;
}
mOnStopProcessingDone = true;
nsresult rv = mStatus;
// Decoded data
nsCString utf8String;
{
// Hold the nsStringBuffer for the bytes from the stack to ensure release
// no matter which return branch is taken.
nsCString bytes = std::move(mBytes);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if (NS_FAILED(mStatus)) {
mSheetLoadData->VerifySheetReadyToParse(mStatus, ""_ns, ""_ns, channel);
mSheetLoadData->VerifySheetReadyToParse(mStatus, ""_ns, ""_ns, channel,
mFinalChannelURI,
mChannelResultPrincipal);
if (!NS_IsMainThread()) {
// When processing OMT, we have code paths in VerifySheetReadyToParse
// that are main-thread only. We bail on such scenarios and continue
// processing them on main thread OnStopRequest.
mOnStopProcessingDone = false;
}
return mStatus;
}
rv = mSheetLoadData->VerifySheetReadyToParse(aStatus, mBOMBytes, bytes,
channel);
rv = mSheetLoadData->VerifySheetReadyToParse(aStatus, mBOMBytes, mBytes,
channel, mFinalChannelURI,
mChannelResultPrincipal);
if (rv != NS_OK_PARSE_SHEET) {
if (!NS_IsMainThread()) {
mOnStopProcessingDone = false;
}
return rv;
}
// BOM detection generally happens during the write callback, but that won't
// have happened if fewer than three bytes were received.
// At this point all the conditions that requires us to run on main
// are checked in VerifySheetReadyToParse
// BOM detection generally happens during the write callback, but that
// won't have happened if fewer than three bytes were received.
if (mEncodingFromBOM.isNothing()) {
HandleBOM();
MOZ_ASSERT(mEncodingFromBOM.isSome());
}
// Hold the nsStringBuffer for the bytes from the stack to ensure release
// after its scope ends
nsCString bytes = std::move(mBytes);
// The BOM handling has happened, but we still may not have an encoding if
// there was no BOM. Ensure we have one.
const Encoding* encoding = mEncodingFromBOM.value();
@ -142,9 +194,11 @@ StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
// For reasons I don't understand, factoring the below lines into
// a method on SheetLoadData resulted in a linker error. Hence,
// accessing fields of mSheetLoadData from here.
mSheetLoadData->mLoader->ParseSheet(utf8String, *mSheetLoadData,
mSheetLoadData->mLoader->ParseSheet(utf8String, mMainThreadSheetLoadData,
Loader::AllowAsyncParse::Yes);
mRequest = nullptr;
return NS_OK;
}
@ -159,9 +213,6 @@ StreamLoader::OnDataAvailable(nsIRequest*, nsIInputStream* aInputStream,
return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy);
}
NS_IMETHODIMP
StreamLoader::OnDataFinished(nsresult aStatus) { return NS_OK; }
void StreamLoader::HandleBOM() {
MOZ_ASSERT(mEncodingFromBOM.isNothing());
MOZ_ASSERT(mBytes.IsEmpty());
@ -176,6 +227,15 @@ void StreamLoader::HandleBOM() {
mBOMBytes.Truncate(bomLength);
}
NS_IMETHODIMP
StreamLoader::OnDataFinished(nsresult aResult) {
if (StaticPrefs::network_send_OnDataFinished_cssLoader()) {
return OnStopRequest(mRequest, aResult);
}
return NS_OK;
}
nsresult StreamLoader::WriteSegmentFun(nsIInputStream*, void* aClosure,
const char* aSegment, uint32_t,
uint32_t aCount, uint32_t* aWriteCount) {

View File

@ -9,6 +9,7 @@
#include "nsIStreamListener.h"
#include "nsIThreadRetargetableStreamListener.h"
#include "nsIURI.h"
#include "nsString.h"
#include "mozilla/css/SheetLoadData.h"
#include "mozilla/Assertions.h"
@ -52,10 +53,15 @@ class StreamLoader : public nsIThreadRetargetableStreamListener {
// mBytes, and store all subsequent data in that buffer.
nsCString mBytes;
nsAutoCStringN<3> mBOMBytes;
nsCOMPtr<nsIRequest> mRequest;
nsCOMPtr<nsIURI> mFinalChannelURI;
nsCOMPtr<nsIPrincipal> mChannelResultPrincipal;
// flag to indicate that we can skip processing of data in OnStopRequest
bool mOnStopProcessingDone{false};
RefPtr<SheetLoadDataHolder> mMainThreadSheetLoadData;
#ifdef NIGHTLY_BUILD
bool mChannelOpenFailed = false;
bool mOnStopRequestCalled = false;
#endif
};

View File

@ -28,7 +28,6 @@
#include "mozilla/css/SheetLoadData.h"
#include "mozAutoDocUpdate.h"
#include "SheetLoadData.h"
namespace mozilla {
@ -72,6 +71,10 @@ StyleSheet::StyleSheet(const StyleSheet& aCopy, StyleSheet* aParentSheetToUse,
State::ModifiedRulesForDevtools);
}
// we could have cloned sheet which has not been parsed yet
// reset the parsing state flag
mState &= ~State::AsyncParseOngoing;
if (aCopy.mMedia) {
// XXX This is wrong; we should be keeping @import rules and
// sheets in sync!
@ -740,7 +743,9 @@ already_AddRefed<dom::Promise> StyleSheet::Replace(const nsACString& aText,
loadData->mIsBeingParsed = true;
MOZ_ASSERT(!mReplacePromise);
mReplacePromise = promise;
ParseSheet(*loader, aText, *loadData)
RefPtr<css::SheetLoadDataHolder> holder(
new css::SheetLoadDataHolder(__func__, loadData, false));
ParseSheet(*loader, aText, holder)
->Then(
target, __func__,
[loadData] { loadData->SheetFinishedParsingAsync(); },
@ -1159,28 +1164,20 @@ already_AddRefed<StyleSheet> StyleSheet::CreateEmptyChildSheet(
return child.forget();
}
// We disable parallel stylesheet parsing if the browser is recording CSS errors
// (which parallel parsing can't handle).
static bool AllowParallelParse(css::Loader& aLoader, URLExtraData* aUrlData) {
Document* doc = aLoader.GetDocument();
if (doc && css::ErrorReporter::ShouldReportErrors(*doc)) {
return false;
}
// Otherwise we can parse in parallel.
return true;
}
RefPtr<StyleSheetParsePromise> StyleSheet::ParseSheet(
css::Loader& aLoader, const nsACString& aBytes,
css::SheetLoadData& aLoadData) {
const RefPtr<css::SheetLoadDataHolder>& aLoadData) {
MOZ_ASSERT(mParsePromise.IsEmpty());
RefPtr<StyleSheetParsePromise> p = mParsePromise.Ensure(__func__);
if (!aLoadData.ShouldDefer()) {
if (!aLoadData->get()->ShouldDefer()) {
mParsePromise.SetTaskPriority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING,
__func__);
}
SetURLExtraData();
MOZ_ASSERT(!IsAsyncParseOngoing());
mState |= State::AsyncParseOngoing;
MOZ_ASSERT_IF(NS_IsMainThread(), !HasParsePromiseResolutionBlocked());
// @import rules are disallowed due to this decision:
// https://github.com/WICG/construct-stylesheets/issues/119#issuecomment-588352418
// We may allow @import rules again in the future.
@ -1191,26 +1188,26 @@ RefPtr<StyleSheetParsePromise> StyleSheet::ParseSheet(
const bool shouldRecordCounters =
aLoader.GetDocument() && aLoader.GetDocument()->GetStyleUseCounters() &&
!urlData->ChromeRulesEnabled();
if (!AllowParallelParse(aLoader, urlData)) {
if (aLoadData->get()->mRecordErrors) {
MOZ_ASSERT(NS_IsMainThread());
UniquePtr<StyleUseCounters> counters;
if (shouldRecordCounters) {
counters.reset(Servo_UseCounters_Create());
}
RefPtr<StyleStylesheetContents> contents =
Servo_StyleSheet_FromUTF8Bytes(
&aLoader, this, &aLoadData, &aBytes, mParsingMode, urlData,
aLoadData.mCompatMode,
&aLoader, this, aLoadData->get(), &aBytes, mParsingMode, urlData,
aLoadData->get()->mCompatMode,
/* reusable_sheets = */ nullptr, counters.get(), allowImportRules,
StyleSanitizationKind::None,
/* sanitized_output = */ nullptr)
.Consume();
FinishAsyncParse(contents.forget(), std::move(counters));
} else {
auto holder = MakeRefPtr<css::SheetLoadDataHolder>(__func__, &aLoadData);
Servo_StyleSheet_FromUTF8BytesAsync(holder, urlData, &aBytes, mParsingMode,
aLoadData.mCompatMode,
shouldRecordCounters, allowImportRules);
Servo_StyleSheet_FromUTF8BytesAsync(
aLoadData, urlData, &aBytes, mParsingMode,
aLoadData->get()->mCompatMode, shouldRecordCounters, allowImportRules);
}
return p;
@ -1219,12 +1216,13 @@ RefPtr<StyleSheetParsePromise> StyleSheet::ParseSheet(
void StyleSheet::FinishAsyncParse(
already_AddRefed<StyleStylesheetContents> aSheetContents,
UniquePtr<StyleUseCounters> aUseCounters) {
mState &= ~State::AsyncParseOngoing;
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mParsePromise.IsEmpty());
Inner().mContents = aSheetContents;
Inner().mUseCounters = std::move(aUseCounters);
FixUpRuleListAfterContentsChangeIfNeeded();
mParsePromise.Resolve(true, __func__);
MayBeResolveParsePromise();
}
void StyleSheet::ParseSheetSync(

View File

@ -15,11 +15,13 @@
#include "mozilla/RefPtr.h"
#include "mozilla/ServoBindingTypes.h"
#include "mozilla/ServoTypes.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StyleSheetInfo.h"
#include "nsICSSLoaderObserver.h"
#include "nsIPrincipal.h"
#include "nsWrapperCache.h"
#include "nsStringFwd.h"
#include "nsProxyRelease.h"
class nsIGlobalObject;
class nsINode;
@ -44,6 +46,7 @@ class Loader;
class LoaderReusableStyleSheets;
class Rule;
class SheetLoadData;
using SheetLoadDataHolder = nsMainThreadPtrHolder<SheetLoadData>;
} // namespace css
namespace dom {
@ -79,6 +82,12 @@ enum class StyleSheetState : uint8_t {
// This flag is set during the async Replace() function to ensure
// that the sheet is not modified until the promise is resolved.
ModificationDisallowed = 1 << 5,
// Flag to indicate if resolution of parse promise is blocked.
// Parse promise resolution should be blocked if we are parsing sheet data
// before main thread OnStopRequest is dispatched.
ParsePromiseResolutionBlocked = 1 << 6,
// Flag to indicate if Parse has been completed
AsyncParseOngoing = 1 << 7
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StyleSheetState)
@ -112,9 +121,9 @@ class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
// SheetLoadData for this stylesheet.
// NOTE: ParseSheet can run synchronously or asynchronously
// based on the result of `AllowParallelParse`
RefPtr<StyleSheetParsePromise> ParseSheet(css::Loader&,
const nsACString& aBytes,
css::SheetLoadData&);
RefPtr<StyleSheetParsePromise> ParseSheet(
css::Loader&, const nsACString& aBytes,
const RefPtr<css::SheetLoadDataHolder>& aLoadData);
// Common code that needs to be called after servo finishes parsing. This is
// shared between the parallel and sequential paths.
@ -220,6 +229,14 @@ class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
return bool(mState & State::ModifiedRulesForDevtools);
}
bool IsAsyncParseOngoing() const {
return bool(mState & State::AsyncParseOngoing);
}
bool HasParsePromiseResolutionBlocked() const {
return bool(mState & State::ParsePromiseResolutionBlocked);
}
bool HasUniqueInner() const { return Inner().mSheets.Length() == 1; }
void AssertHasUniqueInner() const { MOZ_ASSERT(HasUniqueInner()); }
@ -301,7 +318,7 @@ class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
*/
void SetPrincipal(nsIPrincipal* aPrincipal) {
StyleSheetInfo& info = Inner();
MOZ_ASSERT(!info.mPrincipalSet, "Should only set principal once");
MOZ_ASSERT_IF(info.mPrincipalSet, info.mPrincipal == aPrincipal);
if (aPrincipal) {
info.mPrincipal = aPrincipal;
#ifdef DEBUG
@ -465,9 +482,29 @@ class StyleSheet final : public nsICSSLoaderObserver, public nsWrapperCache {
// Rejects mReplacePromise with a NetworkError.
void MaybeRejectReplacePromise();
// Resolves mParsePromise with this sheet
void MayBeResolveParsePromise() {
if (!IsAsyncParseOngoing() && !HasParsePromiseResolutionBlocked() &&
!mParsePromise.IsEmpty()) {
mParsePromise.Resolve(true, __func__);
}
};
// Gets the relevant global if exists.
nsISupports* GetRelevantGlobal() const;
// Blocks/Unblocks resolution of parse promise
void BlockOrUnblockParsePromise(bool aBlock) {
MOZ_ASSERT_IF(aBlock, !HasParsePromiseResolutionBlocked());
MOZ_ASSERT(NS_IsMainThread());
if (aBlock) {
mState |= State::ParsePromiseResolutionBlocked;
} else {
mState &= ~State::ParsePromiseResolutionBlocked;
MayBeResolveParsePromise();
}
}
private:
void SetModifiedRules() {
mState |= State::ModifiedRules | State::ModifiedRulesForDevtools;

View File

@ -12184,12 +12184,18 @@
value: true
mirror: always
# Whether we can send OnDataAvailable to content process directly.
# Whether we can send OnDataFinished to content process directly.
- name: network.send_OnDataFinished.nsInputStreamPump
type: RelaxedAtomicBool
value: true
mirror: always
# Whether we can send OnDataFinished to cssLoader in content process.
- name: network.send_OnDataFinished.cssLoader
type: RelaxedAtomicBool
value: @IS_EARLY_BETA_OR_EARLIER@
mirror: always
# Whether we can send send OnDataFinished only after dispatching
# all the progress events on the main thread
- name: network.send_OnDataFinished_after_progress_updates