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: #include "CanvasImageCache.h" michael@0: #include "nsIImageLoadingContent.h" michael@0: #include "nsExpirationTracker.h" michael@0: #include "imgIRequest.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsTHashtable.h" michael@0: #include "mozilla/dom/HTMLCanvasElement.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace dom; michael@0: using namespace gfx; michael@0: michael@0: struct ImageCacheKey { michael@0: ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) michael@0: : mImage(aImage), mCanvas(aCanvas) {} michael@0: Element* mImage; michael@0: HTMLCanvasElement* mCanvas; michael@0: }; michael@0: michael@0: struct ImageCacheEntryData { michael@0: ImageCacheEntryData(const ImageCacheEntryData& aOther) michael@0: : mImage(aOther.mImage) michael@0: , mILC(aOther.mILC) michael@0: , mCanvas(aOther.mCanvas) michael@0: , mRequest(aOther.mRequest) michael@0: , mSourceSurface(aOther.mSourceSurface) michael@0: , mSize(aOther.mSize) michael@0: {} michael@0: ImageCacheEntryData(const ImageCacheKey& aKey) michael@0: : mImage(aKey.mImage) michael@0: , mILC(nullptr) michael@0: , mCanvas(aKey.mCanvas) michael@0: {} michael@0: michael@0: nsExpirationState* GetExpirationState() { return &mState; } michael@0: michael@0: size_t SizeInBytes() { return mSize.width * mSize.height * 4; } michael@0: michael@0: // Key michael@0: nsRefPtr mImage; michael@0: nsIImageLoadingContent* mILC; michael@0: nsRefPtr mCanvas; michael@0: // Value michael@0: nsCOMPtr mRequest; michael@0: RefPtr mSourceSurface; michael@0: gfxIntSize mSize; michael@0: nsExpirationState mState; michael@0: }; michael@0: michael@0: class ImageCacheEntry : public PLDHashEntryHdr { michael@0: public: michael@0: typedef ImageCacheKey KeyType; michael@0: typedef const ImageCacheKey* KeyTypePointer; michael@0: michael@0: ImageCacheEntry(const KeyType *key) : michael@0: mData(new ImageCacheEntryData(*key)) {} michael@0: ImageCacheEntry(const ImageCacheEntry &toCopy) : michael@0: mData(new ImageCacheEntryData(*toCopy.mData)) {} michael@0: ~ImageCacheEntry() {} michael@0: michael@0: bool KeyEquals(KeyTypePointer key) const michael@0: { michael@0: return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; michael@0: } michael@0: michael@0: static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } michael@0: static PLDHashNumber HashKey(KeyTypePointer key) michael@0: { michael@0: return HashGeneric(key->mImage, key->mCanvas); michael@0: } michael@0: enum { ALLOW_MEMMOVE = true }; michael@0: michael@0: nsAutoPtr mData; michael@0: }; michael@0: michael@0: static bool sPrefsInitialized = false; michael@0: static int32_t sCanvasImageCacheLimit = 0; michael@0: michael@0: class ImageCache MOZ_FINAL : public nsExpirationTracker { michael@0: public: michael@0: // We use 3 generations of 1 second each to get a 2-3 seconds timeout. michael@0: enum { GENERATION_MS = 1000 }; michael@0: ImageCache() michael@0: : nsExpirationTracker(GENERATION_MS) michael@0: , mTotal(0) michael@0: { michael@0: if (!sPrefsInitialized) { michael@0: sPrefsInitialized = true; michael@0: Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0); michael@0: } michael@0: } michael@0: ~ImageCache() { michael@0: AgeAllGenerations(); michael@0: } michael@0: michael@0: virtual void NotifyExpired(ImageCacheEntryData* aObject) michael@0: { michael@0: mTotal -= aObject->SizeInBytes(); michael@0: RemoveObject(aObject); michael@0: // Deleting the entry will delete aObject since the entry owns aObject michael@0: mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); michael@0: } michael@0: michael@0: nsTHashtable mCache; michael@0: size_t mTotal; michael@0: }; michael@0: michael@0: static ImageCache* gImageCache = nullptr; michael@0: michael@0: class CanvasImageCacheShutdownObserver MOZ_FINAL : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: void michael@0: CanvasImageCache::NotifyDrawImage(Element* aImage, michael@0: HTMLCanvasElement* aCanvas, michael@0: imgIRequest* aRequest, michael@0: SourceSurface* aSource, michael@0: const gfxIntSize& aSize) michael@0: { michael@0: if (!gImageCache) { michael@0: gImageCache = new ImageCache(); michael@0: nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); michael@0: } michael@0: michael@0: ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); michael@0: if (entry) { michael@0: if (entry->mData->mSourceSurface) { michael@0: // We are overwriting an existing entry. michael@0: gImageCache->mTotal -= entry->mData->SizeInBytes(); michael@0: gImageCache->RemoveObject(entry->mData); michael@0: } michael@0: gImageCache->AddObject(entry->mData); michael@0: michael@0: nsCOMPtr ilc = do_QueryInterface(aImage); michael@0: if (ilc) { michael@0: ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(entry->mData->mRequest)); michael@0: } michael@0: entry->mData->mILC = ilc; michael@0: entry->mData->mSourceSurface = aSource; michael@0: entry->mData->mSize = aSize; michael@0: michael@0: gImageCache->mTotal += entry->mData->SizeInBytes(); michael@0: } michael@0: michael@0: if (!sCanvasImageCacheLimit) michael@0: return; michael@0: michael@0: // Expire the image cache early if its larger than we want it to be. michael@0: while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit)) michael@0: gImageCache->AgeOneGeneration(); michael@0: } michael@0: michael@0: SourceSurface* michael@0: CanvasImageCache::Lookup(Element* aImage, michael@0: HTMLCanvasElement* aCanvas, michael@0: gfxIntSize* aSize) michael@0: { michael@0: if (!gImageCache) michael@0: return nullptr; michael@0: michael@0: ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); michael@0: if (!entry || !entry->mData->mILC) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr request; michael@0: entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); michael@0: if (request != entry->mData->mRequest) michael@0: return nullptr; michael@0: michael@0: gImageCache->MarkUsed(entry->mData); michael@0: michael@0: *aSize = entry->mData->mSize; michael@0: return entry->mData->mSourceSurface; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { michael@0: delete gImageCache; michael@0: gImageCache = nullptr; michael@0: michael@0: nsContentUtils::UnregisterShutdownObserver(this); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla