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"
12 #include "ImageLogging.h"
14 #include "ImageTypes.h"
15 #include "imgINotificationObserver.h"
16 #include "imgLoader.h"
17 #include "mozilla/Telemetry.h" // for Telemetry
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "mozilla/dom/TabGroup.h" // for TabGroup
20 #include "nsCRTGlue.h"
23 using namespace mozilla
;
24 using namespace mozilla::image
;
25 using mozilla::dom::Document
;
27 // The split of imgRequestProxy and imgRequestProxyStatic means that
28 // certain overridden functions need to be usable in the destructor.
29 // Since virtual functions can't be used in that way, this class
30 // provides a behavioural trait for each class to use instead.
31 class ProxyBehaviour
{
33 virtual ~ProxyBehaviour() = default;
35 virtual already_AddRefed
<mozilla::image::Image
> GetImage() const = 0;
36 virtual bool HasImage() const = 0;
37 virtual already_AddRefed
<ProgressTracker
> GetProgressTracker() const = 0;
38 virtual imgRequest
* GetOwner() const = 0;
39 virtual void SetOwner(imgRequest
* aOwner
) = 0;
42 class RequestBehaviour
: public ProxyBehaviour
{
44 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
46 already_AddRefed
<mozilla::image::Image
> GetImage() const override
;
47 bool HasImage() const override
;
48 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
;
50 imgRequest
* GetOwner() const override
{ return mOwner
; }
52 void SetOwner(imgRequest
* aOwner
) override
{
56 RefPtr
<ProgressTracker
> ownerProgressTracker
= GetProgressTracker();
57 mOwnerHasImage
= ownerProgressTracker
&& ownerProgressTracker
->HasImage();
59 mOwnerHasImage
= false;
64 // We maintain the following invariant:
65 // The proxy is registered at most with a single imgRequest as an observer,
66 // and whenever it is, mOwner points to that object. This helps ensure that
67 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
68 // from whatever request it was registered with (if any). This, in turn,
69 // means that imgRequest::mObservers will not have any stale pointers in it.
70 RefPtr
<imgRequest
> mOwner
;
75 already_AddRefed
<mozilla::image::Image
> RequestBehaviour::GetImage() const {
76 if (!mOwnerHasImage
) {
79 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
80 return progressTracker
->GetImage();
83 already_AddRefed
<ProgressTracker
> RequestBehaviour::GetProgressTracker() const {
84 // NOTE: It's possible that our mOwner has an Image that it didn't notify
85 // us about, if we were Canceled before its Image was constructed.
86 // (Canceling removes us as an observer, so mOwner has no way to notify us).
87 // That's why this method uses mOwner->GetProgressTracker() instead of just
88 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
89 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
90 return mOwner
->GetProgressTracker();
93 NS_IMPL_ADDREF(imgRequestProxy
)
94 NS_IMPL_RELEASE(imgRequestProxy
)
96 NS_INTERFACE_MAP_BEGIN(imgRequestProxy
)
97 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, imgIRequest
)
98 NS_INTERFACE_MAP_ENTRY(imgIRequest
)
99 NS_INTERFACE_MAP_ENTRY(nsIRequest
)
100 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority
)
101 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel
, TimedChannel() != nullptr)
104 imgRequestProxy::imgRequestProxy()
105 : mBehaviour(new RequestBehaviour
),
108 mLoadFlags(nsIRequest::LOAD_NORMAL
),
110 mAnimationConsumers(0),
112 mIsInLoadGroup(false),
113 mForceDispatchLoadGroup(false),
114 mListenerIsStrongRef(false),
115 mDecodeRequested(false),
116 mPendingNotify(false),
119 mHadDispatch(false) {
120 /* member initializers and constructor code */
121 LOG_FUNC(gImgLog
, "imgRequestProxy::imgRequestProxy");
124 imgRequestProxy::~imgRequestProxy() {
125 /* destructor code */
126 MOZ_ASSERT(!mListener
, "Someone forgot to properly cancel this request!");
128 // If we had a listener, that means we would have issued notifications. With
129 // bug 1359833, we added support for main thread scheduler groups. Each
130 // imgRequestProxy may have its own associated listener, document and/or
131 // scheduler group. Typically most imgRequestProxy belong to the same
132 // document, or have no listener, which means we will want to execute all main
133 // thread code in that shared scheduler group. Less frequently, there may be
134 // multiple imgRequests and they have separate documents, which means that
135 // when we issue state notifications, some or all need to be dispatched to the
136 // appropriate scheduler group for each request. This should be rare, so we
137 // want to monitor the frequency of dispatching in the wild.
139 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED
,
143 MOZ_RELEASE_ASSERT(!mLockCount
, "Someone forgot to unlock on time?");
145 ClearAnimationConsumers();
147 // Explicitly set mListener to null to ensure that the RemoveProxy
148 // call below can't send |this| to an arbitrary listener while |this|
149 // is being destroyed. This is all belt-and-suspenders in view of the
153 /* Call RemoveProxy with a successful status. This will keep the
154 channel, if still downloading data, from being canceled if 'this' is
155 the last observer. This allows the image to continue to download and
156 be cached even if no one is using it currently.
159 RemoveFromOwner(NS_OK
);
161 RemoveFromLoadGroup();
162 LOG_FUNC(gImgLog
, "imgRequestProxy::~imgRequestProxy");
165 nsresult
imgRequestProxy::Init(imgRequest
* aOwner
, nsILoadGroup
* aLoadGroup
,
166 Document
* aLoadingDocument
, nsIURI
* aURI
,
167 imgINotificationObserver
* aObserver
) {
168 MOZ_ASSERT(!GetOwner() && !mListener
,
169 "imgRequestProxy is already initialized");
171 LOG_SCOPE_WITH_PARAM(gImgLog
, "imgRequestProxy::Init", "request", aOwner
);
173 MOZ_ASSERT(mAnimationConsumers
== 0, "Cannot have animation before Init");
175 mBehaviour
->SetOwner(aOwner
);
176 mListener
= aObserver
;
177 // Make sure to addref mListener before the AddToOwner call below, since
178 // that call might well want to release it if the imgRequest has
179 // already seen OnStopRequest.
182 mListenerIsStrongRef
= true;
183 NS_ADDREF(mListener
);
185 mLoadGroup
= aLoadGroup
;
188 // Note: AddToOwner won't send all the On* notifications immediately
189 AddToOwner(aLoadingDocument
);
194 nsresult
imgRequestProxy::ChangeOwner(imgRequest
* aNewOwner
) {
195 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
198 // Ensure that this proxy has received all notifications to date
199 // before we clean it up when removing it from the old owner below.
200 SyncNotifyListener();
203 // If we're holding locks, unlock the old image.
204 // Note that UnlockImage decrements mLockCount each time it's called.
205 uint32_t oldLockCount
= mLockCount
;
210 // If we're holding animation requests, undo them.
211 uint32_t oldAnimationConsumers
= mAnimationConsumers
;
212 ClearAnimationConsumers();
214 GetOwner()->RemoveProxy(this, NS_OK
);
216 mBehaviour
->SetOwner(aNewOwner
);
217 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
219 // If we were locked, apply the locks here
220 for (uint32_t i
= 0; i
< oldLockCount
; i
++) {
224 // If we had animation requests, restore them here. Note that we
225 // do this *after* RemoveProxy, which clears out animation consumers
227 for (uint32_t i
= 0; i
< oldAnimationConsumers
; i
++) {
228 IncrementAnimationConsumers();
235 void imgRequestProxy::MarkValidating() {
236 MOZ_ASSERT(GetValidator());
240 void imgRequestProxy::ClearValidating() {
241 MOZ_ASSERT(mValidating
);
242 MOZ_ASSERT(!GetValidator());
245 // If we'd previously requested a synchronous decode, request a decode on the
247 if (mDecodeRequested
) {
248 mDecodeRequested
= false;
249 StartDecoding(imgIContainer::FLAG_NONE
);
253 bool imgRequestProxy::IsOnEventTarget() const { return true; }
255 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
256 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
257 return target
.forget();
260 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
261 already_AddRefed
<nsIRunnable
> aEvent
) {
262 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
264 // This method should only be used when it is *expected* that we are
265 // dispatching an event (e.g. we want to handle an event asynchronously)
266 // rather we need to (e.g. we are in the wrong scheduler group context).
267 // As such, we do not set mHadDispatch for telemetry purposes.
269 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
274 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent
)));
277 void imgRequestProxy::DispatchWithTarget(already_AddRefed
<nsIRunnable
> aEvent
) {
278 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTarget");
280 MOZ_ASSERT(mListener
|| mTabGroup
);
281 MOZ_ASSERT(mEventTarget
);
284 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
288 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
289 // An imgRequestProxy can be initialized with neither a listener nor a
290 // document. The caller could follow up later by cloning the canonical
291 // imgRequestProxy with the actual listener. This is possible because
292 // imgLoader::LoadImage does not require a valid listener to be provided.
294 // Without a listener, we don't need to set our scheduler group, because
295 // we have nothing to signal. However if we were told what document this
296 // is for, it is likely that future listeners will belong to the same
299 // With a listener, we always need to update our scheduler group. A null
300 // scheduler group is valid with or without a document, but that means
301 // we will use the most generic event target possible on dispatch.
302 if (aLoadingDocument
) {
303 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
305 mTabGroup
= docGroup
->GetTabGroup();
306 MOZ_ASSERT(mTabGroup
);
308 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
309 MOZ_ASSERT(mEventTarget
);
313 if (mListener
&& !mEventTarget
) {
314 mEventTarget
= do_GetMainThread();
317 imgRequest
* owner
= GetOwner();
322 owner
->AddProxy(this);
325 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
326 imgRequest
* owner
= GetOwner();
329 imgCacheValidator
* validator
= owner
->GetValidator();
330 MOZ_ASSERT(validator
);
331 validator
->RemoveProxy(this);
335 owner
->RemoveProxy(this, aStatus
);
339 void imgRequestProxy::AddToLoadGroup() {
340 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
341 MOZ_ASSERT(!mForceDispatchLoadGroup
);
343 /* While in theory there could be a dispatch outstanding to remove this
344 request from the load group, in practice we only add to the load group
345 (when previously not in a load group) at initialization. */
346 if (!mIsInLoadGroup
&& mLoadGroup
) {
347 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
348 mLoadGroup
->AddRequest(this, nullptr);
349 mIsInLoadGroup
= true;
353 void imgRequestProxy::RemoveFromLoadGroup() {
354 if (!mIsInLoadGroup
|| !mLoadGroup
) {
358 /* Sometimes we may not be able to remove ourselves from the load group in
359 the current context. This is because our listeners are not re-entrant (e.g.
360 we are in the middle of CancelAndForgetObserver or SyncClone). */
361 if (mForceDispatchLoadGroup
) {
362 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
364 /* We take away the load group from the request temporarily; this prevents
365 additional dispatches via RemoveFromLoadGroup occurring, as well as
366 MoveToBackgroundInLoadGroup from removing and readding. This is safe
367 because we know that once we get here, blocking the load group at all is
369 mIsInLoadGroup
= false;
370 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
371 RefPtr
<imgRequestProxy
> self(this);
372 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
373 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
374 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
379 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
381 /* calling RemoveFromLoadGroup may cause the document to finish
382 loading, which could result in our death. We need to make sure
383 that we stay alive long enough to fight another battle... at
384 least until we exit this function. */
385 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
386 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
387 mLoadGroup
= nullptr;
388 mIsInLoadGroup
= false;
391 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
392 /* Even if we are still in the load group, we may have taken away the load
393 group reference itself because we are in the process of leaving the group.
394 In that case, there is no need to background the request. */
399 /* There is no need to dispatch if we only need to add ourselves to the load
400 group without removal. It is the removal which causes the problematic
401 callbacks (see RemoveFromLoadGroup). */
402 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
404 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
406 RefPtr
<imgRequestProxy
> self(this);
407 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
408 "imgRequestProxy::MoveToBackgroundInLoadGroup",
409 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
413 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
414 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
415 if (mIsInLoadGroup
) {
416 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
419 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
420 mLoadGroup
->AddRequest(this, nullptr);
423 /** nsIRequest / imgIRequest methods **/
426 imgRequestProxy::GetName(nsACString
& aName
) {
430 mURI
->GetSpec(aName
);
437 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
440 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
441 return NS_ERROR_NOT_IMPLEMENTED
;
445 imgRequestProxy::Cancel(nsresult status
) {
447 return NS_ERROR_FAILURE
;
450 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
454 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
455 return DispatchWithTargetIfAvailable(ev
.forget());
458 void imgRequestProxy::DoCancel(nsresult status
) {
459 RemoveFromOwner(status
);
460 RemoveFromLoadGroup();
465 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
466 // If mCanceled is true but mListener is non-null, that means
467 // someone called Cancel() on us but the imgCancelRunnable is still
468 // pending. We still need to null out mListener before returning
469 // from this function in this case. That means we want to do the
470 // RemoveProxy call right now, because we need to deliver the
472 if (mCanceled
&& !mListener
) {
473 return NS_ERROR_FAILURE
;
476 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
479 mForceDispatchLoadGroup
= true;
480 RemoveFromOwner(aStatus
);
481 RemoveFromLoadGroup();
482 mForceDispatchLoadGroup
= false;
490 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
491 // Flag this, so we know to request after validation if pending.
492 if (IsValidating()) {
493 mDecodeRequested
= true;
497 RefPtr
<Image
> image
= GetImage();
499 return image
->StartDecoding(aFlags
);
503 GetOwner()->StartDecoding();
509 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
510 // Flag this, so we know to request after validation if pending.
511 if (IsValidating()) {
512 mDecodeRequested
= true;
516 RefPtr
<Image
> image
= GetImage();
518 return image
->StartDecodingWithResult(aFlags
);
522 GetOwner()->StartDecoding();
528 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags
) {
529 if (IsValidating()) {
530 mDecodeRequested
= true;
534 RefPtr
<Image
> image
= GetImage();
536 return image
->RequestDecodeWithResult(aFlags
);
540 GetOwner()->StartDecoding();
547 imgRequestProxy::LockImage() {
549 RefPtr
<Image
> image
=
550 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
552 return image
->LockImage();
558 imgRequestProxy::UnlockImage() {
559 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
562 RefPtr
<Image
> image
=
563 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
565 return image
->UnlockImage();
571 imgRequestProxy::RequestDiscard() {
572 RefPtr
<Image
> image
= GetImage();
574 return image
->RequestDiscard();
580 imgRequestProxy::IncrementAnimationConsumers() {
581 mAnimationConsumers
++;
582 RefPtr
<Image
> image
=
583 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
585 image
->IncrementAnimationConsumers();
591 imgRequestProxy::DecrementAnimationConsumers() {
592 // We may get here if some responsible code called Increment,
593 // then called us, but we have meanwhile called ClearAnimationConsumers
594 // because we needed to get rid of them earlier (see
595 // imgRequest::RemoveProxy), and hence have nothing left to
596 // decrement. (In such a case we got rid of the animation consumers
597 // early, but not the observer.)
598 if (mAnimationConsumers
> 0) {
599 mAnimationConsumers
--;
600 RefPtr
<Image
> image
=
601 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
603 image
->DecrementAnimationConsumers();
609 void imgRequestProxy::ClearAnimationConsumers() {
610 while (mAnimationConsumers
> 0) {
611 DecrementAnimationConsumers();
616 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
619 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
622 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
623 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
627 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
628 if (loadGroup
!= mLoadGroup
) {
629 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
630 return NS_ERROR_NOT_IMPLEMENTED
;
636 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
641 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
647 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
648 return GetTRRModeImpl(aTRRMode
);
652 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
653 return SetTRRModeImpl(aTRRMode
);
656 /** imgIRequest methods **/
659 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
660 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
661 // It's possible that our owner has an image but hasn't notified us of it -
662 // that'll happen if we get Canceled before the owner instantiates its image
663 // (because Canceling unregisters us as a listener on mOwner). If we're
664 // in that situation, just grab the image off of mOwner.
665 RefPtr
<Image
> image
= GetImage();
666 nsCOMPtr
<imgIContainer
> imageToReturn
;
668 imageToReturn
= image
;
670 if (!imageToReturn
&& GetOwner()) {
671 imageToReturn
= GetOwner()->GetImage();
673 if (!imageToReturn
) {
674 return NS_ERROR_FAILURE
;
677 imageToReturn
.swap(*aImage
);
683 imgRequestProxy::GetProducerId(uint32_t* aId
) {
684 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
686 nsCOMPtr
<imgIContainer
> image
;
687 nsresult rv
= GetImage(getter_AddRefs(image
));
688 if (NS_SUCCEEDED(rv
)) {
689 *aId
= image
->GetProducerId();
691 *aId
= layers::kContainerProducerID_Invalid
;
698 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
699 if (IsValidating()) {
700 // We are currently validating the image, and so our status could revert if
701 // we discard the cache. We should also be deferring notifications, such
702 // that the caller will be notified when validation completes. Rather than
703 // risk misleading the caller, return nothing.
704 *aStatus
= imgIRequest::STATUS_NONE
;
706 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
707 *aStatus
= progressTracker
->GetImageStatus();
714 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
716 return NS_ERROR_FAILURE
;
719 *aStatus
= GetOwner()->GetImageErrorCode();
725 imgRequestProxy::GetURI(nsIURI
** aURI
) {
726 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
727 nsCOMPtr
<nsIURI
> uri
= mURI
;
732 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
734 return NS_ERROR_FAILURE
;
737 return GetOwner()->GetFinalURI(aURI
);
741 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
742 *aObserver
= mListener
;
743 NS_IF_ADDREF(*aObserver
);
748 imgRequestProxy::GetMimeType(char** aMimeType
) {
750 return NS_ERROR_FAILURE
;
753 const char* type
= GetOwner()->GetMimeType();
755 return NS_ERROR_FAILURE
;
758 *aMimeType
= NS_xstrdup(type
);
763 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
764 return new imgRequestProxy();
768 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
769 imgIRequest
** aClone
) {
771 imgRequestProxy
* proxy
;
772 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
777 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
778 Document
* aLoadingDocument
,
779 imgRequestProxy
** aClone
) {
780 return PerformClone(aObserver
, aLoadingDocument
,
781 /* aSyncNotify */ true, aClone
);
784 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
785 Document
* aLoadingDocument
,
786 imgRequestProxy
** aClone
) {
787 return PerformClone(aObserver
, aLoadingDocument
,
788 /* aSyncNotify */ false, aClone
);
791 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
792 Document
* aLoadingDocument
,
794 imgRequestProxy
** aClone
) {
795 MOZ_ASSERT(aClone
, "Null out param");
797 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
800 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
802 nsCOMPtr
<nsILoadGroup
> loadGroup
;
803 if (aLoadingDocument
) {
804 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
807 // It is important to call |SetLoadFlags()| before calling |Init()| because
808 // |Init()| adds the request to the loadgroup.
809 // When a request is added to a loadgroup, its load flags are merged
810 // with the load flags of the loadgroup.
811 // XXXldb That's not true anymore. Stuff from imgLoader adds the
812 // request to the loadgroup.
813 clone
->SetLoadFlags(mLoadFlags
);
814 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
820 // Assign to *aClone before calling Notify so that if the caller expects to
821 // only be notified for requests it's already holding pointers to it won't be
823 NS_ADDREF(*aClone
= clone
);
825 imgCacheValidator
* validator
= GetValidator();
827 // Note that if we have a validator, we don't want to issue notifications at
828 // here because we want to defer until that completes. AddProxy will add us
829 // to the load group; we cannot avoid that in this case, because we don't
830 // know when the validation will complete, and if it will cause us to
831 // discard our cached state anyways. We are probably already blocked by the
832 // original LoadImage(WithChannel) request in any event.
833 clone
->MarkValidating();
834 validator
->AddProxy(clone
);
836 // We only want to add the request to the load group of the owning document
837 // if it is still in progress. Some callers cannot handle a supurious load
838 // group removal (e.g. print preview) so we must be careful. On the other
839 // hand, if after cloning, the original request proxy is cancelled /
840 // destroyed, we need to ensure that any clones still block the load group
841 // if it is incomplete.
842 bool addToLoadGroup
= mIsInLoadGroup
;
843 if (!addToLoadGroup
) {
844 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
846 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
849 if (addToLoadGroup
) {
850 clone
->AddToLoadGroup();
854 // This is wrong!!! We need to notify asynchronously, but there's code
855 // that assumes that we don't. This will be fixed in bug 580466. Note that
856 // if we have a validator, we won't issue notifications anyways because
857 // they are deferred, so there is no point in requesting.
858 clone
->mForceDispatchLoadGroup
= true;
859 clone
->SyncNotifyListener();
860 clone
->mForceDispatchLoadGroup
= false;
862 // Without a validator, we can request asynchronous notifications
863 // immediately. If there was a validator, this would override the deferral
864 // and that would be incorrect.
865 clone
->NotifyListener();
873 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
875 return NS_ERROR_FAILURE
;
878 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
879 principal
.forget(aPrincipal
);
884 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
885 *aHadCrossOriginRedirects
= false;
887 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
889 bool allRedirectsSameOrigin
= false;
890 *aHadCrossOriginRedirects
=
892 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
893 !allRedirectsSameOrigin
;
900 imgRequestProxy::GetMultipart(bool* aMultipart
) {
902 return NS_ERROR_FAILURE
;
905 *aMultipart
= GetOwner()->GetMultipart();
911 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
913 return NS_ERROR_FAILURE
;
916 *aCorsMode
= GetOwner()->GetCORSMode();
922 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
923 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
924 GetOwner()->BoostPriority(aCategory
);
928 /** nsISupportsPriority methods **/
931 imgRequestProxy::GetPriority(int32_t* priority
) {
932 NS_ENSURE_STATE(GetOwner());
933 *priority
= GetOwner()->Priority();
938 imgRequestProxy::SetPriority(int32_t priority
) {
939 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
940 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
945 imgRequestProxy::AdjustPriority(int32_t priority
) {
946 // We don't require |!mCanceled| here. This may be called even if we're
947 // cancelled, because it's invoked as part of the process of removing an image
948 // from the load group.
949 NS_ENSURE_STATE(GetOwner());
950 GetOwner()->AdjustPriority(this, priority
);
954 static const char* NotificationTypeToString(int32_t aType
) {
956 case imgINotificationObserver::SIZE_AVAILABLE
:
957 return "SIZE_AVAILABLE";
958 case imgINotificationObserver::FRAME_UPDATE
:
959 return "FRAME_UPDATE";
960 case imgINotificationObserver::FRAME_COMPLETE
:
961 return "FRAME_COMPLETE";
962 case imgINotificationObserver::LOAD_COMPLETE
:
963 return "LOAD_COMPLETE";
964 case imgINotificationObserver::DECODE_COMPLETE
:
965 return "DECODE_COMPLETE";
966 case imgINotificationObserver::DISCARD
:
968 case imgINotificationObserver::UNLOCKED_DRAW
:
969 return "UNLOCKED_DRAW";
970 case imgINotificationObserver::IS_ANIMATED
:
971 return "IS_ANIMATED";
972 case imgINotificationObserver::HAS_TRANSPARENCY
:
973 return "HAS_TRANSPARENCY";
975 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
976 return "(unknown notification)";
980 void imgRequestProxy::Notify(int32_t aType
,
981 const mozilla::gfx::IntRect
* aRect
) {
982 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
983 "Should call OnLoadComplete");
985 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
986 NotificationTypeToString(aType
));
988 if (!mListener
|| mCanceled
) {
992 if (!IsOnEventTarget()) {
993 RefPtr
<imgRequestProxy
> self(this);
995 const mozilla::gfx::IntRect rect
= *aRect
;
996 DispatchWithTarget(NS_NewRunnableFunction(
997 "imgRequestProxy::Notify",
998 [self
, rect
, aType
]() -> void { self
->Notify(aType
, &rect
); }));
1000 DispatchWithTarget(NS_NewRunnableFunction(
1001 "imgRequestProxy::Notify",
1002 [self
, aType
]() -> void { self
->Notify(aType
, nullptr); }));
1007 // Make sure the listener stays alive while we notify.
1008 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1010 listener
->Notify(this, aType
, aRect
);
1013 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1014 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1016 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1017 // on the listener, the removal from the loadgroup, the release of the
1018 // listener, etc). Don't let them do it.
1019 RefPtr
<imgRequestProxy
> self(this);
1021 if (!IsOnEventTarget()) {
1022 DispatchWithTarget(NS_NewRunnableFunction(
1023 "imgRequestProxy::OnLoadComplete",
1024 [self
, aLastPart
]() -> void { self
->OnLoadComplete(aLastPart
); }));
1028 if (mListener
&& !mCanceled
) {
1029 // Hold a ref to the listener while we call it, just in case.
1030 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1031 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1034 // If we're expecting more data from a multipart channel, re-add ourself
1035 // to the loadgroup so that the document doesn't lose track of the load.
1036 // If the request is already a background request and there's more data
1037 // coming, we can just leave the request in the loadgroup as-is.
1038 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1040 RemoveFromLoadGroup();
1042 // More data is coming, so change the request to be a background request
1043 // and put it back in the loadgroup.
1044 MoveToBackgroundInLoadGroup();
1048 if (mListenerIsStrongRef
&& aLastPart
) {
1049 MOZ_ASSERT(mListener
, "How did that happen?");
1050 // Drop our strong ref to the listener now that we're done with
1051 // everything. Note that this can cancel us and other fun things
1052 // like that. Don't add anything in this method after this point.
1053 imgINotificationObserver
* obs
= mListener
;
1054 mListenerIsStrongRef
= false;
1059 void imgRequestProxy::NullOutListener() {
1060 // If we have animation consumers, then they don't matter anymore
1062 ClearAnimationConsumers();
1065 if (mListenerIsStrongRef
) {
1066 // Releasing could do weird reentery stuff, so just play it super-safe
1067 nsCOMPtr
<imgINotificationObserver
> obs
;
1068 obs
.swap(mListener
);
1069 mListenerIsStrongRef
= false;
1071 mListener
= nullptr;
1074 // Note that we don't free the event target. We actually need that to ensure
1075 // we get removed from the ProgressTracker properly. No harm in keeping it
1077 mTabGroup
= nullptr;
1081 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1082 imgRequestProxy
* proxy
;
1083 nsresult result
= GetStaticRequest(nullptr, &proxy
);
1088 nsresult
imgRequestProxy::GetStaticRequest(Document
* aLoadingDocument
,
1089 imgRequestProxy
** aReturn
) {
1091 RefPtr
<Image
> image
= GetImage();
1094 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1095 // Early exit - we're not animated, so we don't have to do anything.
1096 NS_ADDREF(*aReturn
= this);
1100 // Check for errors in the image. Callers code rely on GetStaticRequest
1101 // failing in this case, though with FrozenImage there's no technical reason
1103 if (image
->HasError()) {
1104 return NS_ERROR_FAILURE
;
1107 // We are animated. We need to create a frozen version of this image.
1108 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1110 // Create a static imgRequestProxy with our new extracted frame.
1111 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1112 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1113 bool hadCrossOriginRedirects
= true;
1114 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1115 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1116 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1117 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1119 NS_ADDREF(*aReturn
= req
);
1124 void imgRequestProxy::NotifyListener() {
1125 // It would be nice to notify the observer directly in the status tracker
1126 // instead of through the proxy, but there are several places we do extra
1127 // processing when we receive notifications (like OnStopRequest()), and we
1128 // need to check mCanceled everywhere too.
1130 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1132 // Send the notifications to our listener asynchronously.
1133 progressTracker
->Notify(this);
1135 // We don't have an imgRequest, so we can only notify the clone of our
1136 // current state, but we still have to do that asynchronously.
1137 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1138 progressTracker
->NotifyCurrentState(this);
1142 void imgRequestProxy::SyncNotifyListener() {
1143 // It would be nice to notify the observer directly in the status tracker
1144 // instead of through the proxy, but there are several places we do extra
1145 // processing when we receive notifications (like OnStopRequest()), and we
1146 // need to check mCanceled everywhere too.
1148 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1149 progressTracker
->SyncNotify(this);
1152 void imgRequestProxy::SetHasImage() {
1153 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1154 MOZ_ASSERT(progressTracker
);
1155 RefPtr
<Image
> image
= progressTracker
->GetImage();
1158 // Force any private status related to the owner to reflect
1159 // the presence of an image;
1160 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1162 // Apply any locks we have
1163 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1167 // Apply any animation consumers we have
1168 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1169 image
->IncrementAnimationConsumers();
1173 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1174 return mBehaviour
->GetProgressTracker();
1177 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1178 return mBehaviour
->GetImage();
1181 bool RequestBehaviour::HasImage() const {
1182 if (!mOwnerHasImage
) {
1185 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1186 return progressTracker
? progressTracker
->HasImage() : false;
1189 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1191 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1193 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1194 imgRequest
* owner
= GetOwner();
1198 return owner
->GetValidator();
1201 ////////////////// imgRequestProxyStatic methods
1203 class StaticBehaviour
: public ProxyBehaviour
{
1205 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1207 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1208 RefPtr
<mozilla::image::Image
> image
= mImage
;
1209 return image
.forget();
1212 bool HasImage() const override
{ return mImage
; }
1214 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1215 return mImage
->GetProgressTracker();
1218 imgRequest
* GetOwner() const override
{ return nullptr; }
1220 void SetOwner(imgRequest
* aOwner
) override
{
1222 "We shouldn't be giving static requests a non-null owner.");
1226 // Our image. We have to hold a strong reference here, because that's normally
1227 // the job of the underlying request.
1228 RefPtr
<mozilla::image::Image
> mImage
;
1231 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1232 nsIPrincipal
* aPrincipal
,
1233 bool aHadCrossOriginRedirects
)
1234 : mPrincipal(aPrincipal
),
1235 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1236 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1240 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1242 return NS_ERROR_FAILURE
;
1245 NS_ADDREF(*aPrincipal
= mPrincipal
);
1251 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1252 bool* aHadCrossOriginRedirects
) {
1253 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1257 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1258 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1259 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1260 bool hadCrossOriginRedirects
= true;
1261 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1262 RefPtr
<mozilla::image::Image
> image
= GetImage();
1263 return new imgRequestProxyStatic(image
, currentPrincipal
,
1264 hadCrossOriginRedirects
);