|
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 "nsIImageLoadingContent.h" |
|
8 #include "nsExpirationTracker.h" |
|
9 #include "imgIRequest.h" |
|
10 #include "mozilla/dom/Element.h" |
|
11 #include "nsTHashtable.h" |
|
12 #include "mozilla/dom/HTMLCanvasElement.h" |
|
13 #include "nsContentUtils.h" |
|
14 #include "mozilla/Preferences.h" |
|
15 #include "mozilla/gfx/2D.h" |
|
16 |
|
17 namespace mozilla { |
|
18 |
|
19 using namespace dom; |
|
20 using namespace gfx; |
|
21 |
|
22 struct ImageCacheKey { |
|
23 ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) |
|
24 : mImage(aImage), mCanvas(aCanvas) {} |
|
25 Element* mImage; |
|
26 HTMLCanvasElement* mCanvas; |
|
27 }; |
|
28 |
|
29 struct ImageCacheEntryData { |
|
30 ImageCacheEntryData(const ImageCacheEntryData& aOther) |
|
31 : mImage(aOther.mImage) |
|
32 , mILC(aOther.mILC) |
|
33 , mCanvas(aOther.mCanvas) |
|
34 , mRequest(aOther.mRequest) |
|
35 , mSourceSurface(aOther.mSourceSurface) |
|
36 , mSize(aOther.mSize) |
|
37 {} |
|
38 ImageCacheEntryData(const ImageCacheKey& aKey) |
|
39 : mImage(aKey.mImage) |
|
40 , mILC(nullptr) |
|
41 , mCanvas(aKey.mCanvas) |
|
42 {} |
|
43 |
|
44 nsExpirationState* GetExpirationState() { return &mState; } |
|
45 |
|
46 size_t SizeInBytes() { return mSize.width * mSize.height * 4; } |
|
47 |
|
48 // Key |
|
49 nsRefPtr<Element> mImage; |
|
50 nsIImageLoadingContent* mILC; |
|
51 nsRefPtr<HTMLCanvasElement> mCanvas; |
|
52 // Value |
|
53 nsCOMPtr<imgIRequest> mRequest; |
|
54 RefPtr<SourceSurface> mSourceSurface; |
|
55 gfxIntSize mSize; |
|
56 nsExpirationState mState; |
|
57 }; |
|
58 |
|
59 class ImageCacheEntry : public PLDHashEntryHdr { |
|
60 public: |
|
61 typedef ImageCacheKey KeyType; |
|
62 typedef const ImageCacheKey* KeyTypePointer; |
|
63 |
|
64 ImageCacheEntry(const KeyType *key) : |
|
65 mData(new ImageCacheEntryData(*key)) {} |
|
66 ImageCacheEntry(const ImageCacheEntry &toCopy) : |
|
67 mData(new ImageCacheEntryData(*toCopy.mData)) {} |
|
68 ~ImageCacheEntry() {} |
|
69 |
|
70 bool KeyEquals(KeyTypePointer key) const |
|
71 { |
|
72 return mData->mImage == key->mImage && mData->mCanvas == key->mCanvas; |
|
73 } |
|
74 |
|
75 static KeyTypePointer KeyToPointer(KeyType& key) { return &key; } |
|
76 static PLDHashNumber HashKey(KeyTypePointer key) |
|
77 { |
|
78 return HashGeneric(key->mImage, key->mCanvas); |
|
79 } |
|
80 enum { ALLOW_MEMMOVE = true }; |
|
81 |
|
82 nsAutoPtr<ImageCacheEntryData> mData; |
|
83 }; |
|
84 |
|
85 static bool sPrefsInitialized = false; |
|
86 static int32_t sCanvasImageCacheLimit = 0; |
|
87 |
|
88 class ImageCache MOZ_FINAL : public nsExpirationTracker<ImageCacheEntryData,4> { |
|
89 public: |
|
90 // We use 3 generations of 1 second each to get a 2-3 seconds timeout. |
|
91 enum { GENERATION_MS = 1000 }; |
|
92 ImageCache() |
|
93 : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS) |
|
94 , mTotal(0) |
|
95 { |
|
96 if (!sPrefsInitialized) { |
|
97 sPrefsInitialized = true; |
|
98 Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0); |
|
99 } |
|
100 } |
|
101 ~ImageCache() { |
|
102 AgeAllGenerations(); |
|
103 } |
|
104 |
|
105 virtual void NotifyExpired(ImageCacheEntryData* aObject) |
|
106 { |
|
107 mTotal -= aObject->SizeInBytes(); |
|
108 RemoveObject(aObject); |
|
109 // Deleting the entry will delete aObject since the entry owns aObject |
|
110 mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas)); |
|
111 } |
|
112 |
|
113 nsTHashtable<ImageCacheEntry> mCache; |
|
114 size_t mTotal; |
|
115 }; |
|
116 |
|
117 static ImageCache* gImageCache = nullptr; |
|
118 |
|
119 class CanvasImageCacheShutdownObserver MOZ_FINAL : public nsIObserver |
|
120 { |
|
121 public: |
|
122 NS_DECL_ISUPPORTS |
|
123 NS_DECL_NSIOBSERVER |
|
124 }; |
|
125 |
|
126 void |
|
127 CanvasImageCache::NotifyDrawImage(Element* aImage, |
|
128 HTMLCanvasElement* aCanvas, |
|
129 imgIRequest* aRequest, |
|
130 SourceSurface* aSource, |
|
131 const gfxIntSize& aSize) |
|
132 { |
|
133 if (!gImageCache) { |
|
134 gImageCache = new ImageCache(); |
|
135 nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver()); |
|
136 } |
|
137 |
|
138 ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); |
|
139 if (entry) { |
|
140 if (entry->mData->mSourceSurface) { |
|
141 // We are overwriting an existing entry. |
|
142 gImageCache->mTotal -= entry->mData->SizeInBytes(); |
|
143 gImageCache->RemoveObject(entry->mData); |
|
144 } |
|
145 gImageCache->AddObject(entry->mData); |
|
146 |
|
147 nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage); |
|
148 if (ilc) { |
|
149 ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, |
|
150 getter_AddRefs(entry->mData->mRequest)); |
|
151 } |
|
152 entry->mData->mILC = ilc; |
|
153 entry->mData->mSourceSurface = aSource; |
|
154 entry->mData->mSize = aSize; |
|
155 |
|
156 gImageCache->mTotal += entry->mData->SizeInBytes(); |
|
157 } |
|
158 |
|
159 if (!sCanvasImageCacheLimit) |
|
160 return; |
|
161 |
|
162 // Expire the image cache early if its larger than we want it to be. |
|
163 while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit)) |
|
164 gImageCache->AgeOneGeneration(); |
|
165 } |
|
166 |
|
167 SourceSurface* |
|
168 CanvasImageCache::Lookup(Element* aImage, |
|
169 HTMLCanvasElement* aCanvas, |
|
170 gfxIntSize* aSize) |
|
171 { |
|
172 if (!gImageCache) |
|
173 return nullptr; |
|
174 |
|
175 ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas)); |
|
176 if (!entry || !entry->mData->mILC) |
|
177 return nullptr; |
|
178 |
|
179 nsCOMPtr<imgIRequest> request; |
|
180 entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request)); |
|
181 if (request != entry->mData->mRequest) |
|
182 return nullptr; |
|
183 |
|
184 gImageCache->MarkUsed(entry->mData); |
|
185 |
|
186 *aSize = entry->mData->mSize; |
|
187 return entry->mData->mSourceSurface; |
|
188 } |
|
189 |
|
190 NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver) |
|
191 |
|
192 NS_IMETHODIMP |
|
193 CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject, |
|
194 const char *aTopic, |
|
195 const char16_t *aData) |
|
196 { |
|
197 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { |
|
198 delete gImageCache; |
|
199 gImageCache = nullptr; |
|
200 |
|
201 nsContentUtils::UnregisterShutdownObserver(this); |
|
202 } |
|
203 |
|
204 return NS_OK; |
|
205 } |
|
206 |
|
207 } // namespace mozilla |