Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "CanvasImageCache.h"
7 : #include "nsAutoPtr.h"
8 : #include "nsIImageLoadingContent.h"
9 : #include "nsExpirationTracker.h"
10 : #include "imgIRequest.h"
11 : #include "mozilla/dom/Element.h"
12 : #include "nsTHashtable.h"
13 : #include "mozilla/dom/HTMLCanvasElement.h"
14 : #include "nsContentUtils.h"
15 : #include "mozilla/Preferences.h"
16 : #include "mozilla/gfx/2D.h"
17 : #include "gfx2DGlue.h"
18 :
19 : namespace mozilla {
20 :
21 : using namespace dom;
22 : using namespace gfx;
23 :
24 : /**
25 : * Used for images specific to this one canvas. Required
26 : * due to CORS security.
27 : */
28 0 : struct ImageCacheKey
29 : {
30 0 : ImageCacheKey(imgIContainer* aImage,
31 : HTMLCanvasElement* aCanvas,
32 : bool aIsAccelerated)
33 0 : : mImage(aImage)
34 : , mCanvas(aCanvas)
35 0 : , mIsAccelerated(aIsAccelerated)
36 0 : {}
37 : nsCOMPtr<imgIContainer> mImage;
38 : HTMLCanvasElement* mCanvas;
39 : bool mIsAccelerated;
40 : };
41 :
42 : /**
43 : * Cache data needs to be separate from the entry
44 : * for nsExpirationTracker.
45 : */
46 0 : struct ImageCacheEntryData
47 : {
48 : ImageCacheEntryData(const ImageCacheEntryData& aOther)
49 : : mImage(aOther.mImage)
50 : , mCanvas(aOther.mCanvas)
51 : , mIsAccelerated(aOther.mIsAccelerated)
52 : , mSourceSurface(aOther.mSourceSurface)
53 : , mSize(aOther.mSize)
54 : {}
55 0 : explicit ImageCacheEntryData(const ImageCacheKey& aKey)
56 0 : : mImage(aKey.mImage)
57 0 : , mCanvas(aKey.mCanvas)
58 0 : , mIsAccelerated(aKey.mIsAccelerated)
59 0 : {}
60 :
61 0 : nsExpirationState* GetExpirationState() { return &mState; }
62 0 : size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
63 :
64 : // Key
65 : nsCOMPtr<imgIContainer> mImage;
66 : HTMLCanvasElement* mCanvas;
67 : bool mIsAccelerated;
68 : // Value
69 : RefPtr<SourceSurface> mSourceSurface;
70 : IntSize mSize;
71 : nsExpirationState mState;
72 : };
73 :
74 : class ImageCacheEntry : public PLDHashEntryHdr
75 : {
76 : public:
77 : typedef ImageCacheKey KeyType;
78 : typedef const ImageCacheKey* KeyTypePointer;
79 :
80 0 : explicit ImageCacheEntry(const KeyType* aKey) :
81 0 : mData(new ImageCacheEntryData(*aKey)) {}
82 : ImageCacheEntry(const ImageCacheEntry& toCopy) :
83 : mData(new ImageCacheEntryData(*toCopy.mData)) {}
84 0 : ~ImageCacheEntry() {}
85 :
86 0 : bool KeyEquals(KeyTypePointer key) const
87 : {
88 0 : return mData->mImage == key->mImage &&
89 0 : mData->mCanvas == key->mCanvas &&
90 0 : mData->mIsAccelerated == key->mIsAccelerated;
91 : }
92 :
93 0 : static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
94 0 : static PLDHashNumber HashKey(KeyTypePointer key)
95 : {
96 0 : return HashGeneric(key->mImage.get(), key->mCanvas, key->mIsAccelerated);
97 : }
98 : enum { ALLOW_MEMMOVE = true };
99 :
100 : nsAutoPtr<ImageCacheEntryData> mData;
101 : };
102 :
103 :
104 : /**
105 : * Used for all images across all canvases.
106 : */
107 0 : struct AllCanvasImageCacheKey
108 : {
109 0 : AllCanvasImageCacheKey(imgIContainer* aImage,
110 : bool aIsAccelerated)
111 0 : : mImage(aImage)
112 0 : , mIsAccelerated(aIsAccelerated)
113 0 : {}
114 :
115 : nsCOMPtr<imgIContainer> mImage;
116 : bool mIsAccelerated;
117 : };
118 :
119 : class AllCanvasImageCacheEntry : public PLDHashEntryHdr {
120 : public:
121 : typedef AllCanvasImageCacheKey KeyType;
122 : typedef const AllCanvasImageCacheKey* KeyTypePointer;
123 :
124 0 : explicit AllCanvasImageCacheEntry(const KeyType* aKey)
125 0 : : mImage(aKey->mImage)
126 0 : , mIsAccelerated(aKey->mIsAccelerated)
127 0 : {}
128 :
129 : AllCanvasImageCacheEntry(const AllCanvasImageCacheEntry &toCopy)
130 : : mImage(toCopy.mImage)
131 : , mIsAccelerated(toCopy.mIsAccelerated)
132 : , mSourceSurface(toCopy.mSourceSurface)
133 : {}
134 :
135 0 : ~AllCanvasImageCacheEntry() {}
136 :
137 0 : bool KeyEquals(KeyTypePointer key) const
138 : {
139 0 : return mImage == key->mImage &&
140 0 : mIsAccelerated == key->mIsAccelerated;
141 : }
142 :
143 0 : static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
144 0 : static PLDHashNumber HashKey(KeyTypePointer key)
145 : {
146 0 : return HashGeneric(key->mImage.get(), key->mIsAccelerated);
147 : }
148 : enum { ALLOW_MEMMOVE = true };
149 :
150 : nsCOMPtr<imgIContainer> mImage;
151 : bool mIsAccelerated;
152 : RefPtr<SourceSurface> mSourceSurface;
153 : };
154 :
155 : static bool sPrefsInitialized = false;
156 : static int32_t sCanvasImageCacheLimit = 0;
157 :
158 : class ImageCacheObserver;
159 :
160 : class ImageCache final : public nsExpirationTracker<ImageCacheEntryData,4>
161 : {
162 : public:
163 : // We use 3 generations of 1 second each to get a 2-3 seconds timeout.
164 : enum { GENERATION_MS = 1000 };
165 : ImageCache();
166 : ~ImageCache();
167 :
168 0 : virtual void NotifyExpired(ImageCacheEntryData* aObject)
169 : {
170 0 : mTotal -= aObject->SizeInBytes();
171 0 : RemoveObject(aObject);
172 :
173 : // Remove from the all canvas cache entry first since nsExpirationTracker
174 : // will delete aObject.
175 0 : mAllCanvasCache.RemoveEntry(AllCanvasImageCacheKey(aObject->mImage, aObject->mIsAccelerated));
176 :
177 : // Deleting the entry will delete aObject since the entry owns aObject.
178 0 : mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas, aObject->mIsAccelerated));
179 0 : }
180 :
181 : nsTHashtable<ImageCacheEntry> mCache;
182 : nsTHashtable<AllCanvasImageCacheEntry> mAllCanvasCache;
183 : size_t mTotal;
184 : RefPtr<ImageCacheObserver> mImageCacheObserver;
185 : };
186 :
187 : static ImageCache* gImageCache = nullptr;
188 :
189 : // Listen memory-pressure event for image cache purge.
190 : class ImageCacheObserver final : public nsIObserver
191 : {
192 : public:
193 : NS_DECL_ISUPPORTS
194 :
195 0 : explicit ImageCacheObserver(ImageCache* aImageCache)
196 0 : : mImageCache(aImageCache)
197 : {
198 0 : RegisterMemoryPressureEvent();
199 0 : }
200 :
201 0 : void Destroy()
202 : {
203 0 : UnregisterMemoryPressureEvent();
204 0 : mImageCache = nullptr;
205 0 : }
206 :
207 0 : NS_IMETHOD Observe(nsISupports* aSubject,
208 : const char* aTopic,
209 : const char16_t* aSomeData) override
210 : {
211 0 : if (!mImageCache || strcmp(aTopic, "memory-pressure")) {
212 0 : return NS_OK;
213 : }
214 :
215 0 : mImageCache->AgeAllGenerations();
216 0 : return NS_OK;
217 : }
218 :
219 : private:
220 0 : virtual ~ImageCacheObserver()
221 0 : {
222 0 : }
223 :
224 0 : void RegisterMemoryPressureEvent()
225 : {
226 : nsCOMPtr<nsIObserverService> observerService =
227 0 : mozilla::services::GetObserverService();
228 :
229 0 : MOZ_ASSERT(observerService);
230 :
231 0 : if (observerService) {
232 0 : observerService->AddObserver(this, "memory-pressure", false);
233 : }
234 0 : }
235 :
236 0 : void UnregisterMemoryPressureEvent()
237 : {
238 : nsCOMPtr<nsIObserverService> observerService =
239 0 : mozilla::services::GetObserverService();
240 :
241 : // Do not assert on observerService here. This might be triggered by
242 : // the cycle collector at a late enough time, that XPCOM services are
243 : // no longer available. See bug 1029504.
244 0 : if (observerService) {
245 0 : observerService->RemoveObserver(this, "memory-pressure");
246 : }
247 0 : }
248 :
249 : ImageCache* mImageCache;
250 : };
251 :
252 0 : NS_IMPL_ISUPPORTS(ImageCacheObserver, nsIObserver)
253 :
254 0 : class CanvasImageCacheShutdownObserver final : public nsIObserver
255 : {
256 0 : ~CanvasImageCacheShutdownObserver() {}
257 : public:
258 : NS_DECL_ISUPPORTS
259 : NS_DECL_NSIOBSERVER
260 : };
261 :
262 0 : ImageCache::ImageCache()
263 : : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS, "ImageCache")
264 0 : , mTotal(0)
265 : {
266 0 : if (!sPrefsInitialized) {
267 0 : sPrefsInitialized = true;
268 0 : Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0);
269 : }
270 0 : mImageCacheObserver = new ImageCacheObserver(this);
271 0 : MOZ_RELEASE_ASSERT(mImageCacheObserver, "GFX: Can't alloc ImageCacheObserver");
272 0 : }
273 :
274 0 : ImageCache::~ImageCache() {
275 0 : AgeAllGenerations();
276 0 : mImageCacheObserver->Destroy();
277 0 : }
278 :
279 : static already_AddRefed<imgIContainer>
280 0 : GetImageContainer(dom::Element* aImage)
281 : {
282 0 : nsCOMPtr<imgIRequest> request;
283 0 : nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
284 0 : if (!ilc) {
285 0 : return nullptr;
286 : }
287 :
288 0 : ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
289 0 : getter_AddRefs(request));
290 0 : if (!request) {
291 0 : return nullptr;
292 : }
293 :
294 0 : nsCOMPtr<imgIContainer> imgContainer;
295 0 : request->GetImage(getter_AddRefs(imgContainer));
296 0 : if (!imgContainer) {
297 0 : return nullptr;
298 : }
299 :
300 0 : return imgContainer.forget();
301 : }
302 :
303 : void
304 0 : CanvasImageCache::NotifyDrawImage(Element* aImage,
305 : HTMLCanvasElement* aCanvas,
306 : SourceSurface* aSource,
307 : const IntSize& aSize,
308 : bool aIsAccelerated)
309 : {
310 0 : if (!gImageCache) {
311 0 : gImageCache = new ImageCache();
312 0 : nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver());
313 : }
314 :
315 0 : nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
316 0 : if (!imgContainer) {
317 0 : return;
318 : }
319 :
320 0 : AllCanvasImageCacheKey allCanvasCacheKey(imgContainer, aIsAccelerated);
321 0 : ImageCacheKey canvasCacheKey(imgContainer, aCanvas, aIsAccelerated);
322 0 : ImageCacheEntry* entry = gImageCache->mCache.PutEntry(canvasCacheKey);
323 :
324 0 : if (entry) {
325 0 : if (entry->mData->mSourceSurface) {
326 : // We are overwriting an existing entry.
327 0 : gImageCache->mTotal -= entry->mData->SizeInBytes();
328 0 : gImageCache->RemoveObject(entry->mData);
329 0 : gImageCache->mAllCanvasCache.RemoveEntry(allCanvasCacheKey);
330 : }
331 :
332 0 : gImageCache->AddObject(entry->mData);
333 0 : entry->mData->mSourceSurface = aSource;
334 0 : entry->mData->mSize = aSize;
335 0 : gImageCache->mTotal += entry->mData->SizeInBytes();
336 :
337 0 : AllCanvasImageCacheEntry* allEntry = gImageCache->mAllCanvasCache.PutEntry(allCanvasCacheKey);
338 0 : if (allEntry) {
339 0 : allEntry->mSourceSurface = aSource;
340 : }
341 : }
342 :
343 0 : if (!sCanvasImageCacheLimit)
344 0 : return;
345 :
346 : // Expire the image cache early if its larger than we want it to be.
347 0 : while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit))
348 0 : gImageCache->AgeOneGeneration();
349 : }
350 :
351 : SourceSurface*
352 0 : CanvasImageCache::LookupAllCanvas(Element* aImage,
353 : bool aIsAccelerated)
354 : {
355 0 : if (!gImageCache) {
356 0 : return nullptr;
357 : }
358 :
359 0 : nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
360 0 : if (!imgContainer) {
361 0 : return nullptr;
362 : }
363 :
364 : AllCanvasImageCacheEntry* entry =
365 0 : gImageCache->mAllCanvasCache.GetEntry(AllCanvasImageCacheKey(imgContainer, aIsAccelerated));
366 0 : if (!entry) {
367 0 : return nullptr;
368 : }
369 :
370 0 : return entry->mSourceSurface;
371 : }
372 :
373 : SourceSurface*
374 0 : CanvasImageCache::LookupCanvas(Element* aImage,
375 : HTMLCanvasElement* aCanvas,
376 : IntSize* aSizeOut,
377 : bool aIsAccelerated)
378 : {
379 0 : if (!gImageCache) {
380 0 : return nullptr;
381 : }
382 :
383 0 : nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
384 0 : if (!imgContainer) {
385 0 : return nullptr;
386 : }
387 :
388 : ImageCacheEntry* entry =
389 0 : gImageCache->mCache.GetEntry(ImageCacheKey(imgContainer, aCanvas, aIsAccelerated));
390 0 : if (!entry) {
391 0 : return nullptr;
392 : }
393 :
394 0 : MOZ_ASSERT(aSizeOut);
395 :
396 0 : gImageCache->MarkUsed(entry->mData);
397 0 : *aSizeOut = entry->mData->mSize;
398 0 : return entry->mData->mSourceSurface;
399 : }
400 :
401 :
402 0 : NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
403 :
404 : NS_IMETHODIMP
405 0 : CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject,
406 : const char *aTopic,
407 : const char16_t *aData)
408 : {
409 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
410 0 : delete gImageCache;
411 0 : gImageCache = nullptr;
412 :
413 0 : nsContentUtils::UnregisterShutdownObserver(this);
414 : }
415 :
416 0 : return NS_OK;
417 : }
418 :
419 : } // namespace mozilla
|