michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * 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: #ifndef imgLoader_h__ michael@0: #define imgLoader_h__ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "imgILoader.h" michael@0: #include "imgICache.h" michael@0: #include "nsWeakReference.h" michael@0: #include "nsIContentSniffer.h" michael@0: #include "nsRefPtrHashtable.h" michael@0: #include "nsExpirationTracker.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "imgRequest.h" michael@0: #include "nsIProgressEventSink.h" michael@0: #include "nsIChannel.h" michael@0: #include "mozIThirdPartyUtil.h" michael@0: #include "nsIThreadRetargetableStreamListener.h" michael@0: #include "imgIRequest.h" michael@0: michael@0: class imgLoader; michael@0: class imgRequestProxy; michael@0: class imgINotificationObserver; michael@0: class nsILoadGroup; michael@0: class imgCacheExpirationTracker; michael@0: class imgMemoryReporter; michael@0: class nsIChannelPolicy; michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: class ImageURL; michael@0: } michael@0: } michael@0: michael@0: class imgCacheEntry michael@0: { michael@0: public: michael@0: imgCacheEntry(imgLoader* loader, imgRequest *request, bool aForcePrincipalCheck); michael@0: ~imgCacheEntry(); michael@0: michael@0: nsrefcnt AddRef() michael@0: { michael@0: NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); michael@0: NS_ABORT_IF_FALSE(_mOwningThread.GetThread() == PR_GetCurrentThread(), "imgCacheEntry addref isn't thread-safe!"); michael@0: ++mRefCnt; michael@0: NS_LOG_ADDREF(this, mRefCnt, "imgCacheEntry", sizeof(*this)); michael@0: return mRefCnt; michael@0: } michael@0: michael@0: nsrefcnt Release() michael@0: { michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: NS_ABORT_IF_FALSE(_mOwningThread.GetThread() == PR_GetCurrentThread(), "imgCacheEntry release isn't thread-safe!"); michael@0: --mRefCnt; michael@0: NS_LOG_RELEASE(this, mRefCnt, "imgCacheEntry"); michael@0: if (mRefCnt == 0) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: delete this; michael@0: return 0; michael@0: } michael@0: return mRefCnt; michael@0: } michael@0: michael@0: uint32_t GetDataSize() const michael@0: { michael@0: return mDataSize; michael@0: } michael@0: void SetDataSize(uint32_t aDataSize) michael@0: { michael@0: int32_t oldsize = mDataSize; michael@0: mDataSize = aDataSize; michael@0: UpdateCache(mDataSize - oldsize); michael@0: } michael@0: michael@0: int32_t GetTouchedTime() const michael@0: { michael@0: return mTouchedTime; michael@0: } michael@0: void SetTouchedTime(int32_t time) michael@0: { michael@0: mTouchedTime = time; michael@0: Touch(/* updateTime = */ false); michael@0: } michael@0: michael@0: int32_t GetExpiryTime() const michael@0: { michael@0: return mExpiryTime; michael@0: } michael@0: void SetExpiryTime(int32_t aExpiryTime) michael@0: { michael@0: mExpiryTime = aExpiryTime; michael@0: Touch(); michael@0: } michael@0: michael@0: bool GetMustValidate() const michael@0: { michael@0: return mMustValidate; michael@0: } michael@0: void SetMustValidate(bool aValidate) michael@0: { michael@0: mMustValidate = aValidate; michael@0: Touch(); michael@0: } michael@0: michael@0: already_AddRefed GetRequest() const michael@0: { michael@0: nsRefPtr req = mRequest; michael@0: return req.forget(); michael@0: } michael@0: michael@0: bool Evicted() const michael@0: { michael@0: return mEvicted; michael@0: } michael@0: michael@0: nsExpirationState *GetExpirationState() michael@0: { michael@0: return &mExpirationState; michael@0: } michael@0: michael@0: bool HasNoProxies() const michael@0: { michael@0: return mHasNoProxies; michael@0: } michael@0: michael@0: bool ForcePrincipalCheck() const michael@0: { michael@0: return mForcePrincipalCheck; michael@0: } michael@0: michael@0: imgLoader* Loader() const michael@0: { michael@0: return mLoader; michael@0: } michael@0: michael@0: private: // methods michael@0: friend class imgLoader; michael@0: friend class imgCacheQueue; michael@0: void Touch(bool updateTime = true); michael@0: void UpdateCache(int32_t diff = 0); michael@0: void SetEvicted(bool evict) michael@0: { michael@0: mEvicted = evict; michael@0: } michael@0: void SetHasNoProxies(bool hasNoProxies); michael@0: michael@0: // Private, unimplemented copy constructor. michael@0: imgCacheEntry(const imgCacheEntry &); michael@0: michael@0: private: // data michael@0: nsAutoRefCnt mRefCnt; michael@0: NS_DECL_OWNINGTHREAD michael@0: michael@0: imgLoader* mLoader; michael@0: nsRefPtr mRequest; michael@0: uint32_t mDataSize; michael@0: int32_t mTouchedTime; michael@0: int32_t mExpiryTime; michael@0: nsExpirationState mExpirationState; michael@0: bool mMustValidate : 1; michael@0: bool mEvicted : 1; michael@0: bool mHasNoProxies : 1; michael@0: bool mForcePrincipalCheck : 1; michael@0: }; michael@0: michael@0: #include michael@0: michael@0: #define NS_IMGLOADER_CID \ michael@0: { /* 9f6a0d2e-1dd1-11b2-a5b8-951f13c846f7 */ \ michael@0: 0x9f6a0d2e, \ michael@0: 0x1dd1, \ michael@0: 0x11b2, \ michael@0: {0xa5, 0xb8, 0x95, 0x1f, 0x13, 0xc8, 0x46, 0xf7} \ michael@0: } michael@0: michael@0: class imgCacheQueue michael@0: { michael@0: public: michael@0: imgCacheQueue(); michael@0: void Remove(imgCacheEntry *); michael@0: void Push(imgCacheEntry *); michael@0: void MarkDirty(); michael@0: bool IsDirty(); michael@0: already_AddRefed Pop(); michael@0: void Refresh(); michael@0: uint32_t GetSize() const; michael@0: void UpdateSize(int32_t diff); michael@0: uint32_t GetNumElements() const; michael@0: typedef std::vector > queueContainer; michael@0: typedef queueContainer::iterator iterator; michael@0: typedef queueContainer::const_iterator const_iterator; michael@0: michael@0: iterator begin(); michael@0: const_iterator begin() const; michael@0: iterator end(); michael@0: const_iterator end() const; michael@0: michael@0: private: michael@0: queueContainer mQueue; michael@0: bool mDirty; michael@0: uint32_t mSize; michael@0: }; michael@0: michael@0: class imgLoader : public imgILoader, michael@0: public nsIContentSniffer, michael@0: public imgICache, michael@0: public nsSupportsWeakReference, michael@0: public nsIObserver michael@0: { michael@0: public: michael@0: typedef mozilla::image::ImageURL ImageURL; michael@0: typedef nsRefPtrHashtable imgCacheTable; michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_IMGILOADER michael@0: NS_DECL_NSICONTENTSNIFFER michael@0: NS_DECL_IMGICACHE michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: static imgLoader* Singleton(); michael@0: static imgLoader* PBSingleton(); michael@0: michael@0: imgLoader(); michael@0: virtual ~imgLoader(); michael@0: michael@0: nsresult Init(); michael@0: michael@0: static imgLoader* Create() michael@0: { michael@0: // Unfortunately, we rely on XPCOM module init happening michael@0: // before imgLoader creation. For now, it's easier michael@0: // to just call CallCreateInstance() which will init michael@0: // the image module instead of calling new imgLoader michael@0: // directly. michael@0: imgILoader *loader; michael@0: CallCreateInstance("@mozilla.org/image/loader;1", &loader); michael@0: // There's only one imgLoader implementation so we michael@0: // can safely cast to it. michael@0: return static_cast(loader); michael@0: } michael@0: michael@0: static already_AddRefed GetInstance(); michael@0: michael@0: nsresult LoadImage(nsIURI *aURI, michael@0: nsIURI *aInitialDocumentURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, michael@0: nsISupports *aCacheKey, michael@0: nsIChannelPolicy *aPolicy, michael@0: const nsAString& initiatorType, michael@0: imgRequestProxy **_retval); michael@0: nsresult LoadImageWithChannel(nsIChannel *channel, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsIStreamListener **listener, michael@0: imgRequestProxy **_retval); michael@0: michael@0: static nsresult GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType); michael@0: // exported for use by mimei.cpp in libxul sdk builds michael@0: static NS_EXPORT_(bool) SupportImageWithMimeType(const char* aMimeType); michael@0: michael@0: static void GlobalInit(); // for use by the factory michael@0: static void Shutdown(); // for use by the factory michael@0: michael@0: nsresult ClearChromeImageCache(); michael@0: nsresult ClearImageCache(); michael@0: void MinimizeCaches(); michael@0: michael@0: nsresult InitCache(); michael@0: michael@0: nsAutoCString GetCacheKey(nsIURI *firstPartyIsolationURI, michael@0: nsIURI* uri, michael@0: bool *isIsolated); michael@0: nsAutoCString GetCacheKey(nsIURI *firstPartyIsolationURI, michael@0: ImageURL *imgURI, michael@0: bool *isIsolated); michael@0: bool RemoveFromCache(ImageURL *aKey); michael@0: bool RemoveFromCache(nsAutoCString key, michael@0: imgCacheTable &cache, michael@0: imgCacheQueue &queue); michael@0: bool RemoveFromCache(imgCacheEntry *entry); michael@0: michael@0: bool PutIntoCache(nsAutoCString key, imgCacheEntry *entry); michael@0: michael@0: michael@0: // Returns true if we should prefer evicting cache entry |two| over cache michael@0: // entry |one|. michael@0: // This mixes units in the worst way, but provides reasonable results. michael@0: inline static bool CompareCacheEntries(const nsRefPtr &one, michael@0: const nsRefPtr &two) michael@0: { michael@0: if (!one) michael@0: return false; michael@0: if (!two) michael@0: return true; michael@0: michael@0: const double sizeweight = 1.0 - sCacheTimeWeight; michael@0: michael@0: // We want large, old images to be evicted first (depending on their michael@0: // relative weights). Since a larger time is actually newer, we subtract michael@0: // time's weight, so an older image has a larger weight. michael@0: double oneweight = double(one->GetDataSize()) * sizeweight - michael@0: double(one->GetTouchedTime()) * sCacheTimeWeight; michael@0: double twoweight = double(two->GetDataSize()) * sizeweight - michael@0: double(two->GetTouchedTime()) * sCacheTimeWeight; michael@0: michael@0: return oneweight < twoweight; michael@0: } michael@0: michael@0: void VerifyCacheSizes(); michael@0: michael@0: // The image loader maintains a hash table of all imgCacheEntries. However, michael@0: // only some of them will be evicted from the cache: those who have no michael@0: // imgRequestProxies watching their imgRequests. michael@0: // michael@0: // Once an imgRequest has no imgRequestProxies, it should notify us by michael@0: // calling HasNoObservers(), and null out its cache entry pointer. michael@0: // michael@0: // Upon having a proxy start observing again, it should notify us by calling michael@0: // HasObservers(). The request's cache entry will be re-set before this michael@0: // happens, by calling imgRequest::SetCacheEntry() when an entry with no michael@0: // observers is re-requested. michael@0: bool SetHasNoProxies(ImageURL *imgURI, imgCacheEntry *entry); michael@0: bool SetHasProxies(nsIURI *firstPartyIsolationURI, ImageURL *imgURI); michael@0: michael@0: private: // methods michael@0: michael@0: bool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aURI, michael@0: nsIURI *aFirstPartyIsolationURI, nsIURI *aReferrerURI, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, bool aCanMakeNewChannel, michael@0: imgRequestProxy **aProxyRequest, michael@0: nsIChannelPolicy *aPolicy, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: int32_t aCORSMode); michael@0: michael@0: bool ValidateRequestWithNewChannel(imgRequest *request, nsIURI *aURI, michael@0: nsIURI *aInitialDocumentURI, michael@0: nsIURI *aReferrerURI, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, nsLoadFlags aLoadFlags, michael@0: imgRequestProxy **aProxyRequest, michael@0: nsIChannelPolicy *aPolicy, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: int32_t aCORSMode); michael@0: michael@0: nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsLoadFlags aLoadFlags, imgRequestProxy **_retval); michael@0: michael@0: void ReadAcceptHeaderPref(); michael@0: michael@0: nsresult EvictEntries(imgCacheTable &aCacheToClear); michael@0: nsresult EvictEntries(imgCacheQueue &aQueueToClear); michael@0: michael@0: imgCacheTable &GetCache(nsIURI *aURI); michael@0: imgCacheQueue &GetCacheQueue(nsIURI *aURI); michael@0: imgCacheTable &GetCache(ImageURL *aURI); michael@0: imgCacheQueue &GetCacheQueue(ImageURL *aURI); michael@0: void CacheEntriesChanged(ImageURL *aURI, int32_t sizediff = 0); michael@0: void CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue); michael@0: bool RemoveMatchingUrlsFromCache(nsIURI *aImgURI); michael@0: michael@0: private: // data michael@0: friend class imgCacheEntry; michael@0: friend class imgMemoryReporter; michael@0: friend class imgRequest; michael@0: michael@0: imgCacheTable mCache; michael@0: imgCacheQueue mCacheQueue; michael@0: michael@0: imgCacheTable mChromeCache; michael@0: imgCacheQueue mChromeCacheQueue; michael@0: michael@0: static double sCacheTimeWeight; michael@0: static uint32_t sCacheMaxSize; michael@0: static imgMemoryReporter* sMemReporter; michael@0: michael@0: static nsCOMPtr sThirdPartyUtilSvc; michael@0: nsCString mAcceptHeader; michael@0: michael@0: nsAutoPtr mCacheTracker; michael@0: bool mRespectPrivacy; michael@0: }; michael@0: michael@0: michael@0: michael@0: /** michael@0: * proxy stream listener class used to handle multipart/x-mixed-replace michael@0: */ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIThreadRetargetableStreamListener.h" michael@0: michael@0: class ProxyListener : public nsIStreamListener michael@0: , public nsIThreadRetargetableStreamListener michael@0: { michael@0: public: michael@0: ProxyListener(nsIStreamListener *dest); michael@0: virtual ~ProxyListener(); michael@0: michael@0: /* additional members */ michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: michael@0: private: michael@0: nsCOMPtr mDestListener; michael@0: }; michael@0: michael@0: /** michael@0: * A class that implements nsIProgressEventSink and forwards all calls to it to michael@0: * the original notification callbacks of the channel. Also implements michael@0: * nsIInterfaceRequestor and gives out itself for nsIProgressEventSink calls, michael@0: * and forwards everything else to the channel's notification callbacks. michael@0: */ michael@0: class nsProgressNotificationProxy MOZ_FINAL michael@0: : public nsIProgressEventSink michael@0: , public nsIChannelEventSink michael@0: , public nsIInterfaceRequestor michael@0: { michael@0: public: michael@0: nsProgressNotificationProxy(nsIChannel* channel, michael@0: imgIRequest* proxy) michael@0: : mImageRequest(proxy) { michael@0: channel->GetNotificationCallbacks(getter_AddRefs(mOriginalCallbacks)); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIPROGRESSEVENTSINK michael@0: NS_DECL_NSICHANNELEVENTSINK michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: private: michael@0: ~nsProgressNotificationProxy() {} michael@0: michael@0: nsCOMPtr mOriginalCallbacks; michael@0: nsCOMPtr mImageRequest; michael@0: }; michael@0: michael@0: /** michael@0: * validate checker michael@0: */ michael@0: michael@0: #include "nsCOMArray.h" michael@0: michael@0: class imgCacheValidator : public nsIStreamListener, michael@0: public nsIThreadRetargetableStreamListener, michael@0: public nsIChannelEventSink, michael@0: public nsIInterfaceRequestor, michael@0: public nsIAsyncVerifyRedirectCallback michael@0: { michael@0: public: michael@0: imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader, michael@0: imgRequest *request, void *aContext, bool forcePrincipalCheckForCacheEntry); michael@0: virtual ~imgCacheValidator(); michael@0: michael@0: void AddProxy(imgRequestProxy *aProxy); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSICHANNELEVENTSINK michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK michael@0: michael@0: private: michael@0: nsCOMPtr mDestListener; michael@0: nsRefPtr mProgressProxy; michael@0: nsCOMPtr mRedirectCallback; michael@0: nsCOMPtr mRedirectChannel; michael@0: michael@0: nsRefPtr mRequest; michael@0: nsCOMArray mProxies; michael@0: michael@0: nsRefPtr mNewRequest; michael@0: nsRefPtr mNewEntry; michael@0: michael@0: void *mContext; michael@0: michael@0: imgLoader* mImgLoader; michael@0: }; michael@0: michael@0: #endif // imgLoader_h__