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"
20 using mozilla::WeakPtr
;
25 ProgressTrackerInit::ProgressTrackerInit(Image
* aImage
,
26 ProgressTracker
* aTracker
)
33 mTracker
= new ProgressTracker();
35 mTracker
->SetImage(aImage
);
36 aImage
->SetProgressTracker(mTracker
);
40 ProgressTrackerInit::~ProgressTrackerInit()
42 mTracker
->ResetImage();
46 CheckProgressConsistency(Progress aProgress
)
48 // Check preconditions for every progress bit.
50 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
53 if (aProgress
& FLAG_DECODE_STARTED
) {
56 if (aProgress
& FLAG_DECODE_COMPLETE
) {
57 MOZ_ASSERT(aProgress
& FLAG_DECODE_STARTED
);
59 if (aProgress
& FLAG_FRAME_COMPLETE
) {
60 MOZ_ASSERT(aProgress
& FLAG_DECODE_STARTED
);
62 if (aProgress
& FLAG_LOAD_COMPLETE
) {
65 if (aProgress
& FLAG_ONLOAD_BLOCKED
) {
68 if (aProgress
& FLAG_ONLOAD_UNBLOCKED
) {
69 MOZ_ASSERT(aProgress
& FLAG_ONLOAD_BLOCKED
);
70 MOZ_ASSERT(aProgress
& (FLAG_SIZE_AVAILABLE
| FLAG_HAS_ERROR
));
72 if (aProgress
& FLAG_IS_ANIMATED
) {
73 MOZ_ASSERT(aProgress
& FLAG_DECODE_STARTED
);
74 MOZ_ASSERT(aProgress
& FLAG_SIZE_AVAILABLE
);
76 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
77 MOZ_ASSERT(aProgress
& FLAG_SIZE_AVAILABLE
);
79 if (aProgress
& FLAG_LAST_PART_COMPLETE
) {
80 MOZ_ASSERT(aProgress
& FLAG_LOAD_COMPLETE
);
82 if (aProgress
& FLAG_HAS_ERROR
) {
88 ProgressTracker::SetImage(Image
* aImage
)
90 MOZ_ASSERT(aImage
, "Setting null image");
91 MOZ_ASSERT(!mImage
, "Setting image when we already have one");
96 ProgressTracker::ResetImage()
98 MOZ_ASSERT(mImage
, "Resetting image when it's already null!");
103 ProgressTracker::GetImageStatus() const
105 uint32_t status
= imgIRequest::STATUS_NONE
;
107 // Translate our current state to a set of imgIRequest::STATE_* flags.
108 if (mProgress
& FLAG_SIZE_AVAILABLE
) {
109 status
|= imgIRequest::STATUS_SIZE_AVAILABLE
;
111 if (mProgress
& FLAG_DECODE_STARTED
) {
112 status
|= imgIRequest::STATUS_DECODE_STARTED
;
114 if (mProgress
& FLAG_DECODE_COMPLETE
) {
115 status
|= imgIRequest::STATUS_DECODE_COMPLETE
;
117 if (mProgress
& FLAG_FRAME_COMPLETE
) {
118 status
|= imgIRequest::STATUS_FRAME_COMPLETE
;
120 if (mProgress
& FLAG_LOAD_COMPLETE
) {
121 status
|= imgIRequest::STATUS_LOAD_COMPLETE
;
123 if (mProgress
& FLAG_IS_ANIMATED
) {
124 status
|= imgIRequest::STATUS_IS_ANIMATED
;
126 if (mProgress
& FLAG_HAS_TRANSPARENCY
) {
127 status
|= imgIRequest::STATUS_HAS_TRANSPARENCY
;
129 if (mProgress
& FLAG_HAS_ERROR
) {
130 status
|= imgIRequest::STATUS_ERROR
;
136 // A helper class to allow us to call SyncNotify asynchronously.
137 class AsyncNotifyRunnable
: public nsRunnable
140 AsyncNotifyRunnable(ProgressTracker
* aTracker
,
141 IProgressObserver
* aObserver
)
144 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
145 MOZ_ASSERT(aTracker
, "aTracker should not be null");
146 MOZ_ASSERT(aObserver
, "aObserver should not be null");
147 mObservers
.AppendElement(aObserver
);
152 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
153 MOZ_ASSERT(mTracker
, "mTracker should not be null");
154 for (uint32_t i
= 0; i
< mObservers
.Length(); ++i
) {
155 mObservers
[i
]->SetNotificationsDeferred(false);
156 mTracker
->SyncNotify(mObservers
[i
]);
159 mTracker
->mRunnable
= nullptr;
163 void AddObserver(IProgressObserver
* aObserver
)
165 mObservers
.AppendElement(aObserver
);
168 void RemoveObserver(IProgressObserver
* aObserver
)
170 mObservers
.RemoveElement(aObserver
);
174 friend class ProgressTracker
;
176 nsRefPtr
<ProgressTracker
> mTracker
;
177 nsTArray
<nsRefPtr
<IProgressObserver
>> mObservers
;
181 ProgressTracker::Notify(IProgressObserver
* aObserver
)
183 MOZ_ASSERT(NS_IsMainThread());
186 if (mImage
&& mImage
->GetURI()) {
187 nsRefPtr
<ImageURL
> uri(mImage
->GetURI());
190 LOG_FUNC_WITH_PARAM(GetImgLog(),
191 "ProgressTracker::Notify async", "uri", spec
.get());
193 LOG_FUNC_WITH_PARAM(GetImgLog(),
194 "ProgressTracker::Notify async", "uri", "<unknown>");
198 aObserver
->SetNotificationsDeferred(true);
200 // If we have an existing runnable that we can use, we just append this
201 // observer to its list of observers to be notified. This ensures we don't
202 // unnecessarily delay onload.
203 AsyncNotifyRunnable
* runnable
=
204 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get());
207 runnable
->AddObserver(aObserver
);
209 mRunnable
= new AsyncNotifyRunnable(this, aObserver
);
210 NS_DispatchToCurrentThread(mRunnable
);
214 // A helper class to allow us to call SyncNotify asynchronously for a given,
216 class AsyncNotifyCurrentStateRunnable
: public nsRunnable
219 AsyncNotifyCurrentStateRunnable(ProgressTracker
* aProgressTracker
,
220 IProgressObserver
* aObserver
)
221 : mProgressTracker(aProgressTracker
)
222 , mObserver(aObserver
)
224 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
225 MOZ_ASSERT(mProgressTracker
, "mProgressTracker should not be null");
226 MOZ_ASSERT(mObserver
, "mObserver should not be null");
227 mImage
= mProgressTracker
->GetImage();
232 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
233 mObserver
->SetNotificationsDeferred(false);
235 mProgressTracker
->SyncNotify(mObserver
);
240 nsRefPtr
<ProgressTracker
> mProgressTracker
;
241 nsRefPtr
<IProgressObserver
> mObserver
;
243 // We have to hold on to a reference to the tracker's image, just in case
244 // it goes away while we're in the event queue.
245 nsRefPtr
<Image
> mImage
;
249 ProgressTracker::NotifyCurrentState(IProgressObserver
* aObserver
)
251 MOZ_ASSERT(NS_IsMainThread());
255 if (mImage
&& mImage
->GetURI()) {
256 mImage
->GetURI()->GetSpec(spec
);
258 LOG_FUNC_WITH_PARAM(GetImgLog(),
259 "ProgressTracker::NotifyCurrentState", "uri", spec
.get());
262 aObserver
->SetNotificationsDeferred(true);
264 nsCOMPtr
<nsIRunnable
> ev
= new AsyncNotifyCurrentStateRunnable(this, aObserver
);
265 NS_DispatchToCurrentThread(ev
);
268 #define NOTIFY_IMAGE_OBSERVERS(OBSERVERS, FUNC) \
270 ObserverArray::ForwardIterator iter(OBSERVERS); \
271 while (iter.HasMore()) { \
272 nsRefPtr<IProgressObserver> observer = iter.GetNext().get(); \
273 if (observer && !observer->NotificationsDeferred()) { \
280 ProgressTracker::SyncNotifyInternal(ObserverArray
& aObservers
,
283 const nsIntRect
& aDirtyRect
)
285 MOZ_ASSERT(NS_IsMainThread());
287 typedef imgINotificationObserver I
;
289 if (aProgress
& FLAG_SIZE_AVAILABLE
) {
290 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::SIZE_AVAILABLE
));
293 if (aProgress
& FLAG_DECODE_STARTED
) {
294 NOTIFY_IMAGE_OBSERVERS(aObservers
, OnStartDecode());
297 if (aProgress
& FLAG_ONLOAD_BLOCKED
) {
298 NOTIFY_IMAGE_OBSERVERS(aObservers
, BlockOnload());
303 // If there's any content in this frame at all (always true for
304 // vector images, true for raster images that have decoded at
305 // least one frame) then send OnFrameUpdate.
306 if (!aDirtyRect
.IsEmpty()) {
307 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::FRAME_UPDATE
, &aDirtyRect
));
310 if (aProgress
& FLAG_FRAME_COMPLETE
) {
311 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::FRAME_COMPLETE
));
314 if (aProgress
& FLAG_HAS_TRANSPARENCY
) {
315 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::HAS_TRANSPARENCY
));
318 if (aProgress
& FLAG_IS_ANIMATED
) {
319 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::IS_ANIMATED
));
323 // Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
324 // observers that can fire events when they receive those notifications to do
325 // so then, instead of being forced to wait for UnblockOnload.
326 if (aProgress
& FLAG_ONLOAD_UNBLOCKED
) {
327 NOTIFY_IMAGE_OBSERVERS(aObservers
, UnblockOnload());
330 if (aProgress
& FLAG_DECODE_COMPLETE
) {
331 MOZ_ASSERT(aHasImage
, "Stopped decoding without ever having an image?");
332 NOTIFY_IMAGE_OBSERVERS(aObservers
, Notify(I::DECODE_COMPLETE
));
335 if (aProgress
& FLAG_LOAD_COMPLETE
) {
336 NOTIFY_IMAGE_OBSERVERS(aObservers
,
337 OnLoadComplete(aProgress
& FLAG_LAST_PART_COMPLETE
));
342 ProgressTracker::SyncNotifyProgress(Progress aProgress
,
343 const nsIntRect
& aInvalidRect
346 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
348 // Don't unblock onload if we're not blocked.
349 Progress progress
= Difference(aProgress
);
350 if (!((mProgress
| progress
) & FLAG_ONLOAD_BLOCKED
)) {
351 progress
&= ~FLAG_ONLOAD_UNBLOCKED
;
354 // XXX(seth): Hack to work around the fact that some observers have bugs and
355 // need to get onload blocking notifications multiple times. We should fix
356 // those observers and remove this.
357 if ((aProgress
& FLAG_DECODE_COMPLETE
) &&
358 (mProgress
& FLAG_ONLOAD_BLOCKED
) &&
359 (mProgress
& FLAG_ONLOAD_UNBLOCKED
)) {
360 progress
|= FLAG_ONLOAD_BLOCKED
| FLAG_ONLOAD_UNBLOCKED
;
363 // Apply the changes.
364 mProgress
|= progress
;
366 CheckProgressConsistency(mProgress
);
368 // Send notifications.
369 SyncNotifyInternal(mObservers
, !!mImage
, progress
, aInvalidRect
);
371 if (progress
& FLAG_HAS_ERROR
) {
372 FireFailureNotification();
377 ProgressTracker::SyncNotify(IProgressObserver
* aObserver
)
379 MOZ_ASSERT(NS_IsMainThread());
383 if (mImage
&& mImage
->GetURI()) {
384 mImage
->GetURI()->GetSpec(spec
);
386 LOG_SCOPE_WITH_PARAM(GetImgLog(),
387 "ProgressTracker::SyncNotify", "uri", spec
.get());
392 if (NS_FAILED(mImage
->GetWidth(&rect
.width
)) ||
393 NS_FAILED(mImage
->GetHeight(&rect
.height
))) {
394 // Either the image has no intrinsic size, or it has an error.
395 rect
= nsIntRect::GetMaxSizedIntRect();
400 array
.AppendElement(aObserver
);
401 SyncNotifyInternal(array
, !!mImage
, mProgress
, rect
);
405 ProgressTracker::EmulateRequestFinished(IProgressObserver
* aObserver
)
407 MOZ_ASSERT(NS_IsMainThread(),
408 "SyncNotifyState and mObservers are not threadsafe");
409 nsRefPtr
<IProgressObserver
> kungFuDeathGrip(aObserver
);
411 if (mProgress
& FLAG_ONLOAD_BLOCKED
&& !(mProgress
& FLAG_ONLOAD_UNBLOCKED
)) {
412 aObserver
->UnblockOnload();
415 if (!(mProgress
& FLAG_LOAD_COMPLETE
)) {
416 aObserver
->OnLoadComplete(true);
421 ProgressTracker::AddObserver(IProgressObserver
* aObserver
)
423 MOZ_ASSERT(NS_IsMainThread());
424 mObservers
.AppendElementUnlessExists(aObserver
);
428 ProgressTracker::RemoveObserver(IProgressObserver
* aObserver
)
430 MOZ_ASSERT(NS_IsMainThread());
432 // Remove the observer from the list.
433 bool removed
= mObservers
.RemoveElement(aObserver
);
435 // Observers can get confused if they don't get all the proper teardown
436 // notifications. Part ways on good terms.
437 if (removed
&& !aObserver
->NotificationsDeferred()) {
438 EmulateRequestFinished(aObserver
);
441 // Make sure we don't give callbacks to an observer that isn't interested in
443 AsyncNotifyRunnable
* runnable
=
444 static_cast<AsyncNotifyRunnable
*>(mRunnable
.get());
446 if (aObserver
->NotificationsDeferred() && runnable
) {
447 runnable
->RemoveObserver(aObserver
);
448 aObserver
->SetNotificationsDeferred(false);
455 ProgressTracker::FirstObserverIs(IProgressObserver
* aObserver
)
457 MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
458 ObserverArray::ForwardIterator
iter(mObservers
);
459 while (iter
.HasMore()) {
460 nsRefPtr
<IProgressObserver
> observer
= iter
.GetNext().get();
462 return observer
.get() == aObserver
;
469 ProgressTracker::OnUnlockedDraw()
471 MOZ_ASSERT(NS_IsMainThread());
472 NOTIFY_IMAGE_OBSERVERS(mObservers
,
473 Notify(imgINotificationObserver::UNLOCKED_DRAW
));
477 ProgressTracker::ResetForNewRequest()
479 MOZ_ASSERT(NS_IsMainThread());
480 mProgress
= NoProgress
;
481 CheckProgressConsistency(mProgress
);
485 ProgressTracker::OnDiscard()
487 MOZ_ASSERT(NS_IsMainThread());
488 NOTIFY_IMAGE_OBSERVERS(mObservers
,
489 Notify(imgINotificationObserver::DISCARD
));
493 ProgressTracker::OnImageAvailable()
495 if (!NS_IsMainThread()) {
496 // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
497 // so subsequent calls or dispatches which Unlock or Decrement~ should
498 // be issued after this to avoid race conditions.
499 NS_DispatchToMainThread(
500 NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable
));
504 // Notify any imgRequestProxys that are observing us that we have an Image.
505 ObserverArray::ForwardIterator
iter(mObservers
);
506 while (iter
.HasMore()) {
507 nsRefPtr
<IProgressObserver
> observer
= iter
.GetNext().get();
509 observer
->SetHasImage();
515 ProgressTracker::FireFailureNotification()
517 MOZ_ASSERT(NS_IsMainThread());
519 // Some kind of problem has happened with image decoding.
520 // Report the URI to net:failed-to-process-uri-conent observers.
522 // Should be on main thread, so ok to create a new nsIURI.
523 nsCOMPtr
<nsIURI
> uri
;
525 nsRefPtr
<ImageURL
> threadsafeUriData
= mImage
->GetURI();
526 uri
= threadsafeUriData
? threadsafeUriData
->ToIURI() : nullptr;
529 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
531 os
->NotifyObservers(uri
, "net:failed-to-process-uri-content", nullptr);
538 } // namespace mozilla