Bug 1467571 [wpt PR 11385] - Make manifest's parsers quicker, a=testonly
[gecko.git] / image / imgRequestProxy.cpp
blobd0da177e80aa2acd330cd8d457d4cd34f245a481
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 "imgRequestProxy.h"
9 #include "ImageLogging.h"
10 #include "imgLoader.h"
11 #include "Image.h"
12 #include "ImageOps.h"
13 #include "nsError.h"
14 #include "nsCRTGlue.h"
15 #include "imgINotificationObserver.h"
16 #include "mozilla/dom/TabGroup.h" // for TabGroup
17 #include "mozilla/dom/DocGroup.h" // for DocGroup
18 #include "mozilla/Move.h"
19 #include "mozilla/Telemetry.h" // for Telemetry
21 using namespace mozilla;
22 using namespace mozilla::image;
24 // The split of imgRequestProxy and imgRequestProxyStatic means that
25 // certain overridden functions need to be usable in the destructor.
26 // Since virtual functions can't be used in that way, this class
27 // provides a behavioural trait for each class to use instead.
28 class ProxyBehaviour
30 public:
31 virtual ~ProxyBehaviour() = default;
33 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
34 virtual bool HasImage() const = 0;
35 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
36 virtual imgRequest* GetOwner() const = 0;
37 virtual void SetOwner(imgRequest* aOwner) = 0;
40 class RequestBehaviour : public ProxyBehaviour
42 public:
43 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
45 already_AddRefed<mozilla::image::Image>GetImage() const override;
46 bool HasImage() const override;
47 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
49 imgRequest* GetOwner() const override {
50 return mOwner;
53 void SetOwner(imgRequest* aOwner) override {
54 mOwner = aOwner;
56 if (mOwner) {
57 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
58 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
59 } else {
60 mOwnerHasImage = false;
64 private:
65 // We maintain the following invariant:
66 // The proxy is registered at most with a single imgRequest as an observer,
67 // and whenever it is, mOwner points to that object. This helps ensure that
68 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
69 // from whatever request it was registered with (if any). This, in turn,
70 // means that imgRequest::mObservers will not have any stale pointers in it.
71 RefPtr<imgRequest> mOwner;
73 bool mOwnerHasImage;
76 already_AddRefed<mozilla::image::Image>
77 RequestBehaviour::GetImage() const
79 if (!mOwnerHasImage) {
80 return nullptr;
82 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
83 return progressTracker->GetImage();
86 already_AddRefed<ProgressTracker>
87 RequestBehaviour::GetProgressTracker() const
89 // NOTE: It's possible that our mOwner has an Image that it didn't notify
90 // us about, if we were Canceled before its Image was constructed.
91 // (Canceling removes us as an observer, so mOwner has no way to notify us).
92 // That's why this method uses mOwner->GetProgressTracker() instead of just
93 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
94 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
95 return mOwner->GetProgressTracker();
98 NS_IMPL_ADDREF(imgRequestProxy)
99 NS_IMPL_RELEASE(imgRequestProxy)
101 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
102 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest)
103 NS_INTERFACE_MAP_ENTRY(imgIRequest)
104 NS_INTERFACE_MAP_ENTRY(nsIRequest)
105 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
106 NS_INTERFACE_MAP_ENTRY(nsISecurityInfoProvider)
107 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel,
108 TimedChannel() != nullptr)
109 NS_INTERFACE_MAP_END
111 imgRequestProxy::imgRequestProxy() :
112 mBehaviour(new RequestBehaviour),
113 mURI(nullptr),
114 mListener(nullptr),
115 mLoadFlags(nsIRequest::LOAD_NORMAL),
116 mLockCount(0),
117 mAnimationConsumers(0),
118 mCanceled(false),
119 mIsInLoadGroup(false),
120 mForceDispatchLoadGroup(false),
121 mListenerIsStrongRef(false),
122 mDecodeRequested(false),
123 mPendingNotify(false),
124 mValidating(false),
125 mHadListener(false),
126 mHadDispatch(false)
128 /* member initializers and constructor code */
129 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
132 imgRequestProxy::~imgRequestProxy()
134 /* destructor code */
135 MOZ_ASSERT(!mListener,
136 "Someone forgot to properly cancel this request!");
138 // If we had a listener, that means we would have issued notifications. With
139 // bug 1359833, we added support for main thread scheduler groups. Each
140 // imgRequestProxy may have its own associated listener, document and/or
141 // scheduler group. Typically most imgRequestProxy belong to the same
142 // document, or have no listener, which means we will want to execute all main
143 // thread code in that shared scheduler group. Less frequently, there may be
144 // multiple imgRequests and they have separate documents, which means that
145 // when we issue state notifications, some or all need to be dispatched to the
146 // appropriate scheduler group for each request. This should be rare, so we
147 // want to monitor the frequency of dispatching in the wild.
148 if (mHadListener) {
149 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
150 mHadDispatch);
153 // Unlock the image the proper number of times if we're holding locks on
154 // it. Note that UnlockImage() decrements mLockCount each time it's called.
155 while (mLockCount) {
156 UnlockImage();
159 ClearAnimationConsumers();
161 // Explicitly set mListener to null to ensure that the RemoveProxy
162 // call below can't send |this| to an arbitrary listener while |this|
163 // is being destroyed. This is all belt-and-suspenders in view of the
164 // above assert.
165 NullOutListener();
167 /* Call RemoveProxy with a successful status. This will keep the
168 channel, if still downloading data, from being canceled if 'this' is
169 the last observer. This allows the image to continue to download and
170 be cached even if no one is using it currently.
172 mCanceled = true;
173 RemoveFromOwner(NS_OK);
175 RemoveFromLoadGroup();
176 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
179 nsresult
180 imgRequestProxy::Init(imgRequest* aOwner,
181 nsILoadGroup* aLoadGroup,
182 nsIDocument* aLoadingDocument,
183 nsIURI* aURI,
184 imgINotificationObserver* aObserver)
186 MOZ_ASSERT(!GetOwner() && !mListener,
187 "imgRequestProxy is already initialized");
189 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request",
190 aOwner);
192 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
194 mBehaviour->SetOwner(aOwner);
195 mListener = aObserver;
196 // Make sure to addref mListener before the AddToOwner call below, since
197 // that call might well want to release it if the imgRequest has
198 // already seen OnStopRequest.
199 if (mListener) {
200 mHadListener = true;
201 mListenerIsStrongRef = true;
202 NS_ADDREF(mListener);
204 mLoadGroup = aLoadGroup;
205 mURI = aURI;
207 // Note: AddToOwner won't send all the On* notifications immediately
208 AddToOwner(aLoadingDocument);
210 return NS_OK;
213 nsresult
214 imgRequestProxy::ChangeOwner(imgRequest* aNewOwner)
216 MOZ_ASSERT(GetOwner(),
217 "Cannot ChangeOwner on a proxy without an owner!");
219 if (mCanceled) {
220 // Ensure that this proxy has received all notifications to date
221 // before we clean it up when removing it from the old owner below.
222 SyncNotifyListener();
225 // If we're holding locks, unlock the old image.
226 // Note that UnlockImage decrements mLockCount each time it's called.
227 uint32_t oldLockCount = mLockCount;
228 while (mLockCount) {
229 UnlockImage();
232 // If we're holding animation requests, undo them.
233 uint32_t oldAnimationConsumers = mAnimationConsumers;
234 ClearAnimationConsumers();
236 GetOwner()->RemoveProxy(this, NS_OK);
238 mBehaviour->SetOwner(aNewOwner);
239 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
241 // If we were locked, apply the locks here
242 for (uint32_t i = 0; i < oldLockCount; i++) {
243 LockImage();
246 // If we had animation requests, restore them here. Note that we
247 // do this *after* RemoveProxy, which clears out animation consumers
248 // (see bug 601723).
249 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
250 IncrementAnimationConsumers();
253 AddToOwner(nullptr);
254 return NS_OK;
257 void
258 imgRequestProxy::MarkValidating()
260 MOZ_ASSERT(GetValidator());
261 mValidating = true;
264 void
265 imgRequestProxy::ClearValidating()
267 MOZ_ASSERT(mValidating);
268 MOZ_ASSERT(!GetValidator());
269 mValidating = false;
271 // If we'd previously requested a synchronous decode, request a decode on the
272 // new image.
273 if (mDecodeRequested) {
274 mDecodeRequested = false;
275 StartDecoding(imgIContainer::FLAG_NONE);
279 bool
280 imgRequestProxy::IsOnEventTarget() const
282 // Ensure we are in some main thread context because the scheduler group
283 // methods are only safe to call on the main thread.
284 MOZ_ASSERT(NS_IsMainThread());
286 if (mTabGroup) {
287 MOZ_ASSERT(mEventTarget);
288 return mTabGroup->IsSafeToRun();
291 if (mListener) {
292 // If we have no scheduler group but we do have a listener, then we know
293 // that the listener requires unlabelled dispatch.
294 MOZ_ASSERT(mEventTarget);
295 return mozilla::SchedulerGroup::IsSafeToRunUnlabeled();
298 // No listener means it is always safe, as there is nothing to do.
299 return true;
302 already_AddRefed<nsIEventTarget>
303 imgRequestProxy::GetEventTarget() const
305 nsCOMPtr<nsIEventTarget> target(mEventTarget);
306 return target.forget();
309 nsresult
310 imgRequestProxy::DispatchWithTargetIfAvailable(already_AddRefed<nsIRunnable> aEvent)
312 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
314 // This method should only be used when it is *expected* that we are
315 // dispatching an event (e.g. we want to handle an event asynchronously)
316 // rather we need to (e.g. we are in the wrong scheduler group context).
317 // As such, we do not set mHadDispatch for telemetry purposes.
318 if (mEventTarget) {
319 mEventTarget->Dispatch(std::move(aEvent), NS_DISPATCH_NORMAL);
320 return NS_OK;
323 return NS_DispatchToMainThread(std::move(aEvent));
326 void
327 imgRequestProxy::DispatchWithTarget(already_AddRefed<nsIRunnable> aEvent)
329 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTarget");
331 MOZ_ASSERT(mListener || mTabGroup);
332 MOZ_ASSERT(mEventTarget);
334 mHadDispatch = true;
335 mEventTarget->Dispatch(std::move(aEvent), NS_DISPATCH_NORMAL);
338 void
339 imgRequestProxy::AddToOwner(nsIDocument* aLoadingDocument)
341 // An imgRequestProxy can be initialized with neither a listener nor a
342 // document. The caller could follow up later by cloning the canonical
343 // imgRequestProxy with the actual listener. This is possible because
344 // imgLoader::LoadImage does not require a valid listener to be provided.
346 // Without a listener, we don't need to set our scheduler group, because
347 // we have nothing to signal. However if we were told what document this
348 // is for, it is likely that future listeners will belong to the same
349 // scheduler group.
351 // With a listener, we always need to update our scheduler group. A null
352 // scheduler group is valid with or without a document, but that means
353 // we will use the most generic event target possible on dispatch.
354 if (aLoadingDocument) {
355 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
356 if (docGroup) {
357 mTabGroup = docGroup->GetTabGroup();
358 MOZ_ASSERT(mTabGroup);
360 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
361 MOZ_ASSERT(mEventTarget);
365 if (mListener && !mEventTarget) {
366 mEventTarget = do_GetMainThread();
369 imgRequest* owner = GetOwner();
370 if (!owner) {
371 return;
374 owner->AddProxy(this);
377 void
378 imgRequestProxy::RemoveFromOwner(nsresult aStatus)
380 imgRequest* owner = GetOwner();
381 if (owner) {
382 if (mValidating) {
383 imgCacheValidator* validator = owner->GetValidator();
384 MOZ_ASSERT(validator);
385 validator->RemoveProxy(this);
386 mValidating = false;
389 owner->RemoveProxy(this, aStatus);
393 void
394 imgRequestProxy::AddToLoadGroup()
396 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
397 MOZ_ASSERT(!mForceDispatchLoadGroup);
399 /* While in theory there could be a dispatch outstanding to remove this
400 request from the load group, in practice we only add to the load group
401 (when previously not in a load group) at initialization. */
402 if (!mIsInLoadGroup && mLoadGroup) {
403 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
404 mLoadGroup->AddRequest(this, nullptr);
405 mIsInLoadGroup = true;
409 void
410 imgRequestProxy::RemoveFromLoadGroup()
412 if (!mIsInLoadGroup || !mLoadGroup) {
413 return;
416 /* Sometimes we may not be able to remove ourselves from the load group in
417 the current context. This is because our listeners are not re-entrant (e.g.
418 we are in the middle of CancelAndForgetObserver or SyncClone). */
419 if (mForceDispatchLoadGroup) {
420 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
422 /* We take away the load group from the request temporarily; this prevents
423 additional dispatches via RemoveFromLoadGroup occurring, as well as
424 MoveToBackgroundInLoadGroup from removing and readding. This is safe
425 because we know that once we get here, blocking the load group at all is
426 unnecessary. */
427 mIsInLoadGroup = false;
428 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
429 RefPtr<imgRequestProxy> self(this);
430 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
431 "imgRequestProxy::RemoveFromLoadGroup",
432 [self, loadGroup]() -> void {
433 loadGroup->RemoveRequest(self, nullptr, NS_OK);
434 }));
435 return;
438 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
440 /* calling RemoveFromLoadGroup may cause the document to finish
441 loading, which could result in our death. We need to make sure
442 that we stay alive long enough to fight another battle... at
443 least until we exit this function. */
444 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
445 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
446 mLoadGroup = nullptr;
447 mIsInLoadGroup = false;
450 void
451 imgRequestProxy::MoveToBackgroundInLoadGroup()
453 /* Even if we are still in the load group, we may have taken away the load
454 group reference itself because we are in the process of leaving the group.
455 In that case, there is no need to background the request. */
456 if (!mLoadGroup) {
457 return;
460 /* There is no need to dispatch if we only need to add ourselves to the load
461 group without removal. It is the removal which causes the problematic
462 callbacks (see RemoveFromLoadGroup). */
463 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
464 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
466 RefPtr<imgRequestProxy> self(this);
467 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
468 "imgRequestProxy::MoveToBackgroundInLoadGroup",
469 [self]() -> void {
470 self->MoveToBackgroundInLoadGroup();
471 }));
472 return;
475 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
476 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
477 if (mIsInLoadGroup) {
478 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
481 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
482 mLoadGroup->AddRequest(this, nullptr);
485 /** nsIRequest / imgIRequest methods **/
487 NS_IMETHODIMP
488 imgRequestProxy::GetName(nsACString& aName)
490 aName.Truncate();
492 if (mURI) {
493 mURI->GetSpec(aName);
496 return NS_OK;
499 NS_IMETHODIMP
500 imgRequestProxy::IsPending(bool* _retval)
502 return NS_ERROR_NOT_IMPLEMENTED;
505 NS_IMETHODIMP
506 imgRequestProxy::GetStatus(nsresult* aStatus)
508 return NS_ERROR_NOT_IMPLEMENTED;
511 NS_IMETHODIMP
512 imgRequestProxy::Cancel(nsresult status)
514 if (mCanceled) {
515 return NS_ERROR_FAILURE;
518 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
520 mCanceled = true;
522 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
523 return DispatchWithTargetIfAvailable(ev.forget());
526 void
527 imgRequestProxy::DoCancel(nsresult status)
529 RemoveFromOwner(status);
530 RemoveFromLoadGroup();
531 NullOutListener();
534 NS_IMETHODIMP
535 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus)
537 // If mCanceled is true but mListener is non-null, that means
538 // someone called Cancel() on us but the imgCancelRunnable is still
539 // pending. We still need to null out mListener before returning
540 // from this function in this case. That means we want to do the
541 // RemoveProxy call right now, because we need to deliver the
542 // onStopRequest.
543 if (mCanceled && !mListener) {
544 return NS_ERROR_FAILURE;
547 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
549 mCanceled = true;
550 mForceDispatchLoadGroup = true;
551 RemoveFromOwner(aStatus);
552 RemoveFromLoadGroup();
553 mForceDispatchLoadGroup = false;
555 NullOutListener();
557 return NS_OK;
560 NS_IMETHODIMP
561 imgRequestProxy::StartDecoding(uint32_t aFlags)
563 // Flag this, so we know to request after validation if pending.
564 if (IsValidating()) {
565 mDecodeRequested = true;
566 return NS_OK;
569 RefPtr<Image> image = GetImage();
570 if (image) {
571 return image->StartDecoding(aFlags);
574 if (GetOwner()) {
575 GetOwner()->StartDecoding();
578 return NS_OK;
581 bool
582 imgRequestProxy::StartDecodingWithResult(uint32_t aFlags)
584 // Flag this, so we know to request after validation if pending.
585 if (IsValidating()) {
586 mDecodeRequested = true;
587 return false;
590 RefPtr<Image> image = GetImage();
591 if (image) {
592 return image->StartDecodingWithResult(aFlags);
595 if (GetOwner()) {
596 GetOwner()->StartDecoding();
599 return false;
602 NS_IMETHODIMP
603 imgRequestProxy::LockImage()
605 mLockCount++;
606 RefPtr<Image> image = GetImage();
607 if (image) {
608 return image->LockImage();
610 return NS_OK;
613 NS_IMETHODIMP
614 imgRequestProxy::UnlockImage()
616 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
618 mLockCount--;
619 RefPtr<Image> image = GetImage();
620 if (image) {
621 return image->UnlockImage();
623 return NS_OK;
626 NS_IMETHODIMP
627 imgRequestProxy::RequestDiscard()
629 RefPtr<Image> image = GetImage();
630 if (image) {
631 return image->RequestDiscard();
633 return NS_OK;
636 NS_IMETHODIMP
637 imgRequestProxy::IncrementAnimationConsumers()
639 mAnimationConsumers++;
640 RefPtr<Image> image = GetImage();
641 if (image) {
642 image->IncrementAnimationConsumers();
644 return NS_OK;
647 NS_IMETHODIMP
648 imgRequestProxy::DecrementAnimationConsumers()
650 // We may get here if some responsible code called Increment,
651 // then called us, but we have meanwhile called ClearAnimationConsumers
652 // because we needed to get rid of them earlier (see
653 // imgRequest::RemoveProxy), and hence have nothing left to
654 // decrement. (In such a case we got rid of the animation consumers
655 // early, but not the observer.)
656 if (mAnimationConsumers > 0) {
657 mAnimationConsumers--;
658 RefPtr<Image> image = GetImage();
659 if (image) {
660 image->DecrementAnimationConsumers();
663 return NS_OK;
666 void
667 imgRequestProxy::ClearAnimationConsumers()
669 while (mAnimationConsumers > 0) {
670 DecrementAnimationConsumers();
674 NS_IMETHODIMP
675 imgRequestProxy::Suspend()
677 return NS_ERROR_NOT_IMPLEMENTED;
680 NS_IMETHODIMP
681 imgRequestProxy::Resume()
683 return NS_ERROR_NOT_IMPLEMENTED;
686 NS_IMETHODIMP
687 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup)
689 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
690 return NS_OK;
692 NS_IMETHODIMP
693 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup)
695 if (loadGroup != mLoadGroup) {
696 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
697 return NS_ERROR_NOT_IMPLEMENTED;
699 return NS_OK;
702 NS_IMETHODIMP
703 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags)
705 *flags = mLoadFlags;
706 return NS_OK;
708 NS_IMETHODIMP
709 imgRequestProxy::SetLoadFlags(nsLoadFlags flags)
711 mLoadFlags = flags;
712 return NS_OK;
715 /** imgIRequest methods **/
717 NS_IMETHODIMP
718 imgRequestProxy::GetImage(imgIContainer** aImage)
720 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
721 // It's possible that our owner has an image but hasn't notified us of it -
722 // that'll happen if we get Canceled before the owner instantiates its image
723 // (because Canceling unregisters us as a listener on mOwner). If we're
724 // in that situation, just grab the image off of mOwner.
725 RefPtr<Image> image = GetImage();
726 nsCOMPtr<imgIContainer> imageToReturn;
727 if (image) {
728 imageToReturn = do_QueryInterface(image);
730 if (!imageToReturn && GetOwner()) {
731 imageToReturn = GetOwner()->GetImage();
733 if (!imageToReturn) {
734 return NS_ERROR_FAILURE;
737 imageToReturn.swap(*aImage);
739 return NS_OK;
742 NS_IMETHODIMP
743 imgRequestProxy::GetImageStatus(uint32_t* aStatus)
745 if (IsValidating()) {
746 // We are currently validating the image, and so our status could revert if
747 // we discard the cache. We should also be deferring notifications, such
748 // that the caller will be notified when validation completes. Rather than
749 // risk misleading the caller, return nothing.
750 *aStatus = imgIRequest::STATUS_NONE;
751 } else {
752 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
753 *aStatus = progressTracker->GetImageStatus();
756 return NS_OK;
759 NS_IMETHODIMP
760 imgRequestProxy::GetImageErrorCode(nsresult* aStatus)
762 if (!GetOwner()) {
763 return NS_ERROR_FAILURE;
766 *aStatus = GetOwner()->GetImageErrorCode();
768 return NS_OK;
771 NS_IMETHODIMP
772 imgRequestProxy::GetURI(nsIURI** aURI)
774 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
775 nsCOMPtr<nsIURI> uri = mURI;
776 uri.forget(aURI);
777 return NS_OK;
780 nsresult
781 imgRequestProxy::GetFinalURI(nsIURI** aURI)
783 if (!GetOwner()) {
784 return NS_ERROR_FAILURE;
787 return GetOwner()->GetFinalURI(aURI);
790 NS_IMETHODIMP
791 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver)
793 *aObserver = mListener;
794 NS_IF_ADDREF(*aObserver);
795 return NS_OK;
798 NS_IMETHODIMP
799 imgRequestProxy::GetMimeType(char** aMimeType)
801 if (!GetOwner()) {
802 return NS_ERROR_FAILURE;
805 const char* type = GetOwner()->GetMimeType();
806 if (!type) {
807 return NS_ERROR_FAILURE;
810 *aMimeType = NS_strdup(type);
812 return NS_OK;
815 imgRequestProxy* imgRequestProxy::NewClonedProxy()
817 return new imgRequestProxy();
820 NS_IMETHODIMP
821 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
822 imgIRequest** aClone)
824 nsresult result;
825 imgRequestProxy* proxy;
826 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
827 *aClone = proxy;
828 return result;
831 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
832 nsIDocument* aLoadingDocument,
833 imgRequestProxy** aClone)
835 return PerformClone(aObserver, aLoadingDocument,
836 /* aSyncNotify */ true, aClone);
839 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
840 nsIDocument* aLoadingDocument,
841 imgRequestProxy** aClone)
843 return PerformClone(aObserver, aLoadingDocument,
844 /* aSyncNotify */ false, aClone);
847 nsresult
848 imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
849 nsIDocument* aLoadingDocument,
850 bool aSyncNotify,
851 imgRequestProxy** aClone)
853 MOZ_ASSERT(aClone, "Null out param");
855 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
857 *aClone = nullptr;
858 RefPtr<imgRequestProxy> clone = NewClonedProxy();
860 nsCOMPtr<nsILoadGroup> loadGroup;
861 if (aLoadingDocument) {
862 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
865 // It is important to call |SetLoadFlags()| before calling |Init()| because
866 // |Init()| adds the request to the loadgroup.
867 // When a request is added to a loadgroup, its load flags are merged
868 // with the load flags of the loadgroup.
869 // XXXldb That's not true anymore. Stuff from imgLoader adds the
870 // request to the loadgroup.
871 clone->SetLoadFlags(mLoadFlags);
872 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup,
873 aLoadingDocument, mURI, aObserver);
874 if (NS_FAILED(rv)) {
875 return rv;
878 // Assign to *aClone before calling Notify so that if the caller expects to
879 // only be notified for requests it's already holding pointers to it won't be
880 // surprised.
881 NS_ADDREF(*aClone = clone);
883 imgCacheValidator* validator = GetValidator();
884 if (validator) {
885 // Note that if we have a validator, we don't want to issue notifications at
886 // here because we want to defer until that completes. AddProxy will add us
887 // to the load group; we cannot avoid that in this case, because we don't
888 // know when the validation will complete, and if it will cause us to
889 // discard our cached state anyways. We are probably already blocked by the
890 // original LoadImage(WithChannel) request in any event.
891 clone->MarkValidating();
892 validator->AddProxy(clone);
893 } else {
894 // We only want to add the request to the load group of the owning document
895 // if it is still in progress. Some callers cannot handle a supurious load
896 // group removal (e.g. print preview) so we must be careful. On the other
897 // hand, if after cloning, the original request proxy is cancelled /
898 // destroyed, we need to ensure that any clones still block the load group
899 // if it is incomplete.
900 bool addToLoadGroup = mIsInLoadGroup;
901 if (!addToLoadGroup) {
902 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
903 addToLoadGroup = tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
906 if (addToLoadGroup) {
907 clone->AddToLoadGroup();
910 if (aSyncNotify) {
911 // This is wrong!!! We need to notify asynchronously, but there's code
912 // that assumes that we don't. This will be fixed in bug 580466. Note that
913 // if we have a validator, we won't issue notifications anyways because
914 // they are deferred, so there is no point in requesting.
915 clone->mForceDispatchLoadGroup = true;
916 clone->SyncNotifyListener();
917 clone->mForceDispatchLoadGroup = false;
918 } else {
919 // Without a validator, we can request asynchronous notifications
920 // immediately. If there was a validator, this would override the deferral
921 // and that would be incorrect.
922 clone->NotifyListener();
926 return NS_OK;
929 NS_IMETHODIMP
930 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal)
932 if (!GetOwner()) {
933 return NS_ERROR_FAILURE;
936 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
937 principal.forget(aPrincipal);
938 return NS_OK;
941 NS_IMETHODIMP
942 imgRequestProxy::GetMultipart(bool* aMultipart)
944 if (!GetOwner()) {
945 return NS_ERROR_FAILURE;
948 *aMultipart = GetOwner()->GetMultipart();
950 return NS_OK;
953 NS_IMETHODIMP
954 imgRequestProxy::GetCORSMode(int32_t* aCorsMode)
956 if (!GetOwner()) {
957 return NS_ERROR_FAILURE;
960 *aCorsMode = GetOwner()->GetCORSMode();
962 return NS_OK;
965 NS_IMETHODIMP
966 imgRequestProxy::BoostPriority(uint32_t aCategory)
968 NS_ENSURE_STATE(GetOwner() && !mCanceled);
969 GetOwner()->BoostPriority(aCategory);
970 return NS_OK;
973 /** nsISupportsPriority methods **/
975 NS_IMETHODIMP
976 imgRequestProxy::GetPriority(int32_t* priority)
978 NS_ENSURE_STATE(GetOwner());
979 *priority = GetOwner()->Priority();
980 return NS_OK;
983 NS_IMETHODIMP
984 imgRequestProxy::SetPriority(int32_t priority)
986 NS_ENSURE_STATE(GetOwner() && !mCanceled);
987 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
988 return NS_OK;
991 NS_IMETHODIMP
992 imgRequestProxy::AdjustPriority(int32_t priority)
994 // We don't require |!mCanceled| here. This may be called even if we're
995 // cancelled, because it's invoked as part of the process of removing an image
996 // from the load group.
997 NS_ENSURE_STATE(GetOwner());
998 GetOwner()->AdjustPriority(this, priority);
999 return NS_OK;
1002 /** nsISecurityInfoProvider methods **/
1004 NS_IMETHODIMP
1005 imgRequestProxy::GetSecurityInfo(nsISupports** _retval)
1007 if (GetOwner()) {
1008 return GetOwner()->GetSecurityInfo(_retval);
1011 *_retval = nullptr;
1012 return NS_OK;
1015 NS_IMETHODIMP
1016 imgRequestProxy::GetHasTransferredData(bool* hasData)
1018 if (GetOwner()) {
1019 *hasData = GetOwner()->HasTransferredData();
1020 } else {
1021 // The safe thing to do is to claim we have data
1022 *hasData = true;
1024 return NS_OK;
1027 static const char*
1028 NotificationTypeToString(int32_t aType)
1030 switch(aType)
1032 case imgINotificationObserver::SIZE_AVAILABLE: return "SIZE_AVAILABLE";
1033 case imgINotificationObserver::FRAME_UPDATE: return "FRAME_UPDATE";
1034 case imgINotificationObserver::FRAME_COMPLETE: return "FRAME_COMPLETE";
1035 case imgINotificationObserver::LOAD_COMPLETE: return "LOAD_COMPLETE";
1036 case imgINotificationObserver::DECODE_COMPLETE: return "DECODE_COMPLETE";
1037 case imgINotificationObserver::DISCARD: return "DISCARD";
1038 case imgINotificationObserver::UNLOCKED_DRAW: return "UNLOCKED_DRAW";
1039 case imgINotificationObserver::IS_ANIMATED: return "IS_ANIMATED";
1040 case imgINotificationObserver::HAS_TRANSPARENCY: return "HAS_TRANSPARENCY";
1041 default:
1042 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
1043 return "(unknown notification)";
1047 void
1048 imgRequestProxy::Notify(int32_t aType, const mozilla::gfx::IntRect* aRect)
1050 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
1051 "Should call OnLoadComplete");
1053 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
1054 NotificationTypeToString(aType));
1056 if (!mListener || mCanceled) {
1057 return;
1060 if (!IsOnEventTarget()) {
1061 RefPtr<imgRequestProxy> self(this);
1062 if (aRect) {
1063 const mozilla::gfx::IntRect rect = *aRect;
1064 DispatchWithTarget(NS_NewRunnableFunction("imgRequestProxy::Notify",
1065 [self, rect, aType]() -> void {
1066 self->Notify(aType, &rect);
1067 }));
1068 } else {
1069 DispatchWithTarget(NS_NewRunnableFunction("imgRequestProxy::Notify",
1070 [self, aType]() -> void {
1071 self->Notify(aType, nullptr);
1072 }));
1074 return;
1077 // Make sure the listener stays alive while we notify.
1078 nsCOMPtr<imgINotificationObserver> listener(mListener);
1080 listener->Notify(this, aType, aRect);
1083 void
1084 imgRequestProxy::OnLoadComplete(bool aLastPart)
1086 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete",
1087 "uri", mURI);
1089 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1090 // on the listener, the removal from the loadgroup, the release of the
1091 // listener, etc). Don't let them do it.
1092 RefPtr<imgRequestProxy> self(this);
1094 if (!IsOnEventTarget()) {
1095 DispatchWithTarget(NS_NewRunnableFunction("imgRequestProxy::OnLoadComplete",
1096 [self, aLastPart]() -> void {
1097 self->OnLoadComplete(aLastPart);
1098 }));
1099 return;
1102 if (mListener && !mCanceled) {
1103 // Hold a ref to the listener while we call it, just in case.
1104 nsCOMPtr<imgINotificationObserver> listener(mListener);
1105 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1108 // If we're expecting more data from a multipart channel, re-add ourself
1109 // to the loadgroup so that the document doesn't lose track of the load.
1110 // If the request is already a background request and there's more data
1111 // coming, we can just leave the request in the loadgroup as-is.
1112 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1113 if (aLastPart) {
1114 RemoveFromLoadGroup();
1115 } else {
1116 // More data is coming, so change the request to be a background request
1117 // and put it back in the loadgroup.
1118 MoveToBackgroundInLoadGroup();
1122 if (mListenerIsStrongRef && aLastPart) {
1123 MOZ_ASSERT(mListener, "How did that happen?");
1124 // Drop our strong ref to the listener now that we're done with
1125 // everything. Note that this can cancel us and other fun things
1126 // like that. Don't add anything in this method after this point.
1127 imgINotificationObserver* obs = mListener;
1128 mListenerIsStrongRef = false;
1129 NS_RELEASE(obs);
1133 void
1134 imgRequestProxy::NullOutListener()
1136 // If we have animation consumers, then they don't matter anymore
1137 if (mListener) {
1138 ClearAnimationConsumers();
1141 if (mListenerIsStrongRef) {
1142 // Releasing could do weird reentery stuff, so just play it super-safe
1143 nsCOMPtr<imgINotificationObserver> obs;
1144 obs.swap(mListener);
1145 mListenerIsStrongRef = false;
1146 } else {
1147 mListener = nullptr;
1150 // Note that we don't free the event target. We actually need that to ensure
1151 // we get removed from the ProgressTracker properly. No harm in keeping it
1152 // however.
1153 mTabGroup = nullptr;
1156 NS_IMETHODIMP
1157 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn)
1159 imgRequestProxy* proxy;
1160 nsresult result = GetStaticRequest(nullptr, &proxy);
1161 *aReturn = proxy;
1162 return result;
1165 nsresult
1166 imgRequestProxy::GetStaticRequest(nsIDocument* aLoadingDocument,
1167 imgRequestProxy** aReturn)
1169 *aReturn = nullptr;
1170 RefPtr<Image> image = GetImage();
1172 bool animated;
1173 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1174 // Early exit - we're not animated, so we don't have to do anything.
1175 NS_ADDREF(*aReturn = this);
1176 return NS_OK;
1179 // Check for errors in the image. Callers code rely on GetStaticRequest
1180 // failing in this case, though with FrozenImage there's no technical reason
1181 // for it anymore.
1182 if (image->HasError()) {
1183 return NS_ERROR_FAILURE;
1186 // We are animated. We need to create a frozen version of this image.
1187 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1189 // Create a static imgRequestProxy with our new extracted frame.
1190 nsCOMPtr<nsIPrincipal> currentPrincipal;
1191 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1192 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(frozenImage,
1193 currentPrincipal);
1194 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1196 NS_ADDREF(*aReturn = req);
1198 return NS_OK;
1201 void
1202 imgRequestProxy::NotifyListener()
1204 // It would be nice to notify the observer directly in the status tracker
1205 // instead of through the proxy, but there are several places we do extra
1206 // processing when we receive notifications (like OnStopRequest()), and we
1207 // need to check mCanceled everywhere too.
1209 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1210 if (GetOwner()) {
1211 // Send the notifications to our listener asynchronously.
1212 progressTracker->Notify(this);
1213 } else {
1214 // We don't have an imgRequest, so we can only notify the clone of our
1215 // current state, but we still have to do that asynchronously.
1216 MOZ_ASSERT(HasImage(),
1217 "if we have no imgRequest, we should have an Image");
1218 progressTracker->NotifyCurrentState(this);
1222 void
1223 imgRequestProxy::SyncNotifyListener()
1225 // It would be nice to notify the observer directly in the status tracker
1226 // instead of through the proxy, but there are several places we do extra
1227 // processing when we receive notifications (like OnStopRequest()), and we
1228 // need to check mCanceled everywhere too.
1230 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1231 progressTracker->SyncNotify(this);
1234 void
1235 imgRequestProxy::SetHasImage()
1237 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1238 MOZ_ASSERT(progressTracker);
1239 RefPtr<Image> image = progressTracker->GetImage();
1240 MOZ_ASSERT(image);
1242 // Force any private status related to the owner to reflect
1243 // the presence of an image;
1244 mBehaviour->SetOwner(mBehaviour->GetOwner());
1246 // Apply any locks we have
1247 for (uint32_t i = 0; i < mLockCount; ++i) {
1248 image->LockImage();
1251 // Apply any animation consumers we have
1252 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1253 image->IncrementAnimationConsumers();
1257 already_AddRefed<ProgressTracker>
1258 imgRequestProxy::GetProgressTracker() const
1260 return mBehaviour->GetProgressTracker();
1263 already_AddRefed<mozilla::image::Image>
1264 imgRequestProxy::GetImage() const
1266 return mBehaviour->GetImage();
1269 bool
1270 RequestBehaviour::HasImage() const
1272 if (!mOwnerHasImage) {
1273 return false;
1275 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1276 return progressTracker ? progressTracker->HasImage() : false;
1279 bool
1280 imgRequestProxy::HasImage() const
1282 return mBehaviour->HasImage();
1285 imgRequest*
1286 imgRequestProxy::GetOwner() const
1288 return mBehaviour->GetOwner();
1291 imgCacheValidator*
1292 imgRequestProxy::GetValidator() const
1294 imgRequest* owner = GetOwner();
1295 if (!owner) {
1296 return nullptr;
1298 return owner->GetValidator();
1301 ////////////////// imgRequestProxyStatic methods
1303 class StaticBehaviour : public ProxyBehaviour
1305 public:
1306 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1308 already_AddRefed<mozilla::image::Image>
1309 GetImage() const override {
1310 RefPtr<mozilla::image::Image> image = mImage;
1311 return image.forget();
1314 bool HasImage() const override {
1315 return mImage;
1318 already_AddRefed<ProgressTracker> GetProgressTracker()
1319 const override {
1320 return mImage->GetProgressTracker();
1323 imgRequest* GetOwner() const override {
1324 return nullptr;
1327 void SetOwner(imgRequest* aOwner) override {
1328 MOZ_ASSERT(!aOwner,
1329 "We shouldn't be giving static requests a non-null owner.");
1332 private:
1333 // Our image. We have to hold a strong reference here, because that's normally
1334 // the job of the underlying request.
1335 RefPtr<mozilla::image::Image> mImage;
1338 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1339 nsIPrincipal* aPrincipal)
1340 : mPrincipal(aPrincipal)
1342 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1345 NS_IMETHODIMP
1346 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal)
1348 if (!mPrincipal) {
1349 return NS_ERROR_FAILURE;
1352 NS_ADDREF(*aPrincipal = mPrincipal);
1354 return NS_OK;
1357 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy()
1359 nsCOMPtr<nsIPrincipal> currentPrincipal;
1360 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1361 RefPtr<mozilla::image::Image> image = GetImage();
1362 return new imgRequestProxyStatic(image, currentPrincipal);