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 "nsCRTGlue.h"
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
, PreloaderBase
)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest
)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest
)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority
)
100 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy
)
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 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
254 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
255 return target
.forget();
258 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
259 already_AddRefed
<nsIRunnable
> aEvent
) {
260 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
262 // This method should only be used when it is *expected* that we are
263 // dispatching an event (e.g. we want to handle an event asynchronously)
264 // rather we need to (e.g. we are in the wrong scheduler group context).
265 // As such, we do not set mHadDispatch for telemetry purposes.
267 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
272 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent
)));
275 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
276 // An imgRequestProxy can be initialized with neither a listener nor a
277 // document. The caller could follow up later by cloning the canonical
278 // imgRequestProxy with the actual listener. This is possible because
279 // imgLoader::LoadImage does not require a valid listener to be provided.
281 // Without a listener, we don't need to set our scheduler group, because
282 // we have nothing to signal. However if we were told what document this
283 // is for, it is likely that future listeners will belong to the same
286 // With a listener, we always need to update our scheduler group. A null
287 // scheduler group is valid with or without a document, but that means
288 // we will use the most generic event target possible on dispatch.
289 if (aLoadingDocument
) {
290 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
292 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
293 MOZ_ASSERT(mEventTarget
);
297 if (mListener
&& !mEventTarget
) {
298 mEventTarget
= do_GetMainThread();
301 imgRequest
* owner
= GetOwner();
306 owner
->AddProxy(this);
309 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
310 imgRequest
* owner
= GetOwner();
313 imgCacheValidator
* validator
= owner
->GetValidator();
314 MOZ_ASSERT(validator
);
315 validator
->RemoveProxy(this);
319 owner
->RemoveProxy(this, aStatus
);
323 void imgRequestProxy::AddToLoadGroup() {
324 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
325 MOZ_ASSERT(!mForceDispatchLoadGroup
);
327 /* While in theory there could be a dispatch outstanding to remove this
328 request from the load group, in practice we only add to the load group
329 (when previously not in a load group) at initialization. */
330 if (!mIsInLoadGroup
&& mLoadGroup
) {
331 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
332 mLoadGroup
->AddRequest(this, nullptr);
333 mIsInLoadGroup
= true;
337 void imgRequestProxy::RemoveFromLoadGroup() {
338 if (!mIsInLoadGroup
|| !mLoadGroup
) {
342 /* Sometimes we may not be able to remove ourselves from the load group in
343 the current context. This is because our listeners are not re-entrant (e.g.
344 we are in the middle of CancelAndForgetObserver or SyncClone). */
345 if (mForceDispatchLoadGroup
) {
346 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
348 /* We take away the load group from the request temporarily; this prevents
349 additional dispatches via RemoveFromLoadGroup occurring, as well as
350 MoveToBackgroundInLoadGroup from removing and readding. This is safe
351 because we know that once we get here, blocking the load group at all is
353 mIsInLoadGroup
= false;
354 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
355 RefPtr
<imgRequestProxy
> self(this);
356 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
357 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
358 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
363 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
365 /* calling RemoveFromLoadGroup may cause the document to finish
366 loading, which could result in our death. We need to make sure
367 that we stay alive long enough to fight another battle... at
368 least until we exit this function. */
369 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
370 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
371 mLoadGroup
= nullptr;
372 mIsInLoadGroup
= false;
375 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
376 /* Even if we are still in the load group, we may have taken away the load
377 group reference itself because we are in the process of leaving the group.
378 In that case, there is no need to background the request. */
383 /* There is no need to dispatch if we only need to add ourselves to the load
384 group without removal. It is the removal which causes the problematic
385 callbacks (see RemoveFromLoadGroup). */
386 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
388 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
390 RefPtr
<imgRequestProxy
> self(this);
391 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
392 "imgRequestProxy::MoveToBackgroundInLoadGroup",
393 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
397 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
398 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
399 if (mIsInLoadGroup
) {
400 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
403 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
404 mLoadGroup
->AddRequest(this, nullptr);
407 /** nsIRequest / imgIRequest methods **/
410 imgRequestProxy::GetName(nsACString
& aName
) {
414 mURI
->GetSpec(aName
);
421 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
424 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
425 return NS_ERROR_NOT_IMPLEMENTED
;
429 imgRequestProxy::Cancel(nsresult status
) {
431 return NS_ERROR_FAILURE
;
434 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
438 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
439 return DispatchWithTargetIfAvailable(ev
.forget());
442 void imgRequestProxy::DoCancel(nsresult status
) {
443 RemoveFromOwner(status
);
444 RemoveFromLoadGroup();
449 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
450 // If mCanceled is true but mListener is non-null, that means
451 // someone called Cancel() on us but the imgCancelRunnable is still
452 // pending. We still need to null out mListener before returning
453 // from this function in this case. That means we want to do the
454 // RemoveProxy call right now, because we need to deliver the
456 if (mCanceled
&& !mListener
) {
457 return NS_ERROR_FAILURE
;
460 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
463 mForceDispatchLoadGroup
= true;
464 RemoveFromOwner(aStatus
);
465 RemoveFromLoadGroup();
466 mForceDispatchLoadGroup
= false;
474 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
475 // Flag this, so we know to request after validation if pending.
476 if (IsValidating()) {
477 mDecodeRequested
= true;
481 RefPtr
<Image
> image
= GetImage();
483 return image
->StartDecoding(aFlags
);
487 GetOwner()->StartDecoding();
493 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
494 // Flag this, so we know to request after validation if pending.
495 if (IsValidating()) {
496 mDecodeRequested
= true;
500 RefPtr
<Image
> image
= GetImage();
502 return image
->StartDecodingWithResult(aFlags
);
506 GetOwner()->StartDecoding();
512 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
514 if (IsValidating()) {
515 mDecodeRequested
= true;
516 return imgIContainer::DECODE_REQUESTED
;
519 RefPtr
<Image
> image
= GetImage();
521 return image
->RequestDecodeWithResult(aFlags
);
525 GetOwner()->StartDecoding();
528 return imgIContainer::DECODE_REQUESTED
;
532 imgRequestProxy::LockImage() {
534 RefPtr
<Image
> image
=
535 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
537 return image
->LockImage();
543 imgRequestProxy::UnlockImage() {
544 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
547 RefPtr
<Image
> image
=
548 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
550 return image
->UnlockImage();
556 imgRequestProxy::RequestDiscard() {
557 RefPtr
<Image
> image
= GetImage();
559 return image
->RequestDiscard();
565 imgRequestProxy::IncrementAnimationConsumers() {
566 mAnimationConsumers
++;
567 RefPtr
<Image
> image
=
568 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
570 image
->IncrementAnimationConsumers();
576 imgRequestProxy::DecrementAnimationConsumers() {
577 // We may get here if some responsible code called Increment,
578 // then called us, but we have meanwhile called ClearAnimationConsumers
579 // because we needed to get rid of them earlier (see
580 // imgRequest::RemoveProxy), and hence have nothing left to
581 // decrement. (In such a case we got rid of the animation consumers
582 // early, but not the observer.)
583 if (mAnimationConsumers
> 0) {
584 mAnimationConsumers
--;
585 RefPtr
<Image
> image
=
586 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
588 image
->DecrementAnimationConsumers();
594 void imgRequestProxy::ClearAnimationConsumers() {
595 while (mAnimationConsumers
> 0) {
596 DecrementAnimationConsumers();
601 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
604 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
607 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
608 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
612 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
613 if (loadGroup
!= mLoadGroup
) {
614 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
615 return NS_ERROR_NOT_IMPLEMENTED
;
621 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
626 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
632 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
633 return GetTRRModeImpl(aTRRMode
);
637 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
638 return SetTRRModeImpl(aTRRMode
);
641 /** imgIRequest methods **/
644 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
645 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
646 // It's possible that our owner has an image but hasn't notified us of it -
647 // that'll happen if we get Canceled before the owner instantiates its image
648 // (because Canceling unregisters us as a listener on mOwner). If we're
649 // in that situation, just grab the image off of mOwner.
650 RefPtr
<Image
> image
= GetImage();
651 nsCOMPtr
<imgIContainer
> imageToReturn
;
653 imageToReturn
= image
;
655 if (!imageToReturn
&& GetOwner()) {
656 imageToReturn
= GetOwner()->GetImage();
658 if (!imageToReturn
) {
659 return NS_ERROR_FAILURE
;
662 imageToReturn
.swap(*aImage
);
668 imgRequestProxy::GetProducerId(uint32_t* aId
) {
669 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
671 nsCOMPtr
<imgIContainer
> image
;
672 nsresult rv
= GetImage(getter_AddRefs(image
));
673 if (NS_SUCCEEDED(rv
)) {
674 *aId
= image
->GetProducerId();
676 *aId
= layers::kContainerProducerID_Invalid
;
683 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
684 if (IsValidating()) {
685 // We are currently validating the image, and so our status could revert if
686 // we discard the cache. We should also be deferring notifications, such
687 // that the caller will be notified when validation completes. Rather than
688 // risk misleading the caller, return nothing.
689 *aStatus
= imgIRequest::STATUS_NONE
;
691 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
692 *aStatus
= progressTracker
->GetImageStatus();
699 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
701 return NS_ERROR_FAILURE
;
704 *aStatus
= GetOwner()->GetImageErrorCode();
710 imgRequestProxy::GetURI(nsIURI
** aURI
) {
711 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
712 nsCOMPtr
<nsIURI
> uri
= mURI
;
717 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
719 return NS_ERROR_FAILURE
;
722 return GetOwner()->GetFinalURI(aURI
);
726 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
727 *aObserver
= mListener
;
728 NS_IF_ADDREF(*aObserver
);
733 imgRequestProxy::GetMimeType(char** aMimeType
) {
735 return NS_ERROR_FAILURE
;
738 const char* type
= GetOwner()->GetMimeType();
740 return NS_ERROR_FAILURE
;
743 *aMimeType
= NS_xstrdup(type
);
748 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
749 return new imgRequestProxy();
753 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
754 imgIRequest
** aClone
) {
756 imgRequestProxy
* proxy
;
757 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
762 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
763 Document
* aLoadingDocument
,
764 imgRequestProxy
** aClone
) {
765 return PerformClone(aObserver
, aLoadingDocument
,
766 /* aSyncNotify */ true, aClone
);
769 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
770 Document
* aLoadingDocument
,
771 imgRequestProxy
** aClone
) {
772 return PerformClone(aObserver
, aLoadingDocument
,
773 /* aSyncNotify */ false, aClone
);
776 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
777 Document
* aLoadingDocument
,
779 imgRequestProxy
** aClone
) {
780 MOZ_ASSERT(aClone
, "Null out param");
782 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
785 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
787 nsCOMPtr
<nsILoadGroup
> loadGroup
;
788 if (aLoadingDocument
) {
789 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
792 // It is important to call |SetLoadFlags()| before calling |Init()| because
793 // |Init()| adds the request to the loadgroup.
794 // When a request is added to a loadgroup, its load flags are merged
795 // with the load flags of the loadgroup.
796 // XXXldb That's not true anymore. Stuff from imgLoader adds the
797 // request to the loadgroup.
798 clone
->SetLoadFlags(mLoadFlags
);
799 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
805 // Assign to *aClone before calling Notify so that if the caller expects to
806 // only be notified for requests it's already holding pointers to it won't be
808 NS_ADDREF(*aClone
= clone
);
810 imgCacheValidator
* validator
= GetValidator();
812 // Note that if we have a validator, we don't want to issue notifications at
813 // here because we want to defer until that completes. AddProxy will add us
814 // to the load group; we cannot avoid that in this case, because we don't
815 // know when the validation will complete, and if it will cause us to
816 // discard our cached state anyways. We are probably already blocked by the
817 // original LoadImage(WithChannel) request in any event.
818 clone
->MarkValidating();
819 validator
->AddProxy(clone
);
821 // We only want to add the request to the load group of the owning document
822 // if it is still in progress. Some callers cannot handle a supurious load
823 // group removal (e.g. print preview) so we must be careful. On the other
824 // hand, if after cloning, the original request proxy is cancelled /
825 // destroyed, we need to ensure that any clones still block the load group
826 // if it is incomplete.
827 bool addToLoadGroup
= mIsInLoadGroup
;
828 if (!addToLoadGroup
) {
829 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
831 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
834 if (addToLoadGroup
) {
835 clone
->AddToLoadGroup();
839 // This is wrong!!! We need to notify asynchronously, but there's code
840 // that assumes that we don't. This will be fixed in bug 580466. Note that
841 // if we have a validator, we won't issue notifications anyways because
842 // they are deferred, so there is no point in requesting.
843 clone
->mForceDispatchLoadGroup
= true;
844 clone
->SyncNotifyListener();
845 clone
->mForceDispatchLoadGroup
= false;
847 // Without a validator, we can request asynchronous notifications
848 // immediately. If there was a validator, this would override the deferral
849 // and that would be incorrect.
850 clone
->NotifyListener();
858 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
860 return NS_ERROR_FAILURE
;
863 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
864 principal
.forget(aPrincipal
);
869 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
870 *aHadCrossOriginRedirects
= false;
872 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
874 bool allRedirectsSameOrigin
= false;
875 *aHadCrossOriginRedirects
=
877 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
878 !allRedirectsSameOrigin
;
885 imgRequestProxy::GetMultipart(bool* aMultipart
) {
887 return NS_ERROR_FAILURE
;
890 *aMultipart
= GetOwner()->GetMultipart();
896 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
898 return NS_ERROR_FAILURE
;
901 *aCorsMode
= GetOwner()->GetCORSMode();
907 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
908 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
909 GetOwner()->BoostPriority(aCategory
);
913 /** nsISupportsPriority methods **/
916 imgRequestProxy::GetPriority(int32_t* priority
) {
917 NS_ENSURE_STATE(GetOwner());
918 *priority
= GetOwner()->Priority();
923 imgRequestProxy::SetPriority(int32_t priority
) {
924 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
925 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
930 imgRequestProxy::AdjustPriority(int32_t priority
) {
931 // We don't require |!mCanceled| here. This may be called even if we're
932 // cancelled, because it's invoked as part of the process of removing an image
933 // from the load group.
934 NS_ENSURE_STATE(GetOwner());
935 GetOwner()->AdjustPriority(this, priority
);
939 static const char* NotificationTypeToString(int32_t aType
) {
941 case imgINotificationObserver::SIZE_AVAILABLE
:
942 return "SIZE_AVAILABLE";
943 case imgINotificationObserver::FRAME_UPDATE
:
944 return "FRAME_UPDATE";
945 case imgINotificationObserver::FRAME_COMPLETE
:
946 return "FRAME_COMPLETE";
947 case imgINotificationObserver::LOAD_COMPLETE
:
948 return "LOAD_COMPLETE";
949 case imgINotificationObserver::DECODE_COMPLETE
:
950 return "DECODE_COMPLETE";
951 case imgINotificationObserver::DISCARD
:
953 case imgINotificationObserver::UNLOCKED_DRAW
:
954 return "UNLOCKED_DRAW";
955 case imgINotificationObserver::IS_ANIMATED
:
956 return "IS_ANIMATED";
957 case imgINotificationObserver::HAS_TRANSPARENCY
:
958 return "HAS_TRANSPARENCY";
960 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
961 return "(unknown notification)";
965 void imgRequestProxy::Notify(int32_t aType
,
966 const mozilla::gfx::IntRect
* aRect
) {
967 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
968 "Should call OnLoadComplete");
970 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
971 NotificationTypeToString(aType
));
973 if (!mListener
|| mCanceled
) {
977 // Make sure the listener stays alive while we notify.
978 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
980 listener
->Notify(this, aType
, aRect
);
983 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
984 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
986 // There's all sorts of stuff here that could kill us (the OnStopRequest call
987 // on the listener, the removal from the loadgroup, the release of the
988 // listener, etc). Don't let them do it.
989 RefPtr
<imgRequestProxy
> self(this);
991 if (mListener
&& !mCanceled
) {
992 // Hold a ref to the listener while we call it, just in case.
993 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
994 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
997 // If we're expecting more data from a multipart channel, re-add ourself
998 // to the loadgroup so that the document doesn't lose track of the load.
999 // If the request is already a background request and there's more data
1000 // coming, we can just leave the request in the loadgroup as-is.
1001 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1003 RemoveFromLoadGroup();
1005 nsresult errorCode
= NS_OK
;
1006 // if the load is cross origin without CORS, or the CORS access is
1007 // rejected, always fire load event to avoid leaking site information for
1008 // <link rel=preload>.
1009 // XXXedgar, currently we don't do the same thing for <img>.
1010 imgRequest
* request
= GetOwner();
1011 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1012 request
->IsCrossSiteNoCORSRequest())) {
1013 uint32_t status
= imgIRequest::STATUS_NONE
;
1014 GetImageStatus(&status
);
1015 if (status
& imgIRequest::STATUS_ERROR
) {
1016 errorCode
= NS_ERROR_FAILURE
;
1019 NotifyStop(errorCode
);
1021 // More data is coming, so change the request to be a background request
1022 // and put it back in the loadgroup.
1023 MoveToBackgroundInLoadGroup();
1027 if (mListenerIsStrongRef
&& aLastPart
) {
1028 MOZ_ASSERT(mListener
, "How did that happen?");
1029 // Drop our strong ref to the listener now that we're done with
1030 // everything. Note that this can cancel us and other fun things
1031 // like that. Don't add anything in this method after this point.
1032 imgINotificationObserver
* obs
= mListener
;
1033 mListenerIsStrongRef
= false;
1038 void imgRequestProxy::NullOutListener() {
1039 // If we have animation consumers, then they don't matter anymore
1041 ClearAnimationConsumers();
1044 if (mListenerIsStrongRef
) {
1045 // Releasing could do weird reentery stuff, so just play it super-safe
1046 nsCOMPtr
<imgINotificationObserver
> obs
;
1047 obs
.swap(mListener
);
1048 mListenerIsStrongRef
= false;
1050 mListener
= nullptr;
1055 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1056 imgRequestProxy
* proxy
;
1057 nsresult result
= GetStaticRequest(nullptr, &proxy
);
1062 nsresult
imgRequestProxy::GetStaticRequest(Document
* aLoadingDocument
,
1063 imgRequestProxy
** aReturn
) {
1065 RefPtr
<Image
> image
= GetImage();
1068 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1069 // Early exit - we're not animated, so we don't have to do anything.
1070 NS_ADDREF(*aReturn
= this);
1074 // Check for errors in the image. Callers code rely on GetStaticRequest
1075 // failing in this case, though with FrozenImage there's no technical reason
1077 if (image
->HasError()) {
1078 return NS_ERROR_FAILURE
;
1081 // We are animated. We need to create a frozen version of this image.
1082 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1084 // Create a static imgRequestProxy with our new extracted frame.
1085 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1086 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1087 bool hadCrossOriginRedirects
= true;
1088 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1089 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1090 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1091 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1093 NS_ADDREF(*aReturn
= req
);
1098 void imgRequestProxy::NotifyListener() {
1099 // It would be nice to notify the observer directly in the status tracker
1100 // instead of through the proxy, but there are several places we do extra
1101 // processing when we receive notifications (like OnStopRequest()), and we
1102 // need to check mCanceled everywhere too.
1104 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1106 // Send the notifications to our listener asynchronously.
1107 progressTracker
->Notify(this);
1109 // We don't have an imgRequest, so we can only notify the clone of our
1110 // current state, but we still have to do that asynchronously.
1111 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1112 progressTracker
->NotifyCurrentState(this);
1116 void imgRequestProxy::SyncNotifyListener() {
1117 // It would be nice to notify the observer directly in the status tracker
1118 // instead of through the proxy, but there are several places we do extra
1119 // processing when we receive notifications (like OnStopRequest()), and we
1120 // need to check mCanceled everywhere too.
1122 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1123 progressTracker
->SyncNotify(this);
1126 void imgRequestProxy::SetHasImage() {
1127 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1128 MOZ_ASSERT(progressTracker
);
1129 RefPtr
<Image
> image
= progressTracker
->GetImage();
1132 // Force any private status related to the owner to reflect
1133 // the presence of an image;
1134 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1136 // Apply any locks we have
1137 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1141 // Apply any animation consumers we have
1142 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1143 image
->IncrementAnimationConsumers();
1147 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1148 return mBehaviour
->GetProgressTracker();
1151 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1152 return mBehaviour
->GetImage();
1155 bool RequestBehaviour::HasImage() const {
1156 if (!mOwnerHasImage
) {
1159 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1160 return progressTracker
? progressTracker
->HasImage() : false;
1163 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1165 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1167 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1168 imgRequest
* owner
= GetOwner();
1172 return owner
->GetValidator();
1175 ////////////////// imgRequestProxyStatic methods
1177 class StaticBehaviour
: public ProxyBehaviour
{
1179 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1181 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1182 RefPtr
<mozilla::image::Image
> image
= mImage
;
1183 return image
.forget();
1186 bool HasImage() const override
{ return mImage
; }
1188 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1189 return mImage
->GetProgressTracker();
1192 imgRequest
* GetOwner() const override
{ return nullptr; }
1194 void SetOwner(imgRequest
* aOwner
) override
{
1196 "We shouldn't be giving static requests a non-null owner.");
1200 // Our image. We have to hold a strong reference here, because that's normally
1201 // the job of the underlying request.
1202 RefPtr
<mozilla::image::Image
> mImage
;
1205 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1206 nsIPrincipal
* aPrincipal
,
1207 bool aHadCrossOriginRedirects
)
1208 : mPrincipal(aPrincipal
),
1209 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1210 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1214 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1216 return NS_ERROR_FAILURE
;
1219 NS_ADDREF(*aPrincipal
= mPrincipal
);
1225 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1226 bool* aHadCrossOriginRedirects
) {
1227 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1231 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1232 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1233 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1234 bool hadCrossOriginRedirects
= true;
1235 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1236 RefPtr
<mozilla::image::Image
> image
= GetImage();
1237 return new imgRequestProxyStatic(image
, currentPrincipal
,
1238 hadCrossOriginRedirects
);