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/AppShutdown.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Services.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(
71 nsCOMPtr
<nsIEventTarget
>(GetMainThreadSerialEventTarget()))),
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::RenderBlockingRunnable::RenderBlockingRunnable(
158 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
)
159 : PrioritizableRunnable(std::move(aEvent
),
160 nsIRunnablePriority::PRIORITY_RENDER_BLOCKING
) {}
162 void ProgressTracker::RenderBlockingRunnable::AddObserver(
163 IProgressObserver
* aObserver
) {
164 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->AddObserver(aObserver
);
167 void ProgressTracker::RenderBlockingRunnable::RemoveObserver(
168 IProgressObserver
* aObserver
) {
169 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->RemoveObserver(aObserver
);
173 already_AddRefed
<ProgressTracker::RenderBlockingRunnable
>
174 ProgressTracker::RenderBlockingRunnable::Create(
175 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
) {
176 MOZ_ASSERT(NS_IsMainThread());
177 RefPtr
<ProgressTracker::RenderBlockingRunnable
> event(
178 new ProgressTracker::RenderBlockingRunnable(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
);
202 } else if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
203 // Avoid dispatch if we are late in shutdown.
204 RefPtr
<AsyncNotifyRunnable
> ev
= new AsyncNotifyRunnable(this, aObserver
);
205 mRunnable
= ProgressTracker::RenderBlockingRunnable::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 // Avoid dispatch if we are late in shutdown.
259 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
260 nsCOMPtr
<nsIRunnable
> ev
=
261 new AsyncNotifyCurrentStateRunnable(this, aObserver
);
262 mEventTarget
->Dispatch(ev
.forget(), NS_DISPATCH_NORMAL
);
267 * ImageObserverNotifier is a helper type that abstracts over the difference
268 * between sending notifications to all of the observers in an ObserverTable,
269 * and sending them to a single observer. This allows the same notification code
270 * to be used for both cases.
272 template <typename T
>
273 struct ImageObserverNotifier
;
276 struct MOZ_STACK_CLASS ImageObserverNotifier
<const ObserverTable
*> {
277 explicit ImageObserverNotifier(const ObserverTable
* aObservers
,
278 bool aIgnoreDeferral
= false)
279 : mObservers(aObservers
), mIgnoreDeferral(aIgnoreDeferral
) {}
281 template <typename Lambda
>
282 void operator()(Lambda aFunc
) {
283 for (const auto& weakObserver
: mObservers
->Values()) {
284 RefPtr
<IProgressObserver
> observer
= weakObserver
.get();
285 if (observer
&& (mIgnoreDeferral
|| !observer
->NotificationsDeferred())) {
292 const ObserverTable
* mObservers
;
293 const bool mIgnoreDeferral
;
297 struct MOZ_STACK_CLASS ImageObserverNotifier
<IProgressObserver
*> {
298 explicit ImageObserverNotifier(IProgressObserver
* aObserver
)
299 : mObserver(aObserver
) {}
301 template <typename Lambda
>
302 void operator()(Lambda aFunc
) {
303 if (mObserver
&& !mObserver
->NotificationsDeferred()) {
309 IProgressObserver
* mObserver
;
312 template <typename T
>
313 void SyncNotifyInternal(const T
& aObservers
, bool aHasImage
, Progress aProgress
,
314 const nsIntRect
& aDirtyRect
) {
315 MOZ_ASSERT(NS_IsMainThread());
317 typedef imgINotificationObserver I
;
318 ImageObserverNotifier
<T
> notify(aObservers
);
320 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
321 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::SIZE_AVAILABLE
); });
326 // If there's any content in this frame at all (always true for
327 // vector images, true for raster images that have decoded at
328 // least one frame) then send OnFrameUpdate.
329 if (!aDirtyRect
.IsEmpty()) {
330 notify([&](IProgressObserver
* aObs
) {
331 aObs
->Notify(I::FRAME_UPDATE
, &aDirtyRect
);
335 if (aProgress
& FLAG_FRAME_COMPLETE
) {
336 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::FRAME_COMPLETE
); });
339 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
341 [](IProgressObserver
* aObs
) { aObs
->Notify(I::HAS_TRANSPARENCY
); });
344 if (aProgress
& FLAG_IS_ANIMATED
) {
345 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::IS_ANIMATED
); });
349 if (aProgress
& FLAG_DECODE_COMPLETE
) {
350 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
351 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::DECODE_COMPLETE
); });
354 if (aProgress
& FLAG_LOAD_COMPLETE
) {
355 notify([=](IProgressObserver
* aObs
) {
356 aObs
->OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
);
361 void ProgressTracker::SyncNotifyProgress(Progress aProgress
,
362 const nsIntRect
& aInvalidRect
363 /* = nsIntRect() */) {
364 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
366 Progress progress
= Difference(aProgress
);
367 CheckProgressConsistency(mProgress
, mProgress
| progress
, mIsMultipart
);
369 // Apply the changes.
370 mProgress
|= progress
;
372 // Send notifications.
373 mObservers
.Read([&](const ObserverTable
* aTable
) {
374 SyncNotifyInternal(aTable
, HasImage(), progress
, aInvalidRect
);
377 if (progress
& FLAG_HAS_ERROR
) {
378 FireFailureNotification();
382 void ProgressTracker::SyncNotify(IProgressObserver
* aObserver
) {
383 MOZ_ASSERT(NS_IsMainThread());
385 RefPtr
<Image
> image
= GetImage();
386 LOG_SCOPE_WITH_PARAM(gImgLog
, "ProgressTracker::SyncNotify", "uri", image
);
390 int32_t width
, height
;
391 if (NS_FAILED(image
->GetWidth(&width
)) ||
392 NS_FAILED(image
->GetHeight(&height
))) {
393 // Either the image has no intrinsic size, or it has an error.
394 rect
= GetMaxSizedIntRect();
396 rect
.SizeTo(width
, height
);
400 SyncNotifyInternal(aObserver
, !!image
, mProgress
, rect
);
403 void ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
) {
404 MOZ_ASSERT(NS_IsMainThread(),
405 "SyncNotifyState and mObservers are not threadsafe");
406 RefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
408 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
409 aObserver
->OnLoadComplete(true);
413 already_AddRefed
<nsIEventTarget
> ProgressTracker::GetEventTarget() const {
414 MutexAutoLock
lock(mMutex
);
415 nsCOMPtr
<nsIEventTarget
> target
= mEventTarget
;
416 return target
.forget();
419 void ProgressTracker::AddObserver(IProgressObserver
* aObserver
) {
420 MOZ_ASSERT(NS_IsMainThread());
421 RefPtr
<IProgressObserver
> observer
= aObserver
;
423 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
425 if (mObserversWithTargets
== 0) {
426 // On the first observer with a target (i.e. listener), always accept its
427 // event target; this may be for a specific DocGroup, or it may be the
428 // unlabelled main thread target.
429 MutexAutoLock
lock(mMutex
);
430 mEventTarget
= WrapNotNull(target
);
431 } else if (mEventTarget
.get() != target
.get()) {
432 // If a subsequent observer comes in with a different target, we need to
433 // switch to use the unlabelled main thread target, if we haven't already.
434 MutexAutoLock
lock(mMutex
);
435 nsCOMPtr
<nsIEventTarget
> mainTarget(do_GetMainThread());
436 mEventTarget
= WrapNotNull(mainTarget
);
438 ++mObserversWithTargets
;
441 mObservers
.Write([=](ObserverTable
* aTable
) {
442 MOZ_ASSERT(!aTable
->Contains(observer
),
443 "Adding duplicate entry for image observer");
445 WeakPtr
<IProgressObserver
> weakPtr
= observer
.get();
446 aTable
->InsertOrUpdate(observer
, weakPtr
);
449 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
452 bool ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
) {
453 MOZ_ASSERT(NS_IsMainThread());
454 RefPtr
<IProgressObserver
> observer
= aObserver
;
456 // Remove the observer from the list.
457 bool removed
= mObservers
.Write(
458 [observer
](ObserverTable
* aTable
) { return aTable
->Remove(observer
); });
460 // Sometimes once an image is decoded, and all of its observers removed, a new
461 // document may request the same image. Thus we need to clear our event target
462 // state when the last observer is removed, so that we select the most
463 // appropriate event target when a new observer is added. Since the event
464 // target may have changed (e.g. due to the scheduler group going away before
465 // we were removed), so we should be cautious comparing this target against
466 // anything at this stage.
468 nsCOMPtr
<nsIEventTarget
> target
= observer
->GetEventTarget();
470 MOZ_ASSERT(mObserversWithTargets
> 0);
471 --mObserversWithTargets
;
473 // If we're shutting down there's no need to update event targets.
474 if ((mObserversWithTargets
== 0) &&
475 !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
476 MutexAutoLock
lock(mMutex
);
477 nsCOMPtr
<nsIEventTarget
> target(do_GetMainThread());
478 mEventTarget
= WrapNotNull(target
);
482 MOZ_ASSERT(mObserversWithTargets
<= ObserverCount());
485 // Observers can get confused if they don't get all the proper teardown
486 // notifications. Part ways on good terms.
487 if (removed
&& !aObserver
->NotificationsDeferred()) {
488 EmulateRequestFinished(aObserver
);
491 // Make sure we don't give callbacks to an observer that isn't interested in
493 if (aObserver
->NotificationsDeferred() && mRunnable
) {
494 mRunnable
->RemoveObserver(aObserver
);
495 aObserver
->ClearPendingNotify();
501 uint32_t ProgressTracker::ObserverCount() const {
502 MOZ_ASSERT(NS_IsMainThread());
503 return mObservers
.Read(
504 [](const ObserverTable
* aTable
) { return aTable
->Count(); });
507 void ProgressTracker::OnUnlockedDraw() {
508 MOZ_ASSERT(NS_IsMainThread());
509 mObservers
.Read([](const ObserverTable
* aTable
) {
510 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
511 notify([](IProgressObserver
* aObs
) {
512 aObs
->Notify(imgINotificationObserver::UNLOCKED_DRAW
);
517 void ProgressTracker::ResetForNewRequest() {
518 MOZ_ASSERT(NS_IsMainThread());
519 mProgress
= NoProgress
;
522 void ProgressTracker::OnDiscard() {
523 MOZ_ASSERT(NS_IsMainThread());
524 mObservers
.Read([](const ObserverTable
* aTable
) {
525 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
526 notify([](IProgressObserver
* aObs
) {
527 aObs
->Notify(imgINotificationObserver::DISCARD
);
532 void ProgressTracker::OnImageAvailable() {
533 MOZ_ASSERT(NS_IsMainThread());
534 // Notify any imgRequestProxys that are observing us that we have an Image.
535 mObservers
.Read([](const ObserverTable
* aTable
) {
536 ImageObserverNotifier
<const ObserverTable
*> notify(
537 aTable
, /* aIgnoreDeferral = */ true);
538 notify([](IProgressObserver
* aObs
) { aObs
->SetHasImage(); });
542 void ProgressTracker::FireFailureNotification() {
543 MOZ_ASSERT(NS_IsMainThread());
545 // Some kind of problem has happened with image decoding.
546 // Report the URI to net:failed-to-process-uri-conent observers.
547 RefPtr
<Image
> image
= GetImage();
549 // Should be on main thread, so ok to create a new nsIURI.
550 nsCOMPtr
<nsIURI
> uri
= image
->GetURI();
552 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
554 os
->NotifyObservers(uri
, "net:failed-to-process-uri-content", nullptr);
561 } // namespace mozilla