Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / canvas / CanvasImageCache.cpp
blobcef9f1ee2d600e4b2a3088d49c2174b9b18bd37d
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/. */
6 #include "CanvasImageCache.h"
7 #include "nsIImageLoadingContent.h"
8 #include "nsExpirationTracker.h"
9 #include "imgIRequest.h"
10 #include "mozilla/dom/Element.h"
11 #include "nsTHashtable.h"
12 #include "nsContentUtils.h"
13 #include "mozilla/Preferences.h"
14 #include "mozilla/UniquePtr.h"
15 #include "mozilla/gfx/2D.h"
16 #include "gfx2DGlue.h"
18 namespace mozilla {
20 using namespace dom;
21 using namespace gfx;
23 /**
24 * Used for images specific to this one canvas. Required
25 * due to CORS security.
27 struct ImageCacheKey {
28 ImageCacheKey(imgIContainer* aImage, CanvasRenderingContext2D* aContext,
29 BackendType aBackendType)
30 : mImage(aImage), mContext(aContext), mBackendType(aBackendType) {}
31 nsCOMPtr<imgIContainer> mImage;
32 CanvasRenderingContext2D* mContext;
33 BackendType mBackendType;
36 /**
37 * Cache data needs to be separate from the entry
38 * for nsExpirationTracker.
40 struct ImageCacheEntryData {
41 ImageCacheEntryData(const ImageCacheEntryData& aOther)
42 : mImage(aOther.mImage),
43 mContext(aOther.mContext),
44 mBackendType(aOther.mBackendType),
45 mSourceSurface(aOther.mSourceSurface),
46 mSize(aOther.mSize),
47 mIntrinsicSize(aOther.mIntrinsicSize),
48 mCropRect(aOther.mCropRect) {}
49 explicit ImageCacheEntryData(const ImageCacheKey& aKey)
50 : mImage(aKey.mImage),
51 mContext(aKey.mContext),
52 mBackendType(aKey.mBackendType) {}
54 nsExpirationState* GetExpirationState() { return &mState; }
55 size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
57 // Key
58 nsCOMPtr<imgIContainer> mImage;
59 CanvasRenderingContext2D* mContext;
60 BackendType mBackendType;
61 // Value
62 RefPtr<SourceSurface> mSourceSurface;
63 IntSize mSize;
64 IntSize mIntrinsicSize;
65 Maybe<IntRect> mCropRect;
66 nsExpirationState mState;
69 class ImageCacheEntry : public PLDHashEntryHdr {
70 public:
71 using KeyType = ImageCacheKey;
72 using KeyTypePointer = const ImageCacheKey*;
74 explicit ImageCacheEntry(const KeyType* aKey)
75 : mData(new ImageCacheEntryData(*aKey)) {}
76 ImageCacheEntry(const ImageCacheEntry& toCopy)
77 : mData(new ImageCacheEntryData(*toCopy.mData)) {}
78 ~ImageCacheEntry() = default;
80 bool KeyEquals(KeyTypePointer key) const {
81 return mData->mImage == key->mImage && mData->mContext == key->mContext &&
82 mData->mBackendType == key->mBackendType;
85 static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
86 static PLDHashNumber HashKey(KeyTypePointer key) {
87 return HashGeneric(key->mImage.get(), key->mContext, key->mBackendType);
89 enum { ALLOW_MEMMOVE = true };
91 UniquePtr<ImageCacheEntryData> mData;
94 /**
95 * Used for all images across all canvases.
97 struct AllCanvasImageCacheKey {
98 explicit AllCanvasImageCacheKey(imgIContainer* aImage,
99 BackendType aBackendType)
100 : mImage(aImage), mBackendType(aBackendType) {}
102 nsCOMPtr<imgIContainer> mImage;
103 BackendType mBackendType;
106 class AllCanvasImageCacheEntry : public PLDHashEntryHdr {
107 public:
108 using KeyType = AllCanvasImageCacheKey;
109 using KeyTypePointer = const AllCanvasImageCacheKey*;
111 explicit AllCanvasImageCacheEntry(const KeyType* aKey)
112 : mImage(aKey->mImage), mBackendType(aKey->mBackendType) {}
114 AllCanvasImageCacheEntry(const AllCanvasImageCacheEntry& toCopy)
115 : mImage(toCopy.mImage),
116 mSourceSurface(toCopy.mSourceSurface),
117 mBackendType(toCopy.mBackendType) {}
119 ~AllCanvasImageCacheEntry() = default;
121 bool KeyEquals(KeyTypePointer key) const {
122 return mImage == key->mImage && mBackendType == key->mBackendType;
125 static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
126 static PLDHashNumber HashKey(KeyTypePointer key) {
127 return HashGeneric(key->mImage.get(), key->mBackendType);
129 enum { ALLOW_MEMMOVE = true };
131 nsCOMPtr<imgIContainer> mImage;
132 RefPtr<SourceSurface> mSourceSurface;
133 BackendType mBackendType;
136 class ImageCacheObserver;
138 class ImageCache final : public nsExpirationTracker<ImageCacheEntryData, 4> {
139 public:
140 // We use 3 generations of 1 second each to get a 2-3 seconds timeout.
141 enum { GENERATION_MS = 1000 };
142 ImageCache();
143 ~ImageCache();
145 virtual void NotifyExpired(ImageCacheEntryData* aObject) override {
146 RemoveObject(aObject);
148 // Remove from the all canvas cache entry first since nsExpirationTracker
149 // will delete aObject.
150 mAllCanvasCache.RemoveEntry(
151 AllCanvasImageCacheKey(aObject->mImage, aObject->mBackendType));
153 // Deleting the entry will delete aObject since the entry owns aObject.
154 mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mContext,
155 aObject->mBackendType));
158 nsTHashtable<ImageCacheEntry> mCache;
159 nsTHashtable<AllCanvasImageCacheEntry> mAllCanvasCache;
160 RefPtr<ImageCacheObserver> mImageCacheObserver;
163 static ImageCache* gImageCache = nullptr;
165 // Listen memory-pressure event for image cache purge.
166 class ImageCacheObserver final : public nsIObserver {
167 public:
168 NS_DECL_ISUPPORTS
170 explicit ImageCacheObserver(ImageCache* aImageCache)
171 : mImageCache(aImageCache) {
172 RegisterObserverEvents();
175 void Destroy() {
176 UnregisterObserverEvents();
177 mImageCache = nullptr;
180 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
181 const char16_t* aSomeData) override {
182 if (!mImageCache || (strcmp(aTopic, "memory-pressure") != 0 &&
183 strcmp(aTopic, "canvas-device-reset") != 0)) {
184 return NS_OK;
187 mImageCache->AgeAllGenerations();
188 return NS_OK;
191 private:
192 virtual ~ImageCacheObserver() = default;
194 void RegisterObserverEvents() {
195 nsCOMPtr<nsIObserverService> observerService =
196 mozilla::services::GetObserverService();
198 MOZ_ASSERT(observerService);
200 if (observerService) {
201 observerService->AddObserver(this, "memory-pressure", false);
202 observerService->AddObserver(this, "canvas-device-reset", false);
206 void UnregisterObserverEvents() {
207 nsCOMPtr<nsIObserverService> observerService =
208 mozilla::services::GetObserverService();
210 // Do not assert on observerService here. This might be triggered by
211 // the cycle collector at a late enough time, that XPCOM services are
212 // no longer available. See bug 1029504.
213 if (observerService) {
214 observerService->RemoveObserver(this, "memory-pressure");
215 observerService->RemoveObserver(this, "canvas-device-reset");
219 ImageCache* mImageCache;
222 NS_IMPL_ISUPPORTS(ImageCacheObserver, nsIObserver)
224 class CanvasImageCacheShutdownObserver final : public nsIObserver {
225 ~CanvasImageCacheShutdownObserver() = default;
227 public:
228 NS_DECL_ISUPPORTS
229 NS_DECL_NSIOBSERVER
232 ImageCache::ImageCache()
233 : nsExpirationTracker<ImageCacheEntryData, 4>(GENERATION_MS, "ImageCache") {
234 mImageCacheObserver = new ImageCacheObserver(this);
235 MOZ_RELEASE_ASSERT(mImageCacheObserver,
236 "GFX: Can't alloc ImageCacheObserver");
239 ImageCache::~ImageCache() {
240 AgeAllGenerations();
241 mImageCacheObserver->Destroy();
244 static already_AddRefed<imgIContainer> GetImageContainer(dom::Element* aImage) {
245 nsCOMPtr<imgIRequest> request;
246 nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
247 if (!ilc) {
248 return nullptr;
251 ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
252 getter_AddRefs(request));
253 if (!request) {
254 return nullptr;
257 nsCOMPtr<imgIContainer> imgContainer;
258 request->GetImage(getter_AddRefs(imgContainer));
259 if (!imgContainer) {
260 return nullptr;
263 return imgContainer.forget();
266 void CanvasImageCache::NotifyCanvasDestroyed(
267 CanvasRenderingContext2D* aContext) {
268 MOZ_ASSERT(aContext);
270 if (!NS_IsMainThread() || !gImageCache) {
271 return;
274 for (auto i = gImageCache->mCache.Iter(); !i.Done(); i.Next()) {
275 ImageCacheEntryData* data = i.Get()->mData.get();
276 if (data->mContext == aContext) {
277 gImageCache->RemoveObject(data);
278 gImageCache->mAllCanvasCache.RemoveEntry(
279 AllCanvasImageCacheKey(data->mImage, data->mBackendType));
280 i.Remove();
285 void CanvasImageCache::NotifyDrawImage(
286 Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
287 SourceSurface* aSource, const IntSize& aSize, const IntSize& aIntrinsicSize,
288 const Maybe<IntRect>& aCropRect) {
289 MOZ_ASSERT(NS_IsMainThread());
290 MOZ_ASSERT(aContext);
292 if (!aTarget || !aContext) {
293 return;
296 if (!gImageCache) {
297 gImageCache = new ImageCache();
298 nsContentUtils::RegisterShutdownObserver(
299 new CanvasImageCacheShutdownObserver());
302 nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
303 if (!imgContainer) {
304 return;
307 BackendType backendType = aTarget->GetBackendType();
308 AllCanvasImageCacheKey allCanvasCacheKey(imgContainer, backendType);
309 ImageCacheKey canvasCacheKey(imgContainer, aContext, backendType);
310 ImageCacheEntry* entry = gImageCache->mCache.PutEntry(canvasCacheKey);
312 if (entry) {
313 if (entry->mData->mSourceSurface) {
314 // We are overwriting an existing entry.
315 gImageCache->RemoveObject(entry->mData.get());
316 gImageCache->mAllCanvasCache.RemoveEntry(allCanvasCacheKey);
319 gImageCache->AddObject(entry->mData.get());
320 entry->mData->mSourceSurface = aSource;
321 entry->mData->mSize = aSize;
322 entry->mData->mIntrinsicSize = aIntrinsicSize;
323 entry->mData->mCropRect = aCropRect;
325 AllCanvasImageCacheEntry* allEntry =
326 gImageCache->mAllCanvasCache.PutEntry(allCanvasCacheKey);
327 if (allEntry) {
328 allEntry->mSourceSurface = aSource;
333 SourceSurface* CanvasImageCache::LookupAllCanvas(Element* aImage,
334 DrawTarget* aTarget) {
335 MOZ_ASSERT(NS_IsMainThread());
337 if (!gImageCache || !aTarget) {
338 return nullptr;
341 nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
342 if (!imgContainer) {
343 return nullptr;
346 AllCanvasImageCacheEntry* entry = gImageCache->mAllCanvasCache.GetEntry(
347 AllCanvasImageCacheKey(imgContainer, aTarget->GetBackendType()));
348 if (!entry) {
349 return nullptr;
352 return entry->mSourceSurface;
355 SourceSurface* CanvasImageCache::LookupCanvas(
356 Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
357 IntSize* aSizeOut, IntSize* aIntrinsicSizeOut,
358 Maybe<IntRect>* aCropRectOut) {
359 MOZ_ASSERT(NS_IsMainThread());
360 MOZ_ASSERT(aContext);
362 if (!gImageCache || !aTarget) {
363 return nullptr;
366 nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
367 if (!imgContainer) {
368 return nullptr;
371 // We filter based on the backend type because we don't want a surface that
372 // was optimized for one backend to be preferred for another backend, as this
373 // could have performance implications. For example, a Skia optimized surface
374 // given to a D2D backend would cause an upload each time, and similarly a D2D
375 // optimized surface given to a Skia backend would cause a readback. For
376 // details, see bug 1794442.
377 ImageCacheEntry* entry = gImageCache->mCache.GetEntry(
378 ImageCacheKey(imgContainer, aContext, aTarget->GetBackendType()));
379 if (!entry) {
380 return nullptr;
383 MOZ_ASSERT(aSizeOut);
385 gImageCache->MarkUsed(entry->mData.get());
386 *aSizeOut = entry->mData->mSize;
387 *aIntrinsicSizeOut = entry->mData->mIntrinsicSize;
388 *aCropRectOut = entry->mData->mCropRect;
389 return entry->mData->mSourceSurface;
392 NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
394 NS_IMETHODIMP
395 CanvasImageCacheShutdownObserver::Observe(nsISupports* aSubject,
396 const char* aTopic,
397 const char16_t* aData) {
398 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
399 delete gImageCache;
400 gImageCache = nullptr;
402 nsContentUtils::UnregisterShutdownObserver(this);
405 return NS_OK;
408 } // namespace mozilla