michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* loading of CSS style sheets using the network APIs */ michael@0: michael@0: #ifndef mozilla_css_Loader_h michael@0: #define mozilla_css_Loader_h michael@0: michael@0: #include "nsIPrincipal.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCompatibility.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsInterfaceHashtable.h" michael@0: #include "nsRefPtrHashtable.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTObserverArray.h" michael@0: #include "nsURIHashKey.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/CORSMode.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: class nsIAtom; michael@0: class nsICSSLoaderObserver; michael@0: class nsCSSStyleSheet; michael@0: class nsIContent; michael@0: class nsIDocument; michael@0: class nsCSSParser; michael@0: class nsMediaList; michael@0: class nsIStyleSheetLinkingElement; michael@0: class nsCycleCollectionTraversalCallback; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: class Element; michael@0: } michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: class URIPrincipalAndCORSModeHashKey : public nsURIHashKey michael@0: { michael@0: public: michael@0: typedef URIPrincipalAndCORSModeHashKey* KeyType; michael@0: typedef const URIPrincipalAndCORSModeHashKey* KeyTypePointer; michael@0: michael@0: URIPrincipalAndCORSModeHashKey(const URIPrincipalAndCORSModeHashKey* aKey) michael@0: : nsURIHashKey(aKey->mKey), mPrincipal(aKey->mPrincipal), michael@0: mCORSMode(aKey->mCORSMode) michael@0: { michael@0: MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey); michael@0: } michael@0: URIPrincipalAndCORSModeHashKey(nsIURI* aURI, nsIPrincipal* aPrincipal, michael@0: CORSMode aCORSMode) michael@0: : nsURIHashKey(aURI), mPrincipal(aPrincipal), mCORSMode(aCORSMode) michael@0: { michael@0: MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey); michael@0: } michael@0: URIPrincipalAndCORSModeHashKey(const URIPrincipalAndCORSModeHashKey& toCopy) michael@0: : nsURIHashKey(toCopy), mPrincipal(toCopy.mPrincipal), michael@0: mCORSMode(toCopy.mCORSMode) michael@0: { michael@0: MOZ_COUNT_CTOR(URIPrincipalAndCORSModeHashKey); michael@0: } michael@0: ~URIPrincipalAndCORSModeHashKey() michael@0: { michael@0: MOZ_COUNT_DTOR(URIPrincipalAndCORSModeHashKey); michael@0: } michael@0: michael@0: URIPrincipalAndCORSModeHashKey* GetKey() const { michael@0: return const_cast(this); michael@0: } michael@0: const URIPrincipalAndCORSModeHashKey* GetKeyPointer() const { return this; } michael@0: michael@0: bool KeyEquals(const URIPrincipalAndCORSModeHashKey* aKey) const { michael@0: if (!nsURIHashKey::KeyEquals(aKey->mKey)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mPrincipal != !aKey->mPrincipal) { michael@0: // One or the other has a principal, but not both... not equal michael@0: return false; michael@0: } michael@0: michael@0: if (mCORSMode != aKey->mCORSMode) { michael@0: // Different CORS modes; we don't match michael@0: return false; michael@0: } michael@0: michael@0: bool eq; michael@0: return !mPrincipal || michael@0: (NS_SUCCEEDED(mPrincipal->Equals(aKey->mPrincipal, &eq)) && eq); michael@0: } michael@0: michael@0: static const URIPrincipalAndCORSModeHashKey* michael@0: KeyToPointer(URIPrincipalAndCORSModeHashKey* aKey) { return aKey; } michael@0: static PLDHashNumber HashKey(const URIPrincipalAndCORSModeHashKey* aKey) { michael@0: return nsURIHashKey::HashKey(aKey->mKey); michael@0: } michael@0: michael@0: nsIURI* GetURI() const { return nsURIHashKey::GetKey(); } michael@0: michael@0: enum { ALLOW_MEMMOVE = true }; michael@0: michael@0: protected: michael@0: nsCOMPtr mPrincipal; michael@0: CORSMode mCORSMode; michael@0: }; michael@0: michael@0: michael@0: michael@0: namespace css { michael@0: michael@0: class SheetLoadData; michael@0: class ImportRule; michael@0: michael@0: /*********************************************************************** michael@0: * Enum that describes the state of the sheet returned by CreateSheet. * michael@0: ***********************************************************************/ michael@0: enum StyleSheetState { michael@0: eSheetStateUnknown = 0, michael@0: eSheetNeedsParser, michael@0: eSheetPending, michael@0: eSheetLoading, michael@0: eSheetComplete michael@0: }; michael@0: michael@0: class Loader MOZ_FINAL { michael@0: public: michael@0: Loader(); michael@0: Loader(nsIDocument*); michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~Loader(); michael@0: michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(Loader) michael@0: michael@0: void DropDocumentReference(); // notification that doc is going away michael@0: michael@0: void SetCompatibilityMode(nsCompatibility aCompatMode) michael@0: { mCompatMode = aCompatMode; } michael@0: nsCompatibility GetCompatibilityMode() { return mCompatMode; } michael@0: nsresult SetPreferredSheet(const nsAString& aTitle); michael@0: michael@0: // XXXbz sort out what the deal is with events! When should they fire? michael@0: michael@0: /** michael@0: * Load an inline style sheet. If a successful result is returned and michael@0: * *aCompleted is false, then aObserver is guaranteed to be notified michael@0: * asynchronously once the sheet is marked complete. If an error is michael@0: * returned, or if *aCompleted is true, aObserver will not be notified. In michael@0: * addition to parsing the sheet, this method will insert it into the michael@0: * stylesheet list of this CSSLoader's document. michael@0: * michael@0: * @param aElement the element linking to the stylesheet. This must not be michael@0: * null and must implement nsIStyleSheetLinkingElement. michael@0: * @param aBuffer the stylesheet data michael@0: * @param aLineNumber the line number at which the stylesheet data started. michael@0: * @param aTitle the title of the sheet. michael@0: * @param aMedia the media string for the sheet. michael@0: * @param aObserver the observer to notify when the load completes. michael@0: * May be null. michael@0: * @param [out] aCompleted whether parsing of the sheet completed. michael@0: * @param [out] aIsAlternate whether the stylesheet ended up being an michael@0: * alternate sheet. michael@0: */ michael@0: nsresult LoadInlineStyle(nsIContent* aElement, michael@0: const nsAString& aBuffer, michael@0: uint32_t aLineNumber, michael@0: const nsAString& aTitle, michael@0: const nsAString& aMedia, michael@0: mozilla::dom::Element* aScopeElement, michael@0: nsICSSLoaderObserver* aObserver, michael@0: bool* aCompleted, michael@0: bool* aIsAlternate); michael@0: michael@0: /** michael@0: * Load a linked (document) stylesheet. If a successful result is returned, michael@0: * aObserver is guaranteed to be notified asynchronously once the sheet is michael@0: * loaded and marked complete. If an error is returned, aObserver will not michael@0: * be notified. In addition to loading the sheet, this method will insert it michael@0: * into the stylesheet list of this CSSLoader's document. michael@0: * michael@0: * @param aElement the element linking to the the stylesheet. May be null. michael@0: * @param aURL the URL of the sheet. michael@0: * @param aTitle the title of the sheet. michael@0: * @param aMedia the media string for the sheet. michael@0: * @param aHasAlternateRel whether the rel for this link included michael@0: * "alternate". michael@0: * @param aCORSMode the CORS mode for this load. michael@0: * @param aObserver the observer to notify when the load completes. michael@0: * May be null. michael@0: * @param [out] aIsAlternate whether the stylesheet actually ended up beinga michael@0: * an alternate sheet. Note that this need not match michael@0: * aHasAlternateRel. michael@0: */ michael@0: nsresult LoadStyleLink(nsIContent* aElement, michael@0: nsIURI* aURL, michael@0: const nsAString& aTitle, michael@0: const nsAString& aMedia, michael@0: bool aHasAlternateRel, michael@0: CORSMode aCORSMode, michael@0: nsICSSLoaderObserver* aObserver, michael@0: bool* aIsAlternate); michael@0: michael@0: /** michael@0: * Load a child (@import-ed) style sheet. In addition to loading the sheet, michael@0: * this method will insert it into the child sheet list of aParentSheet. If michael@0: * there is no sheet currently being parsed and the child sheet is not michael@0: * complete when this method returns, then when the child sheet becomes michael@0: * complete aParentSheet will be QIed to nsICSSLoaderObserver and michael@0: * asynchronously notified, just like for LoadStyleLink. Note that if the michael@0: * child sheet is already complete when this method returns, no michael@0: * nsICSSLoaderObserver notification will be sent. michael@0: * michael@0: * @param aParentSheet the parent of this child sheet michael@0: * @param aURL the URL of the child sheet michael@0: * @param aMedia the already-parsed media list for the child sheet michael@0: * @param aRule the @import rule importing this child. This is used to michael@0: * properly order the child sheet list of aParentSheet. michael@0: */ michael@0: nsresult LoadChildSheet(nsCSSStyleSheet* aParentSheet, michael@0: nsIURI* aURL, michael@0: nsMediaList* aMedia, michael@0: ImportRule* aRule); michael@0: michael@0: /** michael@0: * Synchronously load and return the stylesheet at aURL. Any child sheets michael@0: * will also be loaded synchronously. Note that synchronous loads over some michael@0: * protocols may involve spinning up a new event loop, so use of this method michael@0: * does NOT guarantee not receiving any events before the sheet loads. This michael@0: * method can be used to load sheets not associated with a document. michael@0: * michael@0: * @param aURL the URL of the sheet to load michael@0: * @param aEnableUnsafeRules whether unsafe rules are enabled for this michael@0: * sheet load michael@0: * Unsafe rules are rules that can violate key Gecko invariants if misused. michael@0: * In particular, most anonymous box pseudoelements must be very carefully michael@0: * styled or we will have severe problems. Therefore unsafe rules should michael@0: * never be enabled for stylesheets controlled by untrusted sites; preferably michael@0: * unsafe rules should only be enabled for agent sheets. michael@0: * @param aUseSystemPrincipal if true, give the resulting sheet the system michael@0: * principal no matter where it's being loaded from. michael@0: * @param [out] aSheet the loaded, complete sheet. michael@0: * michael@0: * NOTE: At the moment, this method assumes the sheet will be UTF-8, but michael@0: * ideally it would allow arbitrary encodings. Callers should NOT depend on michael@0: * non-UTF8 sheets being treated as UTF-8 by this method. michael@0: * michael@0: * NOTE: A successful return from this method doesn't indicate anything about michael@0: * whether the data could be parsed as CSS and doesn't indicate anything michael@0: * about the status of child sheets of the returned sheet. michael@0: */ michael@0: nsresult LoadSheetSync(nsIURI* aURL, bool aEnableUnsafeRules, michael@0: bool aUseSystemPrincipal, michael@0: nsCSSStyleSheet** aSheet); michael@0: michael@0: /** michael@0: * As above, but aUseSystemPrincipal and aEnableUnsafeRules are assumed false. michael@0: */ michael@0: nsresult LoadSheetSync(nsIURI* aURL, nsCSSStyleSheet** aSheet) { michael@0: return LoadSheetSync(aURL, false, false, aSheet); michael@0: } michael@0: michael@0: /** michael@0: * Asynchronously load the stylesheet at aURL. If a successful result is michael@0: * returned, aObserver is guaranteed to be notified asynchronously once the michael@0: * sheet is loaded and marked complete. This method can be used to load michael@0: * sheets not associated with a document. michael@0: * michael@0: * @param aURL the URL of the sheet to load michael@0: * @param aOriginPrincipal the principal to use for security checks. This michael@0: * can be null to indicate that these checks should michael@0: * be skipped. michael@0: * @param aCharset the encoding to use for converting the sheet data michael@0: * from bytes to Unicode. May be empty to indicate that the michael@0: * charset of the CSSLoader's document should be used. This michael@0: * is only used if neither the network transport nor the michael@0: * sheet itself indicate an encoding. michael@0: * @param aObserver the observer to notify when the load completes. michael@0: * Must not be null. michael@0: * @param [out] aSheet the sheet to load. Note that the sheet may well michael@0: * not be loaded by the time this method returns. michael@0: */ michael@0: nsresult LoadSheet(nsIURI* aURL, michael@0: nsIPrincipal* aOriginPrincipal, michael@0: const nsCString& aCharset, michael@0: nsICSSLoaderObserver* aObserver, michael@0: nsCSSStyleSheet** aSheet); michael@0: michael@0: /** michael@0: * Same as above, to be used when the caller doesn't care about the michael@0: * not-yet-loaded sheet. michael@0: */ michael@0: nsresult LoadSheet(nsIURI* aURL, michael@0: nsIPrincipal* aOriginPrincipal, michael@0: const nsCString& aCharset, michael@0: nsICSSLoaderObserver* aObserver, michael@0: CORSMode aCORSMode = CORS_NONE); michael@0: michael@0: /** michael@0: * Stop loading all sheets. All nsICSSLoaderObservers involved will be michael@0: * notified with NS_BINDING_ABORTED as the status, possibly synchronously. michael@0: */ michael@0: nsresult Stop(void); michael@0: michael@0: /** michael@0: * nsresult Loader::StopLoadingSheet(nsIURI* aURL), which notifies the michael@0: * nsICSSLoaderObserver with NS_BINDING_ABORTED, was removed in Bug 556446. michael@0: * It can be found in revision 2c44a32052ad. michael@0: */ michael@0: michael@0: /** michael@0: * Whether the loader is enabled or not. michael@0: * When disabled, processing of new styles is disabled and an attempt michael@0: * to do so will fail with a return code of michael@0: * NS_ERROR_NOT_AVAILABLE. Note that this DOES NOT disable michael@0: * currently loading styles or already processed styles. michael@0: */ michael@0: bool GetEnabled() { return mEnabled; } michael@0: void SetEnabled(bool aEnabled) { mEnabled = aEnabled; } michael@0: michael@0: /** michael@0: * Get the document we live for. May return null. michael@0: */ michael@0: nsIDocument* GetDocument() const { return mDocument; } michael@0: michael@0: /** michael@0: * Return true if this loader has pending loads (ones that would send michael@0: * notifications to an nsICSSLoaderObserver attached to this loader). michael@0: * If called from inside nsICSSLoaderObserver::StyleSheetLoaded, this will michael@0: * return false if and only if that is the last StyleSheetLoaded michael@0: * notification the CSSLoader knows it's going to send. In other words, if michael@0: * two sheets load at once (via load coalescing, e.g.), HasPendingLoads() michael@0: * will return true during notification for the first one, and false michael@0: * during notification for the second one. michael@0: */ michael@0: bool HasPendingLoads(); michael@0: michael@0: /** michael@0: * Add an observer to this loader. The observer will be notified michael@0: * for all loads that would have notified their own observers (even michael@0: * if those loads don't have observers attached to them). michael@0: * Load-specific observers will be notified before generic michael@0: * observers. The loader holds a reference to the observer. michael@0: * michael@0: * aObserver must not be null. michael@0: */ michael@0: nsresult AddObserver(nsICSSLoaderObserver* aObserver); michael@0: michael@0: /** michael@0: * Remove an observer added via AddObserver. michael@0: */ michael@0: void RemoveObserver(nsICSSLoaderObserver* aObserver); michael@0: michael@0: // These interfaces are public only for the benefit of static functions michael@0: // within nsCSSLoader.cpp. michael@0: michael@0: // IsAlternate can change our currently selected style set if none michael@0: // is selected and aHasAlternateRel is false. michael@0: bool IsAlternate(const nsAString& aTitle, bool aHasAlternateRel); michael@0: michael@0: typedef nsTArray > LoadDataArray; michael@0: michael@0: // Traverse the cached stylesheets we're holding on to. This should michael@0: // only be called from the document that owns this loader. michael@0: void TraverseCachedSheets(nsCycleCollectionTraversalCallback& cb); michael@0: michael@0: // Unlink the cached stylesheets we're holding on to. Again, this michael@0: // should only be called from the document that owns this loader. michael@0: void UnlinkCachedSheets(); michael@0: michael@0: // Measure our size. michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: // Marks all the sheets at the given URI obsolete, and removes them from the michael@0: // cache. michael@0: nsresult ObsoleteSheet(nsIURI* aURI); michael@0: michael@0: private: michael@0: friend class SheetLoadData; michael@0: michael@0: static PLDHashOperator michael@0: RemoveEntriesWithURI(URIPrincipalAndCORSModeHashKey* aKey, michael@0: nsRefPtr &aSheet, michael@0: void* aUserData); michael@0: michael@0: // Note: null aSourcePrincipal indicates that the content policy and michael@0: // CheckLoadURI checks should be skipped. michael@0: nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal, michael@0: nsIURI* aTargetURI, michael@0: nsISupports* aContext); michael@0: michael@0: michael@0: // For inline style, the aURI param is null, but the aLinkingContent michael@0: // must be non-null then. The loader principal must never be null michael@0: // if aURI is not null. michael@0: // *aIsAlternate is set based on aTitle and aHasAlternateRel. michael@0: nsresult CreateSheet(nsIURI* aURI, michael@0: nsIContent* aLinkingContent, michael@0: nsIPrincipal* aLoaderPrincipal, michael@0: CORSMode aCORSMode, michael@0: bool aSyncLoad, michael@0: bool aHasAlternateRel, michael@0: const nsAString& aTitle, michael@0: StyleSheetState& aSheetState, michael@0: bool *aIsAlternate, michael@0: nsCSSStyleSheet** aSheet); michael@0: michael@0: // Pass in either a media string or the nsMediaList from the michael@0: // CSSParser. Don't pass both. michael@0: // This method will set the sheet's enabled state based on isAlternate michael@0: void PrepareSheet(nsCSSStyleSheet* aSheet, michael@0: const nsAString& aTitle, michael@0: const nsAString& aMediaString, michael@0: nsMediaList* aMediaList, michael@0: dom::Element* aScopeElement, michael@0: bool isAlternate); michael@0: michael@0: nsresult InsertSheetInDoc(nsCSSStyleSheet* aSheet, michael@0: nsIContent* aLinkingContent, michael@0: nsIDocument* aDocument); michael@0: michael@0: nsresult InsertChildSheet(nsCSSStyleSheet* aSheet, michael@0: nsCSSStyleSheet* aParentSheet, michael@0: ImportRule* aParentRule); michael@0: michael@0: nsresult InternalLoadNonDocumentSheet(nsIURI* aURL, michael@0: bool aAllowUnsafeRules, michael@0: bool aUseSystemPrincipal, michael@0: nsIPrincipal* aOriginPrincipal, michael@0: const nsCString& aCharset, michael@0: nsCSSStyleSheet** aSheet, michael@0: nsICSSLoaderObserver* aObserver, michael@0: CORSMode aCORSMode = CORS_NONE); michael@0: michael@0: // Post a load event for aObserver to be notified about aSheet. The michael@0: // notification will be sent with status NS_OK unless the load event is michael@0: // canceled at some point (in which case it will be sent with michael@0: // NS_BINDING_ABORTED). aWasAlternate indicates the state when the load was michael@0: // initiated, not the state at some later time. aURI should be the URI the michael@0: // sheet was loaded from (may be null for inline sheets). aElement is the michael@0: // owning element for this sheet. michael@0: nsresult PostLoadEvent(nsIURI* aURI, michael@0: nsCSSStyleSheet* aSheet, michael@0: nsICSSLoaderObserver* aObserver, michael@0: bool aWasAlternate, michael@0: nsIStyleSheetLinkingElement* aElement); michael@0: michael@0: // Start the loads of all the sheets in mPendingDatas michael@0: void StartAlternateLoads(); michael@0: michael@0: // Handle an event posted by PostLoadEvent michael@0: void HandleLoadEvent(SheetLoadData* aEvent); michael@0: michael@0: // Note: LoadSheet is responsible for releasing aLoadData and setting the michael@0: // sheet to complete on failure. michael@0: nsresult LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState); michael@0: michael@0: // Parse the stylesheet in aLoadData. The sheet data comes from aInput. michael@0: // Set aCompleted to true if the parse finished, false otherwise (e.g. if the michael@0: // sheet had an @import). If aCompleted is true when this returns, then michael@0: // ParseSheet also called SheetComplete on aLoadData. michael@0: nsresult ParseSheet(const nsAString& aInput, michael@0: SheetLoadData* aLoadData, michael@0: bool& aCompleted); michael@0: michael@0: // The load of the sheet in aLoadData is done, one way or another. Do final michael@0: // cleanup, including releasing aLoadData. michael@0: void SheetComplete(SheetLoadData* aLoadData, nsresult aStatus); michael@0: michael@0: // The guts of SheetComplete. This may be called recursively on parent datas michael@0: // or datas that had glommed on to a single load. The array is there so load michael@0: // datas whose observers need to be notified can be added to it. michael@0: void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus, michael@0: LoadDataArray& aDatasToNotify); michael@0: michael@0: struct Sheets { michael@0: nsRefPtrHashtable michael@0: mCompleteSheets; michael@0: nsDataHashtable michael@0: mLoadingDatas; // weak refs michael@0: nsDataHashtable michael@0: mPendingDatas; // weak refs michael@0: }; michael@0: nsAutoPtr mSheets; michael@0: michael@0: // We're not likely to have many levels of @import... But likely to have michael@0: // some. Allocate some storage, what the hell. michael@0: nsAutoTArray mParsingDatas; michael@0: michael@0: // The array of posted stylesheet loaded events (SheetLoadDatas) we have. michael@0: // Note that these are rare. michael@0: LoadDataArray mPostedEvents; michael@0: michael@0: // Our array of "global" observers michael@0: // XXXbz these are strong refs; should we be cycle collecting CSS loaders? michael@0: nsTObserverArray > mObservers; michael@0: michael@0: // the load data needs access to the document... michael@0: nsIDocument* mDocument; // the document we live for michael@0: michael@0: michael@0: // Number of datas still waiting to be notified on if we're notifying on a michael@0: // whole bunch at once (e.g. in one of the stop methods). This is used to michael@0: // make sure that HasPendingLoads() won't return false until we're notifying michael@0: // on the last data we're working with. michael@0: uint32_t mDatasToNotifyOn; michael@0: michael@0: nsCompatibility mCompatMode; michael@0: nsString mPreferredSheet; // title of preferred sheet michael@0: michael@0: bool mEnabled; // is enabled to load new styles michael@0: michael@0: #ifdef DEBUG michael@0: bool mSyncCallback; michael@0: #endif michael@0: }; michael@0: michael@0: } // namespace css michael@0: } // namespace mozilla michael@0: michael@0: #endif /* mozilla_css_Loader_h */