diff -r 000000000000 -r 6474c204b198 content/canvas/src/CanvasImageCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/canvas/src/CanvasImageCache.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,207 @@ +/* -*- 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 "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" + +namespace mozilla { + +using namespace dom; +using namespace gfx; + +struct ImageCacheKey { + ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) + : mImage(aImage), mCanvas(aCanvas) {} + Element* mImage; + HTMLCanvasElement* mCanvas; +}; + +struct ImageCacheEntryData { + ImageCacheEntryData(const ImageCacheEntryData& aOther) + : mImage(aOther.mImage) + , mILC(aOther.mILC) + , mCanvas(aOther.mCanvas) + , mRequest(aOther.mRequest) + , mSourceSurface(aOther.mSourceSurface) + , mSize(aOther.mSize) + {} + ImageCacheEntryData(const ImageCacheKey& aKey) + : mImage(aKey.mImage) + , mILC(nullptr) + , mCanvas(aKey.mCanvas) + {} + + nsExpirationState* GetExpirationState() { return &mState; } + + size_t SizeInBytes() { return mSize.width * mSize.height * 4; } + + // Key + nsRefPtr mImage; + nsIImageLoadingContent* mILC; + nsRefPtr mCanvas; + // Value + nsCOMPtr mRequest; + RefPtr mSourceSurface; + gfxIntSize mSize; + nsExpirationState mState; +}; + +class ImageCacheEntry : public PLDHashEntryHdr { +public: + typedef ImageCacheKey KeyType; + typedef const ImageCacheKey* KeyTypePointer; + + ImageCacheEntry(const KeyType *key) : + mData(new ImageCacheEntryData(*key)) {} + ImageCacheEntry(const ImageCacheEntry &toCopy) : + mData(new ImageCacheEntryData(*toCopy.mData)) {} + ~ImageCacheEntry() {} + + bool KeyEquals(KeyTypePointer key) const + { + return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; + } + + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } + static PLDHashNumber HashKey(KeyTypePointer key) + { + return HashGeneric(key->mImage, key->mCanvas); + } + enum { ALLOW_MEMMOVE = true }; + + nsAutoPtr mData; +}; + +static bool sPrefsInitialized = false; +static int32_t sCanvasImageCacheLimit = 0; + +class ImageCache MOZ_FINAL : public nsExpirationTracker { +public: + // We use 3 generations of 1 second each to get a 2-3 seconds timeout. + enum { GENERATION_MS = 1000 }; + ImageCache() + : nsExpirationTracker(GENERATION_MS) + , mTotal(0) + { + if (!sPrefsInitialized) { + sPrefsInitialized = true; + Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0); + } + } + ~ImageCache() { + AgeAllGenerations(); + } + + virtual void NotifyExpired(ImageCacheEntryData* aObject) + { + mTotal -= aObject->SizeInBytes(); + RemoveObject(aObject); + // Deleting the entry will delete aObject since the entry owns aObject + mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); + } + + nsTHashtable mCache; + size_t mTotal; +}; + +static ImageCache* gImageCache = nullptr; + +class CanvasImageCacheShutdownObserver MOZ_FINAL : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +void +CanvasImageCache::NotifyDrawImage(Element* aImage, + HTMLCanvasElement* aCanvas, + imgIRequest* aRequest, + SourceSurface* aSource, + const gfxIntSize& aSize) +{ + if (!gImageCache) { + gImageCache = new ImageCache(); + nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); + } + + ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); + if (entry) { + if (entry->mData->mSourceSurface) { + // We are overwriting an existing entry. + gImageCache->mTotal -= entry->mData->SizeInBytes(); + gImageCache->RemoveObject(entry->mData); + } + gImageCache->AddObject(entry->mData); + + nsCOMPtr ilc = do_QueryInterface(aImage); + if (ilc) { + ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(entry->mData->mRequest)); + } + entry->mData->mILC = ilc; + entry->mData->mSourceSurface = aSource; + entry->mData->mSize = aSize; + + gImageCache->mTotal += entry->mData->SizeInBytes(); + } + + 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::Lookup(Element* aImage, + HTMLCanvasElement* aCanvas, + gfxIntSize* aSize) +{ + if (!gImageCache) + return nullptr; + + ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); + if (!entry || !entry->mData->mILC) + return nullptr; + + nsCOMPtr request; + entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); + if (request != entry->mData->mRequest) + return nullptr; + + gImageCache->MarkUsed(entry->mData); + + *aSize = 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