Coverage Report

Created: 2018-09-25 14:53

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