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"
13 #include "ImageTypes.h"
15 #include "nsCRTGlue.h"
16 #include "imgINotificationObserver.h"
17 #include "mozilla/dom/TabGroup.h" // for TabGroup
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "mozilla/Move.h"
20 #include "mozilla/Telemetry.h" // for Telemetry
22 using namespace mozilla
;
23 using namespace mozilla::image
;
24 using mozilla::dom::Document
;
26 // The split of imgRequestProxy and imgRequestProxyStatic means that
27 // certain overridden functions need to be usable in the destructor.
28 // Since virtual functions can't be used in that way, this class
29 // provides a behavioural trait for each class to use instead.
30 class ProxyBehaviour
{
32 virtual ~ProxyBehaviour() = default;
34 virtual already_AddRefed
<mozilla::image::Image
> GetImage() const = 0;
35 virtual bool HasImage() const = 0;
36 virtual already_AddRefed
<ProgressTracker
> GetProgressTracker() const = 0;
37 virtual imgRequest
* GetOwner() const = 0;
38 virtual void SetOwner(imgRequest
* aOwner
) = 0;
41 class RequestBehaviour
: public ProxyBehaviour
{
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
{ return mOwner
; }
51 void SetOwner(imgRequest
* aOwner
) override
{
55 RefPtr
<ProgressTracker
> ownerProgressTracker
= GetProgressTracker();
56 mOwnerHasImage
= ownerProgressTracker
&& ownerProgressTracker
->HasImage();
58 mOwnerHasImage
= false;
63 // We maintain the following invariant:
64 // The proxy is registered at most with a single imgRequest as an observer,
65 // and whenever it is, mOwner points to that object. This helps ensure that
66 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
67 // from whatever request it was registered with (if any). This, in turn,
68 // means that imgRequest::mObservers will not have any stale pointers in it.
69 RefPtr
<imgRequest
> mOwner
;
74 already_AddRefed
<mozilla::image::Image
> RequestBehaviour::GetImage() const {
75 if (!mOwnerHasImage
) {
78 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
79 return progressTracker
->GetImage();
82 already_AddRefed
<ProgressTracker
> RequestBehaviour::GetProgressTracker() const {
83 // NOTE: It's possible that our mOwner has an Image that it didn't notify
84 // us about, if we were Canceled before its Image was constructed.
85 // (Canceling removes us as an observer, so mOwner has no way to notify us).
86 // That's why this method uses mOwner->GetProgressTracker() instead of just
87 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
88 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
89 return mOwner
->GetProgressTracker();
92 NS_IMPL_ADDREF(imgRequestProxy
)
93 NS_IMPL_RELEASE(imgRequestProxy
)
95 NS_INTERFACE_MAP_BEGIN(imgRequestProxy
)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, imgIRequest
)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest
)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest
)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority
)
100 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel
, TimedChannel() != nullptr)
103 imgRequestProxy::imgRequestProxy()
104 : mBehaviour(new RequestBehaviour
),
107 mLoadFlags(nsIRequest::LOAD_NORMAL
),
109 mAnimationConsumers(0),
111 mIsInLoadGroup(false),
112 mForceDispatchLoadGroup(false),
113 mListenerIsStrongRef(false),
114 mDecodeRequested(false),
115 mPendingNotify(false),
118 mHadDispatch(false) {
119 /* member initializers and constructor code */
120 LOG_FUNC(gImgLog
, "imgRequestProxy::imgRequestProxy");
123 imgRequestProxy::~imgRequestProxy() {
124 /* destructor code */
125 MOZ_ASSERT(!mListener
, "Someone forgot to properly cancel this request!");
127 // If we had a listener, that means we would have issued notifications. With
128 // bug 1359833, we added support for main thread scheduler groups. Each
129 // imgRequestProxy may have its own associated listener, document and/or
130 // scheduler group. Typically most imgRequestProxy belong to the same
131 // document, or have no listener, which means we will want to execute all main
132 // thread code in that shared scheduler group. Less frequently, there may be
133 // multiple imgRequests and they have separate documents, which means that
134 // when we issue state notifications, some or all need to be dispatched to the
135 // appropriate scheduler group for each request. This should be rare, so we
136 // want to monitor the frequency of dispatching in the wild.
138 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED
,
142 MOZ_RELEASE_ASSERT(!mLockCount
, "Someone forgot to unlock on time?");
144 ClearAnimationConsumers();
146 // Explicitly set mListener to null to ensure that the RemoveProxy
147 // call below can't send |this| to an arbitrary listener while |this|
148 // is being destroyed. This is all belt-and-suspenders in view of the
152 /* Call RemoveProxy with a successful status. This will keep the
153 channel, if still downloading data, from being canceled if 'this' is
154 the last observer. This allows the image to continue to download and
155 be cached even if no one is using it currently.
158 RemoveFromOwner(NS_OK
);
160 RemoveFromLoadGroup();
161 LOG_FUNC(gImgLog
, "imgRequestProxy::~imgRequestProxy");
164 nsresult
imgRequestProxy::Init(imgRequest
* aOwner
, nsILoadGroup
* aLoadGroup
,
165 Document
* aLoadingDocument
, nsIURI
* aURI
,
166 imgINotificationObserver
* aObserver
) {
167 MOZ_ASSERT(!GetOwner() && !mListener
,
168 "imgRequestProxy is already initialized");
170 LOG_SCOPE_WITH_PARAM(gImgLog
, "imgRequestProxy::Init", "request", aOwner
);
172 MOZ_ASSERT(mAnimationConsumers
== 0, "Cannot have animation before Init");
174 mBehaviour
->SetOwner(aOwner
);
175 mListener
= aObserver
;
176 // Make sure to addref mListener before the AddToOwner call below, since
177 // that call might well want to release it if the imgRequest has
178 // already seen OnStopRequest.
181 mListenerIsStrongRef
= true;
182 NS_ADDREF(mListener
);
184 mLoadGroup
= aLoadGroup
;
187 // Note: AddToOwner won't send all the On* notifications immediately
188 AddToOwner(aLoadingDocument
);
193 nsresult
imgRequestProxy::ChangeOwner(imgRequest
* aNewOwner
) {
194 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
197 // Ensure that this proxy has received all notifications to date
198 // before we clean it up when removing it from the old owner below.
199 SyncNotifyListener();
202 // If we're holding locks, unlock the old image.
203 // Note that UnlockImage decrements mLockCount each time it's called.
204 uint32_t oldLockCount
= mLockCount
;
209 // If we're holding animation requests, undo them.
210 uint32_t oldAnimationConsumers
= mAnimationConsumers
;
211 ClearAnimationConsumers();
213 GetOwner()->RemoveProxy(this, NS_OK
);
215 mBehaviour
->SetOwner(aNewOwner
);
216 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
218 // If we were locked, apply the locks here
219 for (uint32_t i
= 0; i
< oldLockCount
; i
++) {
223 // If we had animation requests, restore them here. Note that we
224 // do this *after* RemoveProxy, which clears out animation consumers
226 for (uint32_t i
= 0; i
< oldAnimationConsumers
; i
++) {
227 IncrementAnimationConsumers();
234 void imgRequestProxy::MarkValidating() {
235 MOZ_ASSERT(GetValidator());
239 void imgRequestProxy::ClearValidating() {
240 MOZ_ASSERT(mValidating
);
241 MOZ_ASSERT(!GetValidator());
244 // If we'd previously requested a synchronous decode, request a decode on the
246 if (mDecodeRequested
) {
247 mDecodeRequested
= false;
248 StartDecoding(imgIContainer::FLAG_NONE
);
252 bool imgRequestProxy::IsOnEventTarget() const { return true; }
254 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
255 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
256 return target
.forget();
259 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
260 already_AddRefed
<nsIRunnable
> aEvent
) {
261 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
263 // This method should only be used when it is *expected* that we are
264 // dispatching an event (e.g. we want to handle an event asynchronously)
265 // rather we need to (e.g. we are in the wrong scheduler group context).
266 // As such, we do not set mHadDispatch for telemetry purposes.
268 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
273 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent
)));
276 void imgRequestProxy::DispatchWithTarget(already_AddRefed
<nsIRunnable
> aEvent
) {
277 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTarget");
279 MOZ_ASSERT(mListener
|| mTabGroup
);
280 MOZ_ASSERT(mEventTarget
);
283 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
287 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
288 // An imgRequestProxy can be initialized with neither a listener nor a
289 // document. The caller could follow up later by cloning the canonical
290 // imgRequestProxy with the actual listener. This is possible because
291 // imgLoader::LoadImage does not require a valid listener to be provided.
293 // Without a listener, we don't need to set our scheduler group, because
294 // we have nothing to signal. However if we were told what document this
295 // is for, it is likely that future listeners will belong to the same
298 // With a listener, we always need to update our scheduler group. A null
299 // scheduler group is valid with or without a document, but that means
300 // we will use the most generic event target possible on dispatch.
301 if (aLoadingDocument
) {
302 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
304 mTabGroup
= docGroup
->GetTabGroup();
305 MOZ_ASSERT(mTabGroup
);
307 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
308 MOZ_ASSERT(mEventTarget
);
312 if (mListener
&& !mEventTarget
) {
313 mEventTarget
= do_GetMainThread();
316 imgRequest
* owner
= GetOwner();
321 owner
->AddProxy(this);
324 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
325 imgRequest
* owner
= GetOwner();
328 imgCacheValidator
* validator
= owner
->GetValidator();
329 MOZ_ASSERT(validator
);
330 validator
->RemoveProxy(this);
334 owner
->RemoveProxy(this, aStatus
);
338 void imgRequestProxy::AddToLoadGroup() {
339 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
340 MOZ_ASSERT(!mForceDispatchLoadGroup
);
342 /* While in theory there could be a dispatch outstanding to remove this
343 request from the load group, in practice we only add to the load group
344 (when previously not in a load group) at initialization. */
345 if (!mIsInLoadGroup
&& mLoadGroup
) {
346 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
347 mLoadGroup
->AddRequest(this, nullptr);
348 mIsInLoadGroup
= true;
352 void imgRequestProxy::RemoveFromLoadGroup() {
353 if (!mIsInLoadGroup
|| !mLoadGroup
) {
357 /* Sometimes we may not be able to remove ourselves from the load group in
358 the current context. This is because our listeners are not re-entrant (e.g.
359 we are in the middle of CancelAndForgetObserver or SyncClone). */
360 if (mForceDispatchLoadGroup
) {
361 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
363 /* We take away the load group from the request temporarily; this prevents
364 additional dispatches via RemoveFromLoadGroup occurring, as well as
365 MoveToBackgroundInLoadGroup from removing and readding. This is safe
366 because we know that once we get here, blocking the load group at all is
368 mIsInLoadGroup
= false;
369 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
370 RefPtr
<imgRequestProxy
> self(this);
371 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
372 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
373 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
378 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
380 /* calling RemoveFromLoadGroup may cause the document to finish
381 loading, which could result in our death. We need to make sure
382 that we stay alive long enough to fight another battle... at
383 least until we exit this function. */
384 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
385 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
386 mLoadGroup
= nullptr;
387 mIsInLoadGroup
= false;
390 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
391 /* Even if we are still in the load group, we may have taken away the load
392 group reference itself because we are in the process of leaving the group.
393 In that case, there is no need to background the request. */
398 /* There is no need to dispatch if we only need to add ourselves to the load
399 group without removal. It is the removal which causes the problematic
400 callbacks (see RemoveFromLoadGroup). */
401 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
403 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
405 RefPtr
<imgRequestProxy
> self(this);
406 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
407 "imgRequestProxy::MoveToBackgroundInLoadGroup",
408 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
412 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
413 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
414 if (mIsInLoadGroup
) {
415 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
418 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
419 mLoadGroup
->AddRequest(this, nullptr);
422 /** nsIRequest / imgIRequest methods **/
425 imgRequestProxy::GetName(nsACString
& aName
) {
429 mURI
->GetSpec(aName
);
436 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
439 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
440 return NS_ERROR_NOT_IMPLEMENTED
;
444 imgRequestProxy::Cancel(nsresult status
) {
446 return NS_ERROR_FAILURE
;
449 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
453 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
454 return DispatchWithTargetIfAvailable(ev
.forget());
457 void imgRequestProxy::DoCancel(nsresult status
) {
458 RemoveFromOwner(status
);
459 RemoveFromLoadGroup();
464 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
465 // If mCanceled is true but mListener is non-null, that means
466 // someone called Cancel() on us but the imgCancelRunnable is still
467 // pending. We still need to null out mListener before returning
468 // from this function in this case. That means we want to do the
469 // RemoveProxy call right now, because we need to deliver the
471 if (mCanceled
&& !mListener
) {
472 return NS_ERROR_FAILURE
;
475 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
478 mForceDispatchLoadGroup
= true;
479 RemoveFromOwner(aStatus
);
480 RemoveFromLoadGroup();
481 mForceDispatchLoadGroup
= false;
489 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
490 // Flag this, so we know to request after validation if pending.
491 if (IsValidating()) {
492 mDecodeRequested
= true;
496 RefPtr
<Image
> image
= GetImage();
498 return image
->StartDecoding(aFlags
);
502 GetOwner()->StartDecoding();
508 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
509 // Flag this, so we know to request after validation if pending.
510 if (IsValidating()) {
511 mDecodeRequested
= true;
515 RefPtr
<Image
> image
= GetImage();
517 return image
->StartDecodingWithResult(aFlags
);
521 GetOwner()->StartDecoding();
527 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags
) {
528 if (IsValidating()) {
529 mDecodeRequested
= true;
533 RefPtr
<Image
> image
= GetImage();
535 return image
->RequestDecodeWithResult(aFlags
);
539 GetOwner()->StartDecoding();
546 imgRequestProxy::LockImage() {
548 RefPtr
<Image
> image
=
549 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
551 return image
->LockImage();
557 imgRequestProxy::UnlockImage() {
558 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
561 RefPtr
<Image
> image
=
562 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
564 return image
->UnlockImage();
570 imgRequestProxy::RequestDiscard() {
571 RefPtr
<Image
> image
= GetImage();
573 return image
->RequestDiscard();
579 imgRequestProxy::IncrementAnimationConsumers() {
580 mAnimationConsumers
++;
581 RefPtr
<Image
> image
=
582 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
584 image
->IncrementAnimationConsumers();
590 imgRequestProxy::DecrementAnimationConsumers() {
591 // We may get here if some responsible code called Increment,
592 // then called us, but we have meanwhile called ClearAnimationConsumers
593 // because we needed to get rid of them earlier (see
594 // imgRequest::RemoveProxy), and hence have nothing left to
595 // decrement. (In such a case we got rid of the animation consumers
596 // early, but not the observer.)
597 if (mAnimationConsumers
> 0) {
598 mAnimationConsumers
--;
599 RefPtr
<Image
> image
=
600 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
602 image
->DecrementAnimationConsumers();
608 void imgRequestProxy::ClearAnimationConsumers() {
609 while (mAnimationConsumers
> 0) {
610 DecrementAnimationConsumers();
615 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
618 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
621 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
622 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
626 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
627 if (loadGroup
!= mLoadGroup
) {
628 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
629 return NS_ERROR_NOT_IMPLEMENTED
;
635 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
640 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
646 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
647 return GetTRRModeImpl(aTRRMode
);
651 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
652 return SetTRRModeImpl(aTRRMode
);
655 /** imgIRequest methods **/
658 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
659 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
660 // It's possible that our owner has an image but hasn't notified us of it -
661 // that'll happen if we get Canceled before the owner instantiates its image
662 // (because Canceling unregisters us as a listener on mOwner). If we're
663 // in that situation, just grab the image off of mOwner.
664 RefPtr
<Image
> image
= GetImage();
665 nsCOMPtr
<imgIContainer
> imageToReturn
;
667 imageToReturn
= image
;
669 if (!imageToReturn
&& GetOwner()) {
670 imageToReturn
= GetOwner()->GetImage();
672 if (!imageToReturn
) {
673 return NS_ERROR_FAILURE
;
676 imageToReturn
.swap(*aImage
);
682 imgRequestProxy::GetProducerId(uint32_t* aId
) {
683 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
685 nsCOMPtr
<imgIContainer
> image
;
686 nsresult rv
= GetImage(getter_AddRefs(image
));
687 if (NS_SUCCEEDED(rv
)) {
688 *aId
= image
->GetProducerId();
690 *aId
= layers::kContainerProducerID_Invalid
;
697 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
698 if (IsValidating()) {
699 // We are currently validating the image, and so our status could revert if
700 // we discard the cache. We should also be deferring notifications, such
701 // that the caller will be notified when validation completes. Rather than
702 // risk misleading the caller, return nothing.
703 *aStatus
= imgIRequest::STATUS_NONE
;
705 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
706 *aStatus
= progressTracker
->GetImageStatus();
713 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
715 return NS_ERROR_FAILURE
;
718 *aStatus
= GetOwner()->GetImageErrorCode();
724 imgRequestProxy::GetURI(nsIURI
** aURI
) {
725 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
726 nsCOMPtr
<nsIURI
> uri
= mURI
;
731 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
733 return NS_ERROR_FAILURE
;
736 return GetOwner()->GetFinalURI(aURI
);
740 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
741 *aObserver
= mListener
;
742 NS_IF_ADDREF(*aObserver
);
747 imgRequestProxy::GetMimeType(char** aMimeType
) {
749 return NS_ERROR_FAILURE
;
752 const char* type
= GetOwner()->GetMimeType();
754 return NS_ERROR_FAILURE
;
757 *aMimeType
= NS_xstrdup(type
);
762 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
763 return new imgRequestProxy();
767 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
768 imgIRequest
** aClone
) {
770 imgRequestProxy
* proxy
;
771 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
776 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
777 Document
* aLoadingDocument
,
778 imgRequestProxy
** aClone
) {
779 return PerformClone(aObserver
, aLoadingDocument
,
780 /* aSyncNotify */ true, aClone
);
783 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
784 Document
* aLoadingDocument
,
785 imgRequestProxy
** aClone
) {
786 return PerformClone(aObserver
, aLoadingDocument
,
787 /* aSyncNotify */ false, aClone
);
790 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
791 Document
* aLoadingDocument
,
793 imgRequestProxy
** aClone
) {
794 MOZ_ASSERT(aClone
, "Null out param");
796 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
799 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
801 nsCOMPtr
<nsILoadGroup
> loadGroup
;
802 if (aLoadingDocument
) {
803 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
806 // It is important to call |SetLoadFlags()| before calling |Init()| because
807 // |Init()| adds the request to the loadgroup.
808 // When a request is added to a loadgroup, its load flags are merged
809 // with the load flags of the loadgroup.
810 // XXXldb That's not true anymore. Stuff from imgLoader adds the
811 // request to the loadgroup.
812 clone
->SetLoadFlags(mLoadFlags
);
813 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
819 // Assign to *aClone before calling Notify so that if the caller expects to
820 // only be notified for requests it's already holding pointers to it won't be
822 NS_ADDREF(*aClone
= clone
);
824 imgCacheValidator
* validator
= GetValidator();
826 // Note that if we have a validator, we don't want to issue notifications at
827 // here because we want to defer until that completes. AddProxy will add us
828 // to the load group; we cannot avoid that in this case, because we don't
829 // know when the validation will complete, and if it will cause us to
830 // discard our cached state anyways. We are probably already blocked by the
831 // original LoadImage(WithChannel) request in any event.
832 clone
->MarkValidating();
833 validator
->AddProxy(clone
);
835 // We only want to add the request to the load group of the owning document
836 // if it is still in progress. Some callers cannot handle a supurious load
837 // group removal (e.g. print preview) so we must be careful. On the other
838 // hand, if after cloning, the original request proxy is cancelled /
839 // destroyed, we need to ensure that any clones still block the load group
840 // if it is incomplete.
841 bool addToLoadGroup
= mIsInLoadGroup
;
842 if (!addToLoadGroup
) {
843 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
845 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
848 if (addToLoadGroup
) {
849 clone
->AddToLoadGroup();
853 // This is wrong!!! We need to notify asynchronously, but there's code
854 // that assumes that we don't. This will be fixed in bug 580466. Note that
855 // if we have a validator, we won't issue notifications anyways because
856 // they are deferred, so there is no point in requesting.
857 clone
->mForceDispatchLoadGroup
= true;
858 clone
->SyncNotifyListener();
859 clone
->mForceDispatchLoadGroup
= false;
861 // Without a validator, we can request asynchronous notifications
862 // immediately. If there was a validator, this would override the deferral
863 // and that would be incorrect.
864 clone
->NotifyListener();
872 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
874 return NS_ERROR_FAILURE
;
877 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
878 principal
.forget(aPrincipal
);
883 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
884 *aHadCrossOriginRedirects
= false;
886 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
888 bool allRedirectsSameOrigin
= false;
889 *aHadCrossOriginRedirects
=
891 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
892 !allRedirectsSameOrigin
;
899 imgRequestProxy::GetMultipart(bool* aMultipart
) {
901 return NS_ERROR_FAILURE
;
904 *aMultipart
= GetOwner()->GetMultipart();
910 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
912 return NS_ERROR_FAILURE
;
915 *aCorsMode
= GetOwner()->GetCORSMode();
921 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
922 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
923 GetOwner()->BoostPriority(aCategory
);
927 /** nsISupportsPriority methods **/
930 imgRequestProxy::GetPriority(int32_t* priority
) {
931 NS_ENSURE_STATE(GetOwner());
932 *priority
= GetOwner()->Priority();
937 imgRequestProxy::SetPriority(int32_t priority
) {
938 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
939 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
944 imgRequestProxy::AdjustPriority(int32_t priority
) {
945 // We don't require |!mCanceled| here. This may be called even if we're
946 // cancelled, because it's invoked as part of the process of removing an image
947 // from the load group.
948 NS_ENSURE_STATE(GetOwner());
949 GetOwner()->AdjustPriority(this, priority
);
953 static const char* NotificationTypeToString(int32_t aType
) {
955 case imgINotificationObserver::SIZE_AVAILABLE
:
956 return "SIZE_AVAILABLE";
957 case imgINotificationObserver::FRAME_UPDATE
:
958 return "FRAME_UPDATE";
959 case imgINotificationObserver::FRAME_COMPLETE
:
960 return "FRAME_COMPLETE";
961 case imgINotificationObserver::LOAD_COMPLETE
:
962 return "LOAD_COMPLETE";
963 case imgINotificationObserver::DECODE_COMPLETE
:
964 return "DECODE_COMPLETE";
965 case imgINotificationObserver::DISCARD
:
967 case imgINotificationObserver::UNLOCKED_DRAW
:
968 return "UNLOCKED_DRAW";
969 case imgINotificationObserver::IS_ANIMATED
:
970 return "IS_ANIMATED";
971 case imgINotificationObserver::HAS_TRANSPARENCY
:
972 return "HAS_TRANSPARENCY";
974 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
975 return "(unknown notification)";
979 void imgRequestProxy::Notify(int32_t aType
,
980 const mozilla::gfx::IntRect
* aRect
) {
981 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
982 "Should call OnLoadComplete");
984 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
985 NotificationTypeToString(aType
));
987 if (!mListener
|| mCanceled
) {
991 if (!IsOnEventTarget()) {
992 RefPtr
<imgRequestProxy
> self(this);
994 const mozilla::gfx::IntRect rect
= *aRect
;
995 DispatchWithTarget(NS_NewRunnableFunction(
996 "imgRequestProxy::Notify",
997 [self
, rect
, aType
]() -> void { self
->Notify(aType
, &rect
); }));
999 DispatchWithTarget(NS_NewRunnableFunction(
1000 "imgRequestProxy::Notify",
1001 [self
, aType
]() -> void { self
->Notify(aType
, nullptr); }));
1006 // Make sure the listener stays alive while we notify.
1007 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1009 listener
->Notify(this, aType
, aRect
);
1012 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1013 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1015 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1016 // on the listener, the removal from the loadgroup, the release of the
1017 // listener, etc). Don't let them do it.
1018 RefPtr
<imgRequestProxy
> self(this);
1020 if (!IsOnEventTarget()) {
1021 DispatchWithTarget(NS_NewRunnableFunction(
1022 "imgRequestProxy::OnLoadComplete",
1023 [self
, aLastPart
]() -> void { self
->OnLoadComplete(aLastPart
); }));
1027 if (mListener
&& !mCanceled
) {
1028 // Hold a ref to the listener while we call it, just in case.
1029 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1030 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1033 // If we're expecting more data from a multipart channel, re-add ourself
1034 // to the loadgroup so that the document doesn't lose track of the load.
1035 // If the request is already a background request and there's more data
1036 // coming, we can just leave the request in the loadgroup as-is.
1037 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1039 RemoveFromLoadGroup();
1041 // More data is coming, so change the request to be a background request
1042 // and put it back in the loadgroup.
1043 MoveToBackgroundInLoadGroup();
1047 if (mListenerIsStrongRef
&& aLastPart
) {
1048 MOZ_ASSERT(mListener
, "How did that happen?");
1049 // Drop our strong ref to the listener now that we're done with
1050 // everything. Note that this can cancel us and other fun things
1051 // like that. Don't add anything in this method after this point.
1052 imgINotificationObserver
* obs
= mListener
;
1053 mListenerIsStrongRef
= false;
1058 void imgRequestProxy::NullOutListener() {
1059 // If we have animation consumers, then they don't matter anymore
1061 ClearAnimationConsumers();
1064 if (mListenerIsStrongRef
) {
1065 // Releasing could do weird reentery stuff, so just play it super-safe
1066 nsCOMPtr
<imgINotificationObserver
> obs
;
1067 obs
.swap(mListener
);
1068 mListenerIsStrongRef
= false;
1070 mListener
= nullptr;
1073 // Note that we don't free the event target. We actually need that to ensure
1074 // we get removed from the ProgressTracker properly. No harm in keeping it
1076 mTabGroup
= nullptr;
1080 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1081 imgRequestProxy
* proxy
;
1082 nsresult result
= GetStaticRequest(nullptr, &proxy
);
1087 nsresult
imgRequestProxy::GetStaticRequest(Document
* aLoadingDocument
,
1088 imgRequestProxy
** aReturn
) {
1090 RefPtr
<Image
> image
= GetImage();
1093 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1094 // Early exit - we're not animated, so we don't have to do anything.
1095 NS_ADDREF(*aReturn
= this);
1099 // Check for errors in the image. Callers code rely on GetStaticRequest
1100 // failing in this case, though with FrozenImage there's no technical reason
1102 if (image
->HasError()) {
1103 return NS_ERROR_FAILURE
;
1106 // We are animated. We need to create a frozen version of this image.
1107 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1109 // Create a static imgRequestProxy with our new extracted frame.
1110 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1111 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1112 bool hadCrossOriginRedirects
= true;
1113 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1114 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1115 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1116 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1118 NS_ADDREF(*aReturn
= req
);
1123 void imgRequestProxy::NotifyListener() {
1124 // It would be nice to notify the observer directly in the status tracker
1125 // instead of through the proxy, but there are several places we do extra
1126 // processing when we receive notifications (like OnStopRequest()), and we
1127 // need to check mCanceled everywhere too.
1129 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1131 // Send the notifications to our listener asynchronously.
1132 progressTracker
->Notify(this);
1134 // We don't have an imgRequest, so we can only notify the clone of our
1135 // current state, but we still have to do that asynchronously.
1136 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1137 progressTracker
->NotifyCurrentState(this);
1141 void imgRequestProxy::SyncNotifyListener() {
1142 // It would be nice to notify the observer directly in the status tracker
1143 // instead of through the proxy, but there are several places we do extra
1144 // processing when we receive notifications (like OnStopRequest()), and we
1145 // need to check mCanceled everywhere too.
1147 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1148 progressTracker
->SyncNotify(this);
1151 void imgRequestProxy::SetHasImage() {
1152 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1153 MOZ_ASSERT(progressTracker
);
1154 RefPtr
<Image
> image
= progressTracker
->GetImage();
1157 // Force any private status related to the owner to reflect
1158 // the presence of an image;
1159 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1161 // Apply any locks we have
1162 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1166 // Apply any animation consumers we have
1167 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1168 image
->IncrementAnimationConsumers();
1172 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1173 return mBehaviour
->GetProgressTracker();
1176 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1177 return mBehaviour
->GetImage();
1180 bool RequestBehaviour::HasImage() const {
1181 if (!mOwnerHasImage
) {
1184 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1185 return progressTracker
? progressTracker
->HasImage() : false;
1188 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1190 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1192 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1193 imgRequest
* owner
= GetOwner();
1197 return owner
->GetValidator();
1200 ////////////////// imgRequestProxyStatic methods
1202 class StaticBehaviour
: public ProxyBehaviour
{
1204 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1206 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1207 RefPtr
<mozilla::image::Image
> image
= mImage
;
1208 return image
.forget();
1211 bool HasImage() const override
{ return mImage
; }
1213 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1214 return mImage
->GetProgressTracker();
1217 imgRequest
* GetOwner() const override
{ return nullptr; }
1219 void SetOwner(imgRequest
* aOwner
) override
{
1221 "We shouldn't be giving static requests a non-null owner.");
1225 // Our image. We have to hold a strong reference here, because that's normally
1226 // the job of the underlying request.
1227 RefPtr
<mozilla::image::Image
> mImage
;
1230 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1231 nsIPrincipal
* aPrincipal
,
1232 bool aHadCrossOriginRedirects
)
1233 : mPrincipal(aPrincipal
),
1234 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1235 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1239 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1241 return NS_ERROR_FAILURE
;
1244 NS_ADDREF(*aPrincipal
= mPrincipal
);
1250 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1251 bool* aHadCrossOriginRedirects
) {
1252 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1256 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1257 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1258 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1259 bool hadCrossOriginRedirects
= true;
1260 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1261 RefPtr
<mozilla::image::Image
> image
= GetImage();
1262 return new imgRequestProxyStatic(image
, currentPrincipal
,
1263 hadCrossOriginRedirects
);