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