/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* loading of CSS style sheets using the network APIs */ #ifndef mozilla_css_Loader_h #define mozilla_css_Loader_h #include #include #include "mozilla/Attributes.h" #include "mozilla/CORSMode.h" #include "mozilla/css/StylePreloadKind.h" #include "mozilla/dom/LinkStyle.h" #include "mozilla/MemoryReporting.h" #include "mozilla/UniquePtr.h" #include "nsCompatibility.h" #include "nsCycleCollectionParticipant.h" #include "nsStringFwd.h" #include "nsTArray.h" #include "nsTObserverArray.h" #include "nsURIHashKey.h" #include "nsRefPtrHashtable.h" class nsICSSLoaderObserver; class nsIConsoleReportCollector; class nsIContent; class nsIPrincipal; namespace mozilla { class PreloadHashKey; class SharedStyleSheetCache; class SheetLoadDataHashKey; class StyleSheet; namespace dom { class DocGroup; class Element; enum class FetchPriority : uint8_t; } // namespace dom // The load data for a or @import style-sheet. // // This must contain all the state that affects CSS parsing. class SheetLoadDataHashKey : public PLDHashEntryHdr { public: using KeyType = const SheetLoadDataHashKey&; using KeyTypePointer = const SheetLoadDataHashKey*; explicit SheetLoadDataHashKey(const SheetLoadDataHashKey* aKey) : mURI(aKey->mURI), mPrincipal(aKey->mPrincipal), mLoaderPrincipal(aKey->mLoaderPrincipal), mPartitionPrincipal(aKey->mPartitionPrincipal), mEncodingGuess(aKey->mEncodingGuess), mCORSMode(aKey->mCORSMode), mParsingMode(aKey->mParsingMode), mCompatMode(aKey->mCompatMode), mSRIMetadata(aKey->mSRIMetadata), mIsLinkRelPreload(aKey->mIsLinkRelPreload) { MOZ_COUNT_CTOR(SheetLoadDataHashKey); } SheetLoadDataHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal, nsIPrincipal* aLoaderPrincipal, nsIPrincipal* aPartitionPrincipal, NotNull aEncodingGuess, CORSMode aCORSMode, css::SheetParsingMode aParsingMode, nsCompatibility aCompatMode, const dom::SRIMetadata& aSRIMetadata, css::StylePreloadKind aPreloadKind) : mURI(aURI), mPrincipal(aPrincipal), mLoaderPrincipal(aLoaderPrincipal), mPartitionPrincipal(aPartitionPrincipal), mEncodingGuess(aEncodingGuess), mCORSMode(aCORSMode), mParsingMode(aParsingMode), mCompatMode(aCompatMode), mSRIMetadata(aSRIMetadata), mIsLinkRelPreload(IsLinkRelPreload(aPreloadKind)) { MOZ_ASSERT(aURI); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aLoaderPrincipal); MOZ_COUNT_CTOR(SheetLoadDataHashKey); } SheetLoadDataHashKey(SheetLoadDataHashKey&& toMove) : mURI(std::move(toMove.mURI)), mPrincipal(std::move(toMove.mPrincipal)), mLoaderPrincipal(std::move(toMove.mLoaderPrincipal)), mPartitionPrincipal(std::move(toMove.mPartitionPrincipal)), mEncodingGuess(std::move(toMove.mEncodingGuess)), mCORSMode(std::move(toMove.mCORSMode)), mParsingMode(std::move(toMove.mParsingMode)), mCompatMode(std::move(toMove.mCompatMode)), mSRIMetadata(std::move(toMove.mSRIMetadata)), mIsLinkRelPreload(std::move(toMove.mIsLinkRelPreload)) { MOZ_COUNT_CTOR(SheetLoadDataHashKey); } explicit SheetLoadDataHashKey(const css::SheetLoadData&); MOZ_COUNTED_DTOR(SheetLoadDataHashKey) const SheetLoadDataHashKey& GetKey() const { return *this; } const SheetLoadDataHashKey* GetKeyPointer() const { return this; } bool KeyEquals(const SheetLoadDataHashKey* aKey) const { return KeyEquals(*aKey); } bool KeyEquals(const SheetLoadDataHashKey&) const; static const SheetLoadDataHashKey* KeyToPointer( const SheetLoadDataHashKey& aKey) { return &aKey; } static PLDHashNumber HashKey(const SheetLoadDataHashKey* aKey) { return nsURIHashKey::HashKey(aKey->mURI); } nsIURI* URI() const { return mURI; } nsIPrincipal* Principal() const { return mPrincipal; } nsIPrincipal* LoaderPrincipal() const { return mLoaderPrincipal; } nsIPrincipal* PartitionPrincipal() const { return mPartitionPrincipal; } css::SheetParsingMode ParsingMode() const { return mParsingMode; } enum { ALLOW_MEMMOVE = true }; protected: const nsCOMPtr mURI; const nsCOMPtr mPrincipal; const nsCOMPtr mLoaderPrincipal; const nsCOMPtr mPartitionPrincipal; // The encoding guess is the encoding the sheet would get if the request // didn't have any encoding information like @charset or a Content-Encoding // header. const NotNull mEncodingGuess; const CORSMode mCORSMode; const css::SheetParsingMode mParsingMode; const nsCompatibility mCompatMode; dom::SRIMetadata mSRIMetadata; const bool mIsLinkRelPreload; }; namespace css { class SheetLoadData; class ImportRule; /********************* * Style sheet reuse * *********************/ class MOZ_RAII LoaderReusableStyleSheets { public: LoaderReusableStyleSheets() = default; /** * Look for a reusable sheet (see AddReusableSheet) matching the * given URL. If found, set aResult, remove the reused sheet from * the internal list, and return true. If not found, return false; * in this case, aResult is not modified. * * @param aURL the url to match * @param aResult [out] the style sheet which can be reused */ bool FindReusableStyleSheet(nsIURI* aURL, RefPtr& aResult); /** * Indicate that a certain style sheet is available for reuse if its * URI matches the URI of an @import. Sheets should be added in the * opposite order in which they are intended to be reused. * * @param aSheet the sheet which can be reused */ void AddReusableSheet(StyleSheet* aSheet) { mReusableSheets.AppendElement(aSheet); } private: LoaderReusableStyleSheets(const LoaderReusableStyleSheets&) = delete; LoaderReusableStyleSheets& operator=(const LoaderReusableStyleSheets&) = delete; // The sheets that can be reused. nsTArray> mReusableSheets; }; class Loader final { using ReferrerPolicy = dom::ReferrerPolicy; public: using Completed = dom::LinkStyle::Completed; using HasAlternateRel = dom::LinkStyle::HasAlternateRel; using IsAlternate = dom::LinkStyle::IsAlternate; using IsInline = dom::LinkStyle::IsInline; using IsExplicitlyEnabled = dom::LinkStyle::IsExplicitlyEnabled; using MediaMatched = dom::LinkStyle::MediaMatched; using LoadSheetResult = dom::LinkStyle::Update; using SheetInfo = dom::LinkStyle::SheetInfo; Loader(); // aDocGroup is used for dispatching SheetLoadData in PostLoadEvent(). It // can be null if you want to use this constructor, and there's no // document when the Loader is constructed. explicit Loader(dom::DocGroup*); explicit Loader(dom::Document*); private: // Private destructor, to discourage deletion outside of Release(): ~Loader(); public: NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Loader) NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Loader) void DropDocumentReference(); // notification that doc is going away void DeregisterFromSheetCache(); void RegisterInSheetCache(); void SetCompatibilityMode(nsCompatibility aCompatMode) { mDocumentCompatMode = aCompatMode; } using StylePreloadKind = css::StylePreloadKind; bool HasLoaded(const SheetLoadDataHashKey& aKey) const { return mLoadsPerformed.Contains(aKey); } void WillStartPendingLoad() { MOZ_DIAGNOSTIC_ASSERT(mPendingLoadCount, "Where did this load come from?"); mPendingLoadCount--; } nsCompatibility CompatMode(StylePreloadKind aPreloadKind) const { // For Link header preload, we guess non-quirks, because otherwise it is // useless for modern pages. // // Link element preload is generally good because the speculative html // parser deals with quirks mode properly. if (aPreloadKind == StylePreloadKind::FromLinkRelPreloadHeader) { return eCompatibility_FullStandards; } return mDocumentCompatMode; } // TODO(emilio): Is the complexity of this method and carrying the titles // around worth it? The alternate sheets will load anyhow eventually... void DocumentStyleSheetSetChanged(); // XXXbz sort out what the deal is with events! When should they fire? /** * Load an inline style sheet. If a successful result is returned and * result.WillNotify() is true, then aObserver is guaranteed to be notified * asynchronously once the sheet is marked complete. If an error is * returned, or if result.WillNotify() is false, aObserver will not be * notified. In addition to parsing the sheet, this method will insert it * into the stylesheet list of this CSSLoader's document. * @param aObserver the observer to notify when the load completes. * May be null. * @param aBuffer the stylesheet data */ Result LoadInlineStyle( const SheetInfo&, const nsAString& aBuffer, nsICSSLoaderObserver* aObserver); /** * Load a linked (document) stylesheet. If a successful result is returned, * aObserver is guaranteed to be notified asynchronously once the sheet is * loaded and marked complete, i.e., result.WillNotify() will always return * true. If an error is returned, aObserver will not be notified. In * addition to loading the sheet, this method will insert it into the * stylesheet list of this CSSLoader's document. * @param aObserver the observer to notify when the load completes. * May be null. */ Result LoadStyleLink( const SheetInfo&, nsICSSLoaderObserver* aObserver); /** * Load a child (@import-ed) style sheet. In addition to loading the sheet, * this method will insert it into the child sheet list of aParentSheet. If * there is no sheet currently being parsed and the child sheet is not * complete when this method returns, then when the child sheet becomes * complete aParentSheet will be QIed to nsICSSLoaderObserver and * asynchronously notified, just like for LoadStyleLink. Note that if the * child sheet is already complete when this method returns, no * nsICSSLoaderObserver notification will be sent. * * @param aParentSheet the parent of this child sheet * @param aParentData the SheetLoadData corresponding to the load of the * parent sheet. May be null for @import rules inserted via * CSSOM. * @param aURL the URL of the child sheet * @param aMedia the already-parsed media list for the child sheet * @param aSavedSheets any saved style sheets which could be reused * for this load */ nsresult LoadChildSheet(StyleSheet& aParentSheet, SheetLoadData* aParentData, nsIURI* aURL, dom::MediaList* aMedia, LoaderReusableStyleSheets* aSavedSheets); /** * Called when we hit the internal memory cache with a complete stylesheet. */ void DidHitCompleteSheetCache(const SheetLoadDataHashKey&, const StyleUseCounters* aCounters); enum class UseSystemPrincipal { No, Yes }; /** * Synchronously load and return the stylesheet at aURL. Any child sheets * will also be loaded synchronously. Note that synchronous loads over some * protocols may involve spinning up a new event loop, so use of this method * does NOT guarantee not receiving any events before the sheet loads. This * method can be used to load sheets not associated with a document. * * @param aURL the URL of the sheet to load * @param aParsingMode the mode in which to parse the sheet * (see comments at enum SheetParsingMode, above). * @param aUseSystemPrincipal if true, give the resulting sheet the system * principal no matter where it's being loaded from. * * NOTE: At the moment, this method assumes the sheet will be UTF-8, but * ideally it would allow arbitrary encodings. Callers should NOT depend on * non-UTF8 sheets being treated as UTF-8 by this method. * * NOTE: A successful return from this method doesn't indicate anything about * whether the data could be parsed as CSS and doesn't indicate anything * about the status of child sheets of the returned sheet. */ Result, nsresult> LoadSheetSync( nsIURI*, SheetParsingMode = eAuthorSheetFeatures, UseSystemPrincipal = UseSystemPrincipal::No); /** * Asynchronously load the stylesheet at aURL. If a successful result is * returned, aObserver is guaranteed to be notified asynchronously once the * sheet is loaded and marked complete. This method can be used to load * sheets not associated with a document. * * @param aURL the URL of the sheet to load * @param aParsingMode the mode in which to parse the sheet * (see comments at enum SheetParsingMode, above). * @param aUseSystemPrincipal if true, give the resulting sheet the system * principal no matter where it's being loaded from. * @param aReferrerInfo referrer information of the sheet. * @param aObserver the observer to notify when the load completes. * Must not be null. * @param aEarlyHintPreloaderId to connect back to the early hint preload * channel. Null means no connect back should happen * @return the sheet to load. Note that the sheet may well not be loaded by * the time this method returns. * * NOTE: At the moment, this method assumes the sheet will be UTF-8, but * ideally it would allow arbitrary encodings. Callers should NOT depend on * non-UTF8 sheets being treated as UTF-8 by this method. */ Result, nsresult> LoadSheet( nsIURI* aURI, StylePreloadKind, const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver, uint64_t aEarlyHintPreloaderId, CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity, dom::FetchPriority aFetchPriority); /** * As above, but without caring for a couple things. * Only to be called by `PreloadedStyleSheet::PreloadAsync`. */ Result, nsresult> LoadSheet(nsIURI*, SheetParsingMode, UseSystemPrincipal, nsICSSLoaderObserver*); /** * Stop loading all sheets. All nsICSSLoaderObservers involved will be * notified with NS_BINDING_ABORTED as the status, possibly synchronously. */ void Stop(); /** * nsresult Loader::StopLoadingSheet(nsIURI* aURL), which notifies the * nsICSSLoaderObserver with NS_BINDING_ABORTED, was removed in Bug 556446. * It can be found in revision 2c44a32052ad. */ /** * Whether the loader is enabled or not. * When disabled, processing of new styles is disabled and an attempt * to do so will fail with a return code of * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable * currently loading styles or already processed styles. */ bool GetEnabled() { return mEnabled; } void SetEnabled(bool aEnabled) { mEnabled = aEnabled; } uint32_t ParsedSheetCount() const { return mParsedSheetCount; } /** * Get the document we live for. May return null. */ dom::Document* GetDocument() const { return mDocument; } bool IsDocumentAssociated() const { return mIsDocumentAssociated; } /** * Return true if this loader has pending loads (ones that would send * notifications to an nsICSSLoaderObserver attached to this loader). * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will * return false if and only if that is the last StyleSheetLoaded * notification the CSSLoader knows it's going to send. In other words, if * two sheets load at once (via load coalescing, e.g.), HasPendingLoads() * will return true during notification for the first one, and false * during notification for the second one. */ bool HasPendingLoads(); /** * Add an observer to this loader. The observer will be notified * for all loads that would have notified their own observers (even * if those loads don't have observers attached to them). * Load-specific observers will be notified before generic * observers. The loader holds a reference to the observer. * * aObserver must not be null. */ void AddObserver(nsICSSLoaderObserver* aObserver); /** * Remove an observer added via AddObserver. */ void RemoveObserver(nsICSSLoaderObserver* aObserver); // These interfaces are public only for the benefit of static functions // within nsCSSLoader.cpp. // IsAlternateSheet can change our currently selected style set if none is // selected and aHasAlternateRel is false. IsAlternate IsAlternateSheet(const nsAString& aTitle, bool aHasAlternateRel); // Measure our size. size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; enum class SheetState : uint8_t { NeedsParser = 0, Pending, Loading, Complete }; // The loader principal is the document's node principal, if this loader is // owned by a document, or the system principal otherwise. nsIPrincipal* LoaderPrincipal() const; // The partitioned principal is the document's partitioned principal, if this // loader is owned by a document, or the system principal otherwise. nsIPrincipal* PartitionedPrincipal() const; bool ShouldBypassCache() const; enum class PendingLoad { No, Yes }; private: friend class mozilla::SharedStyleSheetCache; friend class SheetLoadData; friend class StreamLoader; // Only to be called by `LoadSheet`. [[nodiscard]] bool MaybeDeferLoad(SheetLoadData& aLoadData, SheetState aSheetState, PendingLoad aPendingLoad, const SheetLoadDataHashKey& aKey); // Only to be called by `LoadSheet`. bool MaybeCoalesceLoadAndNotifyOpen(SheetLoadData& aLoadData, SheetState aSheetState, const SheetLoadDataHashKey& aKey, const PreloadHashKey& aPreloadKey); // Only to be called by `LoadSheet`. [[nodiscard]] nsresult LoadSheetSyncInternal(SheetLoadData& aLoadData, SheetState aSheetState); void AdjustPriority(const SheetLoadData& aLoadData, nsIChannel* aChannel); // Only to be called by `LoadSheet`. [[nodiscard]] nsresult LoadSheetAsyncInternal( SheetLoadData& aLoadData, uint64_t aEarlyHintPreloaderId, const SheetLoadDataHashKey& aKey); // Helpers to conditionally block onload if mDocument is non-null. void IncrementOngoingLoadCountAndMaybeBlockOnload() { if (!mOngoingLoadCount++) { BlockOnload(); } } void DecrementOngoingLoadCountAndMaybeUnblockOnload() { MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount); MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount > mPendingLoadCount); if (!--mOngoingLoadCount) { UnblockOnload(false); } } void BlockOnload(); void UnblockOnload(bool aFireSync); nsresult CheckContentPolicy(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, nsIURI* aTargetURI, nsINode* aRequestingNode, const nsAString& aNonce, StylePreloadKind); std::tuple, SheetState> CreateSheet( const SheetInfo& aInfo, css::SheetParsingMode aParsingMode, bool aSyncLoad, css::StylePreloadKind aPreloadKind) { nsIPrincipal* triggeringPrincipal = aInfo.mTriggeringPrincipal ? aInfo.mTriggeringPrincipal.get() : LoaderPrincipal(); return CreateSheet(aInfo.mURI, aInfo.mContent, triggeringPrincipal, aParsingMode, aInfo.mCORSMode, /* aPreloadOrParentDataEncoding = */ nullptr, aInfo.mIntegrity, aSyncLoad, aPreloadKind); } // For inline style, the aURI param is null, but the aLinkingContent // must be non-null then. The loader principal must never be null // if aURI is not null. std::tuple, SheetState> CreateSheet( nsIURI* aURI, nsIContent* aLinkingContent, nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode, CORSMode, const Encoding* aPreloadOrParentDataEncoding, const nsAString& aIntegrity, bool aSyncLoad, StylePreloadKind); // Pass in either a media string or the MediaList from the CSSParser. Don't // pass both. // // This method will set the sheet's enabled state based on IsAlternate and co. MediaMatched PrepareSheet(StyleSheet&, const nsAString& aTitle, const nsAString& aMediaString, dom::MediaList*, IsAlternate, IsExplicitlyEnabled); // Inserts a style sheet in a document or a ShadowRoot. void InsertSheetInTree(StyleSheet& aSheet); // Inserts a style sheet into a parent style sheet. void InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet); Result, nsresult> InternalLoadNonDocumentSheet( nsIURI* aURL, StylePreloadKind, SheetParsingMode aParsingMode, UseSystemPrincipal, const Encoding* aPreloadEncoding, nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver, CORSMode aCORSMode, const nsAString& aNonce, const nsAString& aIntegrity, uint64_t aEarlyHintPreloaderId, dom::FetchPriority aFetchPriority); RefPtr LookupInlineSheetInCache(const nsAString&, nsIPrincipal*); // Synchronously notify of a cached load data. void NotifyOfCachedLoad(RefPtr); // Start the loads of all the sheets in mPendingDatas void StartDeferredLoads(); // Note: LoadSheet is responsible for setting the sheet to complete on // failure. nsresult LoadSheet(SheetLoadData&, SheetState, uint64_t aEarlyHintPreloaderId, PendingLoad = PendingLoad::No); enum class AllowAsyncParse { Yes, No, }; // Parse the stylesheet in the load data. // // Returns whether the parse finished. It may not finish e.g. if the sheet had // an @import. // // If this function returns Completed::Yes, then ParseSheet also called // SheetComplete on aLoadData. Completed ParseSheet(const nsACString&, SheetLoadData&, AllowAsyncParse); // The load of the sheet in the load data is done, one way or another. // Do final cleanup. void SheetComplete(SheetLoadData&, nsresult); // Notify observers on an individual data. This is different from // SheetComplete for loads that are shared. void NotifyObservers(SheetLoadData&, nsresult); // Mark the given SheetLoadData, as well as any of its siblings, parents, etc // transitively, as failed. The idea is to mark as failed any load that was // directly or indirectly @importing the sheet this SheetLoadData represents. // // if aOnlyForLoader is non-null, then only loads for a given loader will be // marked as failing. This is useful to only cancel loads associated to a // given loader, in case they were marked as canceled. static void MarkLoadTreeFailed(SheetLoadData&, Loader* aOnlyForLoader = nullptr); // A shorthand to mark a possible link preload as used to supress "unused" // warning in the console. void MaybeNotifyPreloadUsed(SheetLoadData&); nsRefPtrHashtable mInlineSheets; // A set with all the different loads we've done in a given document, for the // purpose of not posting duplicate performance entries for them. nsTHashtable mLoadsPerformed; RefPtr mSheets; // Our array of "global" observers nsTObserverArray> mObservers; // This reference is nulled by the Document in it's destructor through // DropDocumentReference(). dom::Document* MOZ_NON_OWNING_REF mDocument; // the document we live for // For dispatching events via DocGroup::Dispatch() when mDocument is nullptr. RefPtr mDocGroup; nsCompatibility mDocumentCompatMode; nsCOMPtr mReporter; // Number of datas for asynchronous sheet loads still waiting to be notified. // This includes pending stylesheets whose load hasn't started yet but which // we need to, but not inline or constructable stylesheets, though the // constructable stylesheets bit may change, see bug 1642227. uint32_t mOngoingLoadCount = 0; // The number of sheets that have been deferred / are in a pending state. uint32_t mPendingLoadCount = 0; // The number of stylesheets that we have parsed, for testing purposes. uint32_t mParsedSheetCount = 0; bool mEnabled = true; // Whether we had a document at the point of creation. bool mIsDocumentAssociated = false; #ifdef DEBUG // Whether we're in a necko callback atm. bool mSyncCallback = false; #endif }; } // namespace css } // namespace mozilla #endif /* mozilla_css_Loader_h */