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"
18 #include "mozilla/SystemGroup.h"
20 using mozilla::WeakPtr
;
25 static void CheckProgressConsistency(Progress aOldProgress
,
26 Progress aNewProgress
, bool aIsMultipart
) {
27 // Check preconditions for every progress bit.
29 // Error's do not get propagated from the tracker for each image part to the
30 // tracker for the multipart image because we don't want one bad part to
31 // prevent the remaining parts from showing. So we need to consider whether
32 // this is a tracker for a multipart image for these assertions to work.
34 if (aNewProgress
& FLAG_SIZE_AVAILABLE
) {
37 if (aNewProgress
& FLAG_DECODE_COMPLETE
) {
38 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
39 MOZ_ASSERT(aIsMultipart
||
40 aNewProgress
& (FLAG_FRAME_COMPLETE
| FLAG_HAS_ERROR
));
42 if (aNewProgress
& FLAG_FRAME_COMPLETE
) {
43 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
45 if (aNewProgress
& FLAG_LOAD_COMPLETE
) {
46 MOZ_ASSERT(aIsMultipart
||
47 aNewProgress
& (FLAG_SIZE_AVAILABLE
| FLAG_HAS_ERROR
));
49 if (aNewProgress
& FLAG_IS_ANIMATED
) {
50 // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
51 // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
54 if (aNewProgress
& FLAG_HAS_TRANSPARENCY
) {
55 // XXX We'd like to assert that transparency is only set during metadata
56 // decode but we don't have any way to assert that until bug 1254892 is
59 if (aNewProgress
& FLAG_LAST_PART_COMPLETE
) {
60 MOZ_ASSERT(aNewProgress
& FLAG_LOAD_COMPLETE
);
62 if (aNewProgress
& FLAG_HAS_ERROR
) {
67 ProgressTracker::ProgressTracker()
68 : mMutex("ProgressTracker::mMutex"),
70 mEventTarget(WrapNotNull(nsCOMPtr
<nsIEventTarget
>(
71 SystemGroup::EventTargetFor(TaskCategory::Other
)))),
72 mObserversWithTargets(0),
73 mObservers(new ObserverTable
),
74 mProgress(NoProgress
),
75 mIsMultipart(false) {}
77 void ProgressTracker::SetImage(Image
* aImage
) {
78 MutexAutoLock
lock(mMutex
);
79 MOZ_ASSERT(aImage
, "Setting null image");
80 MOZ_ASSERT(!mImage
, "Setting image when we already have one");
84 void ProgressTracker::ResetImage() {
85 MutexAutoLock
lock(mMutex
);
86 MOZ_ASSERT(mImage
, "Resetting image when it's already null!");
90 uint32_t ProgressTracker::GetImageStatus() const {
91 uint32_t status
= imgIRequest::STATUS_NONE
;
93 // Translate our current state to a set of imgIRequest::STATE_* flags.
94 if (mProgress
& FLAG_SIZE_AVAILABLE
) {
95 status
|= imgIRequest::STATUS_SIZE_AVAILABLE
;
97 if (mProgress
& FLAG_DECODE_COMPLETE
) {
98 status
|= imgIRequest::STATUS_DECODE_COMPLETE
;
100 if (mProgress
& FLAG_FRAME_COMPLETE
) {
101 status
|= imgIRequest::STATUS_FRAME_COMPLETE
;
103 if (mProgress
& FLAG_LOAD_COMPLETE
) {
104 status
|= imgIRequest::STATUS_LOAD_COMPLETE
;
106 if (mProgress
& FLAG_IS_ANIMATED
) {
107 status
|= imgIRequest::STATUS_IS_ANIMATED
;
109 if (mProgress
& FLAG_HAS_TRANSPARENCY
) {
110 status
|= imgIRequest::STATUS_HAS_TRANSPARENCY
;
112 if (mProgress
& FLAG_HAS_ERROR
) {
113 status
|= imgIRequest::STATUS_ERROR
;
119 // A helper class to allow us to call SyncNotify asynchronously.
120 class AsyncNotifyRunnable
: public Runnable
{
122 AsyncNotifyRunnable(ProgressTracker
* aTracker
, IProgressObserver
* aObserver
)
123 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker
) {
124 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
125 MOZ_ASSERT(aTracker
, "aTracker should not be null");
126 MOZ_ASSERT(aObserver
, "aObserver should not be null");
127 mObservers
.AppendElement(aObserver
);
130 NS_IMETHOD
Run() override
{
131 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
132 MOZ_ASSERT(mTracker
, "mTracker should not be null");
133 for (uint32_t i
= 0; i
< mObservers
.Length(); ++i
) {
134 mObservers
[i
]->ClearPendingNotify();
135 mTracker
->SyncNotify(mObservers
[i
]);
138 mTracker
->mRunnable
= nullptr;
142 void AddObserver(IProgressObserver
* aObserver
) {
143 mObservers
.AppendElement(aObserver
);
146 void RemoveObserver(IProgressObserver
* aObserver
) {
147 mObservers
.RemoveElement(aObserver
);
151 friend class ProgressTracker
;
153 RefPtr
<ProgressTracker
> mTracker
;
154 nsTArray
<RefPtr
<IProgressObserver
>> mObservers
;
157 ProgressTracker::MediumHighRunnable::MediumHighRunnable(
158 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
)
159 : PrioritizableRunnable(std::move(aEvent
),
160 nsIRunnablePriority::PRIORITY_MEDIUMHIGH
) {}
162 void ProgressTracker::MediumHighRunnable::AddObserver(
163 IProgressObserver
* aObserver
) {
164 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->AddObserver(aObserver
);
167 void ProgressTracker::MediumHighRunnable::RemoveObserver(
168 IProgressObserver
* aObserver
) {
169 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->RemoveObserver(aObserver
);
173 already_AddRefed
<ProgressTracker::MediumHighRunnable
>
174 ProgressTracker::MediumHighRunnable::Create(
175 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
) {
176 MOZ_ASSERT(NS_IsMainThread());
177 RefPtr
<ProgressTracker::MediumHighRunnable
> event(
178 new ProgressTracker::MediumHighRunnable(std::move(aEvent
)));
179 return event
.forget();
182 void ProgressTracker::Notify(IProgressObserver
* aObserver
) {
183 MOZ_ASSERT(NS_IsMainThread());
185 if (aObserver
->NotificationsDeferred()) {
186 // There is a pending notification, or the observer isn't ready yet.
190 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
191 RefPtr
<Image
> image
= GetImage();
192 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::Notify async", "uri", image
);
195 aObserver
->MarkPendingNotify();
197 // If we have an existing runnable that we can use, we just append this
198 // observer to its list of observers to be notified. This ensures we don't
199 // unnecessarily delay onload.
201 mRunnable
->AddObserver(aObserver
);
203 RefPtr
<AsyncNotifyRunnable
> ev
= new AsyncNotifyRunnable(this, aObserver
);
204 mRunnable
= ProgressTracker::MediumHighRunnable::Create(ev
.forget());
205 mEventTarget
->Dispatch(mRunnable
, NS_DISPATCH_NORMAL
);
209 // A helper class to allow us to call SyncNotify asynchronously for a given,
211 class AsyncNotifyCurrentStateRunnable
: public Runnable
{
213 AsyncNotifyCurrentStateRunnable(ProgressTracker
* aProgressTracker
,
214 IProgressObserver
* aObserver
)
215 : Runnable("image::AsyncNotifyCurrentStateRunnable"),
216 mProgressTracker(aProgressTracker
),
217 mObserver(aObserver
) {
218 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
219 MOZ_ASSERT(mProgressTracker
, "mProgressTracker should not be null");
220 MOZ_ASSERT(mObserver
, "mObserver should not be null");
221 mImage
= mProgressTracker
->GetImage();
224 NS_IMETHOD
Run() override
{
225 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
226 mObserver
->ClearPendingNotify();
228 mProgressTracker
->SyncNotify(mObserver
);
233 RefPtr
<ProgressTracker
> mProgressTracker
;
234 RefPtr
<IProgressObserver
> mObserver
;
236 // We have to hold on to a reference to the tracker's image, just in case
237 // it goes away while we're in the event queue.
238 RefPtr
<Image
> mImage
;
241 void ProgressTracker::NotifyCurrentState(IProgressObserver
* aObserver
) {
242 MOZ_ASSERT(NS_IsMainThread());
244 if (aObserver
->NotificationsDeferred()) {
245 // There is a pending notification, or the observer isn't ready yet.
249 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
250 RefPtr
<Image
> image
= GetImage();
251 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::NotifyCurrentState", "uri",
255 aObserver
->MarkPendingNotify();
257 nsCOMPtr
<nsIRunnable
> ev
=
258 new AsyncNotifyCurrentStateRunnable(this, aObserver
);
259 mEventTarget
->Dispatch(ev
.forget(), NS_DISPATCH_NORMAL
);
263 * ImageObserverNotifier is a helper type that abstracts over the difference
264 * between sending notifications to all of the observers in an ObserverTable,
265 * and sending them to a single observer. This allows the same notification code
266 * to be used for both cases.
268 template <typename T
>
269 struct ImageObserverNotifier
;
272 struct MOZ_STACK_CLASS ImageObserverNotifier
<const ObserverTable
*> {
273 explicit ImageObserverNotifier(const ObserverTable
* aObservers
,
274 bool aIgnoreDeferral
= false)
275 : mObservers(aObservers
), mIgnoreDeferral(aIgnoreDeferral
) {}
277 template <typename Lambda
>
278 void operator()(Lambda aFunc
) {
279 for (auto iter
= mObservers
->ConstIter(); !iter
.Done(); iter
.Next()) {
280 RefPtr
<IProgressObserver
> observer
= iter
.Data().get();
281 if (observer
&& (mIgnoreDeferral
|| !observer
->NotificationsDeferred())) {
288 const ObserverTable
* mObservers
;
289 const bool mIgnoreDeferral
;
293 struct MOZ_STACK_CLASS ImageObserverNotifier
<IProgressObserver
*> {
294 explicit ImageObserverNotifier(IProgressObserver
* aObserver
)
295 : mObserver(aObserver
) {}
297 template <typename Lambda
>
298 void operator()(Lambda aFunc
) {
299 if (mObserver
&& !mObserver
->NotificationsDeferred()) {
305 IProgressObserver
* mObserver
;
308 template <typename T
>
309 void SyncNotifyInternal(const T
& aObservers
, bool aHasImage
, Progress aProgress
,
310 const nsIntRect
& aDirtyRect
) {
311 MOZ_ASSERT(NS_IsMainThread());
313 typedef imgINotificationObserver I
;
314 ImageObserverNotifier
<T
> notify(aObservers
);
316 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
317 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::SIZE_AVAILABLE
); });
322 // If there's any content in this frame at all (always true for
323 // vector images, true for raster images that have decoded at
324 // least one frame) then send OnFrameUpdate.
325 if (!aDirtyRect
.IsEmpty()) {
326 notify([&](IProgressObserver
* aObs
) {
327 aObs
->Notify(I::FRAME_UPDATE
, &aDirtyRect
);
331 if (aProgress
& FLAG_FRAME_COMPLETE
) {
332 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::FRAME_COMPLETE
); });
335 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
337 [](IProgressObserver
* aObs
) { aObs
->Notify(I::HAS_TRANSPARENCY
); });
340 if (aProgress
& FLAG_IS_ANIMATED
) {
341 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::IS_ANIMATED
); });
345 if (aProgress
& FLAG_DECODE_COMPLETE
) {
346 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
347 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::DECODE_COMPLETE
); });
350 if (aProgress
& FLAG_LOAD_COMPLETE
) {
351 notify([=](IProgressObserver
* aObs
) {
352 aObs
->OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
);
357 void ProgressTracker::SyncNotifyProgress(Progress aProgress
,
358 const nsIntRect
& aInvalidRect
359 /* = nsIntRect() */) {
360 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
362 Progress progress
= Difference(aProgress
);
363 CheckProgressConsistency(mProgress
, mProgress
| progress
, mIsMultipart
);
365 // Apply the changes.
366 mProgress
|= progress
;
368 // Send notifications.
369 mObservers
.Read([&](const ObserverTable
* aTable
) {
370 SyncNotifyInternal(aTable
, HasImage(), progress
, aInvalidRect
);
373 if (progress
& FLAG_HAS_ERROR
) {
374 FireFailureNotification();
378 void ProgressTracker::SyncNotify(IProgressObserver
* aObserver
) {
379 MOZ_ASSERT(NS_IsMainThread());
381 RefPtr
<Image
> image
= GetImage();
382 LOG_SCOPE_WITH_PARAM(gImgLog
, "ProgressTracker::SyncNotify", "uri", image
);
386 int32_t width
, height
;
387 if (NS_FAILED(image
->GetWidth(&width
)) ||
388 NS_FAILED(image
->GetHeight(&height
))) {
389 // Either the image has no intrinsic size, or it has an error.
390 rect
= GetMaxSizedIntRect();
392 rect
.SizeTo(width
, height
);
396 SyncNotifyInternal(aObserver
, !!image
, mProgress
, rect
);
399 void ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
) {
400 MOZ_ASSERT(NS_IsMainThread(),
401 "SyncNotifyState and mObservers are not threadsafe");
402 RefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
404 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
405 aObserver
->OnLoadComplete(true);
409 already_AddRefed
<nsIEventTarget
> ProgressTracker::GetEventTarget() const {
410 MutexAutoLock
lock(mMutex
);
411 nsCOMPtr
<nsIEventTarget
> target
= mEventTarget
;
412 return target
.forget();
415 void ProgressTracker::AddObserver(IProgressObserver
* aObserver
) {
416 MOZ_ASSERT(NS_IsMainThread());
417 RefPtr
<IProgressObserver
> observer
= aObserver
;
419 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
421 if (mObserversWithTargets
== 0) {
422 // On the first observer with a target (i.e. listener), always accept its
423 // event target; this may be for a specific DocGroup, or it may be the
424 // unlabelled main thread target.
425 MutexAutoLock
lock(mMutex
);
426 mEventTarget
= WrapNotNull(target
);
427 } else if (mEventTarget
.get() != target
.get()) {
428 // If a subsequent observer comes in with a different target, we need to
429 // switch to use the unlabelled main thread target, if we haven't already.
430 MutexAutoLock
lock(mMutex
);
431 nsCOMPtr
<nsIEventTarget
> mainTarget(do_GetMainThread());
432 mEventTarget
= WrapNotNull(mainTarget
);
434 ++mObserversWithTargets
;
437 mObservers
.Write([=](ObserverTable
* aTable
) {
438 MOZ_ASSERT(!aTable
->Get(observer
, nullptr),
439 "Adding duplicate entry for image observer");
441 WeakPtr
<IProgressObserver
> weakPtr
= observer
.get();
442 aTable
->Put(observer
, weakPtr
);
445 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
448 bool ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
) {
449 MOZ_ASSERT(NS_IsMainThread());
450 RefPtr
<IProgressObserver
> observer
= aObserver
;
452 // Remove the observer from the list.
453 bool removed
= mObservers
.Write(
454 [observer
](ObserverTable
* aTable
) { return aTable
->Remove(observer
); });
456 // Sometimes once an image is decoded, and all of its observers removed, a new
457 // document may request the same image. Thus we need to clear our event target
458 // state when the last observer is removed, so that we select the most
459 // appropriate event target when a new observer is added. Since the event
460 // target may have changed (e.g. due to the scheduler group going away before
461 // we were removed), so we should be cautious comparing this target against
462 // anything at this stage.
464 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
466 MOZ_ASSERT(mObserversWithTargets
> 0);
467 --mObserversWithTargets
;
469 if (mObserversWithTargets
== 0) {
470 MutexAutoLock
lock(mMutex
);
471 nsCOMPtr
<nsIEventTarget
> target(
472 SystemGroup::EventTargetFor(TaskCategory::Other
));
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