1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/canvas/src/CanvasImageCache.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,207 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "CanvasImageCache.h" 1.10 +#include "nsIImageLoadingContent.h" 1.11 +#include "nsExpirationTracker.h" 1.12 +#include "imgIRequest.h" 1.13 +#include "mozilla/dom/Element.h" 1.14 +#include "nsTHashtable.h" 1.15 +#include "mozilla/dom/HTMLCanvasElement.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "mozilla/Preferences.h" 1.18 +#include "mozilla/gfx/2D.h" 1.19 + 1.20 +namespace mozilla { 1.21 + 1.22 +using namespace dom; 1.23 +using namespace gfx; 1.24 + 1.25 +struct ImageCacheKey { 1.26 + ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) 1.27 + : mImage(aImage), mCanvas(aCanvas) {} 1.28 + Element* mImage; 1.29 + HTMLCanvasElement* mCanvas; 1.30 +}; 1.31 + 1.32 +struct ImageCacheEntryData { 1.33 + ImageCacheEntryData(const ImageCacheEntryData& aOther) 1.34 + : mImage(aOther.mImage) 1.35 + , mILC(aOther.mILC) 1.36 + , mCanvas(aOther.mCanvas) 1.37 + , mRequest(aOther.mRequest) 1.38 + , mSourceSurface(aOther.mSourceSurface) 1.39 + , mSize(aOther.mSize) 1.40 + {} 1.41 + ImageCacheEntryData(const ImageCacheKey& aKey) 1.42 + : mImage(aKey.mImage) 1.43 + , mILC(nullptr) 1.44 + , mCanvas(aKey.mCanvas) 1.45 + {} 1.46 + 1.47 + nsExpirationState* GetExpirationState() { return &mState; } 1.48 + 1.49 + size_t SizeInBytes() { return mSize.width * mSize.height * 4; } 1.50 + 1.51 + // Key 1.52 + nsRefPtr<Element> mImage; 1.53 + nsIImageLoadingContent* mILC; 1.54 + nsRefPtr<HTMLCanvasElement> mCanvas; 1.55 + // Value 1.56 + nsCOMPtr<imgIRequest> mRequest; 1.57 + RefPtr<SourceSurface> mSourceSurface; 1.58 + gfxIntSize mSize; 1.59 + nsExpirationState mState; 1.60 +}; 1.61 + 1.62 +class ImageCacheEntry : public PLDHashEntryHdr { 1.63 +public: 1.64 + typedef ImageCacheKey KeyType; 1.65 + typedef const ImageCacheKey* KeyTypePointer; 1.66 + 1.67 + ImageCacheEntry(const KeyType *key) : 1.68 + mData(new ImageCacheEntryData(*key)) {} 1.69 + ImageCacheEntry(const ImageCacheEntry &toCopy) : 1.70 + mData(new ImageCacheEntryData(*toCopy.mData)) {} 1.71 + ~ImageCacheEntry() {} 1.72 + 1.73 + bool KeyEquals(KeyTypePointer key) const 1.74 + { 1.75 + return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; 1.76 + } 1.77 + 1.78 + static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } 1.79 + static PLDHashNumber HashKey(KeyTypePointer key) 1.80 + { 1.81 + return HashGeneric(key->mImage, key->mCanvas); 1.82 + } 1.83 + enum { ALLOW_MEMMOVE = true }; 1.84 + 1.85 + nsAutoPtr<ImageCacheEntryData> mData; 1.86 +}; 1.87 + 1.88 +static bool sPrefsInitialized = false; 1.89 +static int32_t sCanvasImageCacheLimit = 0; 1.90 + 1.91 +class ImageCache MOZ_FINAL : public nsExpirationTracker<ImageCacheEntryData,4> { 1.92 +public: 1.93 + // We use 3 generations of 1 second each to get a 2-3 seconds timeout. 1.94 + enum { GENERATION_MS = 1000 }; 1.95 + ImageCache() 1.96 + : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS) 1.97 + , mTotal(0) 1.98 + { 1.99 + if (!sPrefsInitialized) { 1.100 + sPrefsInitialized = true; 1.101 + Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0); 1.102 + } 1.103 + } 1.104 + ~ImageCache() { 1.105 + AgeAllGenerations(); 1.106 + } 1.107 + 1.108 + virtual void NotifyExpired(ImageCacheEntryData* aObject) 1.109 + { 1.110 + mTotal -= aObject->SizeInBytes(); 1.111 + RemoveObject(aObject); 1.112 + // Deleting the entry will delete aObject since the entry owns aObject 1.113 + mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); 1.114 + } 1.115 + 1.116 + nsTHashtable<ImageCacheEntry> mCache; 1.117 + size_t mTotal; 1.118 +}; 1.119 + 1.120 +static ImageCache* gImageCache = nullptr; 1.121 + 1.122 +class CanvasImageCacheShutdownObserver MOZ_FINAL : public nsIObserver 1.123 +{ 1.124 +public: 1.125 + NS_DECL_ISUPPORTS 1.126 + NS_DECL_NSIOBSERVER 1.127 +}; 1.128 + 1.129 +void 1.130 +CanvasImageCache::NotifyDrawImage(Element* aImage, 1.131 + HTMLCanvasElement* aCanvas, 1.132 + imgIRequest* aRequest, 1.133 + SourceSurface* aSource, 1.134 + const gfxIntSize& aSize) 1.135 +{ 1.136 + if (!gImageCache) { 1.137 + gImageCache = new ImageCache(); 1.138 + nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); 1.139 + } 1.140 + 1.141 + ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); 1.142 + if (entry) { 1.143 + if (entry->mData->mSourceSurface) { 1.144 + // We are overwriting an existing entry. 1.145 + gImageCache->mTotal -= entry->mData->SizeInBytes(); 1.146 + gImageCache->RemoveObject(entry->mData); 1.147 + } 1.148 + gImageCache->AddObject(entry->mData); 1.149 + 1.150 + nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage); 1.151 + if (ilc) { 1.152 + ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 1.153 + getter_AddRefs(entry->mData->mRequest)); 1.154 + } 1.155 + entry->mData->mILC = ilc; 1.156 + entry->mData->mSourceSurface = aSource; 1.157 + entry->mData->mSize = aSize; 1.158 + 1.159 + gImageCache->mTotal += entry->mData->SizeInBytes(); 1.160 + } 1.161 + 1.162 + if (!sCanvasImageCacheLimit) 1.163 + return; 1.164 + 1.165 + // Expire the image cache early if its larger than we want it to be. 1.166 + while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit)) 1.167 + gImageCache->AgeOneGeneration(); 1.168 +} 1.169 + 1.170 +SourceSurface* 1.171 +CanvasImageCache::Lookup(Element* aImage, 1.172 + HTMLCanvasElement* aCanvas, 1.173 + gfxIntSize* aSize) 1.174 +{ 1.175 + if (!gImageCache) 1.176 + return nullptr; 1.177 + 1.178 + ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); 1.179 + if (!entry || !entry->mData->mILC) 1.180 + return nullptr; 1.181 + 1.182 + nsCOMPtr<imgIRequest> request; 1.183 + entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); 1.184 + if (request != entry->mData->mRequest) 1.185 + return nullptr; 1.186 + 1.187 + gImageCache->MarkUsed(entry->mData); 1.188 + 1.189 + *aSize = entry->mData->mSize; 1.190 + return entry->mData->mSourceSurface; 1.191 +} 1.192 + 1.193 +NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver) 1.194 + 1.195 +NS_IMETHODIMP 1.196 +CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject, 1.197 + const char *aTopic, 1.198 + const char16_t *aData) 1.199 +{ 1.200 + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { 1.201 + delete gImageCache; 1.202 + gImageCache = nullptr; 1.203 + 1.204 + nsContentUtils::UnregisterShutdownObserver(this); 1.205 + } 1.206 + 1.207 + return NS_OK; 1.208 +} 1.209 + 1.210 +} // namespace mozilla