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