summaryrefslogtreecommitdiffstats
path: root/layout/style/Loader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/Loader.cpp')
-rw-r--r--layout/style/Loader.cpp2694
1 files changed, 2694 insertions, 0 deletions
diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp
new file mode 100644
index 000000000..a1a0fcfd9
--- /dev/null
+++ b/layout/style/Loader.cpp
@@ -0,0 +1,2694 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are Copyright (c)
+ * International Business Machines Corporation, 2000. Modifications
+ * to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/* loading of CSS style sheets using the network APIs */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "mozilla/css/Loader.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "nsIRunnable.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsSyncLoadService.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMDocument.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 "nsCSSParser.h"
+#include "mozilla/css/ImportRule.h"
+#include "nsThreadUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIThreadInternal.h"
+#include "nsINetworkPredictor.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/ConsoleReportCollector.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPrototypeCache.h"
+#endif
+
+#include "nsIMediaList.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsError.h"
+
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/SRICheck.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+using mozilla::dom::EncodingUtils;
+
+using namespace mozilla::dom;
+
+/**
+ * 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:
+ * InsertSheetInDoc() 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.
+ */
+
+namespace mozilla {
+namespace css {
+
+/*********************************************
+ * Data needed to properly load a stylesheet *
+ *********************************************/
+
+static_assert(eAuthorSheetFeatures == 0 &&
+ eUserSheetFeatures == 1 &&
+ eAgentSheetFeatures == 2,
+ "sheet parsing mode constants won't fit "
+ "in SheetLoadData::mParsingMode");
+
+class SheetLoadData final : public nsIRunnable,
+ public nsIUnicharStreamLoaderObserver,
+ public nsIThreadObserver
+{
+protected:
+ virtual ~SheetLoadData(void);
+
+public:
+ // Data for loading a sheet linked from a document
+ SheetLoadData(Loader* aLoader,
+ const nsSubstring& aTitle,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsIStyleSheetLinkingElement* aOwningElement,
+ bool aIsAlternate,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ // Data for loading a sheet linked from an @import rule
+ SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ // Data for loading a non-document sheet
+ SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ bool aSyncLoad,
+ bool aUseSystemPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode);
+
+ already_AddRefed<nsIURI> GetReferrerURI();
+
+ void ScheduleLoadEventIfNeeded(nsresult aStatus);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER
+
+ // Hold a ref to the CSSLoader so we can call back to it to let it
+ // know the load finished
+ RefPtr<Loader> mLoader;
+
+ // Title needed to pull datas out of the pending datas table when
+ // the preferred title is changed
+ nsString mTitle;
+
+ // Charset we decided to use for the sheet
+ nsCString mCharset;
+
+ // URI we're loading. Null for inline sheets
+ nsCOMPtr<nsIURI> mURI;
+
+ // Should be 1 for non-inline sheets.
+ uint32_t mLineNumber;
+
+ // The sheet we're loading data for
+ RefPtr<StyleSheet> mSheet;
+
+ // Linked list of datas for the same URI as us
+ SheetLoadData* mNext; // strong ref
+
+ // Load data for the sheet that @import-ed us if we were @import-ed
+ // during the parse
+ RefPtr<SheetLoadData> mParentData;
+
+ // Number of sheets we @import-ed that are still loading
+ uint32_t mPendingChildren;
+
+ // mSyncLoad is true when the load needs to be synchronous -- right
+ // now only for LoadSheetSync and children of sync loads.
+ bool mSyncLoad : 1;
+
+ // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
+ // LoadSheet or an @import from such a sheet. Non-document sheet loads can
+ // proceed even if we have no document.
+ bool mIsNonDocumentSheet : 1;
+
+ // mIsLoading is true from the moment we are placed in the loader's
+ // "loading datas" table (right after the async channel is opened)
+ // to the moment we are removed from said table (due to the load
+ // completing or being cancelled).
+ bool mIsLoading : 1;
+
+ // mIsCancelled is set to true when a sheet load is stopped by
+ // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
+ // SheetLoadData::OnStreamComplete() checks this to avoid parsing
+ // sheets that have been cancelled and such.
+ bool mIsCancelled : 1;
+
+ // mMustNotify is true if the load data is being loaded async and
+ // the original function call that started the load has returned.
+ // This applies only to observer notifications; load/error events
+ // are fired for any SheetLoadData that has a non-null
+ // mOwningElement.
+ bool mMustNotify : 1;
+
+ // mWasAlternate is true if the sheet was an alternate when the load data was
+ // created.
+ bool mWasAlternate : 1;
+
+ // mUseSystemPrincipal is true if the system principal should be used for
+ // this sheet, no matter what the channel principal is. Only true for sync
+ // loads.
+ bool mUseSystemPrincipal : 1;
+
+ // If true, this SheetLoadData is being used as a way to handle
+ // async observer notification for an already-complete sheet.
+ bool mSheetAlreadyComplete : 1;
+
+ // This is the element that imported the sheet. Needed to get the
+ // charset set on it and to fire load/error events.
+ nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
+
+ // The observer that wishes to be notified of load completion
+ nsCOMPtr<nsICSSLoaderObserver> mObserver;
+
+ // The principal that identifies who started loading us.
+ nsCOMPtr<nsIPrincipal> mLoaderPrincipal;
+
+ // The node that identifies who started loading us.
+ nsCOMPtr<nsINode> mRequestingNode;
+
+ // The charset to use if the transport and sheet don't indicate one.
+ // May be empty. Must be empty if mOwningElement is non-null.
+ nsCString mCharsetHint;
+
+ // The status our load ended up with; this determines whether we
+ // should fire error events or load events. This gets initialized
+ // by ScheduleLoadEventIfNeeded, and is only used after that has
+ // been called.
+ MOZ_INIT_OUTSIDE_CTOR nsresult mStatus;
+
+private:
+ void FireLoadEvent(nsIThreadInternal* aThread);
+};
+
+#include "mozilla/Logging.h"
+
+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[] = {
+ "eSheetStateUnknown",
+ "eSheetNeedsParser",
+ "eSheetPending",
+ "eSheetLoading",
+ "eSheetComplete"
+};
+
+/********************************
+ * SheetLoadData implementation *
+ ********************************/
+NS_IMPL_ISUPPORTS(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
+ nsIThreadObserver)
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ const nsSubstring& aTitle,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsIStyleSheetLinkingElement* aOwningElement,
+ bool aIsAlternate,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mTitle(aTitle),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mPendingChildren(0),
+ mSyncLoad(false),
+ mIsNonDocumentSheet(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(aIsAlternate),
+ mUseSystemPrincipal(false),
+ mSheetAlreadyComplete(false),
+ mOwningElement(aOwningElement),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+}
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ SheetLoadData* aParentData,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mParentData(aParentData),
+ mPendingChildren(0),
+ mSyncLoad(false),
+ mIsNonDocumentSheet(false),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(false),
+ mUseSystemPrincipal(false),
+ mSheetAlreadyComplete(false),
+ mOwningElement(nullptr),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+ if (mParentData) {
+ mSyncLoad = mParentData->mSyncLoad;
+ mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
+ mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
+ ++(mParentData->mPendingChildren);
+ }
+
+ NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+}
+
+SheetLoadData::SheetLoadData(Loader* aLoader,
+ nsIURI* aURI,
+ StyleSheet* aSheet,
+ bool aSyncLoad,
+ bool aUseSystemPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ nsIPrincipal* aLoaderPrincipal,
+ nsINode* aRequestingNode)
+ : mLoader(aLoader),
+ mURI(aURI),
+ mLineNumber(1),
+ mSheet(aSheet),
+ mNext(nullptr),
+ mPendingChildren(0),
+ mSyncLoad(aSyncLoad),
+ mIsNonDocumentSheet(true),
+ mIsLoading(false),
+ mIsCancelled(false),
+ mMustNotify(false),
+ mWasAlternate(false),
+ mUseSystemPrincipal(aUseSystemPrincipal),
+ mSheetAlreadyComplete(false),
+ mOwningElement(nullptr),
+ mObserver(aObserver),
+ mLoaderPrincipal(aLoaderPrincipal),
+ mRequestingNode(aRequestingNode),
+ mCharsetHint(aCharset)
+{
+ NS_PRECONDITION(mLoader, "Must have a loader!");
+ NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
+ "Shouldn't use system principal for async loads");
+}
+
+SheetLoadData::~SheetLoadData()
+{
+ NS_CSS_NS_RELEASE_LIST_MEMBER(SheetLoadData, this, mNext);
+}
+
+NS_IMETHODIMP
+SheetLoadData::Run()
+{
+ mLoader->HandleLoadEvent(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait)
+{
+ // XXXkhuey this is insane!
+ // We want to fire our load even before or after event processing,
+ // whichever comes first.
+ FireLoadEvent(aThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
+ bool aEventWasProcessed)
+{
+ // XXXkhuey this too!
+ // We want to fire our load even before or after event processing,
+ // whichever comes first.
+ FireLoadEvent(aThread);
+ return NS_OK;
+}
+
+void
+SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
+{
+
+ // First remove ourselves as a thread observer. But we need to keep
+ // ourselves alive while doing that!
+ RefPtr<SheetLoadData> kungFuDeathGrip(this);
+ aThread->RemoveObserver(this);
+
+ // Now fire the event
+ nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
+ NS_ASSERTION(node, "How did that happen???");
+
+ nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(),
+ node,
+ NS_SUCCEEDED(mStatus) ?
+ NS_LITERAL_STRING("load") :
+ NS_LITERAL_STRING("error"),
+ false, false);
+
+ // And unblock onload
+ if (mLoader->mDocument) {
+ mLoader->mDocument->UnblockOnload(true);
+ }
+}
+
+void
+SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
+{
+ if (!mOwningElement) {
+ return;
+ }
+
+ mStatus = aStatus;
+
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
+ if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
+ // Make sure to block onload here
+ if (mLoader->mDocument) {
+ mLoader->mDocument->BlockOnload();
+ }
+ }
+}
+
+/*********************
+ * Style sheet reuse *
+ *********************/
+
+bool
+LoaderReusableStyleSheets::FindReusableStyleSheet(nsIURI* aURL,
+ RefPtr<CSSStyleSheet>& aResult)
+{
+ MOZ_ASSERT(aURL);
+ for (size_t i = mReusableSheets.Length(); i > 0; --i) {
+ size_t index = i - 1;
+ bool sameURI;
+ MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
+ nsresult rv = aURL->Equals(mReusableSheets[index]->GetOriginalURI(),
+ &sameURI);
+ if (!NS_FAILED(rv) && sameURI) {
+ aResult = mReusableSheets[index];
+ mReusableSheets.RemoveElementAt(index);
+ return true;
+ }
+ }
+ return false;
+}
+
+/*************************
+ * Loader Implementation *
+ *************************/
+
+Loader::Loader(StyleBackendType aType)
+ : mDocument(nullptr)
+ , mDatasToNotifyOn(0)
+ , mCompatMode(eCompatibility_FullStandards)
+ , mStyleBackendType(Some(aType))
+ , mEnabled(true)
+ , mReporter(new ConsoleReportCollector())
+#ifdef DEBUG
+ , mSyncCallback(false)
+#endif
+{
+}
+
+Loader::Loader(nsIDocument* aDocument)
+ : mDocument(aDocument)
+ , mDatasToNotifyOn(0)
+ , mCompatMode(eCompatibility_FullStandards)
+ , mEnabled(true)
+ , mReporter(new ConsoleReportCollector())
+#ifdef DEBUG
+ , mSyncCallback(false)
+#endif
+{
+ // We can just use the preferred set, since there are no sheets in the
+ // document yet (if there are, how did they get there? _we_ load the sheets!)
+ // and hence the selected set makes no sense at this time.
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
+ if (domDoc) {
+ domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
+ }
+}
+
+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.
+}
+
+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) {
+ StartAlternateLoads();
+ }
+}
+
+nsresult
+Loader::SetPreferredSheet(const nsAString& aTitle)
+{
+#ifdef DEBUG
+ nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
+ if (doc) {
+ nsAutoString currentPreferred;
+ doc->GetLastStyleSheetSet(currentPreferred);
+ if (DOMStringIsNull(currentPreferred)) {
+ doc->GetPreferredStyleSheetSet(currentPreferred);
+ }
+ NS_ASSERTION(currentPreferred.Equals(aTitle),
+ "Unexpected argument to SetPreferredSheet");
+ }
+#endif
+
+ mPreferredSheet = aTitle;
+
+ // start any pending alternates that aren't alternates anymore
+ if (mSheets) {
+ LoadDataArray arr(mSheets->mPendingDatas.Count());
+ for (auto iter = mSheets->mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
+ 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.
+ if (!data->mLoader->IsAlternate(data->mTitle, true)) {
+ arr.AppendElement(data);
+ iter.Remove();
+ }
+ }
+
+ mDatasToNotifyOn += arr.Length();
+ for (uint32_t i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ LoadSheet(arr[i], eSheetNeedsParser, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsACString const& aSegment,
+ nsACString& aCharset)
+{
+ NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
+ "Can't have element _and_ charset hint");
+
+ LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
+
+ // The precedence is (per CSS3 Syntax 2012-11-08 ED):
+ // BOM
+ // Channel
+ // @charset rule
+ // charset attribute on the referrer
+ // encoding of the referrer
+ // UTF-8
+
+ aCharset.Truncate();
+
+ if (nsContentUtils::CheckForBOM((const unsigned char*)aSegment.BeginReading(),
+ aSegment.Length(),
+ aCharset)) {
+ // aCharset is now either "UTF-16BE", "UTF-16BE" or "UTF-8"
+ // which will swallow the BOM.
+ mCharset.Assign(aCharset);
+ LOG((" Setting from BOM to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsAutoCString specified;
+ aLoader->GetChannel(getter_AddRefs(channel));
+ if (channel) {
+ channel->GetContentCharset(specified);
+ if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ if (GetCharsetFromData(aSegment.BeginReading(),
+ aSegment.Length(),
+ specified)) {
+ if (EncodingUtils::FindEncodingForLabel(specified, aCharset)) {
+ // FindEncodingForLabel currently never returns UTF-16LE but will
+ // probably change to never return UTF-16 instead, so check both here
+ // to avoid relying on the exact behavior.
+ if (aCharset.EqualsLiteral("UTF-16") ||
+ aCharset.EqualsLiteral("UTF-16BE") ||
+ aCharset.EqualsLiteral("UTF-16LE")) {
+ // Be consistent with HTML <meta> handling in face of impossibility.
+ // When the @charset rule itself evidently was not UTF-16-encoded,
+ // it saying UTF-16 has to be a lie.
+ aCharset.AssignLiteral("UTF-8");
+ }
+ mCharset.Assign(aCharset);
+ LOG((" Setting from @charset rule to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ // Now try the charset on the <link> or processing instruction
+ // that loaded us
+ if (mOwningElement) {
+ nsAutoString specified16;
+ mOwningElement->GetCharset(specified16);
+ if (EncodingUtils::FindEncodingForLabel(specified16, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from charset attribute to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ // In the preload case, the value of the charset attribute on <link> comes
+ // in via mCharsetHint instead.
+ if (EncodingUtils::FindEncodingForLabel(mCharsetHint, aCharset)) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from charset attribute (preload case) to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ // Try charset from the parent stylesheet.
+ if (mParentData) {
+ aCharset = mParentData->mCharset;
+ if (!aCharset.IsEmpty()) {
+ mCharset.Assign(aCharset);
+ LOG((" Setting from parent sheet to: %s",
+ PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+ }
+
+ if (mLoader->mDocument) {
+ // no useful data on charset. Try the document charset.
+ aCharset = mLoader->mDocument->GetDocumentCharacterSet();
+ MOZ_ASSERT(!aCharset.IsEmpty());
+ mCharset.Assign(aCharset);
+ LOG((" Setting from document to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+ }
+
+ aCharset.AssignLiteral("UTF-8");
+ mCharset = aCharset;
+ LOG((" Setting from default to: %s", PromiseFlatCString(aCharset).get()));
+ return NS_OK;
+}
+
+already_AddRefed<nsIURI>
+SheetLoadData::GetReferrerURI()
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mParentData)
+ uri = mParentData->mSheet->GetSheetURI();
+ if (!uri && mLoader->mDocument)
+ uri = mLoader->mDocument->GetDocumentURI();
+ return uri.forget();
+}
+
+/*
+ * 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.
+ */
+NS_IMETHODIMP
+SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ const nsAString& aBuffer)
+{
+ LOG(("SheetLoadData::OnStreamComplete"));
+ 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%x", aStatus));
+ // Handle sheet not loading error because source was a tracking URL.
+ // 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 (aStatus == NS_ERROR_TRACKING_URI) {
+ nsIDocument* doc = mLoader->GetDocument();
+ if (doc) {
+ for (SheetLoadData* data = this; data; data = data->mNext) {
+ // mOwningElement may be null but AddBlockTrackingNode can cope
+ nsCOMPtr<nsIContent> content = do_QueryInterface(data->mOwningElement);
+ doc->AddBlockedTrackingNode(content);
+ }
+ }
+ }
+ mLoader->SheetComplete(this, aStatus);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
+ if (NS_FAILED(result)) {
+ LOG_WARN((" No channel from loader"));
+ mLoader->SheetComplete(this, result);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ channel->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(channel, 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();
+ 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(channel, getter_AddRefs(principal));
+ }
+ }
+
+ if (NS_FAILED(result)) {
+ LOG_WARN((" Couldn't get principal"));
+ mLoader->SheetComplete(this, result);
+ return NS_OK;
+ }
+
+ mSheet->SetPrincipal(principal);
+
+ // If it's an HTTP channel, we want to make sure this is not an
+ // error document we got.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ 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 contentType;
+ if (channel) {
+ channel->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;
+ }
+
+ const nsAFlatString& specUTF16 =
+ NS_ConvertUTF8toUTF16(channelURI->GetSpecOrDefault());
+ const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
+ const char16_t *strings[] = { specUTF16.get(), ctypeUTF16.get() };
+
+ nsCOMPtr<nsIURI> referrer = GetReferrerURI();
+ nsContentUtils::ReportToConsole(errorFlag,
+ NS_LITERAL_CSTRING("CSS Loader"),
+ mLoader->mDocument,
+ nsContentUtils::eCSS_PROPERTIES,
+ errorMessage,
+ strings, ArrayLength(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()) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ if (loadInfo->GetEnforceSRI()) {
+ LOG((" Load was blocked by SRI"));
+ MOZ_LOG(gSriPRLog, mozilla::LogLevel::Debug,
+ ("css::Loader::OnStreamComplete, required SRI not found"));
+ mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
+ // log the failed load to web console
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
+ nsAutoCString spec;
+ mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(spec);
+ // line number unknown. mRequestingNode doesn't bear this info.
+ csp->LogViolationDetails(
+ nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_STYLE,
+ NS_ConvertUTF8toUTF16(spec), EmptyString(),
+ 0, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+ } else {
+ nsAutoCString sourceUri;
+ if (mLoader->mDocument && mLoader->mDocument->GetDocumentURI()) {
+ mLoader->mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ nsresult rv = SRICheck::VerifyIntegrity(sriMetadata, aLoader, aBuffer,
+ sourceUri, mLoader->mReporter);
+ 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);
+
+ bool completed;
+ result = mLoader->ParseSheet(aBuffer, this, completed);
+ NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
+ return result;
+}
+
+bool
+Loader::IsAlternate(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 false;
+ }
+
+ if (!aHasAlternateRel && mDocument && mPreferredSheet.IsEmpty()) {
+ // There's no preferred set yet, and we now have a sheet with a title.
+ // Make that be the preferred set.
+ mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
+ // We're definitely not an alternate
+ return false;
+ }
+
+ return !aTitle.Equals(mPreferredSheet);
+}
+
+nsresult
+Loader::ObsoleteSheet(nsIURI* aURI)
+{
+ if (!mSheets) {
+ return NS_OK;
+ }
+ if (!aURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ for (auto iter = mSheets->mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
+ nsIURI* sheetURI = iter.Key()->GetURI();
+ bool areEqual;
+ nsresult rv = sheetURI->Equals(aURI, &areEqual);
+ if (NS_SUCCEEDED(rv) && areEqual) {
+ iter.Remove();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+Loader::CheckContentPolicy(nsIPrincipal* aSourcePrincipal,
+ nsIURI* aTargetURI,
+ nsISupports* aContext,
+ bool aIsPreload)
+{
+ // When performing a system load (e.g. aUseSystemPrincipal = true)
+ // then aSourcePrincipal == null; don't consult content policies.
+ if (!aSourcePrincipal) {
+ return NS_OK;
+ }
+
+ nsContentPolicyType contentPolicyType =
+ aIsPreload ? nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_STYLESHEET;
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
+ aTargetURI,
+ aSourcePrincipal,
+ aContext,
+ NS_LITERAL_CSTRING("text/css"),
+ nullptr, //extra param
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_OK;
+}
+
+/**
+ * CreateSheet() creates a CSSStyleSheet object for the given URI,
+ * if any. If there is no URI given, we just create a new style sheet
+ * object. Otherwise, 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().
+ */
+nsresult
+Loader::CreateSheet(nsIURI* aURI,
+ nsIContent* aLinkingContent,
+ nsIPrincipal* aLoaderPrincipal,
+ css::SheetParsingMode aParsingMode,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ bool aSyncLoad,
+ bool aHasAlternateRel,
+ const nsAString& aTitle,
+ StyleSheetState& aSheetState,
+ bool *aIsAlternate,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::CreateSheet"));
+ NS_PRECONDITION(aSheet, "Null out param!");
+
+ if (!mSheets) {
+ mSheets = new Sheets();
+ }
+
+ *aSheet = nullptr;
+ aSheetState = eSheetStateUnknown;
+
+ // Check the alternate state before doing anything else, because it
+ // can mess with our hashtables.
+ *aIsAlternate = IsAlternate(aTitle, aHasAlternateRel);
+
+ // XXXheycam Cached sheets currently must be CSSStyleSheets.
+ if (aURI && GetStyleBackendType() == StyleBackendType::Gecko) {
+ aSheetState = eSheetComplete;
+ RefPtr<StyleSheet> sheet;
+
+ // First, the XUL cache
+#ifdef MOZ_XUL
+ if (IsChromeURI(aURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache) {
+ if (cache->IsEnabled()) {
+ sheet = cache->GetStyleSheet(aURI);
+ LOG((" From XUL cache: %p", sheet.get()));
+ }
+ }
+ }
+#endif
+
+ bool fromCompleteSheets = false;
+ if (!sheet) {
+ // Then our per-document complete sheets.
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+
+ StyleSheet* completeSheet = nullptr;
+ mSheets->mCompleteSheets.Get(&key, &completeSheet);
+ sheet = completeSheet;
+ LOG((" From completed: %p", sheet.get()));
+
+ fromCompleteSheets = !!sheet;
+ }
+
+ if (sheet) {
+ if (sheet->IsServo()) {
+ MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
+ }
+
+ // This sheet came from the XUL cache or our per-document hashtable; it
+ // better be a complete sheet.
+ NS_ASSERTION(sheet->AsGecko()->IsComplete(),
+ "Sheet thinks it's not complete while we think it is");
+
+ // Make sure it hasn't been modified; if it has, we can't use it
+ if (sheet->AsGecko()->IsModified()) {
+ LOG((" Not cloning completed sheet %p because it's been modified",
+ sheet.get()));
+ sheet = nullptr;
+ fromCompleteSheets = false;
+ }
+ }
+
+ // Then loading sheets
+ if (!sheet && !aSyncLoad) {
+ aSheetState = eSheetLoading;
+ SheetLoadData* loadData = nullptr;
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+ mSheets->mLoadingDatas.Get(&key, &loadData);
+ if (loadData) {
+ sheet = loadData->mSheet;
+ LOG((" From loading: %p", sheet.get()));
+
+#ifdef DEBUG
+ bool debugEqual;
+ NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
+ (aLoaderPrincipal && loadData->mLoaderPrincipal &&
+ NS_SUCCEEDED(aLoaderPrincipal->
+ Equals(loadData->mLoaderPrincipal,
+ &debugEqual)) && debugEqual),
+ "Principals should be the same");
+#endif
+ }
+
+ // Then alternate sheets
+ if (!sheet) {
+ aSheetState = eSheetPending;
+ loadData = nullptr;
+ mSheets->mPendingDatas.Get(&key, &loadData);
+ if (loadData) {
+ sheet = loadData->mSheet;
+ LOG((" From pending: %p", sheet.get()));
+
+#ifdef DEBUG
+ bool debugEqual;
+ NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
+ (aLoaderPrincipal && loadData->mLoaderPrincipal &&
+ NS_SUCCEEDED(aLoaderPrincipal->
+ Equals(loadData->mLoaderPrincipal,
+ &debugEqual)) && debugEqual),
+ "Principals should be the same");
+#endif
+ }
+ }
+ }
+
+ if (sheet) {
+ // The sheet we have now should be either incomplete or unmodified
+ if (sheet->IsServo()) {
+ MOZ_CRASH("stylo: can't clone ServoStyleSheets yet");
+ }
+ NS_ASSERTION(!sheet->AsGecko()->IsModified() ||
+ !sheet->AsGecko()->IsComplete(),
+ "Unexpected modified complete sheet");
+ NS_ASSERTION(sheet->AsGecko()->IsComplete() ||
+ aSheetState != eSheetComplete,
+ "Sheet thinks it's not complete while we think it is");
+
+ RefPtr<CSSStyleSheet> clonedSheet =
+ sheet->AsGecko()->Clone(nullptr, nullptr, nullptr, nullptr);
+ *aSheet = Move(clonedSheet);
+ if (*aSheet && fromCompleteSheets &&
+ !sheet->AsGecko()->GetOwnerNode() &&
+ !sheet->AsGecko()->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.
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aURI, aLoaderPrincipal, aCORSMode, aReferrerPolicy);
+ NS_ASSERTION((*aSheet)->AsGecko()->IsComplete(),
+ "Should only be caching complete sheets");
+ mSheets->mCompleteSheets.Put(&key, *aSheet);
+ }
+ }
+ }
+
+ if (!*aSheet) {
+ aSheetState = eSheetNeedsParser;
+ nsIURI *sheetURI;
+ nsCOMPtr<nsIURI> baseURI;
+ nsIURI* originalURI;
+ if (!aURI) {
+ // Inline style. Use the document's base URL so that @import in
+ // the inline sheet picks up the right base.
+ NS_ASSERTION(aLinkingContent, "Inline stylesheet without linking content?");
+ baseURI = aLinkingContent->GetBaseURI();
+ sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
+ originalURI = nullptr;
+ } else {
+ baseURI = aURI;
+ sheetURI = aURI;
+ 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);
+ }
+
+ if (GetStyleBackendType() == StyleBackendType::Gecko) {
+ *aSheet = new CSSStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
+ } else {
+ *aSheet = new ServoStyleSheet(aParsingMode, aCORSMode, aReferrerPolicy, sriMetadata);
+ }
+ (*aSheet)->SetURIs(sheetURI, originalURI, baseURI);
+ }
+
+ NS_ASSERTION(*aSheet, "We should have a sheet by now!");
+ NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
+ LOG((" State: %s", gStateStrings[aSheetState]));
+
+ return NS_OK;
+}
+
+/**
+ * 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.
+ */
+void
+Loader::PrepareSheet(StyleSheet* aSheet,
+ const nsSubstring& aTitle,
+ const nsSubstring& aMediaString,
+ nsMediaList* aMediaList,
+ Element* aScopeElement,
+ bool isAlternate)
+{
+ NS_PRECONDITION(aSheet, "Must have a sheet!");
+
+ // XXXheycam Need to set media, title, etc. on ServoStyleSheets.
+ if (aSheet->IsServo()) {
+ NS_WARNING("stylo: should set metadata on ServoStyleSheets. See bug 1290209.");
+ return;
+ }
+
+ CSSStyleSheet* sheet = aSheet->AsGecko();
+
+ RefPtr<nsMediaList> mediaList(aMediaList);
+
+ if (!aMediaString.IsEmpty()) {
+ NS_ASSERTION(!aMediaList,
+ "must not provide both aMediaString and aMediaList");
+ mediaList = new nsMediaList();
+
+ nsCSSParser mediumParser(this);
+
+ // We have aMediaString only when linked from link elements, style
+ // elements, or PIs, so pass true.
+ mediumParser.ParseMediaList(aMediaString, nullptr, 0, mediaList, true);
+ }
+
+ sheet->SetMedia(mediaList);
+
+ sheet->SetTitle(aTitle);
+ sheet->SetEnabled(!isAlternate);
+ sheet->SetScopeElement(aScopeElement);
+}
+
+/**
+ * InsertSheetInDoc handles ordering of sheets in the document. Here
+ * we have two types of sheets -- those with linking elements and
+ * those without. The latter are loaded by Link: headers.
+ * 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.
+ */
+nsresult
+Loader::InsertSheetInDoc(StyleSheet* aSheet,
+ nsIContent* aLinkingContent,
+ nsIDocument* aDocument)
+{
+ LOG(("css::Loader::InsertSheetInDoc"));
+ NS_PRECONDITION(aSheet, "Nothing to insert");
+ NS_PRECONDITION(aDocument, "Must have a document to insert into");
+
+ // XXX Need to cancel pending sheet loads for this element, if any
+
+ int32_t sheetCount = aDocument->GetNumberOfStyleSheets();
+
+ /*
+ * 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;
+ for (insertionPoint = sheetCount - 1; insertionPoint >= 0; --insertionPoint) {
+ StyleSheet* curSheet = aDocument->GetStyleSheetAt(insertionPoint);
+ NS_ASSERTION(curSheet, "There must be a sheet here!");
+ nsCOMPtr<nsINode> sheetOwner = curSheet->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 linkingNode
+ break;
+ }
+
+ NS_ASSERTION(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; // adjust the index to the spot we want to insert in
+
+ // XXX <meta> elements do not implement nsIStyleSheetLinkingElement;
+ // need to fix this for them to be ordered correctly.
+ nsCOMPtr<nsIStyleSheetLinkingElement>
+ linkingElement = do_QueryInterface(aLinkingContent);
+ if (linkingElement) {
+ linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
+ }
+
+ aDocument->BeginUpdate(UPDATE_STYLE);
+ aDocument->InsertStyleSheetAt(aSheet, insertionPoint);
+ aDocument->EndUpdate(UPDATE_STYLE);
+ LOG((" Inserting into document at position %d", insertionPoint));
+
+ return NS_OK;
+}
+
+/**
+ * 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.)
+ */
+nsresult
+Loader::InsertChildSheet(StyleSheet* aSheet,
+ StyleSheet* aParentSheet,
+ ImportRule* aParentRule)
+{
+ LOG(("css::Loader::InsertChildSheet"));
+ NS_PRECONDITION(aSheet, "Nothing to insert");
+ NS_PRECONDITION(aParentSheet, "Need a parent to insert into");
+ NS_PRECONDITION(aParentSheet, "How did we get imported?");
+
+ // XXXheycam The InsertChildSheet API doesn't work with ServoStyleSheets,
+ // since they won't have Gecko ImportRules in them.
+ if (aSheet->IsServo()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // child sheets should always start out enabled, even if they got
+ // cloned off of top-level sheets which were disabled
+ aSheet->AsGecko()->SetEnabled(true);
+
+ aParentSheet->AppendStyleSheet(aSheet);
+ aParentRule->SetSheet(aSheet->AsGecko()); // This sets the ownerRule on the sheet
+
+ LOG((" Inserting into parent sheet"));
+ // LOG((" Inserting into parent sheet at position %d", insertionPoint));
+
+ return NS_OK;
+}
+
+/**
+ * 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,
+ StyleSheetState aSheetState,
+ bool aIsPreload)
+{
+ LOG(("css::Loader::LoadSheet"));
+ NS_PRECONDITION(aLoadData, "Need a load data");
+ NS_PRECONDITION(aLoadData->mURI, "Need a URI to load");
+ NS_PRECONDITION(aLoadData->mSheet, "Need a sheet to load into");
+ NS_PRECONDITION(aSheetState != eSheetComplete, "Why bother?");
+ NS_PRECONDITION(!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"));
+ NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
+ NS_ASSERTION(aSheetState == eSheetNeedsParser,
+ "Sync loads can't reuse existing async loads");
+
+ // Create a nsIUnicharStreamLoader instance to which we will feed
+ // the data from the sync load. Do this before creating the
+ // channel to make error recovery simpler.
+ nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
+ rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to create stream loader for sync load"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ 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 ? 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.
+ 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;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(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,
+ streamLoader,
+ channel);
+ }
+
+ SheetLoadData* existingData = nullptr;
+
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+ if (aSheetState == eSheetLoading) {
+ mSheets->mLoadingDatas.Get(&key, &existingData);
+ NS_ASSERTION(existingData, "CreateSheet lied about the state");
+ }
+ else if (aSheetState == eSheetPending){
+ mSheets->mPendingDatas.Get(&key, &existingData);
+ 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; // transfer ownership
+ if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
+ // Kick the load off; someone cares about it right away
+
+#ifdef DEBUG
+ SheetLoadData* removedData;
+ NS_ASSERTION(mSheets->mPendingDatas.Get(&key, &removedData) &&
+ removedData == existingData,
+ "Bad pending table.");
+#endif
+
+ mSheets->mPendingDatas.Remove(&key);
+
+ LOG((" Forcing load of pending data"));
+ return LoadSheet(existingData, eSheetNeedsParser, aIsPreload);
+ }
+ // All done here; once the load completes we'll be marked complete
+ // automatically
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ 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;
+ }
+ }
+#ifdef DEBUG
+ 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 ? 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,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIChannel::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI);
+ }
+ 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,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIChannel::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI);
+ }
+
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+ LOG_ERROR((" Failed to create channel"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ if (!aLoadData->mWasAlternate) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Leader);
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // Send a minimal Accept header for text/css
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
+ false);
+ nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
+ if (referrerURI)
+ httpChannel->SetReferrerWithPolicy(referrerURI,
+ aLoadData->mSheet->GetReferrerPolicy());
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
+ if (internalChannel) {
+ internalChannel->SetIntegrityMetadata(sriMetadata.GetIntegrityString());
+ }
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+ if (timedChannel) {
+ if (aLoadData->mParentData) {
+ timedChannel->SetInitiatorType(NS_LITERAL_STRING("css"));
+ } 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<nsIUnicharStreamLoader> streamLoader;
+ rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+ LOG_ERROR((" Failed to create stream loader"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ if (mDocument) {
+ mozilla::net::PredictorLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
+ mDocument);
+ }
+
+ rv = channel->AsyncOpen2(streamLoader);
+
+#ifdef DEBUG
+ mSyncCallback = false;
+#endif
+
+ 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;
+}
+
+/**
+ * ParseSheet handles parsing the data stream. The main idea here is
+ * to push the current load data onto the parse stack before letting
+ * the CSS parser at the data stream. That lets us handle @import
+ * correctly.
+ */
+nsresult
+Loader::ParseSheet(const nsAString& aInput,
+ SheetLoadData* aLoadData,
+ bool& aCompleted)
+{
+ LOG(("css::Loader::ParseSheet"));
+ NS_PRECONDITION(aLoadData, "Must have load data");
+ NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
+
+ aCompleted = false;
+
+ // Push our load data on the stack so any kids can pick it up
+ mParsingDatas.AppendElement(aLoadData);
+ nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
+ nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
+
+ nsresult rv;
+
+ if (aLoadData->mSheet->IsGecko()) {
+ nsCSSParser parser(this, aLoadData->mSheet->AsGecko());
+ rv = parser.ParseSheet(aInput, sheetURI, baseURI,
+ aLoadData->mSheet->Principal(),
+ aLoadData->mLineNumber);
+ } else {
+ rv =
+ aLoadData->mSheet->AsServo()->ParseSheet(aInput, sheetURI, baseURI,
+ aLoadData->mSheet->Principal(),
+ aLoadData->mLineNumber);
+ }
+
+ mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Low-level error in parser!"));
+ SheetComplete(aLoadData, rv);
+ return rv;
+ }
+
+ NS_ASSERTION(aLoadData->mPendingChildren == 0 || !aLoadData->mSyncLoad,
+ "Sync load has leftover pending children!");
+
+ if (aLoadData->mPendingChildren == 0) {
+ LOG((" No pending kids from parse"));
+ aCompleted = true;
+ SheetComplete(aLoadData, NS_OK);
+ }
+ // Otherwise, the children are holding strong refs to the data and
+ // will call SheetComplete() on it when they complete.
+
+ return NS_OK;
+}
+
+/**
+ * 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"));
+
+ if (aLoadData->mSheet->IsServo() && NS_FAILED(aStatus)) {
+ aLoadData->mSheet->AsServo()->LoadFailed();
+ }
+
+ // 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, aStatus, datasToNotify);
+
+ // Now it's safe to go ahead and notify observers
+ uint32_t count = datasToNotify.Length();
+ mDatasToNotifyOn += count;
+ for (uint32_t i = 0; i < count; ++i) {
+ --mDatasToNotifyOn;
+
+ SheetLoadData* data = datasToNotify[i];
+ NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
+ if (data->mObserver) {
+ LOG((" Notifying observer %p for data %p. wasAlternate: %d",
+ data->mObserver.get(), data, data->mWasAlternate));
+ data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
+ aStatus);
+ }
+
+ nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
+ nsCOMPtr<nsICSSLoaderObserver> obs;
+ while (iter.HasMore()) {
+ obs = iter.GetNext();
+ LOG((" Notifying global observer %p for data %p. wasAlternate: %d",
+ obs.get(), data, data->mWasAlternate));
+ obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
+ }
+ }
+
+ if (mSheets->mLoadingDatas.Count() == 0 && mSheets->mPendingDatas.Count() > 0) {
+ LOG((" No more loading sheets; starting alternates"));
+ StartAlternateLoads();
+ }
+}
+
+void
+Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
+ LoadDataArray& aDatasToNotify)
+{
+ LOG(("css::Loader::DoSheetComplete"));
+ NS_PRECONDITION(aLoadData, "Must have a load data!");
+ NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
+ NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
+
+ LOG(("Load completed, status: 0x%x", aStatus));
+
+ // 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) {
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+#ifdef DEBUG
+ SheetLoadData *loadingData;
+ NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
+ loadingData == aLoadData,
+ "Bad loading table");
+#endif
+
+ mSheets->mLoadingDatas.Remove(&key);
+ aLoadData->mIsLoading = false;
+ }
+ }
+
+ // Go through and deal with the whole linked list.
+ SheetLoadData* data = aLoadData;
+ while (data) {
+ 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->IsGecko() &&
+ data->mSheet->AsGecko()->IsModified()),
+ "should not get marked modified during parsing");
+ data->mSheet->SetComplete();
+ data->ScheduleLoadEventIfNeeded(aStatus);
+ }
+ 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 &&
+ !mParsingDatas.Contains(data->mParentData)) {
+ DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
+ }
+
+ data = data->mNext;
+ }
+
+ // Now that it's marked complete, put the sheet in our cache.
+ // If we ever start doing this for failure aStatus, we'll need to
+ // adjust the PostLoadEvent code that thinks anything already
+ // complete must have loaded succesfully.
+ if (NS_SUCCEEDED(aStatus) && 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.
+ if (aLoadData->mSheet->IsGecko()) {
+ data = aLoadData;
+ CSSStyleSheet* sheet = aLoadData->mSheet->AsGecko();
+ while (data) {
+ if (data->mSheet->GetParentSheet() || data->mSheet->GetOwnerNode()) {
+ sheet = data->mSheet->AsGecko();
+ break;
+ }
+ data = data->mNext;
+ }
+#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);
+ }
+ }
+ }
+ else {
+#endif
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(aLoadData->mURI,
+ aLoadData->mLoaderPrincipal,
+ aLoadData->mSheet->GetCORSMode(),
+ aLoadData->mSheet->GetReferrerPolicy());
+ NS_ASSERTION(sheet->IsComplete(),
+ "Should only be caching complete sheets");
+ mSheets->mCompleteSheets.Put(&key, sheet);
+#ifdef MOZ_XUL
+ }
+#endif
+ } else {
+ NS_WARNING("stylo: Stylesheet caching not yet supported - see bug 1290218.");
+ }
+ }
+
+ NS_RELEASE(aLoadData); // this will release parents and siblings and all that
+}
+
+nsresult
+Loader::LoadInlineStyle(nsIContent* aElement,
+ const nsAString& aBuffer,
+ uint32_t aLineNumber,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ Element* aScopeElement,
+ nsICSSLoaderObserver* aObserver,
+ bool* aCompleted,
+ bool* aIsAlternate)
+{
+ LOG(("css::Loader::LoadInlineStyle"));
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ *aCompleted = true;
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
+ 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(). Also, OK to use CORS_NONE for the CORS
+ // mode and mDocument's ReferrerPolicy.
+ StyleSheetState state;
+ RefPtr<StyleSheet> sheet;
+ nsresult rv = CreateSheet(nullptr, aElement, nullptr, eAuthorSheetFeatures,
+ CORS_NONE, mDocument->GetReferrerPolicy(),
+ EmptyString(), // no inline integrity checks
+ false, false, aTitle, state, aIsAlternate,
+ &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(state == eSheetNeedsParser,
+ "Inline sheets should not be cached");
+
+ LOG((" Sheet is alternate: %d", *aIsAlternate));
+
+ PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
+
+ if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+ ShadowRoot* containingShadow = aElement->GetContainingShadow();
+ MOZ_ASSERT(containingShadow);
+ containingShadow->InsertSheet(sheet, aElement);
+ } else {
+ rv = InsertSheetInDoc(sheet, aElement, mDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ SheetLoadData* data = new SheetLoadData(this, aTitle, nullptr, sheet,
+ owningElement, *aIsAlternate,
+ aObserver, nullptr, static_cast<nsINode*>(aElement));
+
+ // We never actually load this, so just set its principal directly
+ sheet->SetPrincipal(aElement->NodePrincipal());
+
+ NS_ADDREF(data);
+ data->mLineNumber = aLineNumber;
+ // Parse completion releases the load data
+ rv = ParseSheet(aBuffer, data, *aCompleted);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If aCompleted is true, |data| may well be deleted by now.
+ if (!*aCompleted) {
+ data->mMustNotify = true;
+ }
+ return rv;
+}
+
+nsresult
+Loader::LoadStyleLink(nsIContent* aElement,
+ nsIURI* aURL,
+ const nsAString& aTitle,
+ const nsAString& aMedia,
+ bool aHasAlternateRel,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity,
+ nsICSSLoaderObserver* aObserver,
+ bool* aIsAlternate)
+{
+ LOG(("css::Loader::LoadStyleLink"));
+ NS_PRECONDITION(aURL, "Must have URL to load");
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ LOG_URI(" Link uri: '%s'", aURL);
+ LOG((" Link title: '%s'", NS_ConvertUTF16toUTF8(aTitle).get()));
+ LOG((" Link media: '%s'", NS_ConvertUTF16toUTF8(aMedia).get()));
+ LOG((" Link alternate rel: %d", aHasAlternateRel));
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ nsIPrincipal* principal =
+ aElement ? aElement->NodePrincipal() : mDocument->NodePrincipal();
+
+ nsISupports* context = aElement;
+ if (!context) {
+ context = mDocument;
+ }
+
+ nsresult rv = CheckContentPolicy(principal, aURL, context, false);
+ 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 (aElement && !mDocument->IsLoadedAsData()) {
+ // Fire an async error event on it.
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(aElement,
+ NS_LITERAL_STRING("error"),
+ false, false);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+ }
+ return rv;
+ }
+
+ StyleSheetState state;
+ RefPtr<StyleSheet> sheet;
+ rv = CreateSheet(aURL, aElement, principal, eAuthorSheetFeatures,
+ aCORSMode, aReferrerPolicy, aIntegrity, false,
+ aHasAlternateRel, aTitle, state, aIsAlternate,
+ &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG((" Sheet is alternate: %d", *aIsAlternate));
+
+ PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
+
+ rv = InsertSheetInDoc(sheet, aElement, mDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
+
+ if (state == eSheetComplete) {
+ LOG((" Sheet already complete: 0x%p", sheet.get()));
+ if (aObserver || !mObservers.IsEmpty() || owningElement) {
+ rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate,
+ owningElement);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // Now we need to actually load it
+ nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
+ SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
+ owningElement, *aIsAlternate,
+ aObserver, principal, requestingNode);
+ NS_ADDREF(data);
+
+ // If we have to parse and it's an alternate non-inline, defer it
+ if (aURL && state == eSheetNeedsParser && mSheets->mLoadingDatas.Count() != 0 &&
+ *aIsAlternate) {
+ LOG((" Deferring alternate sheet load"));
+ URIPrincipalReferrerPolicyAndCORSModeHashKey key(data->mURI,
+ data->mLoaderPrincipal,
+ data->mSheet->GetCORSMode(),
+ data->mSheet->GetReferrerPolicy());
+ mSheets->mPendingDatas.Put(&key, data);
+
+ data->mMustNotify = true;
+ return NS_OK;
+ }
+
+ // Load completion will free the data
+ rv = LoadSheet(data, state, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ data->mMustNotify = true;
+ return rv;
+}
+
+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.
+ while (aData) {
+ if (aData->mParentData &&
+ HaveAncestorDataWithURI(aData->mParentData, aURI)) {
+ return true;
+ }
+
+ aData = aData->mNext;
+ }
+
+ return false;
+}
+
+nsresult
+Loader::LoadChildSheet(StyleSheet* aParentSheet,
+ nsIURI* aURL,
+ nsMediaList* aMedia,
+ ImportRule* aParentRule,
+ LoaderReusableStyleSheets* aReusableSheets)
+{
+ LOG(("css::Loader::LoadChildSheet"));
+ NS_PRECONDITION(aURL, "Must have a URI to load");
+ NS_PRECONDITION(aParentSheet, "Must have a parent sheet");
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG_URI(" Child uri: '%s'", aURL);
+
+ nsCOMPtr<nsINode> owningNode;
+
+ // check for an owning document: if none, don't bother walking up the parent
+ // sheets
+ if (aParentSheet->GetOwningDocument()) {
+ StyleSheet* topSheet = aParentSheet;
+ while (StyleSheet* parent = topSheet->GetParentSheet()) {
+ topSheet = parent;
+ }
+ owningNode = topSheet->GetOwnerNode();
+ }
+
+ nsISupports* context = owningNode;
+ if (!context) {
+ context = mDocument;
+ }
+
+ nsIPrincipal* principal = aParentSheet->Principal();
+ nsresult rv = CheckContentPolicy(principal, aURL, context, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SheetLoadData* parentData = nullptr;
+ nsCOMPtr<nsICSSLoaderObserver> observer;
+
+ int32_t count = mParsingDatas.Length();
+ if (count > 0) {
+ LOG((" Have a parent load"));
+ parentData = mParsingDatas.ElementAt(count - 1);
+ // Check for cycles
+ if (HaveAncestorDataWithURI(parentData, 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(parentData->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.
+ // XXXheycam ServoStyleSheet doesn't implement nsICSSLoaderObserver yet.
+ MOZ_ASSERT(aParentSheet->IsGecko(),
+ "stylo: ServoStyleSheets don't support child sheet loading yet");
+ observer = aParentSheet->AsGecko();
+ }
+
+ // Now that we know it's safe to load this (passes security check and not a
+ // loop) do so.
+ RefPtr<StyleSheet> sheet;
+ RefPtr<CSSStyleSheet> reusableSheet;
+ StyleSheetState state;
+ if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, reusableSheet)) {
+ sheet = reusableSheet;
+ aParentRule->SetSheet(reusableSheet);
+ state = eSheetComplete;
+ } else {
+ bool isAlternate;
+ const nsSubstring& empty = EmptyString();
+ // For now, use CORS_NONE for child sheets
+ rv = CreateSheet(aURL, nullptr, principal,
+ aParentSheet->ParsingMode(),
+ CORS_NONE, aParentSheet->GetReferrerPolicy(),
+ EmptyString(), // integrity is only checked on main sheet
+ parentData ? parentData->mSyncLoad : false,
+ false, empty, state, &isAlternate, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
+ }
+
+ rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (state == eSheetComplete) {
+ 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;
+ }
+
+ nsCOMPtr<nsINode> requestingNode = do_QueryInterface(context);
+ SheetLoadData* data = new SheetLoadData(this, aURL, sheet, parentData,
+ observer, principal, requestingNode);
+
+ NS_ADDREF(data);
+ bool syncLoad = data->mSyncLoad;
+
+ // Load completion will release the data
+ rv = LoadSheet(data, state, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If syncLoad is true, |data| will be deleted by now.
+ if (!syncLoad) {
+ data->mMustNotify = true;
+ }
+ return rv;
+}
+
+nsresult
+Loader::LoadSheetSync(nsIURI* aURL,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::LoadSheetSync"));
+ return InternalLoadNonDocumentSheet(aURL,
+ false, aParsingMode, aUseSystemPrincipal,
+ nullptr, EmptyCString(),
+ aSheet, nullptr);
+}
+
+nsresult
+Loader::LoadSheet(nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ RefPtr<StyleSheet>* aSheet)
+{
+ LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
+ NS_PRECONDITION(aSheet, "aSheet is null");
+ return InternalLoadNonDocumentSheet(aURL,
+ false, eAuthorSheetFeatures, false,
+ aOriginPrincipal, aCharset,
+ aSheet, aObserver);
+}
+
+nsresult
+Loader::LoadSheet(nsIURI* aURL,
+ bool aIsPreload,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity)
+{
+ LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
+ return InternalLoadNonDocumentSheet(aURL,
+ aIsPreload, eAuthorSheetFeatures, false,
+ aOriginPrincipal, aCharset,
+ nullptr, aObserver,
+ aCORSMode, aReferrerPolicy, aIntegrity);
+}
+
+nsresult
+Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
+ bool aIsPreload,
+ SheetParsingMode aParsingMode,
+ bool aUseSystemPrincipal,
+ nsIPrincipal* aOriginPrincipal,
+ const nsCString& aCharset,
+ RefPtr<StyleSheet>* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity)
+{
+ NS_PRECONDITION(aURL, "Must have a URI to load");
+ NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
+ NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
+ "Shouldn't load system-principal sheets async");
+ NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
+
+ LOG_URI(" Non-document sheet uri: '%s'", aURL);
+
+ if (aSheet) {
+ *aSheet = nullptr;
+ }
+
+ if (!mEnabled) {
+ LOG_WARN((" Not enabled"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CheckContentPolicy(aOriginPrincipal, aURL, mDocument, aIsPreload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StyleSheetState state;
+ bool isAlternate;
+ RefPtr<StyleSheet> sheet;
+ bool syncLoad = (aObserver == nullptr);
+ const nsSubstring& empty = EmptyString();
+
+ rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aParsingMode,
+ aCORSMode, aReferrerPolicy, aIntegrity, syncLoad,
+ false, empty, state, &isAlternate, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
+
+ if (state == eSheetComplete) {
+ LOG((" Sheet already complete"));
+ if (aObserver || !mObservers.IsEmpty()) {
+ rv = PostLoadEvent(aURL, sheet, aObserver, false, nullptr);
+ }
+ if (aSheet) {
+ sheet.swap(*aSheet);
+ }
+ return rv;
+ }
+
+ SheetLoadData* data =
+ new SheetLoadData(this, aURL, sheet, syncLoad,
+ aUseSystemPrincipal, aCharset, aObserver,
+ aOriginPrincipal, mDocument);
+
+ NS_ADDREF(data);
+ rv = LoadSheet(data, state, aIsPreload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSheet) {
+ sheet.swap(*aSheet);
+ }
+ if (aObserver) {
+ data->mMustNotify = true;
+ }
+
+ return rv;
+}
+
+nsresult
+Loader::PostLoadEvent(nsIURI* aURI,
+ StyleSheet* aSheet,
+ nsICSSLoaderObserver* aObserver,
+ bool aWasAlternate,
+ nsIStyleSheetLinkingElement* aElement)
+{
+ LOG(("css::Loader::PostLoadEvent"));
+ NS_PRECONDITION(aSheet, "Must have sheet");
+ NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
+ "Must have observer or element");
+
+ RefPtr<SheetLoadData> evt =
+ new SheetLoadData(this, EmptyString(), // title doesn't matter here
+ aURI,
+ aSheet,
+ aElement,
+ aWasAlternate,
+ aObserver,
+ nullptr,
+ mDocument);
+ NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!mPostedEvents.AppendElement(evt)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = NS_DispatchToCurrentThread(evt);
+ 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.
+ if (mDocument) {
+ mDocument->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 use NS_OK for the status. Note that we do this here
+ // and not from inside our SheetComplete so that we don't end up
+ // running the load event async.
+ evt->ScheduleLoadEventIfNeeded(NS_OK);
+ }
+
+ 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 will call Release(), so give it a reference to do
+ // that with.
+ NS_ADDREF(aEvent);
+ SheetComplete(aEvent, NS_OK);
+ }
+
+ if (mDocument) {
+ mDocument->UnblockOnload(true);
+ }
+}
+
+static void
+StopLoadingSheets(
+ nsDataHashtable<URIPrincipalReferrerPolicyAndCORSModeHashKey, SheetLoadData*>& aDatas,
+ Loader::LoadDataArray& aArr)
+{
+ for (auto iter = aDatas.Iter(); !iter.Done(); iter.Next()) {
+ SheetLoadData* data = iter.Data();
+ MOZ_ASSERT(data, "Must have a data!");
+
+ data->mIsLoading = false; // we will handle the removal right here
+ data->mIsCancelled = true;
+
+ aArr.AppendElement(data);
+
+ iter.Remove();
+ }
+}
+
+nsresult
+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) {
+ StopLoadingSheets(mSheets->mPendingDatas, arr);
+ }
+ if (loadingCount) {
+ StopLoadingSheets(mSheets->mLoadingDatas, arr);
+ }
+
+ uint32_t i;
+ for (i = 0; i < mPostedEvents.Length(); ++i) {
+ SheetLoadData* data = mPostedEvents[i];
+ data->mIsCancelled = true;
+ if (arr.AppendElement(data)) {
+ // SheetComplete() calls Release(), so give this an extra ref.
+ NS_ADDREF(data);
+ }
+#ifdef DEBUG
+ else {
+ NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
+ "except we never check that preallocation succeeds.");
+ }
+#endif
+ }
+ mPostedEvents.Clear();
+
+ mDatasToNotifyOn += arr.Length();
+ for (i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ SheetComplete(arr[i], NS_BINDING_ABORTED);
+ }
+ return NS_OK;
+}
+
+bool
+Loader::HasPendingLoads()
+{
+ return
+ (mSheets && mSheets->mLoadingDatas.Count() != 0) ||
+ (mSheets && mSheets->mPendingDatas.Count() != 0) ||
+ mPostedEvents.Length() != 0 ||
+ mDatasToNotifyOn != 0;
+}
+
+nsresult
+Loader::AddObserver(nsICSSLoaderObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver, "Must have observer");
+ if (mObservers.AppendElementUnlessExists(aObserver)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
+{
+ mObservers.RemoveElement(aObserver);
+}
+
+void
+Loader::StartAlternateLoads()
+{
+ NS_PRECONDITION(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 (uint32_t i = 0; i < arr.Length(); ++i) {
+ --mDatasToNotifyOn;
+ LoadSheet(arr[i], eSheetNeedsParser, false);
+ }
+}
+
+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, "Sheet cache nsCSSLoader");
+ if (iter.UserData()->IsGecko()) {
+ CSSStyleSheet* sheet = iter.UserData()->AsGecko();
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMCSSStyleSheet*, sheet));
+ }
+ }
+ }
+ 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->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(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ if (mSheets) {
+ n += mSheets->mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mSheets->mCompleteSheets.ConstIter();
+ !iter.Done();
+ iter.Next()) {
+ // If aSheet 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();
+ n += (sheet->GetOwnerNode() || sheet->GetParentSheet())
+ ? 0
+ : sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ n += mObservers.ShallowSizeOfExcludingThis(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
+ // - mParsingDatas: transient, and should be small
+ // - mPostedEvents: transient, and should be small
+ //
+ // The following members aren't measured:
+ // - mDocument, because it's a weak backpointer
+ // - mPreferredSheet, because it can be a shared string
+
+ return n;
+}
+
+StyleBackendType
+Loader::GetStyleBackendType() const
+{
+ MOZ_ASSERT(mStyleBackendType || mDocument,
+ "you must construct a Loader with a document or set a "
+ "StyleBackendType on it before calling GetStyleBackendType");
+ if (mStyleBackendType) {
+ return *mStyleBackendType;
+ }
+ return mDocument->GetStyleBackendType();
+}
+
+} // namespace css
+} // namespace mozilla