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 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
);
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 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
306 MOZ_ASSERT(mEventTarget
);
310 if (mListener
&& !mEventTarget
) {
311 mEventTarget
= do_GetMainThread();
314 imgRequest
* owner
= GetOwner();
319 owner
->AddProxy(this);
322 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
323 imgRequest
* owner
= GetOwner();
326 imgCacheValidator
* validator
= owner
->GetValidator();
327 MOZ_ASSERT(validator
);
328 validator
->RemoveProxy(this);
332 owner
->RemoveProxy(this, aStatus
);
336 void imgRequestProxy::AddToLoadGroup() {
337 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
338 MOZ_ASSERT(!mForceDispatchLoadGroup
);
340 /* While in theory there could be a dispatch outstanding to remove this
341 request from the load group, in practice we only add to the load group
342 (when previously not in a load group) at initialization. */
343 if (!mIsInLoadGroup
&& mLoadGroup
) {
344 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
345 mLoadGroup
->AddRequest(this, nullptr);
346 mIsInLoadGroup
= true;
350 void imgRequestProxy::RemoveFromLoadGroup() {
351 if (!mIsInLoadGroup
|| !mLoadGroup
) {
355 /* Sometimes we may not be able to remove ourselves from the load group in
356 the current context. This is because our listeners are not re-entrant (e.g.
357 we are in the middle of CancelAndForgetObserver or SyncClone). */
358 if (mForceDispatchLoadGroup
) {
359 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
361 /* We take away the load group from the request temporarily; this prevents
362 additional dispatches via RemoveFromLoadGroup occurring, as well as
363 MoveToBackgroundInLoadGroup from removing and readding. This is safe
364 because we know that once we get here, blocking the load group at all is
366 mIsInLoadGroup
= false;
367 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
368 RefPtr
<imgRequestProxy
> self(this);
369 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
370 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
371 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
376 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
378 /* calling RemoveFromLoadGroup may cause the document to finish
379 loading, which could result in our death. We need to make sure
380 that we stay alive long enough to fight another battle... at
381 least until we exit this function. */
382 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
383 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
384 mLoadGroup
= nullptr;
385 mIsInLoadGroup
= false;
388 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
389 /* Even if we are still in the load group, we may have taken away the load
390 group reference itself because we are in the process of leaving the group.
391 In that case, there is no need to background the request. */
396 /* There is no need to dispatch if we only need to add ourselves to the load
397 group without removal. It is the removal which causes the problematic
398 callbacks (see RemoveFromLoadGroup). */
399 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
401 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
403 RefPtr
<imgRequestProxy
> self(this);
404 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
405 "imgRequestProxy::MoveToBackgroundInLoadGroup",
406 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
410 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
411 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
412 if (mIsInLoadGroup
) {
413 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
416 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
417 mLoadGroup
->AddRequest(this, nullptr);
420 /** nsIRequest / imgIRequest methods **/
423 imgRequestProxy::GetName(nsACString
& aName
) {
427 mURI
->GetSpec(aName
);
434 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
437 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
438 return NS_ERROR_NOT_IMPLEMENTED
;
442 imgRequestProxy::Cancel(nsresult status
) {
444 return NS_ERROR_FAILURE
;
447 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
451 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
452 return DispatchWithTargetIfAvailable(ev
.forget());
455 void imgRequestProxy::DoCancel(nsresult status
) {
456 RemoveFromOwner(status
);
457 RemoveFromLoadGroup();
462 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
463 // If mCanceled is true but mListener is non-null, that means
464 // someone called Cancel() on us but the imgCancelRunnable is still
465 // pending. We still need to null out mListener before returning
466 // from this function in this case. That means we want to do the
467 // RemoveProxy call right now, because we need to deliver the
469 if (mCanceled
&& !mListener
) {
470 return NS_ERROR_FAILURE
;
473 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
476 mForceDispatchLoadGroup
= true;
477 RemoveFromOwner(aStatus
);
478 RemoveFromLoadGroup();
479 mForceDispatchLoadGroup
= false;
487 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
488 // Flag this, so we know to request after validation if pending.
489 if (IsValidating()) {
490 mDecodeRequested
= true;
494 RefPtr
<Image
> image
= GetImage();
496 return image
->StartDecoding(aFlags
);
500 GetOwner()->StartDecoding();
506 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
507 // Flag this, so we know to request after validation if pending.
508 if (IsValidating()) {
509 mDecodeRequested
= true;
513 RefPtr
<Image
> image
= GetImage();
515 return image
->StartDecodingWithResult(aFlags
);
519 GetOwner()->StartDecoding();
525 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
527 if (IsValidating()) {
528 mDecodeRequested
= true;
529 return imgIContainer::DECODE_REQUESTED
;
532 RefPtr
<Image
> image
= GetImage();
534 return image
->RequestDecodeWithResult(aFlags
);
538 GetOwner()->StartDecoding();
541 return imgIContainer::DECODE_REQUESTED
;
545 imgRequestProxy::LockImage() {
547 RefPtr
<Image
> image
=
548 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
550 return image
->LockImage();
556 imgRequestProxy::UnlockImage() {
557 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
560 RefPtr
<Image
> image
=
561 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
563 return image
->UnlockImage();
569 imgRequestProxy::RequestDiscard() {
570 RefPtr
<Image
> image
= GetImage();
572 return image
->RequestDiscard();
578 imgRequestProxy::IncrementAnimationConsumers() {
579 mAnimationConsumers
++;
580 RefPtr
<Image
> image
=
581 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
583 image
->IncrementAnimationConsumers();
589 imgRequestProxy::DecrementAnimationConsumers() {
590 // We may get here if some responsible code called Increment,
591 // then called us, but we have meanwhile called ClearAnimationConsumers
592 // because we needed to get rid of them earlier (see
593 // imgRequest::RemoveProxy), and hence have nothing left to
594 // decrement. (In such a case we got rid of the animation consumers
595 // early, but not the observer.)
596 if (mAnimationConsumers
> 0) {
597 mAnimationConsumers
--;
598 RefPtr
<Image
> image
=
599 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
601 image
->DecrementAnimationConsumers();
607 void imgRequestProxy::ClearAnimationConsumers() {
608 while (mAnimationConsumers
> 0) {
609 DecrementAnimationConsumers();
614 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
617 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
620 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
621 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
625 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
626 if (loadGroup
!= mLoadGroup
) {
627 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
628 return NS_ERROR_NOT_IMPLEMENTED
;
634 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
639 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
645 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
646 return GetTRRModeImpl(aTRRMode
);
650 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
651 return SetTRRModeImpl(aTRRMode
);
654 /** imgIRequest methods **/
657 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
658 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
659 // It's possible that our owner has an image but hasn't notified us of it -
660 // that'll happen if we get Canceled before the owner instantiates its image
661 // (because Canceling unregisters us as a listener on mOwner). If we're
662 // in that situation, just grab the image off of mOwner.
663 RefPtr
<Image
> image
= GetImage();
664 nsCOMPtr
<imgIContainer
> imageToReturn
;
666 imageToReturn
= image
;
668 if (!imageToReturn
&& GetOwner()) {
669 imageToReturn
= GetOwner()->GetImage();
671 if (!imageToReturn
) {
672 return NS_ERROR_FAILURE
;
675 imageToReturn
.swap(*aImage
);
681 imgRequestProxy::GetProducerId(uint32_t* aId
) {
682 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
684 nsCOMPtr
<imgIContainer
> image
;
685 nsresult rv
= GetImage(getter_AddRefs(image
));
686 if (NS_SUCCEEDED(rv
)) {
687 *aId
= image
->GetProducerId();
689 *aId
= layers::kContainerProducerID_Invalid
;
696 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
697 if (IsValidating()) {
698 // We are currently validating the image, and so our status could revert if
699 // we discard the cache. We should also be deferring notifications, such
700 // that the caller will be notified when validation completes. Rather than
701 // risk misleading the caller, return nothing.
702 *aStatus
= imgIRequest::STATUS_NONE
;
704 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
705 *aStatus
= progressTracker
->GetImageStatus();
712 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
714 return NS_ERROR_FAILURE
;
717 *aStatus
= GetOwner()->GetImageErrorCode();
723 imgRequestProxy::GetURI(nsIURI
** aURI
) {
724 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
725 nsCOMPtr
<nsIURI
> uri
= mURI
;
730 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
732 return NS_ERROR_FAILURE
;
735 return GetOwner()->GetFinalURI(aURI
);
739 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
740 *aObserver
= mListener
;
741 NS_IF_ADDREF(*aObserver
);
746 imgRequestProxy::GetMimeType(char** aMimeType
) {
748 return NS_ERROR_FAILURE
;
751 const char* type
= GetOwner()->GetMimeType();
753 return NS_ERROR_FAILURE
;
756 *aMimeType
= NS_xstrdup(type
);
761 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
762 return new imgRequestProxy();
766 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
767 imgIRequest
** aClone
) {
769 imgRequestProxy
* proxy
;
770 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
775 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
776 Document
* aLoadingDocument
,
777 imgRequestProxy
** aClone
) {
778 return PerformClone(aObserver
, aLoadingDocument
,
779 /* aSyncNotify */ true, aClone
);
782 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
783 Document
* aLoadingDocument
,
784 imgRequestProxy
** aClone
) {
785 return PerformClone(aObserver
, aLoadingDocument
,
786 /* aSyncNotify */ false, aClone
);
789 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
790 Document
* aLoadingDocument
,
792 imgRequestProxy
** aClone
) {
793 MOZ_ASSERT(aClone
, "Null out param");
795 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
798 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
800 nsCOMPtr
<nsILoadGroup
> loadGroup
;
801 if (aLoadingDocument
) {
802 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
805 // It is important to call |SetLoadFlags()| before calling |Init()| because
806 // |Init()| adds the request to the loadgroup.
807 // When a request is added to a loadgroup, its load flags are merged
808 // with the load flags of the loadgroup.
809 // XXXldb That's not true anymore. Stuff from imgLoader adds the
810 // request to the loadgroup.
811 clone
->SetLoadFlags(mLoadFlags
);
812 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
818 // Assign to *aClone before calling Notify so that if the caller expects to
819 // only be notified for requests it's already holding pointers to it won't be
821 NS_ADDREF(*aClone
= clone
);
823 imgCacheValidator
* validator
= GetValidator();
825 // Note that if we have a validator, we don't want to issue notifications at
826 // here because we want to defer until that completes. AddProxy will add us
827 // to the load group; we cannot avoid that in this case, because we don't
828 // know when the validation will complete, and if it will cause us to
829 // discard our cached state anyways. We are probably already blocked by the
830 // original LoadImage(WithChannel) request in any event.
831 clone
->MarkValidating();
832 validator
->AddProxy(clone
);
834 // We only want to add the request to the load group of the owning document
835 // if it is still in progress. Some callers cannot handle a supurious load
836 // group removal (e.g. print preview) so we must be careful. On the other
837 // hand, if after cloning, the original request proxy is cancelled /
838 // destroyed, we need to ensure that any clones still block the load group
839 // if it is incomplete.
840 bool addToLoadGroup
= mIsInLoadGroup
;
841 if (!addToLoadGroup
) {
842 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
844 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
847 if (addToLoadGroup
) {
848 clone
->AddToLoadGroup();
852 // This is wrong!!! We need to notify asynchronously, but there's code
853 // that assumes that we don't. This will be fixed in bug 580466. Note that
854 // if we have a validator, we won't issue notifications anyways because
855 // they are deferred, so there is no point in requesting.
856 clone
->mForceDispatchLoadGroup
= true;
857 clone
->SyncNotifyListener();
858 clone
->mForceDispatchLoadGroup
= false;
860 // Without a validator, we can request asynchronous notifications
861 // immediately. If there was a validator, this would override the deferral
862 // and that would be incorrect.
863 clone
->NotifyListener();
871 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
873 return NS_ERROR_FAILURE
;
876 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
877 principal
.forget(aPrincipal
);
882 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
883 *aHadCrossOriginRedirects
= false;
885 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
887 bool allRedirectsSameOrigin
= false;
888 *aHadCrossOriginRedirects
=
890 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
891 !allRedirectsSameOrigin
;
898 imgRequestProxy::GetMultipart(bool* aMultipart
) {
900 return NS_ERROR_FAILURE
;
903 *aMultipart
= GetOwner()->GetMultipart();
909 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
911 return NS_ERROR_FAILURE
;
914 *aCorsMode
= GetOwner()->GetCORSMode();
920 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
921 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
922 GetOwner()->BoostPriority(aCategory
);
926 /** nsISupportsPriority methods **/
929 imgRequestProxy::GetPriority(int32_t* priority
) {
930 NS_ENSURE_STATE(GetOwner());
931 *priority
= GetOwner()->Priority();
936 imgRequestProxy::SetPriority(int32_t priority
) {
937 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
938 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
943 imgRequestProxy::AdjustPriority(int32_t priority
) {
944 // We don't require |!mCanceled| here. This may be called even if we're
945 // cancelled, because it's invoked as part of the process of removing an image
946 // from the load group.
947 NS_ENSURE_STATE(GetOwner());
948 GetOwner()->AdjustPriority(this, priority
);
952 static const char* NotificationTypeToString(int32_t aType
) {
954 case imgINotificationObserver::SIZE_AVAILABLE
:
955 return "SIZE_AVAILABLE";
956 case imgINotificationObserver::FRAME_UPDATE
:
957 return "FRAME_UPDATE";
958 case imgINotificationObserver::FRAME_COMPLETE
:
959 return "FRAME_COMPLETE";
960 case imgINotificationObserver::LOAD_COMPLETE
:
961 return "LOAD_COMPLETE";
962 case imgINotificationObserver::DECODE_COMPLETE
:
963 return "DECODE_COMPLETE";
964 case imgINotificationObserver::DISCARD
:
966 case imgINotificationObserver::UNLOCKED_DRAW
:
967 return "UNLOCKED_DRAW";
968 case imgINotificationObserver::IS_ANIMATED
:
969 return "IS_ANIMATED";
970 case imgINotificationObserver::HAS_TRANSPARENCY
:
971 return "HAS_TRANSPARENCY";
973 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
974 return "(unknown notification)";
978 void imgRequestProxy::Notify(int32_t aType
,
979 const mozilla::gfx::IntRect
* aRect
) {
980 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
981 "Should call OnLoadComplete");
983 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
984 NotificationTypeToString(aType
));
986 if (!mListener
|| mCanceled
) {
990 if (!IsOnEventTarget()) {
991 RefPtr
<imgRequestProxy
> self(this);
993 const mozilla::gfx::IntRect rect
= *aRect
;
994 DispatchWithTarget(NS_NewRunnableFunction(
995 "imgRequestProxy::Notify",
996 [self
, rect
, aType
]() -> void { self
->Notify(aType
, &rect
); }));
998 DispatchWithTarget(NS_NewRunnableFunction(
999 "imgRequestProxy::Notify",
1000 [self
, aType
]() -> void { self
->Notify(aType
, nullptr); }));
1005 // Make sure the listener stays alive while we notify.
1006 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1008 listener
->Notify(this, aType
, aRect
);
1011 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1012 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1014 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1015 // on the listener, the removal from the loadgroup, the release of the
1016 // listener, etc). Don't let them do it.
1017 RefPtr
<imgRequestProxy
> self(this);
1019 if (!IsOnEventTarget()) {
1020 DispatchWithTarget(NS_NewRunnableFunction(
1021 "imgRequestProxy::OnLoadComplete",
1022 [self
, aLastPart
]() -> void { self
->OnLoadComplete(aLastPart
); }));
1026 if (mListener
&& !mCanceled
) {
1027 // Hold a ref to the listener while we call it, just in case.
1028 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1029 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1032 // If we're expecting more data from a multipart channel, re-add ourself
1033 // to the loadgroup so that the document doesn't lose track of the load.
1034 // If the request is already a background request and there's more data
1035 // coming, we can just leave the request in the loadgroup as-is.
1036 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1038 RemoveFromLoadGroup();
1040 nsresult errorCode
= NS_OK
;
1041 // if the load is cross origin without CORS, or the CORS access is
1042 // rejected, always fire load event to avoid leaking site information for
1043 // <link rel=preload>.
1044 // XXXedgar, currently we don't do the same thing for <img>.
1045 imgRequest
* request
= GetOwner();
1046 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1047 request
->IsCrossSiteNoCORSRequest())) {
1048 uint32_t status
= imgIRequest::STATUS_NONE
;
1049 GetImageStatus(&status
);
1050 if (status
& imgIRequest::STATUS_ERROR
) {
1051 errorCode
= NS_ERROR_FAILURE
;
1054 NotifyStop(errorCode
);
1056 // More data is coming, so change the request to be a background request
1057 // and put it back in the loadgroup.
1058 MoveToBackgroundInLoadGroup();
1062 if (mListenerIsStrongRef
&& aLastPart
) {
1063 MOZ_ASSERT(mListener
, "How did that happen?");
1064 // Drop our strong ref to the listener now that we're done with
1065 // everything. Note that this can cancel us and other fun things
1066 // like that. Don't add anything in this method after this point.
1067 imgINotificationObserver
* obs
= mListener
;
1068 mListenerIsStrongRef
= false;
1073 void imgRequestProxy::NullOutListener() {
1074 // If we have animation consumers, then they don't matter anymore
1076 ClearAnimationConsumers();
1079 if (mListenerIsStrongRef
) {
1080 // Releasing could do weird reentery stuff, so just play it super-safe
1081 nsCOMPtr
<imgINotificationObserver
> obs
;
1082 obs
.swap(mListener
);
1083 mListenerIsStrongRef
= false;
1085 mListener
= nullptr;
1090 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1091 imgRequestProxy
* proxy
;
1092 nsresult result
= GetStaticRequest(nullptr, &proxy
);
1097 nsresult
imgRequestProxy::GetStaticRequest(Document
* aLoadingDocument
,
1098 imgRequestProxy
** aReturn
) {
1100 RefPtr
<Image
> image
= GetImage();
1103 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1104 // Early exit - we're not animated, so we don't have to do anything.
1105 NS_ADDREF(*aReturn
= this);
1109 // Check for errors in the image. Callers code rely on GetStaticRequest
1110 // failing in this case, though with FrozenImage there's no technical reason
1112 if (image
->HasError()) {
1113 return NS_ERROR_FAILURE
;
1116 // We are animated. We need to create a frozen version of this image.
1117 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1119 // Create a static imgRequestProxy with our new extracted frame.
1120 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1121 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1122 bool hadCrossOriginRedirects
= true;
1123 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1124 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1125 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1126 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1128 NS_ADDREF(*aReturn
= req
);
1133 void imgRequestProxy::NotifyListener() {
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();
1141 // Send the notifications to our listener asynchronously.
1142 progressTracker
->Notify(this);
1144 // We don't have an imgRequest, so we can only notify the clone of our
1145 // current state, but we still have to do that asynchronously.
1146 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1147 progressTracker
->NotifyCurrentState(this);
1151 void imgRequestProxy::SyncNotifyListener() {
1152 // It would be nice to notify the observer directly in the status tracker
1153 // instead of through the proxy, but there are several places we do extra
1154 // processing when we receive notifications (like OnStopRequest()), and we
1155 // need to check mCanceled everywhere too.
1157 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1158 progressTracker
->SyncNotify(this);
1161 void imgRequestProxy::SetHasImage() {
1162 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1163 MOZ_ASSERT(progressTracker
);
1164 RefPtr
<Image
> image
= progressTracker
->GetImage();
1167 // Force any private status related to the owner to reflect
1168 // the presence of an image;
1169 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1171 // Apply any locks we have
1172 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1176 // Apply any animation consumers we have
1177 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1178 image
->IncrementAnimationConsumers();
1182 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1183 return mBehaviour
->GetProgressTracker();
1186 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1187 return mBehaviour
->GetImage();
1190 bool RequestBehaviour::HasImage() const {
1191 if (!mOwnerHasImage
) {
1194 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1195 return progressTracker
? progressTracker
->HasImage() : false;
1198 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1200 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1202 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1203 imgRequest
* owner
= GetOwner();
1207 return owner
->GetValidator();
1210 ////////////////// imgRequestProxyStatic methods
1212 class StaticBehaviour
: public ProxyBehaviour
{
1214 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1216 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1217 RefPtr
<mozilla::image::Image
> image
= mImage
;
1218 return image
.forget();
1221 bool HasImage() const override
{ return mImage
; }
1223 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1224 return mImage
->GetProgressTracker();
1227 imgRequest
* GetOwner() const override
{ return nullptr; }
1229 void SetOwner(imgRequest
* aOwner
) override
{
1231 "We shouldn't be giving static requests a non-null owner.");
1235 // Our image. We have to hold a strong reference here, because that's normally
1236 // the job of the underlying request.
1237 RefPtr
<mozilla::image::Image
> mImage
;
1240 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1241 nsIPrincipal
* aPrincipal
,
1242 bool aHadCrossOriginRedirects
)
1243 : mPrincipal(aPrincipal
),
1244 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1245 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1249 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1251 return NS_ERROR_FAILURE
;
1254 NS_ADDREF(*aPrincipal
= mPrincipal
);
1260 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1261 bool* aHadCrossOriginRedirects
) {
1262 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1266 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1267 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1268 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1269 bool hadCrossOriginRedirects
= true;
1270 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1271 RefPtr
<mozilla::image::Image
> image
= GetImage();
1272 return new imgRequestProxyStatic(image
, currentPrincipal
,
1273 hadCrossOriginRedirects
);