/src/mozilla-central/image/Image.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 "Image.h" |
7 | | #include "Layers.h" // for LayerManager |
8 | | #include "nsRefreshDriver.h" |
9 | | #include "nsContentUtils.h" |
10 | | #include "mozilla/SizeOfState.h" |
11 | | #include "mozilla/TimeStamp.h" |
12 | | #include "mozilla/Tuple.h" // for Tie |
13 | | |
14 | | namespace mozilla { |
15 | | namespace image { |
16 | | |
17 | | /////////////////////////////////////////////////////////////////////////////// |
18 | | // Memory Reporting |
19 | | /////////////////////////////////////////////////////////////////////////////// |
20 | | |
21 | | ImageMemoryCounter::ImageMemoryCounter(Image* aImage, |
22 | | SizeOfState& aState, |
23 | | bool aIsUsed) |
24 | | : mIsUsed(aIsUsed) |
25 | 0 | { |
26 | 0 | MOZ_ASSERT(aImage); |
27 | 0 |
|
28 | 0 | // Extract metadata about the image. |
29 | 0 | nsCOMPtr<nsIURI> imageURL(aImage->GetURI()); |
30 | 0 | if (imageURL) { |
31 | 0 | imageURL->GetSpec(mURI); |
32 | 0 | } |
33 | 0 |
|
34 | 0 | int32_t width = 0; |
35 | 0 | int32_t height = 0; |
36 | 0 | aImage->GetWidth(&width); |
37 | 0 | aImage->GetHeight(&height); |
38 | 0 | mIntrinsicSize.SizeTo(width, height); |
39 | 0 |
|
40 | 0 | mType = aImage->GetType(); |
41 | 0 |
|
42 | 0 | // Populate memory counters for source and decoded data. |
43 | 0 | mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aState)); |
44 | 0 | aImage->CollectSizeOfSurfaces(mSurfaces, aState.mMallocSizeOf); |
45 | 0 |
|
46 | 0 | // Compute totals. |
47 | 0 | for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) { |
48 | 0 | mValues += surfaceCounter.Values(); |
49 | 0 | } |
50 | 0 | } |
51 | | |
52 | | |
53 | | /////////////////////////////////////////////////////////////////////////////// |
54 | | // Image Base Types |
55 | | /////////////////////////////////////////////////////////////////////////////// |
56 | | |
57 | | bool |
58 | | ImageResource::GetSpecTruncatedTo1k(nsCString& aSpec) const |
59 | 0 | { |
60 | 0 | static const size_t sMaxTruncatedLength = 1024; |
61 | 0 |
|
62 | 0 | mURI->GetSpec(aSpec); |
63 | 0 | if (sMaxTruncatedLength >= aSpec.Length()) { |
64 | 0 | return true; |
65 | 0 | } |
66 | 0 | |
67 | 0 | aSpec.Truncate(sMaxTruncatedLength); |
68 | 0 | return false; |
69 | 0 | } |
70 | | |
71 | | void |
72 | | ImageResource::SetCurrentImage(ImageContainer* aContainer, |
73 | | SourceSurface* aSurface, |
74 | | bool aInTransaction) |
75 | 0 | { |
76 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
77 | 0 | MOZ_ASSERT(aContainer); |
78 | 0 |
|
79 | 0 | if (!aSurface) { |
80 | 0 | // The OS threw out some or all of our buffer. We'll need to wait for the |
81 | 0 | // redecode (which was automatically triggered by GetFrame) to complete. |
82 | 0 | return; |
83 | 0 | } |
84 | 0 | |
85 | 0 | // |image| holds a reference to a SourceSurface which in turn holds a lock on |
86 | 0 | // the current frame's data buffer, ensuring that it doesn't get freed as |
87 | 0 | // long as the layer system keeps this ImageContainer alive. |
88 | 0 | RefPtr<layers::Image> image = new layers::SourceSurfaceImage(aSurface); |
89 | 0 |
|
90 | 0 | // We can share the producer ID with other containers because it is only |
91 | 0 | // used internally to validate the frames given to a particular container |
92 | 0 | // so that another object cannot add its own. Similarly the frame ID is |
93 | 0 | // only used internally to ensure it is always increasing, and skipping |
94 | 0 | // IDs from an individual container's perspective is acceptable. |
95 | 0 | AutoTArray<ImageContainer::NonOwningImage, 1> imageList; |
96 | 0 | imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(), |
97 | 0 | mLastFrameID++, |
98 | 0 | mImageProducerID)); |
99 | 0 |
|
100 | 0 | if (aInTransaction) { |
101 | 0 | aContainer->SetCurrentImagesInTransaction(imageList); |
102 | 0 | } else { |
103 | 0 | aContainer->SetCurrentImages(imageList); |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | ImgDrawResult |
108 | | ImageResource::GetImageContainerImpl(LayerManager* aManager, |
109 | | const IntSize& aSize, |
110 | | const Maybe<SVGImageContext>& aSVGContext, |
111 | | uint32_t aFlags, |
112 | | ImageContainer** aOutContainer) |
113 | 0 | { |
114 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
115 | 0 | MOZ_ASSERT(aManager); |
116 | 0 | MOZ_ASSERT((aFlags & ~(FLAG_SYNC_DECODE | |
117 | 0 | FLAG_SYNC_DECODE_IF_FAST | |
118 | 0 | FLAG_ASYNC_NOTIFY | |
119 | 0 | FLAG_HIGH_QUALITY_SCALING)) |
120 | 0 | == FLAG_NONE, |
121 | 0 | "Unsupported flag passed to GetImageContainer"); |
122 | 0 |
|
123 | 0 | ImgDrawResult drawResult; |
124 | 0 | IntSize size; |
125 | 0 | Tie(drawResult, size) = GetImageContainerSize(aManager, aSize, aFlags); |
126 | 0 | if (drawResult != ImgDrawResult::SUCCESS) { |
127 | 0 | return drawResult; |
128 | 0 | } |
129 | 0 | |
130 | 0 | MOZ_ASSERT(!size.IsEmpty()); |
131 | 0 |
|
132 | 0 | if (mAnimationConsumers == 0) { |
133 | 0 | SendOnUnlockedDraw(aFlags); |
134 | 0 | } |
135 | 0 |
|
136 | 0 | uint32_t flags = (aFlags & ~(FLAG_SYNC_DECODE | |
137 | 0 | FLAG_SYNC_DECODE_IF_FAST)) | FLAG_ASYNC_NOTIFY; |
138 | 0 | RefPtr<layers::ImageContainer> container; |
139 | 0 | ImageContainerEntry* entry = nullptr; |
140 | 0 | int i = mImageContainers.Length() - 1; |
141 | 0 | for (; i >= 0; --i) { |
142 | 0 | entry = &mImageContainers[i]; |
143 | 0 | container = entry->mContainer.get(); |
144 | 0 | if (size == entry->mSize && flags == entry->mFlags && |
145 | 0 | aSVGContext == entry->mSVGContext) { |
146 | 0 | // Lack of a container is handled below. |
147 | 0 | break; |
148 | 0 | } else if (!container) { |
149 | 0 | // Stop tracking if our weak pointer to the image container was freed. |
150 | 0 | mImageContainers.RemoveElementAt(i); |
151 | 0 | } else { |
152 | 0 | // It isn't a match, but still valid. Forget the container so we don't |
153 | 0 | // try to reuse it below. |
154 | 0 | container = nullptr; |
155 | 0 | } |
156 | 0 | } |
157 | 0 |
|
158 | 0 | if (container) { |
159 | 0 | switch (entry->mLastDrawResult) { |
160 | 0 | case ImgDrawResult::SUCCESS: |
161 | 0 | case ImgDrawResult::BAD_IMAGE: |
162 | 0 | case ImgDrawResult::BAD_ARGS: |
163 | 0 | case ImgDrawResult::NOT_SUPPORTED: |
164 | 0 | container.forget(aOutContainer); |
165 | 0 | return entry->mLastDrawResult; |
166 | 0 | case ImgDrawResult::NOT_READY: |
167 | 0 | case ImgDrawResult::INCOMPLETE: |
168 | 0 | case ImgDrawResult::TEMPORARY_ERROR: |
169 | 0 | // Temporary conditions where we need to rerequest the frame to recover. |
170 | 0 | break; |
171 | 0 | case ImgDrawResult::WRONG_SIZE: |
172 | 0 | // Unused by GetFrameInternal |
173 | 0 | default: |
174 | 0 | MOZ_ASSERT_UNREACHABLE("Unhandled ImgDrawResult type!"); |
175 | 0 | container.forget(aOutContainer); |
176 | 0 | return entry->mLastDrawResult; |
177 | 0 | } |
178 | 0 | } |
179 | 0 |
|
180 | | #ifdef DEBUG |
181 | | NotifyDrawingObservers(); |
182 | | #endif |
183 | | |
184 | 0 | IntSize bestSize; |
185 | 0 | RefPtr<SourceSurface> surface; |
186 | 0 | Tie(drawResult, bestSize, surface) = |
187 | 0 | GetFrameInternal(size, aSVGContext, FRAME_CURRENT, |
188 | 0 | aFlags | FLAG_ASYNC_NOTIFY); |
189 | 0 |
|
190 | 0 | // The requested size might be refused by the surface cache (i.e. due to |
191 | 0 | // factor-of-2 mode). In that case we don't want to create an entry for this |
192 | 0 | // specific size, but rather re-use the entry for the substituted size. |
193 | 0 | if (bestSize != size) { |
194 | 0 | MOZ_ASSERT(!bestSize.IsEmpty()); |
195 | 0 |
|
196 | 0 | // We can only remove the entry if we no longer have a container, because if |
197 | 0 | // there are strong references to it remaining, we need to still update it |
198 | 0 | // in UpdateImageContainer. |
199 | 0 | if (i >= 0 && !container) { |
200 | 0 | mImageContainers.RemoveElementAt(i); |
201 | 0 | } |
202 | 0 |
|
203 | 0 | // Forget about the stale container, if any. This lets the entry creation |
204 | 0 | // logic do its job below, if it turns out there is no existing best entry |
205 | 0 | // or the best entry doesn't have a container. |
206 | 0 | container = nullptr; |
207 | 0 |
|
208 | 0 | // We need to do the entry search again for the new size. We skip pruning |
209 | 0 | // because we did this above once already, but ImageContainer is threadsafe, |
210 | 0 | // so there is a remote possibility it got freed. |
211 | 0 | i = mImageContainers.Length() - 1; |
212 | 0 | for (; i >= 0; --i) { |
213 | 0 | entry = &mImageContainers[i]; |
214 | 0 | if (bestSize == entry->mSize && flags == entry->mFlags && |
215 | 0 | aSVGContext == entry->mSVGContext) { |
216 | 0 | container = entry->mContainer.get(); |
217 | 0 | if (container) { |
218 | 0 | switch (entry->mLastDrawResult) { |
219 | 0 | case ImgDrawResult::SUCCESS: |
220 | 0 | case ImgDrawResult::BAD_IMAGE: |
221 | 0 | case ImgDrawResult::BAD_ARGS: |
222 | 0 | case ImgDrawResult::NOT_SUPPORTED: |
223 | 0 | container.forget(aOutContainer); |
224 | 0 | return entry->mLastDrawResult; |
225 | 0 | case ImgDrawResult::NOT_READY: |
226 | 0 | case ImgDrawResult::INCOMPLETE: |
227 | 0 | case ImgDrawResult::TEMPORARY_ERROR: |
228 | 0 | // Temporary conditions where we need to rerequest the frame to |
229 | 0 | // recover. We have already done so! |
230 | 0 | break; |
231 | 0 | case ImgDrawResult::WRONG_SIZE: |
232 | 0 | // Unused by GetFrameInternal |
233 | 0 | default: |
234 | 0 | MOZ_ASSERT_UNREACHABLE("Unhandled DrawResult type!"); |
235 | 0 | container.forget(aOutContainer); |
236 | 0 | return entry->mLastDrawResult; |
237 | 0 | } |
238 | 0 | } |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | } |
242 | 0 | } |
243 | 0 |
|
244 | 0 | if (!container) { |
245 | 0 | // We need a new ImageContainer, so create one. |
246 | 0 | container = LayerManager::CreateImageContainer(); |
247 | 0 |
|
248 | 0 | if (i >= 0) { |
249 | 0 | entry->mContainer = container; |
250 | 0 | } else { |
251 | 0 | entry = mImageContainers.AppendElement( |
252 | 0 | ImageContainerEntry(bestSize, aSVGContext, container.get(), flags)); |
253 | 0 | } |
254 | 0 | } |
255 | 0 |
|
256 | 0 | SetCurrentImage(container, surface, true); |
257 | 0 | entry->mLastDrawResult = drawResult; |
258 | 0 | container.forget(aOutContainer); |
259 | 0 | return drawResult; |
260 | 0 | } |
261 | | |
262 | | void |
263 | | ImageResource::UpdateImageContainer() |
264 | 0 | { |
265 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
266 | 0 |
|
267 | 0 | for (int i = mImageContainers.Length() - 1; i >= 0; --i) { |
268 | 0 | ImageContainerEntry& entry = mImageContainers[i]; |
269 | 0 | RefPtr<ImageContainer> container = entry.mContainer.get(); |
270 | 0 | if (container) { |
271 | 0 | IntSize bestSize; |
272 | 0 | RefPtr<SourceSurface> surface; |
273 | 0 | Tie(entry.mLastDrawResult, bestSize, surface) = |
274 | 0 | GetFrameInternal(entry.mSize, entry.mSVGContext, |
275 | 0 | FRAME_CURRENT, entry.mFlags); |
276 | 0 |
|
277 | 0 | // It is possible that this is a factor-of-2 substitution. Since we |
278 | 0 | // managed to convert the weak reference into a strong reference, that |
279 | 0 | // means that an imagelib user still is holding onto the container. thus |
280 | 0 | // we cannot consolidate and must keep updating the duplicate container. |
281 | 0 | SetCurrentImage(container, surface, false); |
282 | 0 | } else { |
283 | 0 | // Stop tracking if our weak pointer to the image container was freed. |
284 | 0 | mImageContainers.RemoveElementAt(i); |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | | |
289 | | void |
290 | | ImageResource::ReleaseImageContainer() |
291 | 0 | { |
292 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
293 | 0 | mImageContainers.Clear(); |
294 | 0 | } |
295 | | |
296 | | // Constructor |
297 | | ImageResource::ImageResource(nsIURI* aURI) : |
298 | | mURI(aURI), |
299 | | mInnerWindowId(0), |
300 | | mAnimationConsumers(0), |
301 | | mAnimationMode(kNormalAnimMode), |
302 | | mInitialized(false), |
303 | | mAnimating(false), |
304 | | mError(false), |
305 | | mImageProducerID(ImageContainer::AllocateProducerID()), |
306 | | mLastFrameID(0) |
307 | 0 | { } |
308 | | |
309 | | ImageResource::~ImageResource() |
310 | 0 | { |
311 | 0 | // Ask our ProgressTracker to drop its weak reference to us. |
312 | 0 | mProgressTracker->ResetImage(); |
313 | 0 | } |
314 | | |
315 | | void |
316 | | ImageResource::IncrementAnimationConsumers() |
317 | 0 | { |
318 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " |
319 | 0 | "with DecrementAnimationConsumers"); |
320 | 0 | mAnimationConsumers++; |
321 | 0 | } |
322 | | |
323 | | void |
324 | | ImageResource::DecrementAnimationConsumers() |
325 | 0 | { |
326 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization " |
327 | 0 | "with IncrementAnimationConsumers"); |
328 | 0 | MOZ_ASSERT(mAnimationConsumers >= 1, |
329 | 0 | "Invalid no. of animation consumers!"); |
330 | 0 | mAnimationConsumers--; |
331 | 0 | } |
332 | | |
333 | | nsresult |
334 | | ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode) |
335 | 0 | { |
336 | 0 | if (mError) { |
337 | 0 | return NS_ERROR_FAILURE; |
338 | 0 | } |
339 | 0 | |
340 | 0 | NS_ENSURE_ARG_POINTER(aAnimationMode); |
341 | 0 |
|
342 | 0 | *aAnimationMode = mAnimationMode; |
343 | 0 | return NS_OK; |
344 | 0 | } |
345 | | |
346 | | nsresult |
347 | | ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode) |
348 | 0 | { |
349 | 0 | if (mError) { |
350 | 0 | return NS_ERROR_FAILURE; |
351 | 0 | } |
352 | 0 | |
353 | 0 | NS_ASSERTION(aAnimationMode == kNormalAnimMode || |
354 | 0 | aAnimationMode == kDontAnimMode || |
355 | 0 | aAnimationMode == kLoopOnceAnimMode, |
356 | 0 | "Wrong Animation Mode is being set!"); |
357 | 0 |
|
358 | 0 | mAnimationMode = aAnimationMode; |
359 | 0 |
|
360 | 0 | return NS_OK; |
361 | 0 | } |
362 | | |
363 | | bool |
364 | | ImageResource::HadRecentRefresh(const TimeStamp& aTime) |
365 | 0 | { |
366 | 0 | // Our threshold for "recent" is 1/2 of the default refresh-driver interval. |
367 | 0 | // This ensures that we allow for frame rates at least as fast as the |
368 | 0 | // refresh driver's default rate. |
369 | 0 | static TimeDuration recentThreshold = |
370 | 0 | TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0); |
371 | 0 |
|
372 | 0 | if (!mLastRefreshTime.IsNull() && |
373 | 0 | aTime - mLastRefreshTime < recentThreshold) { |
374 | 0 | return true; |
375 | 0 | } |
376 | 0 | |
377 | 0 | // else, we can proceed with a refresh. |
378 | 0 | // But first, update our last refresh time: |
379 | 0 | mLastRefreshTime = aTime; |
380 | 0 | return false; |
381 | 0 | } |
382 | | |
383 | | void |
384 | | ImageResource::EvaluateAnimation() |
385 | 0 | { |
386 | 0 | if (!mAnimating && ShouldAnimate()) { |
387 | 0 | nsresult rv = StartAnimation(); |
388 | 0 | mAnimating = NS_SUCCEEDED(rv); |
389 | 0 | } else if (mAnimating && !ShouldAnimate()) { |
390 | 0 | StopAnimation(); |
391 | 0 | } |
392 | 0 | } |
393 | | |
394 | | void |
395 | | ImageResource::SendOnUnlockedDraw(uint32_t aFlags) |
396 | 0 | { |
397 | 0 | if (!mProgressTracker) { |
398 | 0 | return; |
399 | 0 | } |
400 | 0 | |
401 | 0 | if (!(aFlags & FLAG_ASYNC_NOTIFY)) { |
402 | 0 | mProgressTracker->OnUnlockedDraw(); |
403 | 0 | } else { |
404 | 0 | NotNull<RefPtr<ImageResource>> image = WrapNotNull(this); |
405 | 0 | nsCOMPtr<nsIEventTarget> eventTarget = mProgressTracker->GetEventTarget(); |
406 | 0 | nsCOMPtr<nsIRunnable> ev = NS_NewRunnableFunction( |
407 | 0 | "image::ImageResource::SendOnUnlockedDraw", [=]() -> void { |
408 | 0 | RefPtr<ProgressTracker> tracker = image->GetProgressTracker(); |
409 | 0 | if (tracker) { |
410 | 0 | tracker->OnUnlockedDraw(); |
411 | 0 | } |
412 | 0 | }); |
413 | 0 | eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL); |
414 | 0 | } |
415 | 0 | } |
416 | | |
417 | | #ifdef DEBUG |
418 | | void |
419 | | ImageResource::NotifyDrawingObservers() |
420 | | { |
421 | | if (!mURI || !NS_IsMainThread()) { |
422 | | return; |
423 | | } |
424 | | |
425 | | bool match = false; |
426 | | if ((NS_FAILED(mURI->SchemeIs("resource", &match)) || !match) && |
427 | | (NS_FAILED(mURI->SchemeIs("chrome", &match)) || !match)) { |
428 | | return; |
429 | | } |
430 | | |
431 | | // Record the image drawing for startup performance testing. |
432 | | nsCOMPtr<nsIURI> uri = mURI; |
433 | | nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( |
434 | | "image::ImageResource::NotifyDrawingObservers", [uri]() { |
435 | | nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); |
436 | | NS_WARNING_ASSERTION(obs, "Can't get an observer service handle"); |
437 | | if (obs) { |
438 | | nsAutoCString spec; |
439 | | uri->GetSpec(spec); |
440 | | obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get()); |
441 | | } |
442 | | })); |
443 | | } |
444 | | #endif |
445 | | |
446 | | } // namespace image |
447 | | } // namespace mozilla |