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/. */
8 #include "imgRequest.h"
9 #include "Layers.h" // for LayerManager
10 #include "nsIObserverService.h"
11 #include "nsRefreshDriver.h"
12 #include "nsContentUtils.h"
13 #include "mozilla/gfx/Point.h"
14 #include "mozilla/gfx/Rect.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/SizeOfState.h"
17 #include "mozilla/TimeStamp.h"
18 #include "mozilla/Tuple.h" // for Tie
19 #include "mozilla/layers/SharedSurfacesChild.h"
24 ///////////////////////////////////////////////////////////////////////////////
26 ///////////////////////////////////////////////////////////////////////////////
28 ImageMemoryCounter::ImageMemoryCounter(imgRequest
* aRequest
,
29 SizeOfState
& aState
, bool aIsUsed
)
30 : mProgress(UINT32_MAX
),
37 // We don't have the image object yet, but we can get some information.
38 nsCOMPtr
<nsIURI
> imageURL
;
39 nsresult rv
= aRequest
->GetURI(getter_AddRefs(imageURL
));
40 if (NS_SUCCEEDED(rv
) && imageURL
) {
41 imageURL
->GetSpec(mURI
);
44 mType
= imgIContainer::TYPE_REQUEST
;
45 mHasError
= NS_FAILED(aRequest
->GetImageErrorCode());
46 mValidating
= !!aRequest
->GetValidator();
48 RefPtr
<ProgressTracker
> tracker
= aRequest
->GetProgressTracker();
50 mProgress
= tracker
->GetProgress();
54 ImageMemoryCounter::ImageMemoryCounter(imgRequest
* aRequest
, Image
* aImage
,
55 SizeOfState
& aState
, bool aIsUsed
)
56 : mProgress(UINT32_MAX
),
64 // Extract metadata about the image.
65 nsCOMPtr
<nsIURI
> imageURL(aImage
->GetURI());
67 imageURL
->GetSpec(mURI
);
72 aImage
->GetWidth(&width
);
73 aImage
->GetHeight(&height
);
74 mIntrinsicSize
.SizeTo(width
, height
);
76 mType
= aImage
->GetType();
77 mHasError
= aImage
->HasError();
78 mValidating
= !!aRequest
->GetValidator();
80 RefPtr
<ProgressTracker
> tracker
= aImage
->GetProgressTracker();
82 mProgress
= tracker
->GetProgress();
85 // Populate memory counters for source and decoded data.
86 mValues
.SetSource(aImage
->SizeOfSourceWithComputedFallback(aState
));
87 aImage
->CollectSizeOfSurfaces(mSurfaces
, aState
.mMallocSizeOf
);
90 for (const SurfaceMemoryCounter
& surfaceCounter
: mSurfaces
) {
91 mValues
+= surfaceCounter
.Values();
95 ///////////////////////////////////////////////////////////////////////////////
97 ///////////////////////////////////////////////////////////////////////////////
99 bool ImageResource::GetSpecTruncatedTo1k(nsCString
& aSpec
) const {
100 static const size_t sMaxTruncatedLength
= 1024;
102 mURI
->GetSpec(aSpec
);
103 if (sMaxTruncatedLength
>= aSpec
.Length()) {
107 aSpec
.Truncate(sMaxTruncatedLength
);
111 void ImageResource::SetCurrentImage(layers::ImageContainer
* aContainer
,
112 gfx::SourceSurface
* aSurface
,
113 const Maybe
<gfx::IntRect
>& aDirtyRect
) {
114 MOZ_ASSERT(NS_IsMainThread());
115 MOZ_ASSERT(aContainer
);
118 // The OS threw out some or all of our buffer. We'll need to wait for the
119 // redecode (which was automatically triggered by GetFrame) to complete.
123 // |image| holds a reference to a SourceSurface which in turn holds a lock on
124 // the current frame's data buffer, ensuring that it doesn't get freed as
125 // long as the layer system keeps this ImageContainer alive.
126 RefPtr
<layers::Image
> image
= new layers::SourceSurfaceImage(aSurface
);
128 // We can share the producer ID with other containers because it is only
129 // used internally to validate the frames given to a particular container
130 // so that another object cannot add its own. Similarly the frame ID is
131 // only used internally to ensure it is always increasing, and skipping
132 // IDs from an individual container's perspective is acceptable.
133 AutoTArray
<layers::ImageContainer::NonOwningImage
, 1> imageList
;
134 imageList
.AppendElement(layers::ImageContainer::NonOwningImage(
135 image
, TimeStamp(), mLastFrameID
++, mImageProducerID
));
138 aContainer
->SetCurrentImagesInTransaction(imageList
);
140 aContainer
->SetCurrentImages(imageList
);
143 // If we are animated, then we should request that the image container be
144 // treated as such, to avoid display list rebuilding to update frames for
146 if (mProgressTracker
->GetProgress() & FLAG_IS_ANIMATED
) {
148 layers::SharedSurfacesChild::UpdateAnimation(aContainer
, aSurface
,
151 gfx::IntRect
dirtyRect(gfx::IntPoint(0, 0), aSurface
->GetSize());
152 layers::SharedSurfacesChild::UpdateAnimation(aContainer
, aSurface
,
158 ImgDrawResult
ImageResource::GetImageContainerImpl(
159 layers::LayerManager
* aManager
, const gfx::IntSize
& aSize
,
160 const Maybe
<SVGImageContext
>& aSVGContext
, uint32_t aFlags
,
161 layers::ImageContainer
** aOutContainer
) {
162 MOZ_ASSERT(NS_IsMainThread());
163 MOZ_ASSERT(aManager
);
165 (aFlags
& ~(FLAG_SYNC_DECODE
| FLAG_SYNC_DECODE_IF_FAST
|
166 FLAG_ASYNC_NOTIFY
| FLAG_HIGH_QUALITY_SCALING
)) == FLAG_NONE
,
167 "Unsupported flag passed to GetImageContainer");
169 ImgDrawResult drawResult
;
171 Tie(drawResult
, size
) = GetImageContainerSize(aManager
, aSize
, aFlags
);
172 if (drawResult
!= ImgDrawResult::SUCCESS
) {
176 MOZ_ASSERT(!size
.IsEmpty());
178 if (mAnimationConsumers
== 0) {
179 SendOnUnlockedDraw(aFlags
);
182 uint32_t flags
= (aFlags
& ~(FLAG_SYNC_DECODE
| FLAG_SYNC_DECODE_IF_FAST
)) |
184 RefPtr
<layers::ImageContainer
> container
;
185 ImageContainerEntry
* entry
= nullptr;
186 int i
= mImageContainers
.Length() - 1;
187 for (; i
>= 0; --i
) {
188 entry
= &mImageContainers
[i
];
189 if (size
== entry
->mSize
&& flags
== entry
->mFlags
&&
190 aSVGContext
== entry
->mSVGContext
) {
191 // Lack of a container is handled below.
192 container
= RefPtr
<layers::ImageContainer
>(entry
->mContainer
);
194 } else if (!entry
->mContainer
) {
195 // Stop tracking if our weak pointer to the image container was freed.
196 mImageContainers
.RemoveElementAt(i
);
201 switch (entry
->mLastDrawResult
) {
202 case ImgDrawResult::SUCCESS
:
203 case ImgDrawResult::BAD_IMAGE
:
204 case ImgDrawResult::BAD_ARGS
:
205 case ImgDrawResult::NOT_SUPPORTED
:
206 container
.forget(aOutContainer
);
207 return entry
->mLastDrawResult
;
208 case ImgDrawResult::NOT_READY
:
209 case ImgDrawResult::INCOMPLETE
:
210 case ImgDrawResult::TEMPORARY_ERROR
:
211 // Temporary conditions where we need to rerequest the frame to recover.
213 case ImgDrawResult::WRONG_SIZE
:
214 // Unused by GetFrameInternal
216 MOZ_ASSERT_UNREACHABLE("Unhandled ImgDrawResult type!");
217 container
.forget(aOutContainer
);
218 return entry
->mLastDrawResult
;
223 NotifyDrawingObservers();
226 gfx::IntSize bestSize
;
227 RefPtr
<gfx::SourceSurface
> surface
;
228 Tie(drawResult
, bestSize
, surface
) = GetFrameInternal(
229 size
, aSVGContext
, FRAME_CURRENT
, aFlags
| FLAG_ASYNC_NOTIFY
);
231 // The requested size might be refused by the surface cache (i.e. due to
232 // factor-of-2 mode). In that case we don't want to create an entry for this
233 // specific size, but rather re-use the entry for the substituted size.
234 if (bestSize
!= size
) {
235 MOZ_ASSERT(!bestSize
.IsEmpty());
237 // We can only remove the entry if we no longer have a container, because if
238 // there are strong references to it remaining, we need to still update it
239 // in UpdateImageContainer.
240 if (i
>= 0 && !container
) {
241 mImageContainers
.RemoveElementAt(i
);
244 // Forget about the stale container, if any. This lets the entry creation
245 // logic do its job below, if it turns out there is no existing best entry
246 // or the best entry doesn't have a container.
249 // We need to do the entry search again for the new size. We skip pruning
250 // because we did this above once already, but ImageContainer is threadsafe,
251 // so there is a remote possibility it got freed.
252 i
= mImageContainers
.Length() - 1;
253 for (; i
>= 0; --i
) {
254 entry
= &mImageContainers
[i
];
255 if (bestSize
== entry
->mSize
&& flags
== entry
->mFlags
&&
256 aSVGContext
== entry
->mSVGContext
) {
257 container
= RefPtr
<layers::ImageContainer
>(entry
->mContainer
);
259 switch (entry
->mLastDrawResult
) {
260 case ImgDrawResult::SUCCESS
:
261 case ImgDrawResult::BAD_IMAGE
:
262 case ImgDrawResult::BAD_ARGS
:
263 case ImgDrawResult::NOT_SUPPORTED
:
264 container
.forget(aOutContainer
);
265 return entry
->mLastDrawResult
;
266 case ImgDrawResult::NOT_READY
:
267 case ImgDrawResult::INCOMPLETE
:
268 case ImgDrawResult::TEMPORARY_ERROR
:
269 // Temporary conditions where we need to rerequest the frame to
270 // recover. We have already done so!
272 case ImgDrawResult::WRONG_SIZE
:
273 // Unused by GetFrameInternal
275 MOZ_ASSERT_UNREACHABLE("Unhandled DrawResult type!");
276 container
.forget(aOutContainer
);
277 return entry
->mLastDrawResult
;
286 // We need a new ImageContainer, so create one.
287 container
= layers::LayerManager::CreateImageContainer();
290 entry
->mContainer
= container
;
292 entry
= mImageContainers
.AppendElement(
293 ImageContainerEntry(bestSize
, aSVGContext
, container
.get(), flags
));
297 SetCurrentImage(container
, surface
, Nothing());
298 entry
->mLastDrawResult
= drawResult
;
299 container
.forget(aOutContainer
);
303 bool ImageResource::UpdateImageContainer(
304 const Maybe
<gfx::IntRect
>& aDirtyRect
) {
305 MOZ_ASSERT(NS_IsMainThread());
307 for (int i
= mImageContainers
.Length() - 1; i
>= 0; --i
) {
308 ImageContainerEntry
& entry
= mImageContainers
[i
];
309 RefPtr
<layers::ImageContainer
> container(entry
.mContainer
);
311 gfx::IntSize bestSize
;
312 RefPtr
<gfx::SourceSurface
> surface
;
313 Tie(entry
.mLastDrawResult
, bestSize
, surface
) = GetFrameInternal(
314 entry
.mSize
, entry
.mSVGContext
, FRAME_CURRENT
, entry
.mFlags
);
316 // It is possible that this is a factor-of-2 substitution. Since we
317 // managed to convert the weak reference into a strong reference, that
318 // means that an imagelib user still is holding onto the container. thus
319 // we cannot consolidate and must keep updating the duplicate container.
321 SetCurrentImage(container
, surface
, aDirtyRect
);
323 gfx::IntRect
dirtyRect(gfx::IntPoint(0, 0), bestSize
);
324 SetCurrentImage(container
, surface
, Some(dirtyRect
));
327 // Stop tracking if our weak pointer to the image container was freed.
328 mImageContainers
.RemoveElementAt(i
);
332 return !mImageContainers
.IsEmpty();
335 void ImageResource::ReleaseImageContainer() {
336 MOZ_ASSERT(NS_IsMainThread());
337 mImageContainers
.Clear();
341 ImageResource::ImageResource(nsIURI
* aURI
)
344 mAnimationConsumers(0),
345 mAnimationMode(kNormalAnimMode
),
349 mImageProducerID(layers::ImageContainer::AllocateProducerID()),
352 ImageResource::~ImageResource() {
353 // Ask our ProgressTracker to drop its weak reference to us.
354 mProgressTracker
->ResetImage();
357 void ImageResource::IncrementAnimationConsumers() {
358 MOZ_ASSERT(NS_IsMainThread(),
359 "Main thread only to encourage serialization "
360 "with DecrementAnimationConsumers");
361 mAnimationConsumers
++;
364 void ImageResource::DecrementAnimationConsumers() {
365 MOZ_ASSERT(NS_IsMainThread(),
366 "Main thread only to encourage serialization "
367 "with IncrementAnimationConsumers");
368 MOZ_ASSERT(mAnimationConsumers
>= 1, "Invalid no. of animation consumers!");
369 mAnimationConsumers
--;
372 nsresult
ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode
) {
374 return NS_ERROR_FAILURE
;
377 NS_ENSURE_ARG_POINTER(aAnimationMode
);
379 *aAnimationMode
= mAnimationMode
;
383 nsresult
ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode
) {
385 return NS_ERROR_FAILURE
;
388 NS_ASSERTION(aAnimationMode
== kNormalAnimMode
||
389 aAnimationMode
== kDontAnimMode
||
390 aAnimationMode
== kLoopOnceAnimMode
,
391 "Wrong Animation Mode is being set!");
393 mAnimationMode
= aAnimationMode
;
398 bool ImageResource::HadRecentRefresh(const TimeStamp
& aTime
) {
399 // Our threshold for "recent" is 1/2 of the default refresh-driver interval.
400 // This ensures that we allow for frame rates at least as fast as the
401 // refresh driver's default rate.
402 static TimeDuration recentThreshold
=
403 TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0);
405 if (!mLastRefreshTime
.IsNull() &&
406 aTime
- mLastRefreshTime
< recentThreshold
) {
410 // else, we can proceed with a refresh.
411 // But first, update our last refresh time:
412 mLastRefreshTime
= aTime
;
416 void ImageResource::EvaluateAnimation() {
417 if (!mAnimating
&& ShouldAnimate()) {
418 nsresult rv
= StartAnimation();
419 mAnimating
= NS_SUCCEEDED(rv
);
420 } else if (mAnimating
&& !ShouldAnimate()) {
425 void ImageResource::SendOnUnlockedDraw(uint32_t aFlags
) {
426 if (!mProgressTracker
) {
430 if (!(aFlags
& FLAG_ASYNC_NOTIFY
)) {
431 mProgressTracker
->OnUnlockedDraw();
433 NotNull
<RefPtr
<ImageResource
>> image
= WrapNotNull(this);
434 nsCOMPtr
<nsIEventTarget
> eventTarget
= mProgressTracker
->GetEventTarget();
435 nsCOMPtr
<nsIRunnable
> ev
= NS_NewRunnableFunction(
436 "image::ImageResource::SendOnUnlockedDraw", [=]() -> void {
437 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
439 tracker
->OnUnlockedDraw();
442 eventTarget
->Dispatch(CreateMediumHighRunnable(ev
.forget()),
448 void ImageResource::NotifyDrawingObservers() {
449 if (!mURI
|| !NS_IsMainThread()) {
453 if (!mURI
->SchemeIs("resource") && !mURI
->SchemeIs("chrome")) {
457 // Record the image drawing for startup performance testing.
458 nsCOMPtr
<nsIURI
> uri
= mURI
;
459 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
460 "image::ImageResource::NotifyDrawingObservers", [uri
]() {
461 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
462 NS_WARNING_ASSERTION(obs
, "Can't get an observer service handle");
466 obs
->NotifyObservers(nullptr, "image-drawing",
467 NS_ConvertUTF8toUTF16(spec
).get());
474 } // namespace mozilla