1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ImageLogging.h"
8 #include "ProgressTracker.h"
10 #include "imgINotificationObserver.h"
11 #include "imgIRequest.h"
13 #include "nsNetUtil.h"
14 #include "nsIObserverService.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/Services.h"
19 using mozilla::WeakPtr
;
24 static void CheckProgressConsistency(Progress aOldProgress
,
25 Progress aNewProgress
, bool aIsMultipart
) {
26 // Check preconditions for every progress bit.
28 // Error's do not get propagated from the tracker for each image part to the
29 // tracker for the multipart image because we don't want one bad part to
30 // prevent the remaining parts from showing. So we need to consider whether
31 // this is a tracker for a multipart image for these assertions to work.
33 if (aNewProgress
& FLAG_SIZE_AVAILABLE
) {
36 if (aNewProgress
& FLAG_DECODE_COMPLETE
) {
37 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
38 MOZ_ASSERT(aIsMultipart
||
39 aNewProgress
& (FLAG_FRAME_COMPLETE
| FLAG_HAS_ERROR
));
41 if (aNewProgress
& FLAG_FRAME_COMPLETE
) {
42 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
44 if (aNewProgress
& FLAG_LOAD_COMPLETE
) {
45 MOZ_ASSERT(aIsMultipart
||
46 aNewProgress
& (FLAG_SIZE_AVAILABLE
| FLAG_HAS_ERROR
));
48 if (aNewProgress
& FLAG_IS_ANIMATED
) {
49 // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
50 // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
53 if (aNewProgress
& FLAG_HAS_TRANSPARENCY
) {
54 // XXX We'd like to assert that transparency is only set during metadata
55 // decode but we don't have any way to assert that until bug 1254892 is
58 if (aNewProgress
& FLAG_LAST_PART_COMPLETE
) {
59 MOZ_ASSERT(aNewProgress
& FLAG_LOAD_COMPLETE
);
61 if (aNewProgress
& FLAG_HAS_ERROR
) {
66 ProgressTracker::ProgressTracker()
67 : mMutex("ProgressTracker::mMutex"),
69 mEventTarget(WrapNotNull(
70 nsCOMPtr
<nsIEventTarget
>(GetMainThreadSerialEventTarget()))),
71 mObserversWithTargets(0),
72 mObservers(new ObserverTable
),
73 mProgress(NoProgress
),
74 mIsMultipart(false) {}
76 void ProgressTracker::SetImage(Image
* aImage
) {
77 MutexAutoLock
lock(mMutex
);
78 MOZ_ASSERT(aImage
, "Setting null image");
79 MOZ_ASSERT(!mImage
, "Setting image when we already have one");
83 void ProgressTracker::ResetImage() {
84 MutexAutoLock
lock(mMutex
);
85 MOZ_ASSERT(mImage
, "Resetting image when it's already null!");
89 uint32_t ProgressTracker::GetImageStatus() const {
90 uint32_t status
= imgIRequest::STATUS_NONE
;
92 // Translate our current state to a set of imgIRequest::STATE_* flags.
93 if (mProgress
& FLAG_SIZE_AVAILABLE
) {
94 status
|= imgIRequest::STATUS_SIZE_AVAILABLE
;
96 if (mProgress
& FLAG_DECODE_COMPLETE
) {
97 status
|= imgIRequest::STATUS_DECODE_COMPLETE
;
99 if (mProgress
& FLAG_FRAME_COMPLETE
) {
100 status
|= imgIRequest::STATUS_FRAME_COMPLETE
;
102 if (mProgress
& FLAG_LOAD_COMPLETE
) {
103 status
|= imgIRequest::STATUS_LOAD_COMPLETE
;
105 if (mProgress
& FLAG_IS_ANIMATED
) {
106 status
|= imgIRequest::STATUS_IS_ANIMATED
;
108 if (mProgress
& FLAG_HAS_TRANSPARENCY
) {
109 status
|= imgIRequest::STATUS_HAS_TRANSPARENCY
;
111 if (mProgress
& FLAG_HAS_ERROR
) {
112 status
|= imgIRequest::STATUS_ERROR
;
118 // A helper class to allow us to call SyncNotify asynchronously.
119 class AsyncNotifyRunnable
: public Runnable
{
121 AsyncNotifyRunnable(ProgressTracker
* aTracker
, IProgressObserver
* aObserver
)
122 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker
) {
123 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
124 MOZ_ASSERT(aTracker
, "aTracker should not be null");
125 MOZ_ASSERT(aObserver
, "aObserver should not be null");
126 mObservers
.AppendElement(aObserver
);
129 NS_IMETHOD
Run() override
{
130 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
131 MOZ_ASSERT(mTracker
, "mTracker should not be null");
132 for (uint32_t i
= 0; i
< mObservers
.Length(); ++i
) {
133 mObservers
[i
]->ClearPendingNotify();
134 mTracker
->SyncNotify(mObservers
[i
]);
137 mTracker
->mRunnable
= nullptr;
141 void AddObserver(IProgressObserver
* aObserver
) {
142 mObservers
.AppendElement(aObserver
);
145 void RemoveObserver(IProgressObserver
* aObserver
) {
146 mObservers
.RemoveElement(aObserver
);
150 friend class ProgressTracker
;
152 RefPtr
<ProgressTracker
> mTracker
;
153 nsTArray
<RefPtr
<IProgressObserver
>> mObservers
;
156 ProgressTracker::RenderBlockingRunnable::RenderBlockingRunnable(
157 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
)
158 : PrioritizableRunnable(std::move(aEvent
),
159 nsIRunnablePriority::PRIORITY_RENDER_BLOCKING
) {}
161 void ProgressTracker::RenderBlockingRunnable::AddObserver(
162 IProgressObserver
* aObserver
) {
163 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->AddObserver(aObserver
);
166 void ProgressTracker::RenderBlockingRunnable::RemoveObserver(
167 IProgressObserver
* aObserver
) {
168 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->RemoveObserver(aObserver
);
172 already_AddRefed
<ProgressTracker::RenderBlockingRunnable
>
173 ProgressTracker::RenderBlockingRunnable::Create(
174 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
) {
175 MOZ_ASSERT(NS_IsMainThread());
176 RefPtr
<ProgressTracker::RenderBlockingRunnable
> event(
177 new ProgressTracker::RenderBlockingRunnable(std::move(aEvent
)));
178 return event
.forget();
181 void ProgressTracker::Notify(IProgressObserver
* aObserver
) {
182 MOZ_ASSERT(NS_IsMainThread());
184 if (aObserver
->NotificationsDeferred()) {
185 // There is a pending notification, or the observer isn't ready yet.
189 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
190 RefPtr
<Image
> image
= GetImage();
191 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::Notify async", "uri", image
);
194 aObserver
->MarkPendingNotify();
196 // If we have an existing runnable that we can use, we just append this
197 // observer to its list of observers to be notified. This ensures we don't
198 // unnecessarily delay onload.
200 mRunnable
->AddObserver(aObserver
);
202 RefPtr
<AsyncNotifyRunnable
> ev
= new AsyncNotifyRunnable(this, aObserver
);
203 mRunnable
= ProgressTracker::RenderBlockingRunnable::Create(ev
.forget());
204 mEventTarget
->Dispatch(mRunnable
, NS_DISPATCH_NORMAL
);
208 // A helper class to allow us to call SyncNotify asynchronously for a given,
210 class AsyncNotifyCurrentStateRunnable
: public Runnable
{
212 AsyncNotifyCurrentStateRunnable(ProgressTracker
* aProgressTracker
,
213 IProgressObserver
* aObserver
)
214 : Runnable("image::AsyncNotifyCurrentStateRunnable"),
215 mProgressTracker(aProgressTracker
),
216 mObserver(aObserver
) {
217 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
218 MOZ_ASSERT(mProgressTracker
, "mProgressTracker should not be null");
219 MOZ_ASSERT(mObserver
, "mObserver should not be null");
220 mImage
= mProgressTracker
->GetImage();
223 NS_IMETHOD
Run() override
{
224 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
225 mObserver
->ClearPendingNotify();
227 mProgressTracker
->SyncNotify(mObserver
);
232 RefPtr
<ProgressTracker
> mProgressTracker
;
233 RefPtr
<IProgressObserver
> mObserver
;
235 // We have to hold on to a reference to the tracker's image, just in case
236 // it goes away while we're in the event queue.
237 RefPtr
<Image
> mImage
;
240 void ProgressTracker::NotifyCurrentState(IProgressObserver
* aObserver
) {
241 MOZ_ASSERT(NS_IsMainThread());
243 if (aObserver
->NotificationsDeferred()) {
244 // There is a pending notification, or the observer isn't ready yet.
248 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
249 RefPtr
<Image
> image
= GetImage();
250 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::NotifyCurrentState", "uri",
254 aObserver
->MarkPendingNotify();
256 nsCOMPtr
<nsIRunnable
> ev
=
257 new AsyncNotifyCurrentStateRunnable(this, aObserver
);
258 mEventTarget
->Dispatch(ev
.forget(), NS_DISPATCH_NORMAL
);
262 * ImageObserverNotifier is a helper type that abstracts over the difference
263 * between sending notifications to all of the observers in an ObserverTable,
264 * and sending them to a single observer. This allows the same notification code
265 * to be used for both cases.
267 template <typename T
>
268 struct ImageObserverNotifier
;
271 struct MOZ_STACK_CLASS ImageObserverNotifier
<const ObserverTable
*> {
272 explicit ImageObserverNotifier(const ObserverTable
* aObservers
,
273 bool aIgnoreDeferral
= false)
274 : mObservers(aObservers
), mIgnoreDeferral(aIgnoreDeferral
) {}
276 template <typename Lambda
>
277 void operator()(Lambda aFunc
) {
278 for (const auto& weakObserver
: mObservers
->Values()) {
279 RefPtr
<IProgressObserver
> observer
= weakObserver
.get();
280 if (observer
&& (mIgnoreDeferral
|| !observer
->NotificationsDeferred())) {
287 const ObserverTable
* mObservers
;
288 const bool mIgnoreDeferral
;
292 struct MOZ_STACK_CLASS ImageObserverNotifier
<IProgressObserver
*> {
293 explicit ImageObserverNotifier(IProgressObserver
* aObserver
)
294 : mObserver(aObserver
) {}
296 template <typename Lambda
>
297 void operator()(Lambda aFunc
) {
298 if (mObserver
&& !mObserver
->NotificationsDeferred()) {
304 IProgressObserver
* mObserver
;
307 template <typename T
>
308 void SyncNotifyInternal(const T
& aObservers
, bool aHasImage
, Progress aProgress
,
309 const nsIntRect
& aDirtyRect
) {
310 MOZ_ASSERT(NS_IsMainThread());
312 typedef imgINotificationObserver I
;
313 ImageObserverNotifier
<T
> notify(aObservers
);
315 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
316 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::SIZE_AVAILABLE
); });
321 // If there's any content in this frame at all (always true for
322 // vector images, true for raster images that have decoded at
323 // least one frame) then send OnFrameUpdate.
324 if (!aDirtyRect
.IsEmpty()) {
325 notify([&](IProgressObserver
* aObs
) {
326 aObs
->Notify(I::FRAME_UPDATE
, &aDirtyRect
);
330 if (aProgress
& FLAG_FRAME_COMPLETE
) {
331 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::FRAME_COMPLETE
); });
334 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
336 [](IProgressObserver
* aObs
) { aObs
->Notify(I::HAS_TRANSPARENCY
); });
339 if (aProgress
& FLAG_IS_ANIMATED
) {
340 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::IS_ANIMATED
); });
344 if (aProgress
& FLAG_DECODE_COMPLETE
) {
345 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
346 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::DECODE_COMPLETE
); });
349 if (aProgress
& FLAG_LOAD_COMPLETE
) {
350 notify([=](IProgressObserver
* aObs
) {
351 aObs
->OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
);
356 void ProgressTracker::SyncNotifyProgress(Progress aProgress
,
357 const nsIntRect
& aInvalidRect
358 /* = nsIntRect() */) {
359 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
361 Progress progress
= Difference(aProgress
);
362 CheckProgressConsistency(mProgress
, mProgress
| progress
, mIsMultipart
);
364 // Apply the changes.
365 mProgress
|= progress
;
367 // Send notifications.
368 mObservers
.Read([&](const ObserverTable
* aTable
) {
369 SyncNotifyInternal(aTable
, HasImage(), progress
, aInvalidRect
);
372 if (progress
& FLAG_HAS_ERROR
) {
373 FireFailureNotification();
377 void ProgressTracker::SyncNotify(IProgressObserver
* aObserver
) {
378 MOZ_ASSERT(NS_IsMainThread());
380 RefPtr
<Image
> image
= GetImage();
381 LOG_SCOPE_WITH_PARAM(gImgLog
, "ProgressTracker::SyncNotify", "uri", image
);
385 int32_t width
, height
;
386 if (NS_FAILED(image
->GetWidth(&width
)) ||
387 NS_FAILED(image
->GetHeight(&height
))) {
388 // Either the image has no intrinsic size, or it has an error.
389 rect
= GetMaxSizedIntRect();
391 rect
.SizeTo(width
, height
);
395 SyncNotifyInternal(aObserver
, !!image
, mProgress
, rect
);
398 void ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
) {
399 MOZ_ASSERT(NS_IsMainThread(),
400 "SyncNotifyState and mObservers are not threadsafe");
401 RefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
403 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
404 aObserver
->OnLoadComplete(true);
408 already_AddRefed
<nsIEventTarget
> ProgressTracker::GetEventTarget() const {
409 MutexAutoLock
lock(mMutex
);
410 nsCOMPtr
<nsIEventTarget
> target
= mEventTarget
;
411 return target
.forget();
414 void ProgressTracker::AddObserver(IProgressObserver
* aObserver
) {
415 MOZ_ASSERT(NS_IsMainThread());
416 RefPtr
<IProgressObserver
> observer
= aObserver
;
418 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
420 if (mObserversWithTargets
== 0) {
421 // On the first observer with a target (i.e. listener), always accept its
422 // event target; this may be for a specific DocGroup, or it may be the
423 // unlabelled main thread target.
424 MutexAutoLock
lock(mMutex
);
425 mEventTarget
= WrapNotNull(target
);
426 } else if (mEventTarget
.get() != target
.get()) {
427 // If a subsequent observer comes in with a different target, we need to
428 // switch to use the unlabelled main thread target, if we haven't already.
429 MutexAutoLock
lock(mMutex
);
430 nsCOMPtr
<nsIEventTarget
> mainTarget(do_GetMainThread());
431 mEventTarget
= WrapNotNull(mainTarget
);
433 ++mObserversWithTargets
;
436 mObservers
.Write([=](ObserverTable
* aTable
) {
437 MOZ_ASSERT(!aTable
->Contains(observer
),
438 "Adding duplicate entry for image observer");
440 WeakPtr
<IProgressObserver
> weakPtr
= observer
.get();
441 aTable
->InsertOrUpdate(observer
, weakPtr
);
444 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
447 bool ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
) {
448 MOZ_ASSERT(NS_IsMainThread());
449 RefPtr
<IProgressObserver
> observer
= aObserver
;
451 // Remove the observer from the list.
452 bool removed
= mObservers
.Write(
453 [observer
](ObserverTable
* aTable
) { return aTable
->Remove(observer
); });
455 // Sometimes once an image is decoded, and all of its observers removed, a new
456 // document may request the same image. Thus we need to clear our event target
457 // state when the last observer is removed, so that we select the most
458 // appropriate event target when a new observer is added. Since the event
459 // target may have changed (e.g. due to the scheduler group going away before
460 // we were removed), so we should be cautious comparing this target against
461 // anything at this stage.
463 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
465 MOZ_ASSERT(mObserversWithTargets
> 0);
466 --mObserversWithTargets
;
468 // If we've shutdown the main thread there's no need to update
470 if ((mObserversWithTargets
== 0) && !gXPCOMThreadsShutDown
) {
471 MutexAutoLock
lock(mMutex
);
472 nsCOMPtr
<nsIEventTarget
> target(do_GetMainThread());
473 mEventTarget
= WrapNotNull(target
);
477 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
480 // Observers can get confused if they don't get all the proper teardown
481 // notifications. Part ways on good terms.
482 if (removed
&& !aObserver
->NotificationsDeferred()) {
483 EmulateRequestFinished(aObserver
);
486 // Make sure we don't give callbacks to an observer that isn't interested in
488 if (aObserver
->NotificationsDeferred() && mRunnable
) {
489 mRunnable
->RemoveObserver(aObserver
);
490 aObserver
->ClearPendingNotify();
496 uint32_t ProgressTracker::ObserverCount() const {
497 MOZ_ASSERT(NS_IsMainThread());
498 return mObservers
.Read(
499 [](const ObserverTable
* aTable
) { return aTable
->Count(); });
502 void ProgressTracker::OnUnlockedDraw() {
503 MOZ_ASSERT(NS_IsMainThread());
504 mObservers
.Read([](const ObserverTable
* aTable
) {
505 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
506 notify([](IProgressObserver
* aObs
) {
507 aObs
->Notify(imgINotificationObserver::UNLOCKED_DRAW
);
512 void ProgressTracker::ResetForNewRequest() {
513 MOZ_ASSERT(NS_IsMainThread());
514 mProgress
= NoProgress
;
517 void ProgressTracker::OnDiscard() {
518 MOZ_ASSERT(NS_IsMainThread());
519 mObservers
.Read([](const ObserverTable
* aTable
) {
520 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
521 notify([](IProgressObserver
* aObs
) {
522 aObs
->Notify(imgINotificationObserver::DISCARD
);
527 void ProgressTracker::OnImageAvailable() {
528 MOZ_ASSERT(NS_IsMainThread());
529 // Notify any imgRequestProxys that are observing us that we have an Image.
530 mObservers
.Read([](const ObserverTable
* aTable
) {
531 ImageObserverNotifier
<const ObserverTable
*> notify(
532 aTable
, /* aIgnoreDeferral = */ true);
533 notify([](IProgressObserver
* aObs
) { aObs
->SetHasImage(); });
537 void ProgressTracker::FireFailureNotification() {
538 MOZ_ASSERT(NS_IsMainThread());
540 // Some kind of problem has happened with image decoding.
541 // Report the URI to net:failed-to-process-uri-conent observers.
542 RefPtr
<Image
> image
= GetImage();
544 // Should be on main thread, so ok to create a new nsIURI.
545 nsCOMPtr
<nsIURI
> uri
= image
->GetURI();
547 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
549 os
->NotifyObservers(uri
, "net:failed-to-process-uri-content", nullptr);
556 } // namespace mozilla