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 {
256 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
257 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
258 return target
.forget();
261 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
262 already_AddRefed
<nsIRunnable
> aEvent
) {
263 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
265 // This method should only be used when it is *expected* that we are
266 // dispatching an event (e.g. we want to handle an event asynchronously)
267 // rather we need to (e.g. we are in the wrong scheduler group context).
268 // As such, we do not set mHadDispatch for telemetry purposes.
270 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
275 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent
)));
278 void imgRequestProxy::DispatchWithTarget(already_AddRefed
<nsIRunnable
> aEvent
) {
279 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTarget");
281 MOZ_ASSERT(mListener
|| mTabGroup
);
282 MOZ_ASSERT(mEventTarget
);
285 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
289 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
290 // An imgRequestProxy can be initialized with neither a listener nor a
291 // document. The caller could follow up later by cloning the canonical
292 // imgRequestProxy with the actual listener. This is possible because
293 // imgLoader::LoadImage does not require a valid listener to be provided.
295 // Without a listener, we don't need to set our scheduler group, because
296 // we have nothing to signal. However if we were told what document this
297 // is for, it is likely that future listeners will belong to the same
300 // With a listener, we always need to update our scheduler group. A null
301 // scheduler group is valid with or without a document, but that means
302 // we will use the most generic event target possible on dispatch.
303 if (aLoadingDocument
) {
304 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
306 mTabGroup
= docGroup
->GetTabGroup();
307 MOZ_ASSERT(mTabGroup
);
309 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
310 MOZ_ASSERT(mEventTarget
);
314 if (mListener
&& !mEventTarget
) {
315 mEventTarget
= do_GetMainThread();
318 imgRequest
* owner
= GetOwner();
323 owner
->AddProxy(this);
326 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
327 imgRequest
* owner
= GetOwner();
330 imgCacheValidator
* validator
= owner
->GetValidator();
331 MOZ_ASSERT(validator
);
332 validator
->RemoveProxy(this);
336 owner
->RemoveProxy(this, aStatus
);
340 void imgRequestProxy::AddToLoadGroup() {
341 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
342 MOZ_ASSERT(!mForceDispatchLoadGroup
);
344 /* While in theory there could be a dispatch outstanding to remove this
345 request from the load group, in practice we only add to the load group
346 (when previously not in a load group) at initialization. */
347 if (!mIsInLoadGroup
&& mLoadGroup
) {
348 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
349 mLoadGroup
->AddRequest(this, nullptr);
350 mIsInLoadGroup
= true;
354 void imgRequestProxy::RemoveFromLoadGroup() {
355 if (!mIsInLoadGroup
|| !mLoadGroup
) {
359 /* Sometimes we may not be able to remove ourselves from the load group in
360 the current context. This is because our listeners are not re-entrant (e.g.
361 we are in the middle of CancelAndForgetObserver or SyncClone). */
362 if (mForceDispatchLoadGroup
) {
363 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
365 /* We take away the load group from the request temporarily; this prevents
366 additional dispatches via RemoveFromLoadGroup occurring, as well as
367 MoveToBackgroundInLoadGroup from removing and readding. This is safe
368 because we know that once we get here, blocking the load group at all is
370 mIsInLoadGroup
= false;
371 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
372 RefPtr
<imgRequestProxy
> self(this);
373 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
374 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
375 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
380 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
382 /* calling RemoveFromLoadGroup may cause the document to finish
383 loading, which could result in our death. We need to make sure
384 that we stay alive long enough to fight another battle... at
385 least until we exit this function. */
386 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
387 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
388 mLoadGroup
= nullptr;
389 mIsInLoadGroup
= false;
392 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
393 /* Even if we are still in the load group, we may have taken away the load
394 group reference itself because we are in the process of leaving the group.
395 In that case, there is no need to background the request. */
400 /* There is no need to dispatch if we only need to add ourselves to the load
401 group without removal. It is the removal which causes the problematic
402 callbacks (see RemoveFromLoadGroup). */
403 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
405 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
407 RefPtr
<imgRequestProxy
> self(this);
408 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
409 "imgRequestProxy::MoveToBackgroundInLoadGroup",
410 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
414 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
415 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
416 if (mIsInLoadGroup
) {
417 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
420 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
421 mLoadGroup
->AddRequest(this, nullptr);
424 /** nsIRequest / imgIRequest methods **/
427 imgRequestProxy::GetName(nsACString
& aName
) {
431 mURI
->GetSpec(aName
);
438 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
441 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
442 return NS_ERROR_NOT_IMPLEMENTED
;
446 imgRequestProxy::Cancel(nsresult status
) {
448 return NS_ERROR_FAILURE
;
451 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
455 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
456 return DispatchWithTargetIfAvailable(ev
.forget());
459 void imgRequestProxy::DoCancel(nsresult status
) {
460 RemoveFromOwner(status
);
461 RemoveFromLoadGroup();
466 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
467 // If mCanceled is true but mListener is non-null, that means
468 // someone called Cancel() on us but the imgCancelRunnable is still
469 // pending. We still need to null out mListener before returning
470 // from this function in this case. That means we want to do the
471 // RemoveProxy call right now, because we need to deliver the
473 if (mCanceled
&& !mListener
) {
474 return NS_ERROR_FAILURE
;
477 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
480 mForceDispatchLoadGroup
= true;
481 RemoveFromOwner(aStatus
);
482 RemoveFromLoadGroup();
483 mForceDispatchLoadGroup
= false;
491 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
492 // Flag this, so we know to request after validation if pending.
493 if (IsValidating()) {
494 mDecodeRequested
= true;
498 RefPtr
<Image
> image
= GetImage();
500 return image
->StartDecoding(aFlags
);
504 GetOwner()->StartDecoding();
510 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
511 // Flag this, so we know to request after validation if pending.
512 if (IsValidating()) {
513 mDecodeRequested
= true;
517 RefPtr
<Image
> image
= GetImage();
519 return image
->StartDecodingWithResult(aFlags
);
523 GetOwner()->StartDecoding();
529 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags
) {
530 if (IsValidating()) {
531 mDecodeRequested
= true;
535 RefPtr
<Image
> image
= GetImage();
537 return image
->RequestDecodeWithResult(aFlags
);
541 GetOwner()->StartDecoding();
548 imgRequestProxy::LockImage() {
550 RefPtr
<Image
> image
=
551 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
553 return image
->LockImage();
559 imgRequestProxy::UnlockImage() {
560 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
563 RefPtr
<Image
> image
=
564 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
566 return image
->UnlockImage();
572 imgRequestProxy::RequestDiscard() {
573 RefPtr
<Image
> image
= GetImage();
575 return image
->RequestDiscard();
581 imgRequestProxy::IncrementAnimationConsumers() {
582 mAnimationConsumers
++;
583 RefPtr
<Image
> image
=
584 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
586 image
->IncrementAnimationConsumers();
592 imgRequestProxy::DecrementAnimationConsumers() {
593 // We may get here if some responsible code called Increment,
594 // then called us, but we have meanwhile called ClearAnimationConsumers
595 // because we needed to get rid of them earlier (see
596 // imgRequest::RemoveProxy), and hence have nothing left to
597 // decrement. (In such a case we got rid of the animation consumers
598 // early, but not the observer.)
599 if (mAnimationConsumers
> 0) {
600 mAnimationConsumers
--;
601 RefPtr
<Image
> image
=
602 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
604 image
->DecrementAnimationConsumers();
610 void imgRequestProxy::ClearAnimationConsumers() {
611 while (mAnimationConsumers
> 0) {
612 DecrementAnimationConsumers();
617 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
620 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
623 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
624 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
628 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
629 if (loadGroup
!= mLoadGroup
) {
630 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
631 return NS_ERROR_NOT_IMPLEMENTED
;
637 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
642 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
647 /** imgIRequest methods **/
650 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
651 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
652 // It's possible that our owner has an image but hasn't notified us of it -
653 // that'll happen if we get Canceled before the owner instantiates its image
654 // (because Canceling unregisters us as a listener on mOwner). If we're
655 // in that situation, just grab the image off of mOwner.
656 RefPtr
<Image
> image
= GetImage();
657 nsCOMPtr
<imgIContainer
> imageToReturn
;
659 imageToReturn
= image
;
661 if (!imageToReturn
&& GetOwner()) {
662 imageToReturn
= GetOwner()->GetImage();
664 if (!imageToReturn
) {
665 return NS_ERROR_FAILURE
;
668 imageToReturn
.swap(*aImage
);
674 imgRequestProxy::GetProducerId(uint32_t* aId
) {
675 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
677 nsCOMPtr
<imgIContainer
> image
;
678 nsresult rv
= GetImage(getter_AddRefs(image
));
679 if (NS_SUCCEEDED(rv
)) {
680 *aId
= image
->GetProducerId();
682 *aId
= layers::kContainerProducerID_Invalid
;
689 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
690 if (IsValidating()) {
691 // We are currently validating the image, and so our status could revert if
692 // we discard the cache. We should also be deferring notifications, such
693 // that the caller will be notified when validation completes. Rather than
694 // risk misleading the caller, return nothing.
695 *aStatus
= imgIRequest::STATUS_NONE
;
697 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
698 *aStatus
= progressTracker
->GetImageStatus();
705 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
707 return NS_ERROR_FAILURE
;
710 *aStatus
= GetOwner()->GetImageErrorCode();
716 imgRequestProxy::GetURI(nsIURI
** aURI
) {
717 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
718 nsCOMPtr
<nsIURI
> uri
= mURI
;
723 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
725 return NS_ERROR_FAILURE
;
728 return GetOwner()->GetFinalURI(aURI
);
732 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
733 *aObserver
= mListener
;
734 NS_IF_ADDREF(*aObserver
);
739 imgRequestProxy::GetMimeType(char** aMimeType
) {
741 return NS_ERROR_FAILURE
;
744 const char* type
= GetOwner()->GetMimeType();
746 return NS_ERROR_FAILURE
;
749 *aMimeType
= NS_xstrdup(type
);
754 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
755 return new imgRequestProxy();
759 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
760 imgIRequest
** aClone
) {
762 imgRequestProxy
* proxy
;
763 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
768 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
769 Document
* aLoadingDocument
,
770 imgRequestProxy
** aClone
) {
771 return PerformClone(aObserver
, aLoadingDocument
,
772 /* aSyncNotify */ true, aClone
);
775 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
776 Document
* aLoadingDocument
,
777 imgRequestProxy
** aClone
) {
778 return PerformClone(aObserver
, aLoadingDocument
,
779 /* aSyncNotify */ false, aClone
);
782 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
783 Document
* aLoadingDocument
,
785 imgRequestProxy
** aClone
) {
786 MOZ_ASSERT(aClone
, "Null out param");
788 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
791 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
793 nsCOMPtr
<nsILoadGroup
> loadGroup
;
794 if (aLoadingDocument
) {
795 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
798 // It is important to call |SetLoadFlags()| before calling |Init()| because
799 // |Init()| adds the request to the loadgroup.
800 // When a request is added to a loadgroup, its load flags are merged
801 // with the load flags of the loadgroup.
802 // XXXldb That's not true anymore. Stuff from imgLoader adds the
803 // request to the loadgroup.
804 clone
->SetLoadFlags(mLoadFlags
);
805 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
811 // Assign to *aClone before calling Notify so that if the caller expects to
812 // only be notified for requests it's already holding pointers to it won't be
814 NS_ADDREF(*aClone
= clone
);
816 imgCacheValidator
* validator
= GetValidator();
818 // Note that if we have a validator, we don't want to issue notifications at
819 // here because we want to defer until that completes. AddProxy will add us
820 // to the load group; we cannot avoid that in this case, because we don't
821 // know when the validation will complete, and if it will cause us to
822 // discard our cached state anyways. We are probably already blocked by the
823 // original LoadImage(WithChannel) request in any event.
824 clone
->MarkValidating();
825 validator
->AddProxy(clone
);
827 // We only want to add the request to the load group of the owning document
828 // if it is still in progress. Some callers cannot handle a supurious load
829 // group removal (e.g. print preview) so we must be careful. On the other
830 // hand, if after cloning, the original request proxy is cancelled /
831 // destroyed, we need to ensure that any clones still block the load group
832 // if it is incomplete.
833 bool addToLoadGroup
= mIsInLoadGroup
;
834 if (!addToLoadGroup
) {
835 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
837 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
840 if (addToLoadGroup
) {
841 clone
->AddToLoadGroup();
845 // This is wrong!!! We need to notify asynchronously, but there's code
846 // that assumes that we don't. This will be fixed in bug 580466. Note that
847 // if we have a validator, we won't issue notifications anyways because
848 // they are deferred, so there is no point in requesting.
849 clone
->mForceDispatchLoadGroup
= true;
850 clone
->SyncNotifyListener();
851 clone
->mForceDispatchLoadGroup
= false;
853 // Without a validator, we can request asynchronous notifications
854 // immediately. If there was a validator, this would override the deferral
855 // and that would be incorrect.
856 clone
->NotifyListener();
864 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
866 return NS_ERROR_FAILURE
;
869 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
870 principal
.forget(aPrincipal
);
875 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
876 *aHadCrossOriginRedirects
= false;
878 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
880 bool allRedirectsSameOrigin
= false;
881 *aHadCrossOriginRedirects
=
883 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
884 !allRedirectsSameOrigin
;
891 imgRequestProxy::GetMultipart(bool* aMultipart
) {
893 return NS_ERROR_FAILURE
;
896 *aMultipart
= GetOwner()->GetMultipart();
902 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
904 return NS_ERROR_FAILURE
;
907 *aCorsMode
= GetOwner()->GetCORSMode();
913 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
914 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
915 GetOwner()->BoostPriority(aCategory
);
919 /** nsISupportsPriority methods **/
922 imgRequestProxy::GetPriority(int32_t* priority
) {
923 NS_ENSURE_STATE(GetOwner());
924 *priority
= GetOwner()->Priority();
929 imgRequestProxy::SetPriority(int32_t priority
) {
930 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
931 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
936 imgRequestProxy::AdjustPriority(int32_t priority
) {
937 // We don't require |!mCanceled| here. This may be called even if we're
938 // cancelled, because it's invoked as part of the process of removing an image
939 // from the load group.
940 NS_ENSURE_STATE(GetOwner());
941 GetOwner()->AdjustPriority(this, priority
);
945 static const char* NotificationTypeToString(int32_t aType
) {
947 case imgINotificationObserver::SIZE_AVAILABLE
:
948 return "SIZE_AVAILABLE";
949 case imgINotificationObserver::FRAME_UPDATE
:
950 return "FRAME_UPDATE";
951 case imgINotificationObserver::FRAME_COMPLETE
:
952 return "FRAME_COMPLETE";
953 case imgINotificationObserver::LOAD_COMPLETE
:
954 return "LOAD_COMPLETE";
955 case imgINotificationObserver::DECODE_COMPLETE
:
956 return "DECODE_COMPLETE";
957 case imgINotificationObserver::DISCARD
:
959 case imgINotificationObserver::UNLOCKED_DRAW
:
960 return "UNLOCKED_DRAW";
961 case imgINotificationObserver::IS_ANIMATED
:
962 return "IS_ANIMATED";
963 case imgINotificationObserver::HAS_TRANSPARENCY
:
964 return "HAS_TRANSPARENCY";
966 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
967 return "(unknown notification)";
971 void imgRequestProxy::Notify(int32_t aType
,
972 const mozilla::gfx::IntRect
* aRect
) {
973 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
974 "Should call OnLoadComplete");
976 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
977 NotificationTypeToString(aType
));
979 if (!mListener
|| mCanceled
) {
983 if (!IsOnEventTarget()) {
984 RefPtr
<imgRequestProxy
> self(this);
986 const mozilla::gfx::IntRect rect
= *aRect
;
987 DispatchWithTarget(NS_NewRunnableFunction(
988 "imgRequestProxy::Notify",
989 [self
, rect
, aType
]() -> void { self
->Notify(aType
, &rect
); }));
991 DispatchWithTarget(NS_NewRunnableFunction(
992 "imgRequestProxy::Notify",
993 [self
, aType
]() -> void { self
->Notify(aType
, nullptr); }));
998 // Make sure the listener stays alive while we notify.
999 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1001 listener
->Notify(this, aType
, aRect
);
1004 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1005 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1007 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1008 // on the listener, the removal from the loadgroup, the release of the
1009 // listener, etc). Don't let them do it.
1010 RefPtr
<imgRequestProxy
> self(this);
1012 if (!IsOnEventTarget()) {
1013 DispatchWithTarget(NS_NewRunnableFunction(
1014 "imgRequestProxy::OnLoadComplete",
1015 [self
, aLastPart
]() -> void { self
->OnLoadComplete(aLastPart
); }));
1019 if (mListener
&& !mCanceled
) {
1020 // Hold a ref to the listener while we call it, just in case.
1021 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1022 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1025 // If we're expecting more data from a multipart channel, re-add ourself
1026 // to the loadgroup so that the document doesn't lose track of the load.
1027 // If the request is already a background request and there's more data
1028 // coming, we can just leave the request in the loadgroup as-is.
1029 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1031 RemoveFromLoadGroup();
1033 // More data is coming, so change the request to be a background request
1034 // and put it back in the loadgroup.
1035 MoveToBackgroundInLoadGroup();
1039 if (mListenerIsStrongRef
&& aLastPart
) {
1040 MOZ_ASSERT(mListener
, "How did that happen?");
1041 // Drop our strong ref to the listener now that we're done with
1042 // everything. Note that this can cancel us and other fun things
1043 // like that. Don't add anything in this method after this point.
1044 imgINotificationObserver
* obs
= mListener
;
1045 mListenerIsStrongRef
= false;
1050 void imgRequestProxy::NullOutListener() {
1051 // If we have animation consumers, then they don't matter anymore
1053 ClearAnimationConsumers();
1056 if (mListenerIsStrongRef
) {
1057 // Releasing could do weird reentery stuff, so just play it super-safe
1058 nsCOMPtr
<imgINotificationObserver
> obs
;
1059 obs
.swap(mListener
);
1060 mListenerIsStrongRef
= false;
1062 mListener
= nullptr;
1065 // Note that we don't free the event target. We actually need that to ensure
1066 // we get removed from the ProgressTracker properly. No harm in keeping it
1068 mTabGroup
= nullptr;
1072 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1073 imgRequestProxy
* proxy
;
1074 nsresult result
= GetStaticRequest(nullptr, &proxy
);
1079 nsresult
imgRequestProxy::GetStaticRequest(Document
* aLoadingDocument
,
1080 imgRequestProxy
** aReturn
) {
1082 RefPtr
<Image
> image
= GetImage();
1085 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1086 // Early exit - we're not animated, so we don't have to do anything.
1087 NS_ADDREF(*aReturn
= this);
1091 // Check for errors in the image. Callers code rely on GetStaticRequest
1092 // failing in this case, though with FrozenImage there's no technical reason
1094 if (image
->HasError()) {
1095 return NS_ERROR_FAILURE
;
1098 // We are animated. We need to create a frozen version of this image.
1099 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1101 // Create a static imgRequestProxy with our new extracted frame.
1102 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1103 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1104 bool hadCrossOriginRedirects
= true;
1105 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1106 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1107 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1108 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1110 NS_ADDREF(*aReturn
= req
);
1115 void imgRequestProxy::NotifyListener() {
1116 // It would be nice to notify the observer directly in the status tracker
1117 // instead of through the proxy, but there are several places we do extra
1118 // processing when we receive notifications (like OnStopRequest()), and we
1119 // need to check mCanceled everywhere too.
1121 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1123 // Send the notifications to our listener asynchronously.
1124 progressTracker
->Notify(this);
1126 // We don't have an imgRequest, so we can only notify the clone of our
1127 // current state, but we still have to do that asynchronously.
1128 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1129 progressTracker
->NotifyCurrentState(this);
1133 void imgRequestProxy::SyncNotifyListener() {
1134 // It would be nice to notify the observer directly in the status tracker
1135 // instead of through the proxy, but there are several places we do extra
1136 // processing when we receive notifications (like OnStopRequest()), and we
1137 // need to check mCanceled everywhere too.
1139 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1140 progressTracker
->SyncNotify(this);
1143 void imgRequestProxy::SetHasImage() {
1144 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1145 MOZ_ASSERT(progressTracker
);
1146 RefPtr
<Image
> image
= progressTracker
->GetImage();
1149 // Force any private status related to the owner to reflect
1150 // the presence of an image;
1151 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1153 // Apply any locks we have
1154 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1158 // Apply any animation consumers we have
1159 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1160 image
->IncrementAnimationConsumers();
1164 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1165 return mBehaviour
->GetProgressTracker();
1168 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1169 return mBehaviour
->GetImage();
1172 bool RequestBehaviour::HasImage() const {
1173 if (!mOwnerHasImage
) {
1176 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1177 return progressTracker
? progressTracker
->HasImage() : false;
1180 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1182 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1184 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1185 imgRequest
* owner
= GetOwner();
1189 return owner
->GetValidator();
1192 ////////////////// imgRequestProxyStatic methods
1194 class StaticBehaviour
: public ProxyBehaviour
{
1196 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1198 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1199 RefPtr
<mozilla::image::Image
> image
= mImage
;
1200 return image
.forget();
1203 bool HasImage() const override
{ return mImage
; }
1205 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1206 return mImage
->GetProgressTracker();
1209 imgRequest
* GetOwner() const override
{ return nullptr; }
1211 void SetOwner(imgRequest
* aOwner
) override
{
1213 "We shouldn't be giving static requests a non-null owner.");
1217 // Our image. We have to hold a strong reference here, because that's normally
1218 // the job of the underlying request.
1219 RefPtr
<mozilla::image::Image
> mImage
;
1222 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1223 nsIPrincipal
* aPrincipal
,
1224 bool aHadCrossOriginRedirects
)
1225 : mPrincipal(aPrincipal
),
1226 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1227 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1231 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1233 return NS_ERROR_FAILURE
;
1236 NS_ADDREF(*aPrincipal
= mPrincipal
);
1242 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1243 bool* aHadCrossOriginRedirects
) {
1244 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1248 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1249 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1250 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1251 bool hadCrossOriginRedirects
= true;
1252 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1253 RefPtr
<mozilla::image::Image
> image
= GetImage();
1254 return new imgRequestProxyStatic(image
, currentPrincipal
,
1255 hadCrossOriginRedirects
);