diff options
Diffstat (limited to 'dom/canvas/CanvasImageCache.cpp')
-rw-r--r-- | dom/canvas/CanvasImageCache.cpp | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/dom/canvas/CanvasImageCache.cpp b/dom/canvas/CanvasImageCache.cpp new file mode 100644 index 000000000..82b3f277c --- /dev/null +++ b/dom/canvas/CanvasImageCache.cpp @@ -0,0 +1,419 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#include "CanvasImageCache.h" +#include "nsAutoPtr.h" +#include "nsIImageLoadingContent.h" +#include "nsExpirationTracker.h" +#include "imgIRequest.h" +#include "mozilla/dom/Element.h" +#include "nsTHashtable.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "nsContentUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/gfx/2D.h" +#include "gfx2DGlue.h" + +namespace mozilla { + +using namespace dom; +using namespace gfx; + +/** + * Used for images specific to this one canvas. Required + * due to CORS security. + */ +struct ImageCacheKey +{ + ImageCacheKey(imgIContainer* aImage, + HTMLCanvasElement* aCanvas, + bool aIsAccelerated) + : mImage(aImage) + , mCanvas(aCanvas) + , mIsAccelerated(aIsAccelerated) + {} + nsCOMPtr<imgIContainer> mImage; + HTMLCanvasElement* mCanvas; + bool mIsAccelerated; +}; + +/** + * Cache data needs to be separate from the entry + * for nsExpirationTracker. + */ +struct ImageCacheEntryData +{ + ImageCacheEntryData(const ImageCacheEntryData& aOther) + : mImage(aOther.mImage) + , mCanvas(aOther.mCanvas) + , mIsAccelerated(aOther.mIsAccelerated) + , mSourceSurface(aOther.mSourceSurface) + , mSize(aOther.mSize) + {} + explicit ImageCacheEntryData(const ImageCacheKey& aKey) + : mImage(aKey.mImage) + , mCanvas(aKey.mCanvas) + , mIsAccelerated(aKey.mIsAccelerated) + {} + + nsExpirationState* GetExpirationState() { return &mState; } + size_t SizeInBytes() { return mSize.width * mSize.height * 4; } + + // Key + nsCOMPtr<imgIContainer> mImage; + HTMLCanvasElement* mCanvas; + bool mIsAccelerated; + // Value + RefPtr<SourceSurface> mSourceSurface; + IntSize mSize; + nsExpirationState mState; +}; + +class ImageCacheEntry : public PLDHashEntryHdr +{ +public: + typedef ImageCacheKey KeyType; + typedef const ImageCacheKey* KeyTypePointer; + + explicit ImageCacheEntry(const KeyType* aKey) : + mData(new ImageCacheEntryData(*aKey)) {} + ImageCacheEntry(const ImageCacheEntry& toCopy) : + mData(new ImageCacheEntryData(*toCopy.mData)) {} + ~ImageCacheEntry() {} + + bool KeyEquals(KeyTypePointer key) const + { + return mData->mImage == key->mImage && + mData->mCanvas == key->mCanvas && + mData->mIsAccelerated == key->mIsAccelerated; + } + + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } + static PLDHashNumber HashKey(KeyTypePointer key) + { + return HashGeneric(key->mImage.get(), key->mCanvas, key->mIsAccelerated); + } + enum { ALLOW_MEMMOVE = true }; + + nsAutoPtr<ImageCacheEntryData> mData; +}; + + +/** + * Used for all images across all canvases. + */ +struct AllCanvasImageCacheKey +{ + AllCanvasImageCacheKey(imgIContainer* aImage, + bool aIsAccelerated) + : mImage(aImage) + , mIsAccelerated(aIsAccelerated) + {} + + nsCOMPtr<imgIContainer> mImage; + bool mIsAccelerated; +}; + +class AllCanvasImageCacheEntry : public PLDHashEntryHdr { +public: + typedef AllCanvasImageCacheKey KeyType; + typedef const AllCanvasImageCacheKey* KeyTypePointer; + + explicit AllCanvasImageCacheEntry(const KeyType* aKey) + : mImage(aKey->mImage) + , mIsAccelerated(aKey->mIsAccelerated) + {} + + AllCanvasImageCacheEntry(const AllCanvasImageCacheEntry &toCopy) + : mImage(toCopy.mImage) + , mIsAccelerated(toCopy.mIsAccelerated) + , mSourceSurface(toCopy.mSourceSurface) + {} + + ~AllCanvasImageCacheEntry() {} + + bool KeyEquals(KeyTypePointer key) const + { + return mImage == key->mImage && + mIsAccelerated == key->mIsAccelerated; + } + + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } + static PLDHashNumber HashKey(KeyTypePointer key) + { + return HashGeneric(key->mImage.get(), key->mIsAccelerated); + } + enum { ALLOW_MEMMOVE = true }; + + nsCOMPtr<imgIContainer> mImage; + bool mIsAccelerated; + RefPtr<SourceSurface> mSourceSurface; +}; + +static bool sPrefsInitialized = false; +static int32_t sCanvasImageCacheLimit = 0; + +class ImageCacheObserver; + +class ImageCache final : public nsExpirationTracker<ImageCacheEntryData,4> +{ +public: + // We use 3 generations of 1 second each to get a 2-3 seconds timeout. + enum { GENERATION_MS = 1000 }; + ImageCache(); + ~ImageCache(); + + virtual void NotifyExpired(ImageCacheEntryData* aObject) + { + mTotal -= aObject->SizeInBytes(); + RemoveObject(aObject); + + // Remove from the all canvas cache entry first since nsExpirationTracker + // will delete aObject. + mAllCanvasCache.RemoveEntry(AllCanvasImageCacheKey(aObject->mImage, aObject->mIsAccelerated)); + + // Deleting the entry will delete aObject since the entry owns aObject. + mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas, aObject->mIsAccelerated)); + } + + nsTHashtable<ImageCacheEntry> mCache; + nsTHashtable<AllCanvasImageCacheEntry> mAllCanvasCache; + size_t mTotal; + RefPtr<ImageCacheObserver> mImageCacheObserver; +}; + +static ImageCache* gImageCache = nullptr; + +// Listen memory-pressure event for image cache purge. +class ImageCacheObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + + explicit ImageCacheObserver(ImageCache* aImageCache) + : mImageCache(aImageCache) + { + RegisterMemoryPressureEvent(); + } + + void Destroy() + { + UnregisterMemoryPressureEvent(); + mImageCache = nullptr; + } + + NS_IMETHOD Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) override + { + if (!mImageCache || strcmp(aTopic, "memory-pressure")) { + return NS_OK; + } + + mImageCache->AgeAllGenerations(); + return NS_OK; + } + +private: + virtual ~ImageCacheObserver() + { + } + + void RegisterMemoryPressureEvent() + { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + MOZ_ASSERT(observerService); + + if (observerService) { + observerService->AddObserver(this, "memory-pressure", false); + } + } + + void UnregisterMemoryPressureEvent() + { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + // Do not assert on observerService here. This might be triggered by + // the cycle collector at a late enough time, that XPCOM services are + // no longer available. See bug 1029504. + if (observerService) { + observerService->RemoveObserver(this, "memory-pressure"); + } + } + + ImageCache* mImageCache; +}; + +NS_IMPL_ISUPPORTS(ImageCacheObserver, nsIObserver) + +class CanvasImageCacheShutdownObserver final : public nsIObserver +{ + ~CanvasImageCacheShutdownObserver() {} +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +ImageCache::ImageCache() + : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS, "ImageCache") + , mTotal(0) +{ + if (!sPrefsInitialized) { + sPrefsInitialized = true; + Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0); + } + mImageCacheObserver = new ImageCacheObserver(this); + MOZ_RELEASE_ASSERT(mImageCacheObserver, "GFX: Can't alloc ImageCacheObserver"); +} + +ImageCache::~ImageCache() { + AgeAllGenerations(); + mImageCacheObserver->Destroy(); +} + +static already_AddRefed<imgIContainer> +GetImageContainer(dom::Element* aImage) +{ + nsCOMPtr<imgIRequest> request; + nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage); + if (!ilc) { + return nullptr; + } + + ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(request)); + if (!request) { + return nullptr; + } + + nsCOMPtr<imgIContainer> imgContainer; + request->GetImage(getter_AddRefs(imgContainer)); + if (!imgContainer) { + return nullptr; + } + + return imgContainer.forget(); +} + +void +CanvasImageCache::NotifyDrawImage(Element* aImage, + HTMLCanvasElement* aCanvas, + SourceSurface* aSource, + const IntSize& aSize, + bool aIsAccelerated) +{ + if (!gImageCache) { + gImageCache = new ImageCache(); + nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); + } + + nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage); + if (!imgContainer) { + return; + } + + AllCanvasImageCacheKey allCanvasCacheKey(imgContainer, aIsAccelerated); + ImageCacheKey canvasCacheKey(imgContainer, aCanvas, aIsAccelerated); + ImageCacheEntry* entry = gImageCache->mCache.PutEntry(canvasCacheKey); + + if (entry) { + if (entry->mData->mSourceSurface) { + // We are overwriting an existing entry. + gImageCache->mTotal -= entry->mData->SizeInBytes(); + gImageCache->RemoveObject(entry->mData); + gImageCache->mAllCanvasCache.RemoveEntry(allCanvasCacheKey); + } + + gImageCache->AddObject(entry->mData); + entry->mData->mSourceSurface = aSource; + entry->mData->mSize = aSize; + gImageCache->mTotal += entry->mData->SizeInBytes(); + + AllCanvasImageCacheEntry* allEntry = gImageCache->mAllCanvasCache.PutEntry(allCanvasCacheKey); + if (allEntry) { + allEntry->mSourceSurface = aSource; + } + } + + if (!sCanvasImageCacheLimit) + return; + + // Expire the image cache early if its larger than we want it to be. + while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit)) + gImageCache->AgeOneGeneration(); +} + +SourceSurface* +CanvasImageCache::LookupAllCanvas(Element* aImage, + bool aIsAccelerated) +{ + if (!gImageCache) { + return nullptr; + } + + nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage); + if (!imgContainer) { + return nullptr; + } + + AllCanvasImageCacheEntry* entry = + gImageCache->mAllCanvasCache.GetEntry(AllCanvasImageCacheKey(imgContainer, aIsAccelerated)); + if (!entry) { + return nullptr; + } + + return entry->mSourceSurface; +} + +SourceSurface* +CanvasImageCache::LookupCanvas(Element* aImage, + HTMLCanvasElement* aCanvas, + IntSize* aSizeOut, + bool aIsAccelerated) +{ + if (!gImageCache) { + return nullptr; + } + + nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage); + if (!imgContainer) { + return nullptr; + } + + ImageCacheEntry* entry = + gImageCache->mCache.GetEntry(ImageCacheKey(imgContainer, aCanvas, aIsAccelerated)); + if (!entry) { + return nullptr; + } + + MOZ_ASSERT(aSizeOut); + + gImageCache->MarkUsed(entry->mData); + *aSizeOut = entry->mData->mSize; + return entry->mData->mSourceSurface; +} + + +NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver) + +NS_IMETHODIMP +CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + delete gImageCache; + gImageCache = nullptr; + + nsContentUtils::UnregisterShutdownObserver(this); + } + + return NS_OK; +} + +} // namespace mozilla |