/* -*- 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