gecko-dev/layout/style/Loader.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2533 строки
89 KiB
C++
Исходник Обычный вид История

/* -*- 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
2012-05-21 15:12:37 +04:00
* 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/SystemGroup.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/URLPreloader.h"
#include "nsIRunnable.h"
#include "nsITimedChannel.h"
#include "nsSyncLoadService.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsIProtocolHandler.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 "nsIStyleSheetLinkingElement.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/ServoBindings.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/css/StreamLoader.h"
#include "ReferrerInfo.h"
1999-05-19 03:15:10 +04:00
#ifdef MOZ_XUL
# include "nsXULPrototypeCache.h"
#endif
#include "nsError.h"
#include "nsIContentSecurityPolicy.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.
*/
static 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[] = {"Unknown", "NeedsParser", "Pending",
"Loading", "Complete"};
1999-05-19 03:15:10 +04:00
namespace mozilla {
class SheetLoadDataHashKey : public nsURIHashKey {
public:
typedef SheetLoadDataHashKey* KeyType;
typedef const SheetLoadDataHashKey* KeyTypePointer;
explicit SheetLoadDataHashKey(const SheetLoadDataHashKey* aKey)
: nsURIHashKey(aKey->mKey),
mPrincipal(aKey->mPrincipal),
mReferrerInfo(aKey->mReferrerInfo),
mCORSMode(aKey->mCORSMode),
mParsingMode(aKey->mParsingMode) {
MOZ_COUNT_CTOR(SheetLoadDataHashKey);
}
SheetLoadDataHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
nsIReferrerInfo* aReferrerInfo, CORSMode aCORSMode,
css::SheetParsingMode aParsingMode)
: nsURIHashKey(aURI),
mPrincipal(aPrincipal),
mReferrerInfo(aReferrerInfo),
mCORSMode(aCORSMode),
mParsingMode(aParsingMode) {
MOZ_COUNT_CTOR(SheetLoadDataHashKey);
}
SheetLoadDataHashKey(SheetLoadDataHashKey&& toMove)
: nsURIHashKey(std::move(toMove)),
mPrincipal(std::move(toMove.mPrincipal)),
mReferrerInfo(std::move(toMove.mReferrerInfo)),
mCORSMode(std::move(toMove.mCORSMode)),
mParsingMode(std::move(toMove.mParsingMode)) {
MOZ_COUNT_CTOR(SheetLoadDataHashKey);
}
explicit SheetLoadDataHashKey(css::SheetLoadData&);
~SheetLoadDataHashKey() { MOZ_COUNT_DTOR(SheetLoadDataHashKey); }
SheetLoadDataHashKey* GetKey() const {
return const_cast<SheetLoadDataHashKey*>(this);
}
const SheetLoadDataHashKey* GetKeyPointer() const { return this; }
bool KeyEquals(const SheetLoadDataHashKey* aKey) const {
if (!nsURIHashKey::KeyEquals(aKey->mKey)) {
return false;
}
if (!mPrincipal != !aKey->mPrincipal) {
// One or the other has a principal, but not both... not equal
return false;
}
if (mCORSMode != aKey->mCORSMode) {
// Different CORS modes; we don't match
return false;
}
if (mParsingMode != aKey->mParsingMode) {
return false;
}
bool eq;
if (NS_FAILED(mReferrerInfo->Equals(aKey->mReferrerInfo, &eq)) || !eq) {
return false;
}
return !mPrincipal ||
(NS_SUCCEEDED(mPrincipal->Equals(aKey->mPrincipal, &eq)) && eq);
}
static const SheetLoadDataHashKey* KeyToPointer(SheetLoadDataHashKey* aKey) {
return aKey;
}
static PLDHashNumber HashKey(const SheetLoadDataHashKey* aKey) {
return nsURIHashKey::HashKey(aKey->mKey);
}
nsIURI* GetURI() const { return nsURIHashKey::GetKey(); }
nsIPrincipal* GetPrincipal() const { return mPrincipal; }
css::SheetParsingMode ParsingMode() const { return mParsingMode; }
enum { ALLOW_MEMMOVE = true };
protected:
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
CORSMode mCORSMode;
css::SheetParsingMode mParsingMode;
};
SheetLoadDataHashKey::SheetLoadDataHashKey(css::SheetLoadData& aLoadData)
: nsURIHashKey(aLoadData.mURI),
mPrincipal(aLoadData.mLoaderPrincipal),
mReferrerInfo(aLoadData.ReferrerInfo()),
mCORSMode(aLoadData.mSheet->GetCORSMode()),
mParsingMode(aLoadData.mSheet->ParsingMode()) {
MOZ_COUNT_CTOR(SheetLoadDataHashKey);
}
} // namespace mozilla
namespace mozilla {
namespace css {
/********************************
* SheetLoadData implementation *
********************************/
NS_IMPL_ISUPPORTS(SheetLoadData, nsIRunnable, nsIThreadObserver)
SheetLoadData::SheetLoadData(
Loader* aLoader, const nsAString& aTitle, nsIURI* aURI, StyleSheet* aSheet,
bool aSyncLoad, nsIStyleSheetLinkingElement* aOwningElement,
IsAlternate aIsAlternate, MediaMatched aMediaMatches,
nsICSSLoaderObserver* aObserver, nsIPrincipal* aLoaderPrincipal,
nsIReferrerInfo* aReferrerInfo, nsINode* aRequestingNode)
: mLoader(aLoader),
mTitle(aTitle),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(aSyncLoad),
mIsNonDocumentSheet(false),
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),
mOwningElement(aOwningElement),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mPreloadEncoding(nullptr) {
MOZ_ASSERT(mLoader, "Must have a loader!");
1999-05-19 03:15:10 +04:00
}
SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
SheetLoadData* aParentData,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsIReferrerInfo* aReferrerInfo,
nsINode* aRequestingNode)
: mLoader(aLoader),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mParentData(aParentData),
mPendingChildren(0),
mSyncLoad(false),
mIsNonDocumentSheet(false),
mIsLoading(false),
mIsBeingParsed(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mMediaMatched(true),
mUseSystemPrincipal(false),
mSheetAlreadyComplete(false),
mIsCrossOriginNoCORS(false),
mBlockResourceTiming(false),
mLoadFailed(false),
mOwningElement(nullptr),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mPreloadEncoding(nullptr) {
MOZ_ASSERT(mLoader, "Must have a loader!");
if (mParentData) {
mSyncLoad = mParentData->mSyncLoad;
mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
++(mParentData->mPendingChildren);
}
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
1999-05-19 03:15:10 +04:00
}
SheetLoadData::SheetLoadData(Loader* aLoader, nsIURI* aURI, StyleSheet* aSheet,
bool aSyncLoad, bool aUseSystemPrincipal,
const Encoding* aPreloadEncoding,
nsICSSLoaderObserver* aObserver,
nsIPrincipal* aLoaderPrincipal,
nsIReferrerInfo* aReferrerInfo,
nsINode* aRequestingNode)
: mLoader(aLoader),
mEncoding(nullptr),
mURI(aURI),
mLineNumber(1),
mSheet(aSheet),
mNext(nullptr),
mPendingChildren(0),
mSyncLoad(aSyncLoad),
mIsNonDocumentSheet(true),
mIsLoading(false),
mIsBeingParsed(false),
mIsCancelled(false),
mMustNotify(false),
mWasAlternate(false),
mMediaMatched(true),
mUseSystemPrincipal(aUseSystemPrincipal),
mSheetAlreadyComplete(false),
mIsCrossOriginNoCORS(false),
mBlockResourceTiming(false),
mLoadFailed(false),
mOwningElement(nullptr),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal),
mReferrerInfo(aReferrerInfo),
mRequestingNode(aRequestingNode),
mPreloadEncoding(aPreloadEncoding) {
MOZ_ASSERT(mLoader, "Must have a loader!");
MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
"Shouldn't use system principal for async loads");
}
SheetLoadData::~SheetLoadData() {
// Do this iteratively to avoid blowing up the stack.
RefPtr<SheetLoadData> next = mNext.forget();
while (next) {
next = next->mNext.forget();
}
1999-05-19 03:15:10 +04:00
}
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) {
Bug 1179909: Refactor stable state handling. r=smaug This is motivated by three separate but related problems: 1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1). 2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this). 3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread. The solve this, this patch does the following: 1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime. 2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs. 3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with. 4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways: a) It fires between microtasks, which was the motivation for starting this. b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this. 5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.
2015-08-11 16:10:46 +03:00
// 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) {
Bug 1179909: Refactor stable state handling. r=smaug This is motivated by three separate but related problems: 1. Our concept of recursion depth is broken for things that run from AfterProcessNextEvent observers (e.g. Promises). We decrement the recursionDepth counter before firing observers, so a Promise callback running at the lowest event loop depth has a recursion depth of 0 (whereas a regular nsIRunnable would be 1). This is a problem because it's impossible to distinguish a Promise running after a sync XHR's onreadystatechange handler from a top-level event (since the former runs with depth 2 - 1 = 1, and the latter runs with just 1). 2. The nsIThreadObserver mechanism that is used by a lot of code to run "after" the current event is a poor fit for anything that runs script. First, the order the observers fire in is the order they were added, not anything fixed by spec. Additionally, running script can cause the event loop to spin, which is a big source of pain here (bholley has some nasty bug caused by this). 3. We run Promises from different points in the code for workers and main thread. The latter runs from XPConnect's nsIThreadObserver callbacks, while the former runs from a hardcoded call to run Promises in the worker event loop. What workers do is particularly problematic because it means we can't get the right recursion depth no matter what we do to nsThread. The solve this, this patch does the following: 1. Consolidate some handling of microtasks and all handling of stable state from appshell and WorkerPrivate into CycleCollectedJSRuntime. 2. Make the recursionDepth counter only available to CycleCollectedJSRuntime (and its consumers) and remove it from the nsIThreadInternal and nsIThreadObserver APIs. 3. Adjust the recursionDepth counter so that microtasks run with the recursionDepth of the task they are associated with. 4. Introduce the concept of metastable state to replace appshell's RunBeforeNextEvent. Metastable state is reached after every microtask or task is completed. This provides the semantics that bent and I want for IndexedDB, where transactions autocommit at the end of a microtask and do not "spill" from one microtask into a subsequent microtask. This differs from appshell's RunBeforeNextEvent in two ways: a) It fires between microtasks, which was the motivation for starting this. b) It no longer ensures that we're at the same event loop depth in the native event queue. bent decided we don't care about this. 5. Reorder stable state to happen after microtasks such as Promises, per HTML. Right now we call the regular thread observers, including appshell, before the main thread observer (XPConnect), so stable state tasks happen before microtasks.
2015-08-11 16:10:46 +03:00
// 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::FireLoadEvent(nsIThreadInternal* aThread) {
// First remove ourselves as a thread observer. But we need to keep
// ourselves alive while doing that!
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<SheetLoadData> kungFuDeathGrip(this);
aThread->RemoveObserver(this);
// Now fire the event
nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
NS_ASSERTION(node, "How did that happen???");
nsContentUtils::DispatchTrustedEvent(
node->OwnerDoc(), node,
mLoadFailed ? NS_LITERAL_STRING("error") : NS_LITERAL_STRING("load"),
CanBubble::eNo, Cancelable::eNo);
// And unblock onload
mLoader->UnblockOnload(true);
}
void SheetLoadData::ScheduleLoadEventIfNeeded() {
if (!mOwningElement) {
return;
}
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
// Make sure to block onload here
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;
}
// A struct keeping alive various records of sheets that are loading, deferred,
// or already loaded (the later one for caching purposes).
struct Loader::Sheets {
nsRefPtrHashtable<SheetLoadDataHashKey, StyleSheet> mCompleteSheets;
nsRefPtrHashtable<SheetLoadDataHashKey, SheetLoadData> mPendingDatas;
// The SheetLoadData pointers in mLoadingDatas below are weak references.
nsDataHashtable<SheetLoadDataHashKey, SheetLoadData*> mLoadingDatas;
nsRefPtrHashtable<nsStringHashKey, StyleSheet> mInlineSheets;
RefPtr<StyleSheet> LookupInline(const nsAString&);
// A cache hit or miss. It is a miss if the `StyleSheet` is null.
using CacheResult = Tuple<RefPtr<StyleSheet>, SheetState>;
CacheResult Lookup(SheetLoadDataHashKey&, bool aSyncLoad);
size_t SizeOfIncludingThis(MallocSizeOf) const;
};
RefPtr<StyleSheet> Loader::Sheets::LookupInline(const nsAString& aBuffer) {
auto result = mInlineSheets.Lookup(aBuffer);
if (!result) {
return nullptr;
}
if (result.Data()->HasForcedUniqueInner()) {
// Remove it now that we know that we're never going to use this stylesheet
// again.
result.Remove();
return nullptr;
}
return result.Data()->Clone(nullptr, nullptr, nullptr, nullptr);
}
static void AssertComplete(const StyleSheet& aSheet) {
// This sheet came from the XUL cache or our per-document hashtable; it
// better be a complete sheet.
MOZ_ASSERT(aSheet.IsComplete(),
"Sheet thinks it's not complete while we think it is");
}
static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
const SheetLoadDataHashKey& aKey) {
#ifdef DEBUG
bool debugEqual;
MOZ_ASSERT((!aKey.GetPrincipal() && !aData.mLoaderPrincipal) ||
(aKey.GetPrincipal() && aData.mLoaderPrincipal &&
NS_SUCCEEDED(aKey.GetPrincipal()->Equals(
aData.mLoaderPrincipal, &debugEqual)) &&
debugEqual),
"Principals should be the same");
#endif
MOZ_ASSERT(!aData.mSheet->HasForcedUniqueInner(),
"CSSOM shouldn't allow access to incomplete sheets");
}
auto Loader::Sheets::Lookup(SheetLoadDataHashKey& aKey, bool aSyncLoad)
-> CacheResult {
auto CloneSheet = [](StyleSheet& aSheet) -> RefPtr<StyleSheet> {
return aSheet.Clone(nullptr, nullptr, nullptr, nullptr);
};
nsIURI* uri = aKey.GetURI();
// Try to find first in the XUL prototype cache.
#ifdef MOZ_XUL
if (IsChromeURI(uri)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache && cache->IsEnabled()) {
if (StyleSheet* sheet = cache->GetStyleSheet(uri)) {
LOG((" From XUL cache: %p", sheet));
AssertComplete(*sheet);
// We need to check the parsing mode manually because the XUL cache only
// keys off the URI. See below for the unique inner check.
if (!sheet->HasForcedUniqueInner() &&
sheet->ParsingMode() == aKey.ParsingMode()) {
return MakeTuple(CloneSheet(*sheet), SheetState::Complete);
}
LOG(
(" Not cloning due to forced unique inner or mismatched "
"parsing mode"));
}
}
}
#endif
// Now complete sheets.
if (auto lookup = mCompleteSheets.Lookup(&aKey)) {
LOG((" From completed: %p", lookup.Data().get()));
AssertComplete(*lookup.Data());
MOZ_ASSERT(lookup.Data()->ParsingMode() == aKey.ParsingMode());
// Make sure it hasn't been forced to have a unique inner; that is an
// indication that its rules have been exposed to CSSOM and so we can't use
// it.
if (!lookup.Data()->HasForcedUniqueInner()) {
RefPtr<StyleSheet> clone = CloneSheet(*lookup.Data());
if (!lookup.Data()->GetOwnerNode() && !lookup.Data()->GetParentSheet()) {
// The sheet we're cloning isn't actually referenced by anyone. Replace
// it in the cache, so that if our CSSOM is later modified we don't end
// up with two copies of our inner hanging around.
lookup.Data() = clone;
}
return MakeTuple(std::move(clone), SheetState::Complete);
}
LOG((" Not cloning due to forced unique inner"));
// Remove it now that we know that we're never going to use this stylesheet
// again.
lookup.Remove();
}
if (aSyncLoad) {
return {};
}
if (SheetLoadData* data = mLoadingDatas.Get(&aKey)) {
LOG((" From loading: %p", data->mSheet.get()));
AssertIncompleteSheetMatches(*data, aKey);
return MakeTuple(CloneSheet(*data->mSheet), SheetState::Loading);
}
if (SheetLoadData* data = mPendingDatas.GetWeak(&aKey)) {
LOG((" From pending: %p", data->mSheet.get()));
AssertIncompleteSheetMatches(*data, aKey);
return MakeTuple(CloneSheet(*data->mSheet), SheetState::Pending);
}
return {};
}
size_t Loader::Sheets::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto iter = mCompleteSheets.ConstIter(); !iter.Done(); iter.Next()) {
// If the sheet has a parent, then its parent will report it so we don't
// have to worry about it here. Likewise, if aSheet has an owning node, then
// the document that node is in will report it.
const StyleSheet* sheet = iter.UserData();
if (!sheet->GetOwnerNode() && !sheet->GetParentSheet()) {
n += sheet->SizeOfIncludingThis(aMallocSizeOf);
}
}
n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto iter = mInlineSheets.ConstIter(); !iter.Done(); iter.Next()) {
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
// If the sheet has a parent, then its parent will report it so we don't
// have to worry about it here.
const StyleSheet* sheet = iter.UserData();
MOZ_ASSERT(!sheet->GetParentSheet(),
"How did an @import rule end up here?");
if (!sheet->GetOwnerNode()) {
n += sheet->SizeOfIncludingThis(aMallocSizeOf);
}
}
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mLoadingDatas: transient, and should be small
// - mPendingDatas: transient, and should be small
return n;
}
/*************************
* Loader Implementation *
*************************/
Loader::Loader()
: mDocument(nullptr),
mDatasToNotifyOn(0),
mCompatMode(eCompatibility_FullStandards),
mEnabled(true),
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;
mCompatMode = aDocument->GetCompatibilityMode();
1999-05-19 03:15:10 +04:00
}
Loader::~Loader() {
NS_ASSERTION(!mSheets || mSheets->mLoadingDatas.Count() == 0,
"How did we get destroyed when there are loading data?");
NS_ASSERTION(!mSheets || mSheets->mPendingDatas.Count() == 0,
"How did we get destroyed when there are pending data?");
// 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.
1999-05-19 03:15:10 +04:00
}
void Loader::DropDocumentReference(void) {
mDocument = nullptr;
// Flush out pending datas just so we don't leak by accident. These
// loads should short-circuit through the mDocument check in
// LoadSheet and just end up in SheetComplete immediately
if (mSheets) {
StartDeferredLoads();
}
}
void Loader::DocumentStyleSheetSetChanged() {
MOZ_ASSERT(mDocument);
1999-05-19 03:15:10 +04:00
// start any pending alternates that aren't alternates anymore
if (!mSheets) {
return;
}
LoadDataArray arr(mSheets->mPendingDatas.Count());
for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
RefPtr<SheetLoadData>& data = iter.Data();
MOZ_ASSERT(data, "Must have a data");
// Note that we don't want to affect what the selected style set is, so
// use true for aHasAlternateRel.
auto isAlternate = data->mLoader->IsAlternateSheet(data->mTitle, true);
if (isAlternate == IsAlternate::No) {
arr.AppendElement(std::move(data));
iter.Remove();
}
}
mDatasToNotifyOn += arr.Length();
for (RefPtr<SheetLoadData>& data : arr) {
--mDatasToNotifyOn;
LoadSheet(*data, SheetState::NeedsParser, IsPreload::No);
}
1999-05-19 03:15:10 +04:00
}
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(
nsACString const& aSegment, nsIChannel* aChannel) {
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);
}
}
// Now try the charset on the <link> or processing instruction
// that loaded us
if (mOwningElement) {
nsAutoString label16;
mOwningElement->GetCharset(label16);
encoding = Encoding::ForLabel(label16);
if (encoding) {
return WrapNotNull(encoding);
}
}
// In the preload case, the value of the charset attribute on <link> comes
// in via mPreloadEncoding instead.
if (mPreloadEncoding) {
return WrapNotNull(mPreloadEncoding);
}
// Try charset from the parent stylesheet.
if (mParentData) {
encoding = mParentData->mEncoding;
if (encoding) {
return WrapNotNull(encoding);
}
}
if (mLoader->mDocument) {
// Use the document charset.
return mLoader->mDocument->GetDocumentCharacterSet();
}
return UTF_8_ENCODING;
}
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(), mozilla::LogLevel::Debug)) {
nsAutoCString requestURL;
nsCOMPtr<nsIURI> originalURI;
if (aChannel &&
NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
originalURI) {
originalURI->GetAsciiSpec(requestURL);
}
MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::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);
}
/*
* 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 (mIsCancelled) {
// Just return. Don't call SheetComplete -- it's already been
// called and calling it again will lead to an extra NS_RELEASE on
// this data and a likely crash.
return NS_OK;
}
if (!mLoader->mDocument && !mIsNonDocumentSheet) {
// Sorry, we don't care about this load anymore
LOG_WARN((" No document and not non-document sheet; dropping load"));
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) {
// mOwningElement may be null but AddBlockTrackingNode can cope
nsCOMPtr<nsIContent> content =
do_QueryInterface(data->mOwningElement);
doc->AddBlockedNodeByClassifier(content);
}
}
}
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 (mLoaderPrincipal && mSheet->GetCORSMode() == CORS_NONE) {
bool subsumed;
result = mLoaderPrincipal->Subsumes(principal, &subsumed);
if (NS_FAILED(result) || !subsumed) {
mIsCrossOriginNoCORS = true;
}
}
// If it's an HTTP channel, we want to make sure this is not an
// error document we got.
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
if (httpChannel) {
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;
if (mLoaderPrincipal) {
bool subsumed;
result = mLoaderPrincipal->Subsumes(principal, &subsumed);
if (NS_FAILED(result) || !subsumed) {
sameOrigin = false;
}
}
if (sameOrigin && mLoader->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, NS_LITERAL_CSTRING("CSS Loader"), 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, mozilla::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;
1999-05-19 03:15:10 +04:00
}
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;
1999-05-19 03:15:10 +04:00
}
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;
1999-05-19 03:15:10 +04:00
}
nsresult Loader::CheckContentPolicy(nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aTriggeringPrincipal,
nsIURI* aTargetURI,
nsINode* aRequestingNode,
IsPreload aIsPreload) {
// When performing a system load (e.g. aUseSystemPrincipal = true)
// then aLoadingPrincipal == null; don't consult content policies.
if (!aLoadingPrincipal) {
return NS_OK;
}
nsContentPolicyType contentPolicyType =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
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) {
nsCOMPtr<Element> element = do_QueryInterface(aRequestingNode);
if (element && element->IsHTMLElement()) {
nsAutoString cspNonce;
element->GetAttribute(NS_LITERAL_STRING("nonce"), cspNonce);
secCheckLoadInfo->SetCspNonce(cspNonce);
}
}
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentLoadPolicy(
aTargetURI, secCheckLoadInfo, NS_LITERAL_CSTRING("text/css"), &shouldLoad,
nsContentUtils::GetContentPolicy());
if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
return NS_ERROR_CONTENT_BLOCKED;
}
return NS_OK;
}
/**
* 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().
*/
Tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
nsIURI* aURI, nsIContent* aLinkingContent, nsIPrincipal* aLoaderPrincipal,
css::SheetParsingMode aParsingMode, CORSMode aCORSMode,
nsIReferrerInfo* aLoadingReferrerInfo, const nsAString& aIntegrity,
bool aSyncLoad) {
MOZ_ASSERT(aURI, "This path is not taken for inline stylesheets");
LOG(("css::Loader::CreateSheet(%s)", aURI->GetSpecOrDefault().get()));
if (!mSheets) {
mSheets = MakeUnique<Sheets>();
}
SheetLoadDataHashKey key(aURI, aLoaderPrincipal, aLoadingReferrerInfo,
aCORSMode, aParsingMode);
auto cacheResult = mSheets->Lookup(key, aSyncLoad);
if (Get<0>(cacheResult)) {
LOG((" Hit cache with state: %s",
gStateStrings[size_t(Get<1>(cacheResult))]));
return cacheResult;
}
nsIURI* sheetURI = aURI;
nsIURI* baseURI = aURI;
nsIURI* originalURI = aURI;
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(gSriPRLog, mozilla::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);
}
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 MakeTuple(std::move(sheet), SheetState::NeedsParser);
1999-05-19 03:15:10 +04:00
}
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(aMediaString);
1999-05-19 03:15:10 +04:00
}
aSheet.SetMedia(mediaList);
1999-05-19 03:15:10 +04:00
aSheet.SetTitle(aTitle);
aSheet.SetEnabled(aIsAlternate == IsAlternate::No ||
aIsExplicitlyEnabled == IsExplicitlyEnabled::Yes);
return MediaListMatches(mediaList, mDocument);
}
1999-05-19 03:15:10 +04:00
/**
* 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,
nsIContent* aLinkingContent) {
LOG(("css::Loader::InsertSheetInTree"));
MOZ_ASSERT(mDocument, "Must have a document to insert into");
MOZ_ASSERT(!aLinkingContent || aLinkingContent->IsInUncomposedDoc() ||
aLinkingContent->IsInShadowTree(),
"Why would we insert it anywhere?");
nsCOMPtr<nsIStyleSheetLinkingElement> linkingElement =
do_QueryInterface(aLinkingContent);
if (linkingElement) {
linkingElement->SetStyleSheet(&aSheet);
}
ShadowRoot* shadow =
aLinkingContent ? aLinkingContent->GetContainingShadow() : nullptr;
auto& target = shadow ? static_cast<DocumentOrShadowRoot&>(*shadow)
: static_cast<DocumentOrShadowRoot&>(*mDocument);
1999-05-19 03:15:10 +04:00
// 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 && !aLinkingContent) {
// 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(aLinkingContent != sheetOwner,
"Why do we still have our old sheet?");
// Have to compare
if (nsContentUtils::PositionIsBefore(sheetOwner, aLinkingContent)) {
// 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));
1999-05-19 03:15:10 +04:00
}
/**
* 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
* bug 1220506.)
*/
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.PrependStyleSheet(&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,
IsPreload aIsPreload) {
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");
NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
LOG_URI(" Load from: '%s'", aLoadData.mURI);
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.
nsCOMPtr<nsIStreamListener> streamLoader = new StreamLoader(aLoadData);
if (mDocument) {
mozilla::net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
mDocument);
}
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_ALLOW_CHROME;
nsContentPolicyType contentPolicyType =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
// 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 && aLoadData.mLoaderPrincipal) {
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
aLoadData.mLoaderPrincipal, securityFlags, contentPolicyType);
} else {
// either we are loading something inside a document, in which case
// we should always have a requestingNode, or we are loading something
// outside a document, in which case the loadingPrincipal and the
// triggeringPrincipal should always be the systemPrincipal.
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(),
nsContentUtils::GetSystemPrincipal(),
securityFlags, contentPolicyType);
} else {
rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
nsContentUtils::GetSystemPrincipal(), securityFlags,
contentPolicyType);
}
}
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) {
nsCOMPtr<Element> element = do_QueryInterface(aLoadData.mRequestingNode);
if (element && element->IsHTMLElement()) {
nsAutoString cspNonce;
element->GetAttribute(NS_LITERAL_STRING("nonce"), 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"));
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(NS_LITERAL_CSTRING("UTF-8"));
// 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);
}
SheetLoadData* existingData = nullptr;
SheetLoadDataHashKey key(aLoadData);
if (aSheetState == SheetState::Loading) {
existingData = mSheets->mLoadingDatas.Get(&key);
NS_ASSERTION(existingData, "CreateSheet lied about the state");
} else if (aSheetState == SheetState::Pending) {
existingData = mSheets->mPendingDatas.GetWeak(&key);
NS_ASSERTION(existingData, "CreateSheet lied about the state");
}
if (existingData) {
LOG((" Glomming on to existing load"));
SheetLoadData* data = existingData;
while (data->mNext) {
data = data->mNext;
}
data->mNext = &aLoadData;
if (aSheetState == SheetState::Pending && !aLoadData.ShouldDefer()) {
// Kick the load off; someone cares about it right away
RefPtr<SheetLoadData> removedData;
mSheets->mPendingDatas.Remove(&key, getter_AddRefs(removedData));
MOZ_ASSERT(removedData == existingData, "Bad loading table");
LOG((" Forcing load of pending data"));
return LoadSheet(*removedData, SheetState::NeedsParser, aIsPreload);
}
// All done here; once the load completes we'll be marked complete
// automatically
return NS_OK;
}
nsCOMPtr<nsILoadGroup> loadGroup;
nsCOMPtr<nsICookieSettings> cookieSettings;
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;
}
cookieSettings = mDocument->CookieSettings();
}
#ifdef DEBUG
AutoRestore<bool> syncCallbackGuard(mSyncCallback);
mSyncCallback = true;
#endif
CORSMode ourCORSMode = aLoadData.mSheet->GetCORSMode();
nsSecurityFlags securityFlags =
ourCORSMode == CORS_NONE
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
: nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
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 =
aIsPreload == IsPreload::Yes
? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
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 && aLoadData.mLoaderPrincipal) {
rv = NS_NewChannelWithTriggeringPrincipal(
getter_AddRefs(channel), aLoadData.mURI, aLoadData.mRequestingNode,
aLoadData.mLoaderPrincipal, securityFlags, contentPolicyType,
/* PerformanceStorage */ nullptr, loadGroup);
} else {
// either we are loading something inside a document, in which case
// we should always have a requestingNode, or we are loading something
// outside a document, in which case the loadingPrincipal and the
// triggeringPrincipal should always be the systemPrincipal.
rv = NS_NewChannel(getter_AddRefs(channel), aLoadData.mURI,
nsContentUtils::GetSystemPrincipal(), securityFlags,
contentPolicyType, cookieSettings,
/* 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) {
nsCOMPtr<Element> element = do_QueryInterface(aLoadData.mRequestingNode);
if (element && element->IsHTMLElement()) {
nsAutoString cspNonce;
element->GetAttribute(NS_LITERAL_STRING("nonce"), cspNonce);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(cspNonce);
}
}
if (!aLoadData.ShouldDefer()) {
if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
cos->AddClassFlags(nsIClassOfService::Leader);
}
}
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(NS_LITERAL_STRING("css"));
// 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(NS_LITERAL_STRING("link"));
}
}
}
// 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(NS_LITERAL_CSTRING("text/css"));
// 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
nsCOMPtr<nsIStreamListener> streamLoader = new StreamLoader(aLoadData);
if (mDocument) {
mozilla::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"));
SheetComplete(aLoadData, rv);
return rv;
}
mSheets->mLoadingDatas.Put(&key, &aLoadData);
aLoadData.mIsLoading = true;
return NS_OK;
1999-05-19 03:15:10 +04:00
}
/**
* ParseSheet handles parsing the data stream.
*/
Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
SheetLoadData& aLoadData,
AllowAsyncParse aAllowAsync) {
LOG(("css::Loader::ParseSheet"));
AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
aLoadData.mIsBeingParsed = true;
// Tell the record/replay system about any sheets that are being parsed,
// so that devtools code can find them later.
if (recordreplay::IsRecordingOrReplaying() && aLoadData.mURI) {
recordreplay::NoteContentParse(
&aLoadData, aLoadData.mURI->GetSpecOrDefault().get(), "text/css",
reinterpret_cast<const Utf8Unit*>(aBytes.BeginReading()),
aBytes.Length());
}
StyleSheet* sheet = aLoadData.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.mLineNumber);
aLoadData.mIsBeingParsed = false;
bool noPendingChildren = aLoadData.mPendingChildren == 0;
MOZ_ASSERT_IF(aLoadData.mSyncLoad, noPendingChildren);
if (noPendingChildren) {
SheetComplete(aLoadData, NS_OK);
return Completed::Yes;
}
return Completed::No;
}
// This parse does not need to be synchronous. \o/
//
// Note that we need to block onload because there may be no network requests
// pending.
BlockOnload();
nsCOMPtr<nsISerialEventTarget> target = DispatchTarget();
sheet->ParseSheet(*this, aBytes, aLoadData)
->Then(
target, __func__,
[loadData = RefPtr<SheetLoadData>(&aLoadData)](bool aDummy) {
MOZ_ASSERT(NS_IsMainThread());
loadData->mIsBeingParsed = false;
loadData->mLoader->UnblockOnload(/* aFireSync = */ false);
// If there are no child sheets outstanding, mark us as complete.
// Otherwise, the children are holding strong refs to the data
// and will call SheetComplete() on it when they complete.
if (loadData->mPendingChildren == 0) {
loadData->mLoader->SheetComplete(*loadData, NS_OK);
}
},
[] { MOZ_CRASH("rejected parse promise"); });
return Completed::No;
}
/**
* SheetComplete is the do-it-all cleanup function. It removes the
* load data from the "loading" hashtable, adds the sheet to the
* "completed" hashtable, massages the XUL cache, handles siblings of
* the load data (other loads for the same URI), handles unblocking
* blocked parent loads as needed, and most importantly calls
* NS_RELEASE on the load data to destroy the whole mess.
*/
void Loader::SheetComplete(SheetLoadData& aLoadData, nsresult aStatus) {
LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32,
static_cast<uint32_t>(aStatus)));
// If aStatus is a failure we need to mark this data failed. We also need to
// mark any ancestors of a failing data as failed and any sibling of a
// failing data as failed. Note that SheetComplete is never called on a
// SheetLoadData that is the mNext of some other SheetLoadData.
if (NS_FAILED(aStatus)) {
MarkLoadTreeFailed(aLoadData);
}
if (mDocument) {
mDocument->MaybeWarnAboutZoom();
}
// 8 is probably big enough for all our common cases. It's not likely that
// imports will nest more than 8 deep, and multiple sheets with the same URI
// are rare.
AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
DoSheetComplete(aLoadData, datasToNotify);
// Now it's safe to go ahead and notify observers
uint32_t count = datasToNotify.Length();
mDatasToNotifyOn += count;
for (RefPtr<SheetLoadData>& data : datasToNotify) {
--mDatasToNotifyOn;
MOZ_ASSERT(data, "How did this data get here?");
if (data->mObserver) {
LOG((" Notifying observer %p for data %p. deferred: %d",
data->mObserver.get(), data.get(), data->ShouldDefer()));
data->mObserver->StyleSheetLoaded(data->mSheet, data->ShouldDefer(),
aStatus);
}
nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator iter(
mObservers);
nsCOMPtr<nsICSSLoaderObserver> obs;
2007-12-20 10:30:04 +03:00
while (iter.HasMore()) {
obs = iter.GetNext();
LOG((" Notifying global observer %p for data %p. deferred: %d",
obs.get(), data.get(), data->ShouldDefer()));
obs->StyleSheetLoaded(data->mSheet, data->ShouldDefer(), aStatus);
}
}
if (mSheets && mSheets->mLoadingDatas.Count() == 0 &&
mSheets->mPendingDatas.Count() > 0) {
LOG((" No more loading sheets; starting deferred loads"));
StartDeferredLoads();
}
}
void Loader::DoSheetComplete(SheetLoadData& aLoadData,
LoadDataArray& aDatasToNotify) {
LOG(("css::Loader::DoSheetComplete"));
MOZ_ASSERT(aLoadData.mSheet, "Must have a sheet");
NS_ASSERTION(mSheets || !aLoadData.mURI,
"mLoadingDatas should be initialized by now.");
// Twiddle the hashtables
if (aLoadData.mURI) {
LOG_URI(" Finished loading: '%s'", aLoadData.mURI);
// Remove the data from the list of loading datas
if (aLoadData.mIsLoading) {
SheetLoadDataHashKey key(aLoadData);
2003-07-27 19:46:52 +04:00
#ifdef DEBUG
SheetLoadData* loadingData;
NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
loadingData == &aLoadData,
"Bad loading table");
2003-07-27 19:46:52 +04:00
#endif
mSheets->mLoadingDatas.Remove(&key);
aLoadData.mIsLoading = false;
}
}
// Go through and deal with the whole linked list.
SheetLoadData* data = &aLoadData;
do {
if (!data->mSheetAlreadyComplete) {
// If mSheetAlreadyComplete, then the sheet could well be modified between
// when we posted the async call to SheetComplete and now, since the sheet
// was page-accessible during that whole time.
MOZ_ASSERT(!data->mSheet->HasForcedUniqueInner(),
"should not get a forced unique inner during parsing");
data->mSheet->SetComplete();
data->ScheduleLoadEventIfNeeded();
}
if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
// Don't notify here so we don't trigger script. Remember the
// info we need to notify, then do it later when it's safe.
aDatasToNotify.AppendElement(data);
// On append failure, just press on. We'll fail to notify the observer,
// but not much we can do about that....
}
NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
"Broken pending child count on our parent");
// If we have a parent, our parent is no longer being parsed, and
// we are the last pending child, then our load completion
// completes the parent too. Note that the parent _can_ still be
// being parsed (eg if the child (us) failed to open the channel
// or some such).
if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
!data->mParentData->mIsBeingParsed) {
DoSheetComplete(*data->mParentData, aDatasToNotify);
}
data = data->mNext;
} while (data);
// Now that it's marked complete, put the sheet in our cache.
// If we ever start doing this for failed loads, we'll need to
// adjust the PostLoadEvent code that thinks anything already
// complete must have loaded succesfully.
if (!aLoadData.mLoadFailed && aLoadData.mURI) {
// Pick our sheet to cache carefully. Ideally, we want to cache
// one of the sheets that will be kept alive by a document or
// parent sheet anyway, so that if someone then accesses it via
// CSSOM we won't have extra clones of the inner lying around.
data = &aLoadData;
StyleSheet* sheet = aLoadData.mSheet;
do {
if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
sheet = data->mSheet;
break;
}
data = data->mNext;
} while (data);
#ifdef MOZ_XUL
if (IsChromeURI(aLoadData.mURI)) {
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
if (cache && cache->IsEnabled()) {
if (!cache->GetStyleSheet(aLoadData.mURI)) {
LOG((" Putting sheet in XUL prototype cache"));
NS_ASSERTION(sheet->IsComplete(),
"Should only be caching complete sheets");
cache->PutStyleSheet(sheet);
1999-05-19 03:15:10 +04:00
}
}
} else {
#endif
SheetLoadDataHashKey key(aLoadData);
NS_ASSERTION(sheet->IsComplete(),
"Should only be caching complete sheets");
mSheets->mCompleteSheets.Put(&key, sheet);
#ifdef MOZ_XUL
}
#endif
}
1999-05-19 03:15:10 +04:00
}
void Loader::MarkLoadTreeFailed(SheetLoadData& aLoadData) {
if (aLoadData.mURI) {
LOG_URI(" Load failed: '%s'", aLoadData.mURI);
}
SheetLoadData* data = &aLoadData;
do {
data->mLoadFailed = true;
if (data->mParentData) {
MarkLoadTreeFailed(*data->mParentData);
}
data = data->mNext;
} while (data);
}
Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
const SheetInfo& aInfo, const nsAString& aBuffer, uint32_t aLineNumber,
nsICSSLoaderObserver* aObserver) {
LOG(("css::Loader::LoadInlineStyle"));
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return Err(NS_ERROR_NOT_AVAILABLE);
}
if (!mDocument) {
return Err(NS_ERROR_NOT_INITIALIZED);
}
nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(
do_QueryInterface(aInfo.mContent));
NS_ASSERTION(owningElement, "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();
nsIURI* sheetURI = aInfo.mContent->OwnerDoc()->GetDocumentURI();
nsIURI* originalURI = nullptr;
MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
// We only cache sheets if in shadow trees, since regular document sheets are
// likely to be unique.
const bool isWorthCaching = aInfo.mContent->IsInShadowTree();
RefPtr<StyleSheet> sheet;
if (isWorthCaching) {
if (!mSheets) {
mSheets = MakeUnique<Sheets>();
}
sheet = mSheets->LookupInline(aBuffer);
}
const bool sheetFromCache = !!sheet;
if (!sheet) {
sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
SRIMetadata{});
sheet->SetURIs(sheetURI, originalURI, baseURI);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
sheet->SetReferrerInfo(referrerInfo);
nsIPrincipal* principal = aInfo.mContent->NodePrincipal();
if (aInfo.mTriggeringPrincipal) {
// 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.
principal =
BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
}
// We never actually load this, so just set its principal directly
sheet->SetPrincipal(principal);
}
auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
isAlternate, aInfo.mIsExplicitlyEnabled);
InsertSheetInTree(*sheet, aInfo.mContent);
Completed completed;
if (sheetFromCache) {
MOZ_ASSERT(sheet->IsComplete());
completed = Completed::Yes;
} else {
auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, nullptr, sheet, false, owningElement, isAlternate,
matched, aObserver, nullptr, aInfo.mReferrerInfo, aInfo.mContent);
data->mLineNumber = aLineNumber;
// 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);
completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
if (completed == Completed::Yes) {
// TODO(emilio): Try to cache sheets with @import rules, maybe?
if (isWorthCaching) {
mSheets->mInlineSheets.Put(aBuffer, sheet);
}
} else {
data->mMustNotify = true;
}
}
return LoadSheetResult{completed, isAlternate, matched};
}
1999-05-19 03:15:10 +04:00
Result<Loader::LoadSheetResult, nsresult> Loader::LoadStyleLink(
const SheetInfo& aInfo, nsICSSLoaderObserver* aObserver) {
MOZ_ASSERT(aInfo.mURI, "Must have URL to load");
LOG(("css::Loader::LoadStyleLink"));
LOG_URI(" Link uri: '%s'", aInfo.mURI);
LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aInfo.mTitle).get()));
LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aInfo.mMedia).get()));
LOG((" Link alternate rel: %d", aInfo.mHasAlternateRel));
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return Err(NS_ERROR_NOT_AVAILABLE);
}
if (!mDocument) {
return Err(NS_ERROR_NOT_INITIALIZED);
}
nsIPrincipal* loadingPrincipal = aInfo.mContent
? aInfo.mContent->NodePrincipal()
: mDocument->NodePrincipal();
nsIPrincipal* principal = aInfo.mTriggeringPrincipal
? aInfo.mTriggeringPrincipal.get()
: loadingPrincipal;
nsINode* context = aInfo.mContent;
if (!context) {
context = mDocument;
}
bool syncLoad = aInfo.mContent && aInfo.mContent->IsInUAWidget() &&
IsChromeURI(aInfo.mURI);
LOG((" Link sync load: '%s'", syncLoad ? "true" : "false"));
MOZ_ASSERT_IF(syncLoad, !aObserver);
nsresult rv = CheckContentPolicy(loadingPrincipal, principal, aInfo.mURI,
context, IsPreload::No);
if (NS_WARN_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.
RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
new LoadBlockingAsyncEventDispatcher(
aInfo.mContent, NS_LITERAL_STRING("error"), 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);
SheetState state;
RefPtr<StyleSheet> sheet;
Tie(sheet, state) =
CreateSheet(aInfo, principal, eAuthorSheetFeatures, syncLoad);
LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
isAlternate, aInfo.mIsExplicitlyEnabled);
InsertSheetInTree(*sheet, aInfo.mContent);
1999-05-19 03:15:10 +04:00
nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(
do_QueryInterface(aInfo.mContent));
if (state == SheetState::Complete) {
LOG((" Sheet already complete: 0x%p", sheet.get()));
if (aObserver || !mObservers.IsEmpty() || owningElement) {
rv = PostLoadEvent(aInfo.mURI, sheet, aObserver, isAlternate, matched,
aInfo.mReferrerInfo, owningElement);
if (NS_FAILED(rv)) {
return Err(rv);
}
}
// The load hasn't been completed yet, will be done in PostLoadEvent.
return LoadSheetResult{Completed::No, isAlternate, matched};
}
// Now we need to actually load it.
auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, aInfo.mURI, sheet, syncLoad, owningElement,
isAlternate, matched, aObserver, principal, aInfo.mReferrerInfo, context);
auto result = LoadSheetResult{Completed::No, isAlternate, matched};
MOZ_ASSERT(result.ShouldBlock() == !data->ShouldDefer(),
"These should better match!");
// If we have to parse and it's a non-blocking non-inline sheet, defer it.
if (!syncLoad && state == SheetState::NeedsParser &&
mSheets->mLoadingDatas.Count() != 0 && !result.ShouldBlock()) {
LOG((" Deferring sheet load"));
SheetLoadDataHashKey key(*data);
mSheets->mPendingDatas.Put(&key, data);
data->mMustNotify = true;
return result;
}
// Load completion will free the data
rv = LoadSheet(*data, state, IsPreload::No);
if (NS_FAILED(rv)) {
return Err(rv);
}
if (!syncLoad) {
data->mMustNotify = true;
}
return result;
1999-05-19 03:15:10 +04:00
}
static bool 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?");
return false;
}
bool equal;
if (NS_FAILED(aData.mURI->Equals(aURI, &equal)) || equal) {
return true;
}
// 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);
return false;
}
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;
}
LOG_URI(" Child uri: '%s'", aURL);
1999-05-19 03:15:10 +04:00
nsCOMPtr<nsINode> owningNode;
// Check for an associated document or shadow root: if none, don't bother
// walking up the parent sheets.
if (aParentSheet.GetAssociatedDocumentOrShadowRoot()) {
StyleSheet* topSheet = &aParentSheet;
while (StyleSheet* parent = topSheet->GetParentSheet()) {
topSheet = parent;
}
owningNode = topSheet->GetOwnerNode();
}
nsINode* context = nullptr;
nsIPrincipal* loadingPrincipal = nullptr;
if (owningNode) {
context = owningNode;
loadingPrincipal = owningNode->NodePrincipal();
} else if (mDocument) {
context = mDocument;
loadingPrincipal = mDocument->NodePrincipal();
}
nsIPrincipal* principal = aParentSheet.Principal();
nsresult rv = CheckContentPolicy(loadingPrincipal, principal, aURL, context,
IsPreload::No);
if (NS_WARN_IF(NS_FAILED(rv))) {
if (aParentData) {
MarkLoadTreeFailed(*aParentData);
}
return rv;
}
nsCOMPtr<nsICSSLoaderObserver> observer;
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;
}
1999-05-19 03:15:10 +04:00
// Now that we know it's safe to load this (passes security check and not a
// loop) do so.
RefPtr<StyleSheet> sheet;
SheetState state;
if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
state = SheetState::Complete;
} else {
// For now, use CORS_NONE for child sheets
Tie(sheet, state) =
CreateSheet(aURL, nullptr, principal, aParentSheet.ParsingMode(),
CORS_NONE, aParentSheet.GetReferrerInfo(),
EmptyString(), // integrity is only checked on main sheet
aParentData ? aParentData->mSyncLoad : false);
PrepareSheet(*sheet, EmptyString(), EmptyString(), aMedia, IsAlternate::No,
IsExplicitlyEnabled::No);
}
MOZ_ASSERT(sheet);
InsertChildSheet(*sheet, aParentSheet);
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.
return NS_OK;
1999-05-19 03:15:10 +04:00
}
auto data = MakeRefPtr<SheetLoadData>(
this, aURL, sheet, aParentData, observer, principal,
aParentSheet.GetReferrerInfo(), context);
bool syncLoad = data->mSyncLoad;
// Load completion will release the data
rv = LoadSheet(*data, state, IsPreload::No);
NS_ENSURE_SUCCESS(rv, rv);
if (!syncLoad) {
data->mMustNotify = true;
}
return rv;
}
Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheetSync(
nsIURI* aURL, SheetParsingMode aParsingMode,
UseSystemPrincipal aUseSystemPrincipal) {
LOG(("css::Loader::LoadSheetSync"));
nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
return InternalLoadNonDocumentSheet(
aURL, IsPreload::No, aParsingMode, aUseSystemPrincipal, nullptr, nullptr,
referrerInfo, nullptr, CORS_NONE, EmptyString());
1999-05-19 03:15:10 +04:00
}
Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
nsIURI* aURI, SheetParsingMode aParsingMode,
UseSystemPrincipal aUseSystemPrincipal, nsICSSLoaderObserver* aObserver) {
nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr);
return InternalLoadNonDocumentSheet(
aURI, IsPreload::No, aParsingMode, aUseSystemPrincipal, nullptr, nullptr,
referrerInfo, aObserver, CORS_NONE, EmptyString());
}
Result<RefPtr<StyleSheet>, nsresult> Loader::LoadSheet(
nsIURI* aURL, IsPreload aIsPreload, nsIPrincipal* aOriginPrincipal,
const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
const nsAString& aIntegrity) {
LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
return InternalLoadNonDocumentSheet(aURL, aIsPreload, eAuthorSheetFeatures,
UseSystemPrincipal::No, aOriginPrincipal,
aPreloadEncoding, aReferrerInfo,
aObserver, aCORSMode, aIntegrity);
}
Result<RefPtr<StyleSheet>, nsresult> Loader::InternalLoadNonDocumentSheet(
nsIURI* aURL, IsPreload aIsPreload, SheetParsingMode aParsingMode,
UseSystemPrincipal aUseSystemPrincipal, nsIPrincipal* aOriginPrincipal,
const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo,
nsICSSLoaderObserver* aObserver, CORSMode aCORSMode,
const nsAString& aIntegrity) {
MOZ_ASSERT(aURL, "Must have a URI to load");
MOZ_ASSERT(aUseSystemPrincipal == UseSystemPrincipal::No || !aObserver,
"Shouldn't load system-principal sheets async");
MOZ_ASSERT(aReferrerInfo, "Must have referrerInfo");
LOG_URI(" Non-document sheet uri: '%s'", aURL);
if (!mEnabled) {
LOG_WARN((" Not enabled"));
return Err(NS_ERROR_NOT_AVAILABLE);
}
nsCOMPtr<nsIPrincipal> loadingPrincipal =
(aOriginPrincipal && mDocument ? mDocument->NodePrincipal() : nullptr);
nsresult rv = CheckContentPolicy(loadingPrincipal, aOriginPrincipal, aURL,
mDocument, aIsPreload);
if (NS_FAILED(rv)) {
return Err(rv);
}
bool syncLoad = !aObserver;
SheetState state;
RefPtr<StyleSheet> sheet;
Tie(sheet, state) =
CreateSheet(aURL, nullptr, aOriginPrincipal, aParsingMode, aCORSMode,
aReferrerInfo, aIntegrity, syncLoad);
PrepareSheet(*sheet, EmptyString(), EmptyString(), nullptr, IsAlternate::No,
IsExplicitlyEnabled::No);
if (state == SheetState::Complete) {
LOG((" Sheet already complete"));
if (aObserver || !mObservers.IsEmpty()) {
rv = PostLoadEvent(aURL, sheet, aObserver, IsAlternate::No,
MediaMatched::Yes, aReferrerInfo, nullptr);
if (NS_FAILED(rv)) {
return Err(rv);
}
}
return sheet;
}
1999-05-19 03:15:10 +04:00
auto data = MakeRefPtr<SheetLoadData>(
this, aURL, sheet, syncLoad,
aUseSystemPrincipal == UseSystemPrincipal::Yes, aPreloadEncoding,
aObserver, aOriginPrincipal, aReferrerInfo, mDocument);
rv = LoadSheet(*data, state, aIsPreload);
if (NS_FAILED(rv)) {
return Err(rv);
}
if (aObserver) {
data->mMustNotify = true;
}
return sheet;
}
1999-05-19 03:15:10 +04:00
nsresult Loader::PostLoadEvent(nsIURI* aURI, StyleSheet* aSheet,
nsICSSLoaderObserver* aObserver,
IsAlternate aWasAlternate,
MediaMatched aMediaMatched,
nsIReferrerInfo* aReferrerInfo,
nsIStyleSheetLinkingElement* aElement) {
LOG(("css::Loader::PostLoadEvent"));
MOZ_ASSERT(aSheet, "Must have sheet");
MOZ_ASSERT(aObserver || !mObservers.IsEmpty() || aElement,
"Must have observer or element");
RefPtr<SheetLoadData> evt = new SheetLoadData(
this,
EmptyString(), // title doesn't matter here
aURI, aSheet, false, aElement, aWasAlternate, aMediaMatched, aObserver,
nullptr, aReferrerInfo, mDocument);
mPostedEvents.AppendElement(evt);
nsresult rv;
RefPtr<SheetLoadData> runnable(evt);
if (mDocument) {
rv = mDocument->Dispatch(TaskCategory::Other, runnable.forget());
} else if (mDocGroup) {
rv = mDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
} else {
rv = SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
}
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch stylesheet load event");
mPostedEvents.RemoveElement(evt);
} else {
// We'll unblock onload when we handle the event.
BlockOnload();
// We want to notify the observer for this data.
evt->mMustNotify = true;
evt->mSheetAlreadyComplete = true;
// If we get to this code, aSheet 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 async.
MOZ_ASSERT(!evt->mLoadFailed, "Why are we marked as failed?");
evt->ScheduleLoadEventIfNeeded();
}
return rv;
}
void Loader::HandleLoadEvent(SheetLoadData& aEvent) {
// XXXbz can't assert this yet.... May not have an observer because
// we're unblocking the parser
// NS_ASSERTION(aEvent->mObserver, "Must have observer");
NS_ASSERTION(aEvent.mSheet, "Must have sheet");
// Very important: this needs to come before the SheetComplete call
// below, so that HasPendingLoads() will test true as needed under
// notifications we send from that SheetComplete call.
mPostedEvents.RemoveElement(&aEvent);
if (!aEvent.mIsCancelled) {
SheetComplete(aEvent, NS_OK);
}
UnblockOnload(true);
}
void Loader::Stop() {
uint32_t pendingCount = mSheets ? mSheets->mPendingDatas.Count() : 0;
uint32_t loadingCount = mSheets ? mSheets->mLoadingDatas.Count() : 0;
LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
if (pendingCount) {
for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
RefPtr<SheetLoadData>& data = iter.Data();
data->mIsLoading = false; // we will handle the removal right here
data->mIsCancelled = true;
arr.AppendElement(std::move(data));
}
mSheets->mPendingDatas.Clear();
}
if (loadingCount) {
for (auto iter = mSheets->mLoadingDatas.Iter(); !iter.Done(); iter.Next()) {
SheetLoadData* data = iter.Data();
data->mIsLoading = false; // we will handle the removal right here
data->mIsCancelled = true;
arr.AppendElement(data);
}
mSheets->mLoadingDatas.Clear();
}
for (RefPtr<SheetLoadData>& data : mPostedEvents) {
data->mIsCancelled = true;
// Move since we're about to get rid of the array below.
arr.AppendElement(std::move(data));
}
mPostedEvents.Clear();
mDatasToNotifyOn += arr.Length();
for (RefPtr<SheetLoadData>& data : arr) {
--mDatasToNotifyOn;
SheetComplete(*data, NS_BINDING_ABORTED);
}
}
bool Loader::HasPendingLoads() {
return (mSheets && mSheets->mLoadingDatas.Count() != 0) ||
(mSheets && mSheets->mPendingDatas.Count() != 0) ||
mPostedEvents.Length() != 0 || mDatasToNotifyOn != 0;
}
void Loader::AddObserver(nsICSSLoaderObserver* aObserver) {
MOZ_ASSERT(aObserver, "Must have observer");
mObservers.AppendElementUnlessExists(aObserver);
}
void Loader::RemoveObserver(nsICSSLoaderObserver* aObserver) {
mObservers.RemoveElement(aObserver);
}
void Loader::StartDeferredLoads() {
MOZ_ASSERT(mSheets, "Don't call me!");
LoadDataArray arr(mSheets->mPendingDatas.Count());
for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
arr.AppendElement(iter.Data());
iter.Remove();
}
mDatasToNotifyOn += arr.Length();
for (RefPtr<SheetLoadData>& data : arr) {
--mDatasToNotifyOn;
LoadSheet(*data, SheetState::NeedsParser, IsPreload::No);
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(Loader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Loader)
if (tmp->mSheets) {
for (auto iter = tmp->mSheets->mCompleteSheets.Iter(); !iter.Done();
iter.Next()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "OOL sheet cache in Loader");
cb.NoteXPCOMChild(iter.UserData());
}
for (auto iter = tmp->mSheets->mInlineSheets.Iter(); !iter.Done();
iter.Next()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
cb.NoteXPCOMChild(iter.UserData());
}
}
nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>>::ForwardIterator it(
tmp->mObservers);
while (it.HasMore()) {
ImplCycleCollectionTraverse(cb, it.GetNext(),
"mozilla::css::Loader.mObservers");
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
if (tmp->mSheets) {
tmp->mSheets->mCompleteSheets.Clear();
tmp->mSheets->mInlineSheets.Clear();
}
tmp->mObservers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
if (mSheets) {
n += mSheets->SizeOfIncludingThis(aMallocSizeOf);
}
n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mPostedEvents: transient, and should be small
//
// The following members aren't measured:
// - mDocument, because it's a weak backpointer
return n;
}
void Loader::BlockOnload() {
if (mDocument) {
mDocument->BlockOnload();
}
}
void Loader::UnblockOnload(bool aFireSync) {
if (mDocument) {
mDocument->UnblockOnload(aFireSync);
}
}
already_AddRefed<nsISerialEventTarget> Loader::DispatchTarget() {
nsCOMPtr<nsISerialEventTarget> target;
if (mDocument) {
target = mDocument->EventTargetFor(TaskCategory::Other);
} else if (mDocGroup) {
target = mDocGroup->EventTargetFor(TaskCategory::Other);
} else {
target = SystemGroup::EventTargetFor(TaskCategory::Other);
}
return target.forget();
}
} // namespace css
} // namespace mozilla