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/SchedulerGroup.h"
19 #include "mozilla/Services.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 mObservers(new ObserverTable
),
72 mProgress(NoProgress
),
73 mIsMultipart(false) {}
75 void ProgressTracker::SetImage(Image
* aImage
) {
76 MutexAutoLock
lock(mMutex
);
77 MOZ_ASSERT(aImage
, "Setting null image");
78 MOZ_ASSERT(!mImage
, "Setting image when we already have one");
82 void ProgressTracker::ResetImage() {
83 MutexAutoLock
lock(mMutex
);
84 MOZ_ASSERT(mImage
, "Resetting image when it's already null!");
88 uint32_t ProgressTracker::GetImageStatus() const {
89 uint32_t status
= imgIRequest::STATUS_NONE
;
91 // Translate our current state to a set of imgIRequest::STATE_* flags.
92 if (mProgress
& FLAG_SIZE_AVAILABLE
) {
93 status
|= imgIRequest::STATUS_SIZE_AVAILABLE
;
95 if (mProgress
& FLAG_DECODE_COMPLETE
) {
96 status
|= imgIRequest::STATUS_DECODE_COMPLETE
;
98 if (mProgress
& FLAG_FRAME_COMPLETE
) {
99 status
|= imgIRequest::STATUS_FRAME_COMPLETE
;
101 if (mProgress
& FLAG_LOAD_COMPLETE
) {
102 status
|= imgIRequest::STATUS_LOAD_COMPLETE
;
104 if (mProgress
& FLAG_IS_ANIMATED
) {
105 status
|= imgIRequest::STATUS_IS_ANIMATED
;
107 if (mProgress
& FLAG_HAS_TRANSPARENCY
) {
108 status
|= imgIRequest::STATUS_HAS_TRANSPARENCY
;
110 if (mProgress
& FLAG_HAS_ERROR
) {
111 status
|= imgIRequest::STATUS_ERROR
;
117 // A helper class to allow us to call SyncNotify asynchronously.
118 class AsyncNotifyRunnable
: public Runnable
{
120 AsyncNotifyRunnable(ProgressTracker
* aTracker
, IProgressObserver
* aObserver
)
121 : Runnable("ProgressTracker::AsyncNotifyRunnable"), mTracker(aTracker
) {
122 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
123 MOZ_ASSERT(aTracker
, "aTracker should not be null");
124 MOZ_ASSERT(aObserver
, "aObserver should not be null");
125 mObservers
.AppendElement(aObserver
);
128 NS_IMETHOD
Run() override
{
129 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
130 MOZ_ASSERT(mTracker
, "mTracker should not be null");
131 for (uint32_t i
= 0; i
< mObservers
.Length(); ++i
) {
132 mObservers
[i
]->ClearPendingNotify();
133 mTracker
->SyncNotify(mObservers
[i
]);
136 mTracker
->mRunnable
= nullptr;
140 void AddObserver(IProgressObserver
* aObserver
) {
141 mObservers
.AppendElement(aObserver
);
144 void RemoveObserver(IProgressObserver
* aObserver
) {
145 mObservers
.RemoveElement(aObserver
);
149 friend class ProgressTracker
;
151 RefPtr
<ProgressTracker
> mTracker
;
152 nsTArray
<RefPtr
<IProgressObserver
>> mObservers
;
155 ProgressTracker::RenderBlockingRunnable::RenderBlockingRunnable(
156 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
)
157 : PrioritizableRunnable(std::move(aEvent
),
158 nsIRunnablePriority::PRIORITY_RENDER_BLOCKING
) {}
160 void ProgressTracker::RenderBlockingRunnable::AddObserver(
161 IProgressObserver
* aObserver
) {
162 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->AddObserver(aObserver
);
165 void ProgressTracker::RenderBlockingRunnable::RemoveObserver(
166 IProgressObserver
* aObserver
) {
167 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get())->RemoveObserver(aObserver
);
171 already_AddRefed
<ProgressTracker::RenderBlockingRunnable
>
172 ProgressTracker::RenderBlockingRunnable::Create(
173 already_AddRefed
<AsyncNotifyRunnable
>&& aEvent
) {
174 MOZ_ASSERT(NS_IsMainThread());
175 RefPtr
<ProgressTracker::RenderBlockingRunnable
> event(
176 new ProgressTracker::RenderBlockingRunnable(std::move(aEvent
)));
177 return event
.forget();
180 void ProgressTracker::Notify(IProgressObserver
* aObserver
) {
181 MOZ_ASSERT(NS_IsMainThread());
183 if (aObserver
->NotificationsDeferred()) {
184 // There is a pending notification, or the observer isn't ready yet.
188 if (MOZ_LOG_TEST(gImgLog
, LogLevel::Debug
)) {
189 RefPtr
<Image
> image
= GetImage();
190 LOG_FUNC_WITH_PARAM(gImgLog
, "ProgressTracker::Notify async", "uri", image
);
193 aObserver
->MarkPendingNotify();
195 // If we have an existing runnable that we can use, we just append this
196 // observer to its list of observers to be notified. This ensures we don't
197 // unnecessarily delay onload.
199 mRunnable
->AddObserver(aObserver
);
200 } else if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
201 // Avoid dispatch if we are late in shutdown.
202 RefPtr
<AsyncNotifyRunnable
> ev
= new AsyncNotifyRunnable(this, aObserver
);
203 mRunnable
= ProgressTracker::RenderBlockingRunnable::Create(ev
.forget());
204 SchedulerGroup::Dispatch(do_AddRef(mRunnable
));
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 // Avoid dispatch if we are late in shutdown.
257 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads
)) {
258 nsCOMPtr
<nsIRunnable
> ev
=
259 new AsyncNotifyCurrentStateRunnable(this, aObserver
);
260 SchedulerGroup::Dispatch(ev
.forget());
265 * ImageObserverNotifier is a helper type that abstracts over the difference
266 * between sending notifications to all of the observers in an ObserverTable,
267 * and sending them to a single observer. This allows the same notification code
268 * to be used for both cases.
270 template <typename T
>
271 struct ImageObserverNotifier
;
274 struct MOZ_STACK_CLASS ImageObserverNotifier
<const ObserverTable
*> {
275 explicit ImageObserverNotifier(const ObserverTable
* aObservers
,
276 bool aIgnoreDeferral
= false)
277 : mObservers(aObservers
), mIgnoreDeferral(aIgnoreDeferral
) {}
279 template <typename Lambda
>
280 void operator()(Lambda aFunc
) {
281 for (const auto& weakObserver
: mObservers
->Values()) {
282 RefPtr
<IProgressObserver
> observer
= weakObserver
.get();
283 if (observer
&& (mIgnoreDeferral
|| !observer
->NotificationsDeferred())) {
290 const ObserverTable
* mObservers
;
291 const bool mIgnoreDeferral
;
295 struct MOZ_STACK_CLASS ImageObserverNotifier
<IProgressObserver
*> {
296 explicit ImageObserverNotifier(IProgressObserver
* aObserver
)
297 : mObserver(aObserver
) {}
299 template <typename Lambda
>
300 void operator()(Lambda aFunc
) {
301 if (mObserver
&& !mObserver
->NotificationsDeferred()) {
307 IProgressObserver
* mObserver
;
310 template <typename T
>
311 void SyncNotifyInternal(const T
& aObservers
, bool aHasImage
, Progress aProgress
,
312 const nsIntRect
& aDirtyRect
) {
313 MOZ_ASSERT(NS_IsMainThread());
315 typedef imgINotificationObserver I
;
316 ImageObserverNotifier
<T
> notify(aObservers
);
318 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
319 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::SIZE_AVAILABLE
); });
324 // If there's any content in this frame at all (always true for
325 // vector images, true for raster images that have decoded at
326 // least one frame) then send OnFrameUpdate.
327 if (!aDirtyRect
.IsEmpty()) {
328 notify([&](IProgressObserver
* aObs
) {
329 aObs
->Notify(I::FRAME_UPDATE
, &aDirtyRect
);
333 if (aProgress
& FLAG_FRAME_COMPLETE
) {
334 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::FRAME_COMPLETE
); });
337 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
339 [](IProgressObserver
* aObs
) { aObs
->Notify(I::HAS_TRANSPARENCY
); });
342 if (aProgress
& FLAG_IS_ANIMATED
) {
343 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::IS_ANIMATED
); });
347 if (aProgress
& FLAG_DECODE_COMPLETE
) {
348 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
349 notify([](IProgressObserver
* aObs
) { aObs
->Notify(I::DECODE_COMPLETE
); });
352 if (aProgress
& FLAG_LOAD_COMPLETE
) {
353 notify([=](IProgressObserver
* aObs
) {
354 aObs
->OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
);
359 void ProgressTracker::SyncNotifyProgress(Progress aProgress
,
360 const nsIntRect
& aInvalidRect
361 /* = nsIntRect() */) {
362 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
364 Progress progress
= Difference(aProgress
);
365 CheckProgressConsistency(mProgress
, mProgress
| progress
, mIsMultipart
);
367 // Apply the changes.
368 mProgress
|= progress
;
370 // Send notifications.
371 mObservers
.Read([&](const ObserverTable
* aTable
) {
372 SyncNotifyInternal(aTable
, HasImage(), progress
, aInvalidRect
);
375 if (progress
& FLAG_HAS_ERROR
) {
376 FireFailureNotification();
380 void ProgressTracker::SyncNotify(IProgressObserver
* aObserver
) {
381 MOZ_ASSERT(NS_IsMainThread());
383 RefPtr
<Image
> image
= GetImage();
384 LOG_SCOPE_WITH_PARAM(gImgLog
, "ProgressTracker::SyncNotify", "uri", image
);
388 int32_t width
, height
;
389 if (NS_FAILED(image
->GetWidth(&width
)) ||
390 NS_FAILED(image
->GetHeight(&height
))) {
391 // Either the image has no intrinsic size, or it has an error.
392 rect
= GetMaxSizedIntRect();
394 rect
.SizeTo(width
, height
);
398 SyncNotifyInternal(aObserver
, !!image
, mProgress
, rect
);
401 void ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
) {
402 MOZ_ASSERT(NS_IsMainThread(),
403 "SyncNotifyState and mObservers are not threadsafe");
404 RefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
406 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
407 aObserver
->OnLoadComplete(true);
411 void ProgressTracker::AddObserver(IProgressObserver
* aObserver
) {
412 MOZ_ASSERT(NS_IsMainThread());
413 RefPtr
<IProgressObserver
> observer
= aObserver
;
414 mObservers
.Write([=](ObserverTable
* aTable
) {
415 MOZ_ASSERT(!aTable
->Contains(observer
),
416 "Adding duplicate entry for image observer");
418 WeakPtr
<IProgressObserver
> weakPtr
= observer
.get();
419 aTable
->InsertOrUpdate(observer
, weakPtr
);
423 bool ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
) {
424 MOZ_ASSERT(NS_IsMainThread());
425 RefPtr
<IProgressObserver
> observer
= aObserver
;
427 // Remove the observer from the list.
428 bool removed
= mObservers
.Write(
429 [observer
](ObserverTable
* aTable
) { return aTable
->Remove(observer
); });
431 // Observers can get confused if they don't get all the proper teardown
432 // notifications. Part ways on good terms.
433 if (removed
&& !aObserver
->NotificationsDeferred()) {
434 EmulateRequestFinished(aObserver
);
437 // Make sure we don't give callbacks to an observer that isn't interested in
439 if (aObserver
->NotificationsDeferred() && mRunnable
) {
440 mRunnable
->RemoveObserver(aObserver
);
441 aObserver
->ClearPendingNotify();
447 uint32_t ProgressTracker::ObserverCount() const {
448 MOZ_ASSERT(NS_IsMainThread());
449 return mObservers
.Read(
450 [](const ObserverTable
* aTable
) { return aTable
->Count(); });
453 void ProgressTracker::OnUnlockedDraw() {
454 MOZ_ASSERT(NS_IsMainThread());
455 mObservers
.Read([](const ObserverTable
* aTable
) {
456 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
457 notify([](IProgressObserver
* aObs
) {
458 aObs
->Notify(imgINotificationObserver::UNLOCKED_DRAW
);
463 void ProgressTracker::ResetForNewRequest() {
464 MOZ_ASSERT(NS_IsMainThread());
465 mProgress
= NoProgress
;
468 void ProgressTracker::OnDiscard() {
469 MOZ_ASSERT(NS_IsMainThread());
470 mObservers
.Read([](const ObserverTable
* aTable
) {
471 ImageObserverNotifier
<const ObserverTable
*> notify(aTable
);
472 notify([](IProgressObserver
* aObs
) {
473 aObs
->Notify(imgINotificationObserver::DISCARD
);
478 void ProgressTracker::OnImageAvailable() {
479 MOZ_ASSERT(NS_IsMainThread());
480 // Notify any imgRequestProxys that are observing us that we have an Image.
481 mObservers
.Read([](const ObserverTable
* aTable
) {
482 ImageObserverNotifier
<const ObserverTable
*> notify(
483 aTable
, /* aIgnoreDeferral = */ true);
484 notify([](IProgressObserver
* aObs
) { aObs
->SetHasImage(); });
488 void ProgressTracker::FireFailureNotification() {
489 MOZ_ASSERT(NS_IsMainThread());
491 // Some kind of problem has happened with image decoding.
492 // Report the URI to net:failed-to-process-uri-conent observers.
493 RefPtr
<Image
> image
= GetImage();
495 // Should be on main thread, so ok to create a new nsIURI.
496 nsCOMPtr
<nsIURI
> uri
= image
->GetURI();
498 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
500 os
->NotifyObservers(uri
, "net:failed-to-process-uri-content", nullptr);
507 } // namespace mozilla