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 "imgIContainer.h"
11 #include "imgINotificationObserver.h"
12 #include "imgIRequest.h"
14 #include "nsNetUtil.h"
15 #include "nsIObserverService.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/SystemGroup.h"
21 using mozilla::WeakPtr
;
26 static void CheckProgressConsistency(Progress aOldProgress
,
27 Progress aNewProgress
, bool aIsMultipart
) {
28 // Check preconditions for every progress bit.
30 // Error's do not get propagated from the tracker for each image part to the
31 // tracker for the multipart image because we don't want one bad part to
32 // prevent the remaining parts from showing. So we need to consider whether
33 // this is a tracker for a multipart image for these assertions to work.
35 if (aNewProgress
& FLAG_SIZE_AVAILABLE
) {
38 if (aNewProgress
& FLAG_DECODE_COMPLETE
) {
39 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
40 MOZ_ASSERT(aIsMultipart
||
41 aNewProgress
& (FLAG_FRAME_COMPLETE
| FLAG_HAS_ERROR
));
43 if (aNewProgress
& FLAG_FRAME_COMPLETE
) {
44 MOZ_ASSERT(aNewProgress
& FLAG_SIZE_AVAILABLE
);
46 if (aNewProgress
& FLAG_LOAD_COMPLETE
) {
47 MOZ_ASSERT(aIsMultipart
||
48 aNewProgress
& (FLAG_SIZE_AVAILABLE
| FLAG_HAS_ERROR
));
50 if (aNewProgress
& FLAG_IS_ANIMATED
) {
51 // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
52 // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
55 if (aNewProgress
& FLAG_HAS_TRANSPARENCY
) {
56 // XXX We'd like to assert that transparency is only set during metadata
57 // decode but we don't have any way to assert that until bug 1254892 is
60 if (aNewProgress
& FLAG_LAST_PART_COMPLETE
) {
61 MOZ_ASSERT(aNewProgress
& FLAG_LOAD_COMPLETE
);
63 if (aNewProgress
& FLAG_HAS_ERROR
) {
68 ProgressTracker::ProgressTracker()
69 : mMutex("ProgressTracker::mMutex"),
71 mEventTarget(WrapNotNull(nsCOMPtr
<nsIEventTarget
>(
72 SystemGroup::EventTargetFor(TaskCategory::Other
)))),
73 mObserversWithTargets(0),
74 mObservers(new ObserverTable
),
75 mProgress(NoProgress
),
76 mIsMultipart(false) {}
78 void ProgressTracker::SetImage(Image
* aImage
) {
79 MutexAutoLock
lock(mMutex
);
80 MOZ_ASSERT(aImage
, "Setting null image");
81 MOZ_ASSERT(!mImage
, "Setting image when we already have one");
85 void ProgressTracker::ResetImage() {
86 MutexAutoLock
lock(mMutex
);
87 MOZ_ASSERT(mImage
, "Resetting image when it's already null!");
91 uint32_t ProgressTracker::GetImageStatus() const {
92 uint32_t status
= imgIRequest::STATUS_NONE
;
94 // Translate our current state to a set of imgIRequest::STATE_* flags.
95 if (mProgress
& FLAG_SIZE_AVAILABLE
) {
96 status
|= imgIRequest::STATUS_SIZE_AVAILABLE
;
98 if (mProgress
& FLAG_DECODE_COMPLETE
) {
99 status
|= imgIRequest::STATUS_DECODE_COMPLETE
;
101 if (mProgress
& FLAG_FRAME_COMPLETE
) {
102 status
|= imgIRequest::STATUS_FRAME_COMPLETE
;
104 if (mProgress
& FLAG_LOAD_COMPLETE
) {
105 status
|= imgIRequest::STATUS_LOAD_COMPLETE
;
107 if (mProgress
& FLAG_IS_ANIMATED
) {
108 status
|= imgIRequest::STATUS_IS_ANIMATED
;
110 if (mProgress
& FLAG_HAS_TRANSPARENCY
) {
111 status
|= imgIRequest::STATUS_HAS_TRANSPARENCY
;
113 if (mProgress
& FLAG_HAS_ERROR
) {
114 status
|= imgIRequest::STATUS_ERROR
;
120 // A helper class to allow us to call SyncNotify asynchronously.
121 class AsyncNotifyRunnable
: public Runnable
{
123 AsyncNotifyRunnable(ProgressTracker
* aTracker
, IProgressObserver
* aObserver
)
124 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker
) {
125 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
126 MOZ_ASSERT(aTracker
, "aTracker should not be null");
127 MOZ_ASSERT(aObserver
, "aObserver should not be null");
128 mObservers
.AppendElement(aObserver
);
131 NS_IMETHOD
Run() override
{
132 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
133 MOZ_ASSERT(mTracker
, "mTracker should not be null");
134 for (uint32_t i
= 0; i
< mObservers
.Length(); ++i
) {
135 mObservers
[i
]->ClearPendingNotify();
136 mTracker
->SyncNotify(mObservers
[i
]);
139 mTracker
->mRunnable
= nullptr;
143 void AddObserver(IProgressObserver
* aObserver
) {
144 mObservers
.AppendElement(aObserver
);
147 void RemoveObserver(IProgressObserver
* aObserver
) {
148 mObservers
.RemoveElement(aObserver
);
152 friend class ProgressTracker
;
154 RefPtr
<ProgressTracker
> mTracker
;
155 nsTArray
<RefPtr
<IProgressObserver
>> mObservers
;
158 ProgressTracker::MediumHighRunnable::MediumHighRunnable(
159 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
)
160 : PrioritizableRunnable(std::move(aEvent
),
161 nsIRunnablePriority::PRIORITY_MEDIUMHIGH
) {}
163 void ProgressTracker::MediumHighRunnable::AddObserver(
164 IProgressObserver
* aObserver
) {
165 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->AddObserver(aObserver
);
168 void ProgressTracker::MediumHighRunnable::RemoveObserver(
169 IProgressObserver
* aObserver
) {
170 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->RemoveObserver(aObserver
);
174 already_AddRefed
<ProgressTracker::MediumHighRunnable
>
175 ProgressTracker::MediumHighRunnable::Create(
176 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
) {
177 MOZ_ASSERT(NS_IsMainThread());
178 RefPtr
<ProgressTracker::MediumHighRunnable
> event(
179 new ProgressTracker::MediumHighRunnable(std::move(aEvent
)));
180 return event
.forget();
183 void ProgressTracker::Notify(IProgressObserver
* aObserver
) {
184 MOZ_ASSERT(NS_IsMainThread());
186 if (aObserver
->NotificationsDeferred()) {
187 // There is a pending notification, or the observer isn't ready yet.
191 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
192 RefPtr
<Image
> image
= GetImage();
193 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::Notify async", "uri", image
);
196 aObserver
->MarkPendingNotify();
198 // If we have an existing runnable that we can use, we just append this
199 // observer to its list of observers to be notified. This ensures we don't
200 // unnecessarily delay onload.
202 mRunnable
->AddObserver(aObserver
);
204 RefPtr
<AsyncNotifyRunnable
> ev
= new AsyncNotifyRunnable(this, aObserver
);
205 mRunnable
= ProgressTracker::MediumHighRunnable::Create(ev
.forget());
206 mEventTarget
->Dispatch(mRunnable
, NS_DISPATCH_NORMAL
);
210 // A helper class to allow us to call SyncNotify asynchronously for a given,
212 class AsyncNotifyCurrentStateRunnable
: public Runnable
{
214 AsyncNotifyCurrentStateRunnable(ProgressTracker
* aProgressTracker
,
215 IProgressObserver
* aObserver
)
216 : Runnable("image::AsyncNotifyCurrentStateRunnable"),
217 mProgressTracker(aProgressTracker
),
218 mObserver(aObserver
) {
219 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
220 MOZ_ASSERT(mProgressTracker
, "mProgressTracker should not be null");
221 MOZ_ASSERT(mObserver
, "mObserver should not be null");
222 mImage
= mProgressTracker
->GetImage();
225 NS_IMETHOD
Run() override
{
226 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
227 mObserver
->ClearPendingNotify();
229 mProgressTracker
->SyncNotify(mObserver
);
234 RefPtr
<ProgressTracker
> mProgressTracker
;
235 RefPtr
<IProgressObserver
> mObserver
;
237 // We have to hold on to a reference to the tracker's image, just in case
238 // it goes away while we're in the event queue.
239 RefPtr
<Image
> mImage
;
242 void ProgressTracker::NotifyCurrentState(IProgressObserver
* aObserver
) {
243 MOZ_ASSERT(NS_IsMainThread());
245 if (aObserver
->NotificationsDeferred()) {
246 // There is a pending notification, or the observer isn't ready yet.
250 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
251 RefPtr
<Image
> image
= GetImage();
252 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::NotifyCurrentState", "uri",
256 aObserver
->MarkPendingNotify();
258 nsCOMPtr
<nsIRunnable
> ev
=
259 new AsyncNotifyCurrentStateRunnable(this, aObserver
);
260 mEventTarget
->Dispatch(ev
.forget(), NS_DISPATCH_NORMAL
);
264 * ImageObserverNotifier is a helper type that abstracts over the difference
265 * between sending notifications to all of the observers in an ObserverTable,
266 * and sending them to a single observer. This allows the same notification code
267 * to be used for both cases.
269 template <typename T
>
270 struct ImageObserverNotifier
;
273 struct MOZ_STACK_CLASS ImageObserverNotifier
<const ObserverTable
*> {
274 explicit ImageObserverNotifier(const ObserverTable
* aObservers
,
275 bool aIgnoreDeferral
= false)
276 : mObservers(aObservers
), mIgnoreDeferral(aIgnoreDeferral
) {}
278 template <typename Lambda
>
279 void operator()(Lambda aFunc
) {
280 for (auto iter
= mObservers
->ConstIter(); !iter
.Done(); iter
.Next()) {
281 RefPtr
<IProgressObserver
> observer
= iter
.Data().get();
282 if (observer
&& (mIgnoreDeferral
|| !observer
->NotificationsDeferred())) {
289 const ObserverTable
* mObservers
;
290 const bool mIgnoreDeferral
;
294 struct MOZ_STACK_CLASS ImageObserverNotifier
<IProgressObserver
*> {
295 explicit ImageObserverNotifier(IProgressObserver
* aObserver
)
296 : mObserver(aObserver
) {}
298 template <typename Lambda
>
299 void operator()(Lambda aFunc
) {
300 if (mObserver
&& !mObserver
->NotificationsDeferred()) {
306 IProgressObserver
* mObserver
;
309 template <typename T
>
310 void SyncNotifyInternal(const T
& aObservers
, bool aHasImage
, Progress aProgress
,
311 const nsIntRect
& aDirtyRect
) {
312 MOZ_ASSERT(NS_IsMainThread());
314 typedef imgINotificationObserver I
;
315 ImageObserverNotifier
<T
> notify(aObservers
);
317 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
318 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::SIZE_AVAILABLE
); });
323 // If there's any content in this frame at all (always true for
324 // vector images, true for raster images that have decoded at
325 // least one frame) then send OnFrameUpdate.
326 if (!aDirtyRect
.IsEmpty()) {
327 notify([&](IProgressObserver
* aObs
) {
328 aObs
->Notify(I::FRAME_UPDATE
, &aDirtyRect
);
332 if (aProgress
& FLAG_FRAME_COMPLETE
) {
333 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::FRAME_COMPLETE
); });
336 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
338 [](IProgressObserver
* aObs
) { aObs
->Notify(I::HAS_TRANSPARENCY
); });
341 if (aProgress
& FLAG_IS_ANIMATED
) {
342 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::IS_ANIMATED
); });
346 if (aProgress
& FLAG_DECODE_COMPLETE
) {
347 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
348 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::DECODE_COMPLETE
); });
351 if (aProgress
& FLAG_LOAD_COMPLETE
) {
352 notify([=](IProgressObserver
* aObs
) {
353 aObs
->OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
);
358 void ProgressTracker::SyncNotifyProgress(Progress aProgress
,
359 const nsIntRect
& aInvalidRect
360 /* = nsIntRect() */) {
361 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
363 Progress progress
= Difference(aProgress
);
364 CheckProgressConsistency(mProgress
, mProgress
| progress
, mIsMultipart
);
366 // Apply the changes.
367 mProgress
|= progress
;
369 // Send notifications.
370 mObservers
.Read([&](const ObserverTable
* aTable
) {
371 SyncNotifyInternal(aTable
, HasImage(), progress
, aInvalidRect
);
374 if (progress
& FLAG_HAS_ERROR
) {
375 FireFailureNotification();
379 void ProgressTracker::SyncNotify(IProgressObserver
* aObserver
) {
380 MOZ_ASSERT(NS_IsMainThread());
382 RefPtr
<Image
> image
= GetImage();
383 LOG_SCOPE_WITH_PARAM(gImgLog
, "ProgressTracker::SyncNotify", "uri", image
);
387 int32_t width
, height
;
388 if (NS_FAILED(image
->GetWidth(&width
)) ||
389 NS_FAILED(image
->GetHeight(&height
))) {
390 // Either the image has no intrinsic size, or it has an error.
391 rect
= GetMaxSizedIntRect();
393 rect
.SizeTo(width
, height
);
397 SyncNotifyInternal(aObserver
, !!image
, mProgress
, rect
);
400 void ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
) {
401 MOZ_ASSERT(NS_IsMainThread(),
402 "SyncNotifyState and mObservers are not threadsafe");
403 RefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
405 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
406 aObserver
->OnLoadComplete(true);
410 already_AddRefed
<nsIEventTarget
> ProgressTracker::GetEventTarget() const {
411 MutexAutoLock
lock(mMutex
);
412 nsCOMPtr
<nsIEventTarget
> target
= mEventTarget
;
413 return target
.forget();
416 void ProgressTracker::AddObserver(IProgressObserver
* aObserver
) {
417 MOZ_ASSERT(NS_IsMainThread());
418 RefPtr
<IProgressObserver
> observer
= aObserver
;
420 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
422 if (mObserversWithTargets
== 0) {
423 // On the first observer with a target (i.e. listener), always accept its
424 // event target; this may be for a specific DocGroup, or it may be the
425 // unlabelled main thread target.
426 MutexAutoLock
lock(mMutex
);
427 mEventTarget
= WrapNotNull(target
);
428 } else if (mEventTarget
.get() != target
.get()) {
429 // If a subsequent observer comes in with a different target, we need to
430 // switch to use the unlabelled main thread target, if we haven't already.
431 MutexAutoLock
lock(mMutex
);
432 nsCOMPtr
<nsIEventTarget
> mainTarget(do_GetMainThread());
433 mEventTarget
= WrapNotNull(mainTarget
);
435 ++mObserversWithTargets
;
438 mObservers
.Write([=](ObserverTable
* aTable
) {
439 MOZ_ASSERT(!aTable
->Get(observer
, nullptr),
440 "Adding duplicate entry for image observer");
442 WeakPtr
<IProgressObserver
> weakPtr
= observer
.get();
443 aTable
->Put(observer
, weakPtr
);
446 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
449 bool ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
) {
450 MOZ_ASSERT(NS_IsMainThread());
451 RefPtr
<IProgressObserver
> observer
= aObserver
;
453 // Remove the observer from the list.
454 bool removed
= mObservers
.Write(
455 [observer
](ObserverTable
* aTable
) { return aTable
->Remove(observer
); });
457 // Sometimes once an image is decoded, and all of its observers removed, a new
458 // document may request the same image. Thus we need to clear our event target
459 // state when the last observer is removed, so that we select the most
460 // appropriate event target when a new observer is added. Since the event
461 // target may have changed (e.g. due to the scheduler group going away before
462 // we were removed), so we should be cautious comparing this target against
463 // anything at this stage.
465 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
467 MOZ_ASSERT(mObserversWithTargets
> 0);
468 --mObserversWithTargets
;
470 if (mObserversWithTargets
== 0) {
471 MutexAutoLock
lock(mMutex
);
472 nsCOMPtr
<nsIEventTarget
> target(
473 SystemGroup::EventTargetFor(TaskCategory::Other
));
474 mEventTarget
= WrapNotNull(target
);
478 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
481 // Observers can get confused if they don't get all the proper teardown
482 // notifications. Part ways on good terms.
483 if (removed
&& !aObserver
->NotificationsDeferred()) {
484 EmulateRequestFinished(aObserver
);
487 // Make sure we don't give callbacks to an observer that isn't interested in
489 if (aObserver
->NotificationsDeferred() && mRunnable
) {
490 mRunnable
->RemoveObserver(aObserver
);
491 aObserver
->ClearPendingNotify();
497 uint32_t ProgressTracker::ObserverCount() const {
498 MOZ_ASSERT(NS_IsMainThread());
499 return mObservers
.Read(
500 [](const ObserverTable
* aTable
) { return aTable
->Count(); });
503 void ProgressTracker::OnUnlockedDraw() {
504 MOZ_ASSERT(NS_IsMainThread());
505 mObservers
.Read([](const ObserverTable
* aTable
) {
506 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
507 notify([](IProgressObserver
* aObs
) {
508 aObs
->Notify(imgINotificationObserver::UNLOCKED_DRAW
);
513 void ProgressTracker::ResetForNewRequest() {
514 MOZ_ASSERT(NS_IsMainThread());
515 mProgress
= NoProgress
;
518 void ProgressTracker::OnDiscard() {
519 MOZ_ASSERT(NS_IsMainThread());
520 mObservers
.Read([](const ObserverTable
* aTable
) {
521 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
522 notify([](IProgressObserver
* aObs
) {
523 aObs
->Notify(imgINotificationObserver::DISCARD
);
528 void ProgressTracker::OnImageAvailable() {
529 MOZ_ASSERT(NS_IsMainThread());
530 // Notify any imgRequestProxys that are observing us that we have an Image.
531 mObservers
.Read([](const ObserverTable
* aTable
) {
532 ImageObserverNotifier
<const ObserverTable
*> notify(
533 aTable
, /* aIgnoreDeferral = */ true);
534 notify([](IProgressObserver
* aObs
) { aObs
->SetHasImage(); });
538 void ProgressTracker::FireFailureNotification() {
539 MOZ_ASSERT(NS_IsMainThread());
541 // Some kind of problem has happened with image decoding.
542 // Report the URI to net:failed-to-process-uri-conent observers.
543 RefPtr
<Image
> image
= GetImage();
545 // Should be on main thread, so ok to create a new nsIURI.
546 nsCOMPtr
<nsIURI
> uri
= image
->GetURI();
548 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
550 os
->NotifyObservers(uri
, "net:failed-to-process-uri-content", nullptr);
557 } // namespace mozilla