diff options
Diffstat (limited to 'layout/style/Loader.h')
-rw-r--r-- | layout/style/Loader.h | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/layout/style/Loader.h b/layout/style/Loader.h new file mode 100644 index 0000000000..a9f5647ea9 --- /dev/null +++ b/layout/style/Loader.h @@ -0,0 +1,631 @@ +/* -*- 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 <tuple> +#include <utility> + +#include "mozilla/Attributes.h" +#include "mozilla/CORSMode.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; +} // namespace dom + +// The load data for a <link> or @import style-sheet. +// +// This must contain all the state that affects CSS parsing. +class SheetLoadDataHashKey : public PLDHashEntryHdr { + public: + enum class IsPreload : uint8_t { + No, + // This is a speculative load initiated by a <link rel=stylesheet> tag + // scanned by the parser, or @import rules found in a <style> tag. + FromParser, + // This is a speculative load as well, but initiated by + // <link rel="preload" as="style"> + FromLink, + }; + + 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), + mIsLinkPreload(aKey->mIsLinkPreload) { + MOZ_COUNT_CTOR(SheetLoadDataHashKey); + } + + SheetLoadDataHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal, + nsIPrincipal* aLoaderPrincipal, + nsIPrincipal* aPartitionPrincipal, + NotNull<const Encoding*> aEncodingGuess, + CORSMode aCORSMode, css::SheetParsingMode aParsingMode, + nsCompatibility aCompatMode, + const dom::SRIMetadata& aSRIMetadata, + IsPreload aIsPreload) + : mURI(aURI), + mPrincipal(aPrincipal), + mLoaderPrincipal(aLoaderPrincipal), + mPartitionPrincipal(aPartitionPrincipal), + mEncodingGuess(aEncodingGuess), + mCORSMode(aCORSMode), + mParsingMode(aParsingMode), + mCompatMode(aCompatMode), + mSRIMetadata(aSRIMetadata), + mIsLinkPreload(aIsPreload == IsPreload::FromLink) { + 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)), + mIsLinkPreload(std::move(toMove.mIsLinkPreload)) { + 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; } + + css::SheetParsingMode ParsingMode() const { return mParsingMode; } + + enum { ALLOW_MEMMOVE = true }; + + protected: + const nsCOMPtr<nsIURI> mURI; + const nsCOMPtr<nsIPrincipal> mPrincipal; + const nsCOMPtr<nsIPrincipal> mLoaderPrincipal; + const nsCOMPtr<nsIPrincipal> 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<const Encoding*> mEncodingGuess; + const CORSMode mCORSMode; + const css::SheetParsingMode mParsingMode; + const nsCompatibility mCompatMode; + dom::SRIMetadata mSRIMetadata; + const bool mIsLinkPreload; +}; + +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<StyleSheet>& 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<RefPtr<StyleSheet>> 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) { + mCompatMode = aCompatMode; + } + nsCompatibility GetCompatibilityMode() { return mCompatMode; } + + // 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 + * @param aLineNumber the line number at which the stylesheet data started. + */ + Result<LoadSheetResult, nsresult> LoadInlineStyle( + const SheetInfo&, const nsAString& aBuffer, uint32_t aLineNumber, + 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<LoadSheetResult, nsresult> 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<RefPtr<StyleSheet>, nsresult> LoadSheetSync( + nsIURI*, SheetParsingMode = eAuthorSheetFeatures, + UseSystemPrincipal = UseSystemPrincipal::No); + + using IsPreload = SheetLoadDataHashKey::IsPreload; + + /** + * 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. + * @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<RefPtr<StyleSheet>, nsresult> LoadSheet( + nsIURI* aURI, IsPreload, const Encoding* aPreloadEncoding, + nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver, + CORSMode = CORS_NONE, const nsAString& aIntegrity = u""_ns); + + /** + * As above, but without caring for a couple things. + */ + Result<RefPtr<StyleSheet>, 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; } + + /** + * 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); + + typedef nsTArray<RefPtr<SheetLoadData>> LoadDataArray; + + // 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; + + private: + friend class mozilla::SharedStyleSheetCache; + friend class SheetLoadData; + friend class StreamLoader; + + // Helpers to conditionally block onload if mDocument is non-null. + void IncrementOngoingLoadCount() { + if (!mOngoingLoadCount++) { + BlockOnload(); + } + } + + void DecrementOngoingLoadCount() { + MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount); + MOZ_DIAGNOSTIC_ASSERT(mOngoingLoadCount > mPendingLoadCount); + if (!--mOngoingLoadCount) { + UnblockOnload(false); + } + } + + void BlockOnload(); + void UnblockOnload(bool aFireSync); + + // Helper to select the correct dispatch target for asynchronous events for + // this loader. + already_AddRefed<nsISerialEventTarget> DispatchTarget(); + + nsresult CheckContentPolicy(nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + nsIURI* aTargetURI, nsINode* aRequestingNode, + const nsAString& aNonce, IsPreload); + + std::tuple<RefPtr<StyleSheet>, SheetState> CreateSheet( + const SheetInfo& aInfo, css::SheetParsingMode aParsingMode, + bool aSyncLoad, IsPreload aIsPreload) { + nsIPrincipal* triggeringPrincipal = aInfo.mTriggeringPrincipal + ? aInfo.mTriggeringPrincipal.get() + : LoaderPrincipal(); + return CreateSheet(aInfo.mURI, aInfo.mContent, triggeringPrincipal, + aParsingMode, aInfo.mCORSMode, + /* aPreloadOrParentDataEncoding = */ nullptr, + aInfo.mIntegrity, aSyncLoad, aIsPreload); + } + + // 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<RefPtr<StyleSheet>, SheetState> CreateSheet( + nsIURI* aURI, nsIContent* aLinkingContent, + nsIPrincipal* aTriggeringPrincipal, css::SheetParsingMode, CORSMode, + const Encoding* aPreloadOrParentDataEncoding, const nsAString& aIntegrity, + bool aSyncLoad, IsPreload aIsPreload); + + // 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, nsINode* aOwningNode); + // Inserts a style sheet into a parent style sheet. + void InsertChildSheet(StyleSheet& aSheet, StyleSheet& aParentSheet); + + Result<RefPtr<StyleSheet>, nsresult> InternalLoadNonDocumentSheet( + nsIURI* aURL, IsPreload, SheetParsingMode aParsingMode, + UseSystemPrincipal, const Encoding* aPreloadEncoding, + nsIReferrerInfo* aReferrerInfo, nsICSSLoaderObserver* aObserver, + CORSMode aCORSMode, const nsAString& aIntegrity); + + RefPtr<StyleSheet> LookupInlineSheetInCache(const nsAString&); + + // Post a load event for aObserver to be notified about aSheet. The + // notification will be sent with status NS_OK unless the load event is + // canceled at some point (in which case it will be sent with + // NS_BINDING_ABORTED). + nsresult PostLoadEvent(RefPtr<SheetLoadData>); + + // Start the loads of all the sheets in mPendingDatas + void StartDeferredLoads(); + + void HandleLoadEvent(SheetLoadData&); + + // Note: LoadSheet is responsible for setting the sheet to complete on + // failure. + enum class PendingLoad { No, Yes }; + nsresult LoadSheet(SheetLoadData&, SheetState, 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<nsStringHashKey, StyleSheet> 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<const SheetLoadDataHashKey> mLoadsPerformed; + + RefPtr<SharedStyleSheetCache> mSheets; + + // The array of posted stylesheet loaded events (SheetLoadDatas) we have. + // Note that these are rare. + LoadDataArray mPostedEvents; + + // Our array of "global" observers + nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver>> 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<dom::DocGroup> mDocGroup; + + nsCompatibility mCompatMode; + + nsCOMPtr<nsIConsoleReportCollector> 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; + +#ifdef DEBUG + // Whether we're in a necko callback atm. + bool mSyncCallback = false; +#endif +}; + +} // namespace css +} // namespace mozilla + +#endif /* mozilla_css_Loader_h */ |