Fix typo in 9b54bd30006c008b4a951331b273613d5bac3abf
[pm.git] / image / src / ProgressTracker.cpp
blob8d156819746a73988b158af69e9ff0ed400d7be1
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"
13 #include "Image.h"
14 #include "nsNetUtil.h"
15 #include "nsIObserverService.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Services.h"
20 using mozilla::WeakPtr;
22 namespace mozilla {
23 namespace image {
25 ProgressTrackerInit::ProgressTrackerInit(Image* aImage,
26 ProgressTracker* aTracker)
28 MOZ_ASSERT(aImage);
30 if (aTracker) {
31 mTracker = aTracker;
32 } else {
33 mTracker = new ProgressTracker();
35 mTracker->SetImage(aImage);
36 aImage->SetProgressTracker(mTracker);
37 MOZ_ASSERT(mTracker);
40 ProgressTrackerInit::~ProgressTrackerInit()
42 mTracker->ResetImage();
45 static void
46 CheckProgressConsistency(Progress aProgress)
48 // Check preconditions for every progress bit.
50 if (aProgress & FLAG_SIZE_AVAILABLE) {
51 // No preconditions.
53 if (aProgress & FLAG_DECODE_STARTED) {
54 // No preconditions.
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) {
63 // No preconditions.
65 if (aProgress & FLAG_ONLOAD_BLOCKED) {
66 // No preconditions.
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) {
83 // No preconditions.
87 void
88 ProgressTracker::SetImage(Image* aImage)
90 MOZ_ASSERT(aImage, "Setting null image");
91 MOZ_ASSERT(!mImage, "Setting image when we already have one");
92 mImage = aImage;
95 void
96 ProgressTracker::ResetImage()
98 MOZ_ASSERT(mImage, "Resetting image when it's already null!");
99 mImage = nullptr;
102 uint32_t
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;
133 return status;
136 // A helper class to allow us to call SyncNotify asynchronously.
137 class AsyncNotifyRunnable : public nsRunnable
139 public:
140 AsyncNotifyRunnable(ProgressTracker* aTracker,
141 IProgressObserver* aObserver)
142 : mTracker(aTracker)
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);
150 NS_IMETHOD Run()
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;
160 return NS_OK;
163 void AddObserver(IProgressObserver* aObserver)
165 mObservers.AppendElement(aObserver);
168 void RemoveObserver(IProgressObserver* aObserver)
170 mObservers.RemoveElement(aObserver);
173 private:
174 friend class ProgressTracker;
176 nsRefPtr<ProgressTracker> mTracker;
177 nsTArray<nsRefPtr<IProgressObserver>> mObservers;
180 void
181 ProgressTracker::Notify(IProgressObserver* aObserver)
183 MOZ_ASSERT(NS_IsMainThread());
185 #ifdef PR_LOGGING
186 if (mImage && mImage->GetURI()) {
187 nsRefPtr<ImageURL> uri(mImage->GetURI());
188 nsAutoCString spec;
189 uri->GetSpec(spec);
190 LOG_FUNC_WITH_PARAM(GetImgLog(),
191 "ProgressTracker::Notify async", "uri", spec.get());
192 } else {
193 LOG_FUNC_WITH_PARAM(GetImgLog(),
194 "ProgressTracker::Notify async", "uri", "<unknown>");
196 #endif
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());
206 if (runnable) {
207 runnable->AddObserver(aObserver);
208 } else {
209 mRunnable = new AsyncNotifyRunnable(this, aObserver);
210 NS_DispatchToCurrentThread(mRunnable);
214 // A helper class to allow us to call SyncNotify asynchronously for a given,
215 // fixed, state.
216 class AsyncNotifyCurrentStateRunnable : public nsRunnable
218 public:
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();
230 NS_IMETHOD Run()
232 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
233 mObserver->SetNotificationsDeferred(false);
235 mProgressTracker->SyncNotify(mObserver);
236 return NS_OK;
239 private:
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;
248 void
249 ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
251 MOZ_ASSERT(NS_IsMainThread());
253 #ifdef PR_LOGGING
254 nsAutoCString spec;
255 if (mImage && mImage->GetURI()) {
256 mImage->GetURI()->GetSpec(spec);
258 LOG_FUNC_WITH_PARAM(GetImgLog(),
259 "ProgressTracker::NotifyCurrentState", "uri", spec.get());
260 #endif
262 aObserver->SetNotificationsDeferred(true);
264 nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this, aObserver);
265 NS_DispatchToCurrentThread(ev);
268 #define NOTIFY_IMAGE_OBSERVERS(OBSERVERS, FUNC) \
269 do { \
270 ObserverArray::ForwardIterator iter(OBSERVERS); \
271 while (iter.HasMore()) { \
272 nsRefPtr<IProgressObserver> observer = iter.GetNext().get(); \
273 if (observer && !observer->NotificationsDeferred()) { \
274 observer->FUNC; \
277 } while (false);
279 /* static */ void
280 ProgressTracker::SyncNotifyInternal(ObserverArray& aObservers,
281 bool aHasImage,
282 Progress aProgress,
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());
301 if (aHasImage) {
302 // OnFrameUpdate
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));
341 void
342 ProgressTracker::SyncNotifyProgress(Progress aProgress,
343 const nsIntRect& aInvalidRect
344 /* = nsIntRect() */)
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();
376 void
377 ProgressTracker::SyncNotify(IProgressObserver* aObserver)
379 MOZ_ASSERT(NS_IsMainThread());
381 #ifdef PR_LOGGING
382 nsAutoCString spec;
383 if (mImage && mImage->GetURI()) {
384 mImage->GetURI()->GetSpec(spec);
386 LOG_SCOPE_WITH_PARAM(GetImgLog(),
387 "ProgressTracker::SyncNotify", "uri", spec.get());
388 #endif
390 nsIntRect rect;
391 if (mImage) {
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();
399 ObserverArray array;
400 array.AppendElement(aObserver);
401 SyncNotifyInternal(array, !!mImage, mProgress, rect);
404 void
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);
420 void
421 ProgressTracker::AddObserver(IProgressObserver* aObserver)
423 MOZ_ASSERT(NS_IsMainThread());
424 mObservers.AppendElementUnlessExists(aObserver);
427 bool
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
442 // them any more.
443 AsyncNotifyRunnable* runnable =
444 static_cast<AsyncNotifyRunnable*>(mRunnable.get());
446 if (aObserver->NotificationsDeferred() && runnable) {
447 runnable->RemoveObserver(aObserver);
448 aObserver->SetNotificationsDeferred(false);
451 return removed;
454 bool
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();
461 if (observer) {
462 return observer.get() == aObserver;
465 return false;
468 void
469 ProgressTracker::OnUnlockedDraw()
471 MOZ_ASSERT(NS_IsMainThread());
472 NOTIFY_IMAGE_OBSERVERS(mObservers,
473 Notify(imgINotificationObserver::UNLOCKED_DRAW));
476 void
477 ProgressTracker::ResetForNewRequest()
479 MOZ_ASSERT(NS_IsMainThread());
480 mProgress = NoProgress;
481 CheckProgressConsistency(mProgress);
484 void
485 ProgressTracker::OnDiscard()
487 MOZ_ASSERT(NS_IsMainThread());
488 NOTIFY_IMAGE_OBSERVERS(mObservers,
489 Notify(imgINotificationObserver::DISCARD));
492 void
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));
501 return;
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();
508 if (observer) {
509 observer->SetHasImage();
514 void
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.
521 if (mImage) {
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;
528 if (uri) {
529 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
530 if (os) {
531 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
537 } // namespace image
538 } // namespace mozilla