Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 "imgStatusTracker.h"
9 #include "imgIContainer.h"
10 #include "imgRequestProxy.h"
11 #include "imgDecoderObserver.h"
12 #include "Image.h"
13 #include "ImageLogging.h"
14 #include "nsNetUtil.h"
15 #include "nsIObserverService.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Services.h"
20 using namespace mozilla::image;
21 using mozilla::WeakPtr;
23 class imgStatusTrackerObserver : public imgDecoderObserver
24 {
25 public:
26 imgStatusTrackerObserver(imgStatusTracker* aTracker)
27 : mTracker(aTracker->asWeakPtr())
28 {
29 MOZ_ASSERT(aTracker);
30 }
32 void SetTracker(imgStatusTracker* aTracker)
33 {
34 MOZ_ASSERT(aTracker);
35 mTracker = aTracker->asWeakPtr();
36 }
38 /** imgDecoderObserver methods **/
40 virtual void OnStartDecode() MOZ_OVERRIDE
41 {
42 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartDecode");
43 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
44 if (!tracker) { return; }
45 tracker->RecordStartDecode();
46 if (!tracker->IsMultipart()) {
47 tracker->RecordBlockOnload();
48 }
49 }
51 virtual void OnStartRequest() MOZ_OVERRIDE
52 {
53 NS_NOTREACHED("imgStatusTrackerObserver(imgDecoderObserver)::OnStartRequest");
54 }
56 virtual void OnStartContainer() MOZ_OVERRIDE
57 {
58 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartContainer");
59 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
60 if (!tracker) { return; }
61 nsRefPtr<Image> image = tracker->GetImage();;
62 tracker->RecordStartContainer(image);
63 }
65 virtual void OnStartFrame() MOZ_OVERRIDE
66 {
67 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartFrame");
68 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
69 if (!tracker) { return; }
70 tracker->RecordStartFrame();
71 }
73 virtual void FrameChanged(const nsIntRect* dirtyRect) MOZ_OVERRIDE
74 {
75 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::FrameChanged");
76 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
77 if (!tracker) { return; }
78 tracker->RecordFrameChanged(dirtyRect);
79 }
81 virtual void OnStopFrame() MOZ_OVERRIDE
82 {
83 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopFrame");
84 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
85 if (!tracker) { return; }
86 tracker->RecordStopFrame();
87 tracker->RecordUnblockOnload();
88 }
90 virtual void OnStopDecode(nsresult aStatus) MOZ_OVERRIDE
91 {
92 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopDecode");
93 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
94 if (!tracker) { return; }
95 tracker->RecordStopDecode(aStatus);
97 // This is really hacky. We need to handle the case where we start decoding,
98 // block onload, but then hit an error before we get to our first frame.
99 tracker->RecordUnblockOnload();
100 }
102 virtual void OnStopRequest(bool aLastPart, nsresult aStatus) MOZ_OVERRIDE
103 {
104 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopRequest");
105 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
106 if (!tracker) { return; }
107 tracker->RecordStopRequest(aLastPart, aStatus);
108 }
110 virtual void OnDiscard() MOZ_OVERRIDE
111 {
112 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnDiscard");
113 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
114 if (!tracker) { return; }
115 tracker->RecordDiscard();
116 }
118 virtual void OnUnlockedDraw() MOZ_OVERRIDE
119 {
120 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnUnlockedDraw");
121 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
122 if (!tracker) { return; }
123 NS_ABORT_IF_FALSE(tracker->HasImage(),
124 "OnUnlockedDraw callback before we've created our image");
125 tracker->RecordUnlockedDraw();
126 }
128 virtual void OnImageIsAnimated() MOZ_OVERRIDE
129 {
130 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnImageIsAnimated");
131 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
132 if (!tracker) { return; }
133 tracker->RecordImageIsAnimated();
134 }
136 virtual void OnError() MOZ_OVERRIDE
137 {
138 LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnError");
139 nsRefPtr<imgStatusTracker> tracker = mTracker.get();
140 if (!tracker) { return; }
141 tracker->RecordError();
142 }
144 protected:
145 virtual ~imgStatusTrackerObserver() {}
147 private:
148 WeakPtr<imgStatusTracker> mTracker;
149 };
151 // imgStatusTracker methods
153 imgStatusTracker::imgStatusTracker(Image* aImage)
154 : mImage(aImage),
155 mState(0),
156 mImageStatus(imgIRequest::STATUS_NONE),
157 mIsMultipart(false),
158 mHadLastPart(false),
159 mHasBeenDecoded(false)
160 {
161 mTrackerObserver = new imgStatusTrackerObserver(this);
162 }
164 // Private, used only by CloneForRecording.
165 imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
166 : mImage(aOther.mImage),
167 mState(aOther.mState),
168 mImageStatus(aOther.mImageStatus),
169 mIsMultipart(aOther.mIsMultipart),
170 mHadLastPart(aOther.mHadLastPart),
171 mHasBeenDecoded(aOther.mHasBeenDecoded)
172 // Note: we explicitly don't copy several fields:
173 // - mRequestRunnable, because it won't be nulled out when the
174 // mRequestRunnable's Run function eventually gets called.
175 // - mProperties, because we don't need it and it'd just point at the same
176 // object
177 // - mConsumers, because we don't need to talk to consumers
178 // - mInvalidRect, because the point of it is to be fired off and reset
179 {
180 mTrackerObserver = new imgStatusTrackerObserver(this);
181 }
183 imgStatusTracker::~imgStatusTracker()
184 {}
186 imgStatusTrackerInit::imgStatusTrackerInit(mozilla::image::Image* aImage,
187 imgStatusTracker* aTracker)
188 {
189 MOZ_ASSERT(aImage);
191 if (aTracker) {
192 mTracker = aTracker;
193 mTracker->SetImage(aImage);
194 } else {
195 mTracker = new imgStatusTracker(aImage);
196 }
197 aImage->SetStatusTracker(mTracker);
198 MOZ_ASSERT(mTracker);
199 }
201 imgStatusTrackerInit::~imgStatusTrackerInit()
202 {
203 mTracker->ResetImage();
204 }
206 void
207 imgStatusTracker::SetImage(Image* aImage)
208 {
209 NS_ABORT_IF_FALSE(aImage, "Setting null image");
210 NS_ABORT_IF_FALSE(!mImage, "Setting image when we already have one");
211 mImage = aImage;
212 }
214 void
215 imgStatusTracker::ResetImage()
216 {
217 NS_ABORT_IF_FALSE(mImage, "Resetting image when it's already null!");
218 mImage = nullptr;
219 }
221 bool
222 imgStatusTracker::IsLoading() const
223 {
224 // Checking for whether OnStopRequest has fired allows us to say we're
225 // loading before OnStartRequest gets called, letting the request properly
226 // get removed from the cache in certain cases.
227 return !(mState & stateRequestStopped);
228 }
230 uint32_t
231 imgStatusTracker::GetImageStatus() const
232 {
233 return mImageStatus;
234 }
236 // A helper class to allow us to call SyncNotify asynchronously.
237 class imgRequestNotifyRunnable : public nsRunnable
238 {
239 public:
240 imgRequestNotifyRunnable(imgStatusTracker* aTracker,
241 imgRequestProxy* aRequestProxy)
242 : mTracker(aTracker)
243 {
244 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
245 MOZ_ASSERT(aRequestProxy, "aRequestProxy should not be null");
246 MOZ_ASSERT(aTracker, "aTracker should not be null");
247 mProxies.AppendElement(aRequestProxy);
248 }
250 NS_IMETHOD Run()
251 {
252 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
253 MOZ_ASSERT(mTracker, "mTracker should not be null");
254 for (uint32_t i = 0; i < mProxies.Length(); ++i) {
255 mProxies[i]->SetNotificationsDeferred(false);
256 mTracker->SyncNotify(mProxies[i]);
257 }
259 mTracker->mRequestRunnable = nullptr;
260 return NS_OK;
261 }
263 void AddProxy(imgRequestProxy* aRequestProxy)
264 {
265 mProxies.AppendElement(aRequestProxy);
266 }
268 void RemoveProxy(imgRequestProxy* aRequestProxy)
269 {
270 mProxies.RemoveElement(aRequestProxy);
271 }
273 private:
274 friend class imgStatusTracker;
276 nsRefPtr<imgStatusTracker> mTracker;
277 nsTArray< nsRefPtr<imgRequestProxy> > mProxies;
278 };
280 void
281 imgStatusTracker::Notify(imgRequestProxy* proxy)
282 {
283 MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
284 #ifdef PR_LOGGING
285 if (mImage && mImage->GetURI()) {
286 nsRefPtr<ImageURL> uri(mImage->GetURI());
287 nsAutoCString spec;
288 uri->GetSpec(spec);
289 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", spec.get());
290 } else {
291 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", "<unknown>");
292 }
293 #endif
295 proxy->SetNotificationsDeferred(true);
297 // If we have an existing runnable that we can use, we just append this proxy
298 // to its list of proxies to be notified. This ensures we don't unnecessarily
299 // delay onload.
300 imgRequestNotifyRunnable* runnable = static_cast<imgRequestNotifyRunnable*>(mRequestRunnable.get());
301 if (runnable) {
302 runnable->AddProxy(proxy);
303 } else {
304 mRequestRunnable = new imgRequestNotifyRunnable(this, proxy);
305 NS_DispatchToCurrentThread(mRequestRunnable);
306 }
307 }
309 // A helper class to allow us to call SyncNotify asynchronously for a given,
310 // fixed, state.
311 class imgStatusNotifyRunnable : public nsRunnable
312 {
313 public:
314 imgStatusNotifyRunnable(imgStatusTracker* statusTracker,
315 imgRequestProxy* requestproxy)
316 : mStatusTracker(statusTracker), mProxy(requestproxy)
317 {
318 MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
319 MOZ_ASSERT(requestproxy, "requestproxy cannot be null");
320 MOZ_ASSERT(statusTracker, "status should not be null");
321 mImage = statusTracker->GetImage();
322 }
324 NS_IMETHOD Run()
325 {
326 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
327 mProxy->SetNotificationsDeferred(false);
329 mStatusTracker->SyncNotify(mProxy);
330 return NS_OK;
331 }
333 private:
334 nsRefPtr<imgStatusTracker> mStatusTracker;
335 // We have to hold on to a reference to the tracker's image, just in case
336 // it goes away while we're in the event queue.
337 nsRefPtr<Image> mImage;
338 nsRefPtr<imgRequestProxy> mProxy;
339 };
341 void
342 imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
343 {
344 MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
345 #ifdef PR_LOGGING
346 nsRefPtr<ImageURL> uri;
347 proxy->GetURI(getter_AddRefs(uri));
348 nsAutoCString spec;
349 uri->GetSpec(spec);
350 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::NotifyCurrentState", "uri", spec.get());
351 #endif
353 proxy->SetNotificationsDeferred(true);
355 // We don't keep track of
356 nsCOMPtr<nsIRunnable> ev = new imgStatusNotifyRunnable(this, proxy);
357 NS_DispatchToCurrentThread(ev);
358 }
360 #define NOTIFY_IMAGE_OBSERVERS(func) \
361 do { \
362 ProxyArray::ForwardIterator iter(proxies); \
363 while (iter.HasMore()) { \
364 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get(); \
365 if (proxy && !proxy->NotificationsDeferred()) { \
366 proxy->func; \
367 } \
368 } \
369 } while (false);
371 /* static */ void
372 imgStatusTracker::SyncNotifyState(ProxyArray& proxies,
373 bool hasImage, uint32_t state,
374 nsIntRect& dirtyRect, bool hadLastPart)
375 {
376 MOZ_ASSERT(NS_IsMainThread());
377 // OnStartRequest
378 if (state & stateRequestStarted)
379 NOTIFY_IMAGE_OBSERVERS(OnStartRequest());
381 // OnStartContainer
382 if (state & stateHasSize)
383 NOTIFY_IMAGE_OBSERVERS(OnStartContainer());
385 // OnStartDecode
386 if (state & stateDecodeStarted)
387 NOTIFY_IMAGE_OBSERVERS(OnStartDecode());
389 // BlockOnload
390 if (state & stateBlockingOnload)
391 NOTIFY_IMAGE_OBSERVERS(BlockOnload());
393 if (hasImage) {
394 // OnFrameUpdate
395 // If there's any content in this frame at all (always true for
396 // vector images, true for raster images that have decoded at
397 // least one frame) then send OnFrameUpdate.
398 if (!dirtyRect.IsEmpty())
399 NOTIFY_IMAGE_OBSERVERS(OnFrameUpdate(&dirtyRect));
401 if (state & stateFrameStopped)
402 NOTIFY_IMAGE_OBSERVERS(OnStopFrame());
404 // OnImageIsAnimated
405 if (state & stateImageIsAnimated)
406 NOTIFY_IMAGE_OBSERVERS(OnImageIsAnimated());
407 }
409 if (state & stateDecodeStopped) {
410 NS_ABORT_IF_FALSE(hasImage, "stopped decoding without ever having an image?");
411 NOTIFY_IMAGE_OBSERVERS(OnStopDecode());
412 }
414 if (state & stateRequestStopped) {
415 NOTIFY_IMAGE_OBSERVERS(OnStopRequest(hadLastPart));
416 }
417 }
419 ImageStatusDiff
420 imgStatusTracker::Difference(imgStatusTracker* aOther) const
421 {
422 MOZ_ASSERT(aOther, "aOther cannot be null");
423 ImageStatusDiff diff;
424 diff.diffState = ~mState & aOther->mState & ~stateRequestStarted;
425 diff.diffImageStatus = ~mImageStatus & aOther->mImageStatus;
426 diff.unblockedOnload = mState & stateBlockingOnload && !(aOther->mState & stateBlockingOnload);
427 diff.unsetDecodeStarted = mImageStatus & imgIRequest::STATUS_DECODE_STARTED
428 && !(aOther->mImageStatus & imgIRequest::STATUS_DECODE_STARTED);
429 diff.foundError = (mImageStatus != imgIRequest::STATUS_ERROR)
430 && (aOther->mImageStatus == imgIRequest::STATUS_ERROR);
432 MOZ_ASSERT(!mIsMultipart || aOther->mIsMultipart, "mIsMultipart should be monotonic");
433 diff.foundIsMultipart = !mIsMultipart && aOther->mIsMultipart;
434 diff.foundLastPart = !mHadLastPart && aOther->mHadLastPart;
436 diff.gotDecoded = !mHasBeenDecoded && aOther->mHasBeenDecoded;
438 // Only record partial invalidations if we haven't been decoded before.
439 // When images are re-decoded after discarding, we don't want to display
440 // partially decoded versions to the user.
441 const uint32_t combinedStatus = mImageStatus | aOther->mImageStatus;
442 const bool doInvalidations = !(mHasBeenDecoded || aOther->mHasBeenDecoded)
443 || combinedStatus & imgIRequest::STATUS_ERROR
444 || combinedStatus & imgIRequest::STATUS_DECODE_COMPLETE;
446 // Record and reset the invalid rectangle.
447 // XXX(seth): We shouldn't be resetting anything here; see bug 910441.
448 if (doInvalidations) {
449 diff.invalidRect = aOther->mInvalidRect;
450 aOther->mInvalidRect.SetEmpty();
451 }
453 return diff;
454 }
456 ImageStatusDiff
457 imgStatusTracker::DecodeStateAsDifference() const
458 {
459 ImageStatusDiff diff;
460 diff.diffState = mState & ~stateRequestStarted;
462 // All other ImageStatusDiff fields are intentionally left at their default
463 // values; we only want to notify decode state changes.
465 return diff;
466 }
468 void
469 imgStatusTracker::ApplyDifference(const ImageStatusDiff& aDiff)
470 {
471 LOG_SCOPE(GetImgLog(), "imgStatusTracker::ApplyDifference");
473 // We must not modify or notify for the start-load state, which happens from Necko callbacks.
474 uint32_t loadState = mState & stateRequestStarted;
476 // Synchronize our state.
477 mState |= aDiff.diffState | loadState;
478 if (aDiff.unblockedOnload)
479 mState &= ~stateBlockingOnload;
481 mIsMultipart = mIsMultipart || aDiff.foundIsMultipart;
482 mHadLastPart = mHadLastPart || aDiff.foundLastPart;
483 mHasBeenDecoded = mHasBeenDecoded || aDiff.gotDecoded;
485 // Update the image status. There are some subtle points which are handled below.
486 mImageStatus |= aDiff.diffImageStatus;
488 // Unset bits which can get unset as part of the decoding process.
489 if (aDiff.unsetDecodeStarted)
490 mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
492 // The error state is sticky and overrides all other bits.
493 if (mImageStatus & imgIRequest::STATUS_ERROR)
494 mImageStatus = imgIRequest::STATUS_ERROR;
495 }
497 void
498 imgStatusTracker::SyncNotifyDifference(const ImageStatusDiff& diff)
499 {
500 MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
501 LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference");
503 nsIntRect invalidRect = mInvalidRect.Union(diff.invalidRect);
505 SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect, mHadLastPart);
507 mInvalidRect.SetEmpty();
509 if (diff.unblockedOnload) {
510 ProxyArray::ForwardIterator iter(mConsumers);
511 while (iter.HasMore()) {
512 // Hold on to a reference to this proxy, since notifying the state can
513 // cause it to disappear.
514 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
516 if (proxy && !proxy->NotificationsDeferred()) {
517 SendUnblockOnload(proxy);
518 }
519 }
520 }
522 if (diff.foundError) {
523 FireFailureNotification();
524 }
525 }
527 already_AddRefed<imgStatusTracker>
528 imgStatusTracker::CloneForRecording()
529 {
530 // Grab a ref to this to ensure it isn't deleted.
531 nsRefPtr<imgStatusTracker> thisStatusTracker = this;
532 nsRefPtr<imgStatusTracker> clone = new imgStatusTracker(*thisStatusTracker);
533 return clone.forget();
534 }
536 void
537 imgStatusTracker::SyncNotify(imgRequestProxy* proxy)
538 {
539 MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe");
540 #ifdef PR_LOGGING
541 nsRefPtr<ImageURL> uri;
542 proxy->GetURI(getter_AddRefs(uri));
543 nsAutoCString spec;
544 uri->GetSpec(spec);
545 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgStatusTracker::SyncNotify", "uri", spec.get());
546 #endif
548 nsIntRect r;
549 if (mImage) {
550 // XXX - Should only send partial rects here, but that needs to
551 // wait until we fix up the observer interface
552 r = mImage->FrameRect(imgIContainer::FRAME_CURRENT);
553 }
555 ProxyArray array;
556 array.AppendElement(proxy->asWeakPtr());
557 SyncNotifyState(array, !!mImage, mState, r, mHadLastPart);
558 }
560 void
561 imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy,
562 nsresult aStatus)
563 {
564 MOZ_ASSERT(NS_IsMainThread(),
565 "SyncNotifyState and mConsumers are not threadsafe");
566 nsCOMPtr<imgIRequest> kungFuDeathGrip(aProxy);
568 // In certain cases the request might not have started yet.
569 // We still need to fulfill the contract.
570 if (!(mState & stateRequestStarted)) {
571 aProxy->OnStartRequest();
572 }
574 if (mState & stateBlockingOnload) {
575 aProxy->UnblockOnload();
576 }
578 if (!(mState & stateRequestStopped)) {
579 aProxy->OnStopRequest(true);
580 }
581 }
583 void
584 imgStatusTracker::AddConsumer(imgRequestProxy* aConsumer)
585 {
586 MOZ_ASSERT(NS_IsMainThread());
587 mConsumers.AppendElementUnlessExists(aConsumer->asWeakPtr());
588 }
590 // XXX - The last argument should go away.
591 bool
592 imgStatusTracker::RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus)
593 {
594 MOZ_ASSERT(NS_IsMainThread());
595 // Remove the proxy from the list.
596 bool removed = mConsumers.RemoveElement(aConsumer);
598 // Consumers can get confused if they don't get all the proper teardown
599 // notifications. Part ways on good terms.
600 if (removed && !aConsumer->NotificationsDeferred()) {
601 EmulateRequestFinished(aConsumer, aStatus);
602 }
604 // Make sure we don't give callbacks to a consumer that isn't interested in
605 // them any more.
606 imgRequestNotifyRunnable* runnable = static_cast<imgRequestNotifyRunnable*>(mRequestRunnable.get());
607 if (aConsumer->NotificationsDeferred() && runnable) {
608 runnable->RemoveProxy(aConsumer);
609 aConsumer->SetNotificationsDeferred(false);
610 }
612 return removed;
613 }
615 bool
616 imgStatusTracker::FirstConsumerIs(imgRequestProxy* aConsumer)
617 {
618 MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
619 ProxyArray::ForwardIterator iter(mConsumers);
620 while (iter.HasMore()) {
621 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
622 if (proxy) {
623 return proxy.get() == aConsumer;
624 }
625 }
626 return false;
627 }
629 void
630 imgStatusTracker::RecordCancel()
631 {
632 if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
633 mImageStatus = imgIRequest::STATUS_ERROR;
634 }
636 void
637 imgStatusTracker::RecordLoaded()
638 {
639 NS_ABORT_IF_FALSE(mImage, "RecordLoaded called before we have an Image");
640 mState |= stateRequestStarted | stateHasSize | stateRequestStopped;
641 mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE | imgIRequest::STATUS_LOAD_COMPLETE;
642 mHadLastPart = true;
643 }
645 void
646 imgStatusTracker::RecordDecoded()
647 {
648 NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image");
649 mState |= stateDecodeStarted | stateDecodeStopped | stateFrameStopped;
650 mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE | imgIRequest::STATUS_DECODE_COMPLETE;
651 mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
652 }
654 void
655 imgStatusTracker::RecordStartDecode()
656 {
657 NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image");
658 mState |= stateDecodeStarted;
659 mImageStatus |= imgIRequest::STATUS_DECODE_STARTED;
660 }
662 void
663 imgStatusTracker::SendStartDecode(imgRequestProxy* aProxy)
664 {
665 MOZ_ASSERT(NS_IsMainThread());
666 if (!aProxy->NotificationsDeferred())
667 aProxy->OnStartDecode();
668 }
670 void
671 imgStatusTracker::RecordStartContainer(imgIContainer* aContainer)
672 {
673 NS_ABORT_IF_FALSE(mImage,
674 "RecordStartContainer called before we have an Image");
675 NS_ABORT_IF_FALSE(mImage == aContainer,
676 "RecordStartContainer called with wrong Image");
677 mState |= stateHasSize;
678 mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE;
679 }
681 void
682 imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy)
683 {
684 MOZ_ASSERT(NS_IsMainThread());
685 if (!aProxy->NotificationsDeferred())
686 aProxy->OnStartContainer();
687 }
689 void
690 imgStatusTracker::RecordStartFrame()
691 {
692 mInvalidRect.SetEmpty();
693 }
695 // No SendStartFrame since it's not observed below us.
697 void
698 imgStatusTracker::RecordStopFrame()
699 {
700 NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image");
701 mState |= stateFrameStopped;
702 mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE;
703 }
705 void
706 imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy)
707 {
708 MOZ_ASSERT(NS_IsMainThread());
709 if (!aProxy->NotificationsDeferred())
710 aProxy->OnStopFrame();
711 }
713 void
714 imgStatusTracker::RecordStopDecode(nsresult aStatus)
715 {
716 NS_ABORT_IF_FALSE(mImage,
717 "RecordStopDecode called before we have an Image");
718 mState |= stateDecodeStopped;
720 if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR) {
721 mImageStatus |= imgIRequest::STATUS_DECODE_COMPLETE;
722 mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
723 mHasBeenDecoded = true;
724 // If we weren't successful, clear all success status bits and set error.
725 } else {
726 mImageStatus = imgIRequest::STATUS_ERROR;
727 }
728 }
730 void
731 imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy,
732 nsresult aStatus)
733 {
734 MOZ_ASSERT(NS_IsMainThread());
735 if (!aProxy->NotificationsDeferred())
736 aProxy->OnStopDecode();
737 }
739 void
740 imgStatusTracker::RecordDiscard()
741 {
742 NS_ABORT_IF_FALSE(mImage,
743 "RecordDiscard called before we have an Image");
744 // Clear the state bits we no longer deserve.
745 uint32_t stateBitsToClear = stateDecodeStopped;
746 mState &= ~stateBitsToClear;
748 // Clear the status bits we no longer deserve.
749 uint32_t statusBitsToClear = imgIRequest::STATUS_DECODE_STARTED |
750 imgIRequest::STATUS_FRAME_COMPLETE |
751 imgIRequest::STATUS_DECODE_COMPLETE;
752 mImageStatus &= ~statusBitsToClear;
753 }
755 void
756 imgStatusTracker::SendDiscard(imgRequestProxy* aProxy)
757 {
758 MOZ_ASSERT(NS_IsMainThread());
759 if (!aProxy->NotificationsDeferred())
760 aProxy->OnDiscard();
761 }
764 void
765 imgStatusTracker::RecordUnlockedDraw()
766 {
767 NS_ABORT_IF_FALSE(mImage,
768 "RecordUnlockedDraw called before we have an Image");
769 }
771 void
772 imgStatusTracker::RecordImageIsAnimated()
773 {
774 NS_ABORT_IF_FALSE(mImage,
775 "RecordImageIsAnimated called before we have an Image");
776 mState |= stateImageIsAnimated;
777 }
779 void
780 imgStatusTracker::SendImageIsAnimated(imgRequestProxy* aProxy)
781 {
782 MOZ_ASSERT(NS_IsMainThread());
783 if (!aProxy->NotificationsDeferred())
784 aProxy->OnImageIsAnimated();
785 }
787 void
788 imgStatusTracker::SendUnlockedDraw(imgRequestProxy* aProxy)
789 {
790 MOZ_ASSERT(NS_IsMainThread());
791 if (!aProxy->NotificationsDeferred())
792 aProxy->OnUnlockedDraw();
793 }
795 void
796 imgStatusTracker::OnUnlockedDraw()
797 {
798 MOZ_ASSERT(NS_IsMainThread());
799 RecordUnlockedDraw();
800 ProxyArray::ForwardIterator iter(mConsumers);
801 while (iter.HasMore()) {
802 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
803 if (proxy) {
804 SendUnlockedDraw(proxy);
805 }
806 }
807 }
809 void
810 imgStatusTracker::RecordFrameChanged(const nsIntRect* aDirtyRect)
811 {
812 NS_ABORT_IF_FALSE(mImage,
813 "RecordFrameChanged called before we have an Image");
814 mInvalidRect = mInvalidRect.Union(*aDirtyRect);
815 }
817 void
818 imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy,
819 const nsIntRect* aDirtyRect)
820 {
821 MOZ_ASSERT(NS_IsMainThread());
822 if (!aProxy->NotificationsDeferred())
823 aProxy->OnFrameUpdate(aDirtyRect);
824 }
826 /* non-virtual sort-of-nsIRequestObserver methods */
827 void
828 imgStatusTracker::RecordStartRequest()
829 {
830 // We're starting a new load, so clear any status and state bits indicating
831 // load/decode
832 mImageStatus &= ~imgIRequest::STATUS_LOAD_PARTIAL;
833 mImageStatus &= ~imgIRequest::STATUS_LOAD_COMPLETE;
834 mImageStatus &= ~imgIRequest::STATUS_FRAME_COMPLETE;
835 mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
836 mImageStatus &= ~imgIRequest::STATUS_DECODE_COMPLETE;
837 mState &= ~stateRequestStarted;
838 mState &= ~stateDecodeStarted;
839 mState &= ~stateDecodeStopped;
840 mState &= ~stateRequestStopped;
841 mState &= ~stateBlockingOnload;
842 mState &= ~stateImageIsAnimated;
844 mState |= stateRequestStarted;
845 }
847 void
848 imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy)
849 {
850 MOZ_ASSERT(NS_IsMainThread());
851 if (!aProxy->NotificationsDeferred())
852 aProxy->OnStartRequest();
853 }
855 void
856 imgStatusTracker::OnStartRequest()
857 {
858 MOZ_ASSERT(NS_IsMainThread());
859 RecordStartRequest();
860 ProxyArray::ForwardIterator iter(mConsumers);
861 while (iter.HasMore()) {
862 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
863 if (proxy) {
864 SendStartRequest(proxy);
865 }
866 }
867 }
869 void
870 imgStatusTracker::RecordStopRequest(bool aLastPart,
871 nsresult aStatus)
872 {
873 mHadLastPart = aLastPart;
874 mState |= stateRequestStopped;
876 // If we were successful in loading, note that the image is complete.
877 if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR)
878 mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
879 else
880 mImageStatus = imgIRequest::STATUS_ERROR;
881 }
883 void
884 imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy,
885 bool aLastPart,
886 nsresult aStatus)
887 {
888 MOZ_ASSERT(NS_IsMainThread());
889 if (!aProxy->NotificationsDeferred()) {
890 aProxy->OnStopRequest(aLastPart);
891 }
892 }
894 class OnStopRequestEvent : public nsRunnable
895 {
896 public:
897 OnStopRequestEvent(imgStatusTracker* aTracker,
898 bool aLastPart,
899 nsresult aStatus)
900 : mTracker(aTracker)
901 , mLastPart(aLastPart)
902 , mStatus(aStatus)
903 {
904 MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
905 MOZ_ASSERT(aTracker, "aTracker should not be null");
906 }
908 NS_IMETHOD Run()
909 {
910 MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
911 MOZ_ASSERT(mTracker, "mTracker should not be null");
912 mTracker->OnStopRequest(mLastPart, mStatus);
913 return NS_OK;
914 }
915 private:
916 nsRefPtr<imgStatusTracker> mTracker;
917 bool mLastPart;
918 nsresult mStatus;
919 };
921 void
922 imgStatusTracker::OnStopRequest(bool aLastPart,
923 nsresult aStatus)
924 {
925 if (!NS_IsMainThread()) {
926 NS_DispatchToMainThread(
927 new OnStopRequestEvent(this, aLastPart, aStatus));
928 return;
929 }
930 bool preexistingError = mImageStatus == imgIRequest::STATUS_ERROR;
932 RecordStopRequest(aLastPart, aStatus);
933 /* notify the kids */
934 ProxyArray::ForwardIterator srIter(mConsumers);
935 while (srIter.HasMore()) {
936 nsRefPtr<imgRequestProxy> proxy = srIter.GetNext().get();
937 if (proxy) {
938 SendStopRequest(proxy, aLastPart, aStatus);
939 }
940 }
942 if (NS_FAILED(aStatus) && !preexistingError) {
943 FireFailureNotification();
944 }
945 }
947 void
948 imgStatusTracker::OnDiscard()
949 {
950 MOZ_ASSERT(NS_IsMainThread());
951 RecordDiscard();
953 /* notify the kids */
954 ProxyArray::ForwardIterator iter(mConsumers);
955 while (iter.HasMore()) {
956 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
957 if (proxy) {
958 SendDiscard(proxy);
959 }
960 }
961 }
963 void
964 imgStatusTracker::FrameChanged(const nsIntRect* aDirtyRect)
965 {
966 MOZ_ASSERT(NS_IsMainThread());
967 RecordFrameChanged(aDirtyRect);
969 /* notify the kids */
970 ProxyArray::ForwardIterator iter(mConsumers);
971 while (iter.HasMore()) {
972 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
973 if (proxy) {
974 SendFrameChanged(proxy, aDirtyRect);
975 }
976 }
977 }
979 void
980 imgStatusTracker::OnStopFrame()
981 {
982 MOZ_ASSERT(NS_IsMainThread());
983 RecordStopFrame();
985 /* notify the kids */
986 ProxyArray::ForwardIterator iter(mConsumers);
987 while (iter.HasMore()) {
988 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
989 if (proxy) {
990 SendStopFrame(proxy);
991 }
992 }
993 }
995 void
996 imgStatusTracker::OnDataAvailable()
997 {
998 if (!NS_IsMainThread()) {
999 // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
1000 // so subsequent calls or dispatches which Unlock or Decrement~ should
1001 // be issued after this to avoid race conditions.
1002 NS_DispatchToMainThread(
1003 NS_NewRunnableMethod(this, &imgStatusTracker::OnDataAvailable));
1004 return;
1005 }
1006 // Notify any imgRequestProxys that are observing us that we have an Image.
1007 ProxyArray::ForwardIterator iter(mConsumers);
1008 while (iter.HasMore()) {
1009 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
1010 if (proxy) {
1011 proxy->SetHasImage();
1012 }
1013 }
1014 }
1016 void
1017 imgStatusTracker::RecordBlockOnload()
1018 {
1019 MOZ_ASSERT(!(mState & stateBlockingOnload));
1020 mState |= stateBlockingOnload;
1021 }
1023 void
1024 imgStatusTracker::SendBlockOnload(imgRequestProxy* aProxy)
1025 {
1026 MOZ_ASSERT(NS_IsMainThread());
1027 if (!aProxy->NotificationsDeferred()) {
1028 aProxy->BlockOnload();
1029 }
1030 }
1032 void
1033 imgStatusTracker::RecordUnblockOnload()
1034 {
1035 mState &= ~stateBlockingOnload;
1036 }
1038 void
1039 imgStatusTracker::SendUnblockOnload(imgRequestProxy* aProxy)
1040 {
1041 MOZ_ASSERT(NS_IsMainThread());
1042 if (!aProxy->NotificationsDeferred()) {
1043 aProxy->UnblockOnload();
1044 }
1045 }
1047 void
1048 imgStatusTracker::MaybeUnblockOnload()
1049 {
1050 if (!NS_IsMainThread()) {
1051 NS_DispatchToMainThread(
1052 NS_NewRunnableMethod(this, &imgStatusTracker::MaybeUnblockOnload));
1053 return;
1054 }
1055 if (!(mState & stateBlockingOnload)) {
1056 return;
1057 }
1059 RecordUnblockOnload();
1061 ProxyArray::ForwardIterator iter(mConsumers);
1062 while (iter.HasMore()) {
1063 nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
1064 if (proxy) {
1065 SendUnblockOnload(proxy);
1066 }
1067 }
1068 }
1070 void
1071 imgStatusTracker::RecordError()
1072 {
1073 mImageStatus = imgIRequest::STATUS_ERROR;
1074 }
1076 void
1077 imgStatusTracker::FireFailureNotification()
1078 {
1079 MOZ_ASSERT(NS_IsMainThread());
1081 // Some kind of problem has happened with image decoding.
1082 // Report the URI to net:failed-to-process-uri-conent observers.
1083 if (mImage) {
1084 // Should be on main thread, so ok to create a new nsIURI.
1085 nsCOMPtr<nsIURI> uri;
1086 {
1087 nsRefPtr<ImageURL> threadsafeUriData = mImage->GetURI();
1088 uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr;
1089 }
1090 if (uri) {
1091 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1092 if (os) {
1093 os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
1094 }
1095 }
1096 }
1097 }