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/dom/Document.h"
18 #include "mozilla/Telemetry.h" // for Telemetry
19 #include "mozilla/dom/DocGroup.h" // for DocGroup
20 #include "nsCRTGlue.h"
23 using namespace mozilla
;
24 using namespace mozilla::image
;
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),
113 mIsInLoadGroup(false),
114 mForceDispatchLoadGroup(false),
115 mListenerIsStrongRef(false),
116 mDecodeRequested(false),
117 mPendingNotify(false),
120 mHadDispatch(false) {
121 /* member initializers and constructor code */
122 LOG_FUNC(gImgLog
, "imgRequestProxy::imgRequestProxy");
125 imgRequestProxy::~imgRequestProxy() {
126 /* destructor code */
127 MOZ_ASSERT(!mListener
, "Someone forgot to properly cancel this request!");
129 // If we had a listener, that means we would have issued notifications. With
130 // bug 1359833, we added support for main thread scheduler groups. Each
131 // imgRequestProxy may have its own associated listener, document and/or
132 // scheduler group. Typically most imgRequestProxy belong to the same
133 // document, or have no listener, which means we will want to execute all main
134 // thread code in that shared scheduler group. Less frequently, there may be
135 // multiple imgRequests and they have separate documents, which means that
136 // when we issue state notifications, some or all need to be dispatched to the
137 // appropriate scheduler group for each request. This should be rare, so we
138 // want to monitor the frequency of dispatching in the wild.
140 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED
,
144 MOZ_RELEASE_ASSERT(!mLockCount
, "Someone forgot to unlock on time?");
146 ClearAnimationConsumers();
148 // Explicitly set mListener to null to ensure that the RemoveProxy
149 // call below can't send |this| to an arbitrary listener while |this|
150 // is being destroyed. This is all belt-and-suspenders in view of the
154 /* Call RemoveProxy with a successful status. This will keep the
155 channel, if still downloading data, from being canceled if 'this' is
156 the last observer. This allows the image to continue to download and
157 be cached even if no one is using it currently.
160 RemoveFromOwner(NS_OK
);
162 RemoveFromLoadGroup();
163 LOG_FUNC(gImgLog
, "imgRequestProxy::~imgRequestProxy");
166 nsresult
imgRequestProxy::Init(imgRequest
* aOwner
, nsILoadGroup
* aLoadGroup
,
167 Document
* aLoadingDocument
, nsIURI
* aURI
,
168 imgINotificationObserver
* aObserver
) {
169 MOZ_ASSERT(!GetOwner() && !mListener
,
170 "imgRequestProxy is already initialized");
172 LOG_SCOPE_WITH_PARAM(gImgLog
, "imgRequestProxy::Init", "request", aOwner
);
174 MOZ_ASSERT(mAnimationConsumers
== 0, "Cannot have animation before Init");
176 mBehaviour
->SetOwner(aOwner
);
177 mListener
= aObserver
;
178 // Make sure to addref mListener before the AddToOwner call below, since
179 // that call might well want to release it if the imgRequest has
180 // already seen OnStopRequest.
183 mListenerIsStrongRef
= true;
184 NS_ADDREF(mListener
);
186 mLoadGroup
= aLoadGroup
;
189 // Note: AddToOwner won't send all the On* notifications immediately
190 AddToOwner(aLoadingDocument
);
195 nsresult
imgRequestProxy::ChangeOwner(imgRequest
* aNewOwner
) {
196 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
199 // Ensure that this proxy has received all notifications to date
200 // before we clean it up when removing it from the old owner below.
201 SyncNotifyListener();
204 // If we're holding locks, unlock the old image.
205 // Note that UnlockImage decrements mLockCount each time it's called.
206 uint32_t oldLockCount
= mLockCount
;
211 // If we're holding animation requests, undo them.
212 uint32_t oldAnimationConsumers
= mAnimationConsumers
;
213 ClearAnimationConsumers();
215 GetOwner()->RemoveProxy(this, NS_OK
);
217 mBehaviour
->SetOwner(aNewOwner
);
218 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
220 // If we were locked, apply the locks here
221 for (uint32_t i
= 0; i
< oldLockCount
; i
++) {
225 // If we had animation requests, restore them here. Note that we
226 // do this *after* RemoveProxy, which clears out animation consumers
228 for (uint32_t i
= 0; i
< oldAnimationConsumers
; i
++) {
229 IncrementAnimationConsumers();
236 NS_IMETHODIMP
imgRequestProxy::GetTriggeringPrincipal(
237 nsIPrincipal
** aTriggeringPrincipal
) {
238 MOZ_ASSERT(GetOwner());
239 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
=
240 GetOwner()->GetTriggeringPrincipal();
241 triggeringPrincipal
.forget(aTriggeringPrincipal
);
245 void imgRequestProxy::MarkValidating() {
246 MOZ_ASSERT(GetValidator());
250 void imgRequestProxy::ClearValidating() {
251 MOZ_ASSERT(mValidating
);
252 MOZ_ASSERT(!GetValidator());
255 // If we'd previously requested a synchronous decode, request a decode on the
257 if (mDecodeRequested
) {
258 mDecodeRequested
= false;
259 StartDecoding(imgIContainer::FLAG_NONE
);
263 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
264 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
265 return target
.forget();
268 bool imgRequestProxy::HasDecodedPixels() {
269 if (IsValidating()) {
273 RefPtr
<Image
> image
= GetImage();
275 return image
->HasDecodedPixels();
281 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
282 already_AddRefed
<nsIRunnable
> aEvent
) {
283 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
285 // This method should only be used when it is *expected* that we are
286 // dispatching an event (e.g. we want to handle an event asynchronously)
287 // rather we need to (e.g. we are in the wrong scheduler group context).
288 // As such, we do not set mHadDispatch for telemetry purposes.
290 mEventTarget
->Dispatch(CreateRenderBlockingRunnable(std::move(aEvent
)),
295 return NS_DispatchToMainThread(
296 CreateRenderBlockingRunnable(std::move(aEvent
)));
299 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
300 // An imgRequestProxy can be initialized with neither a listener nor a
301 // document. The caller could follow up later by cloning the canonical
302 // imgRequestProxy with the actual listener. This is possible because
303 // imgLoader::LoadImage does not require a valid listener to be provided.
305 // Without a listener, we don't need to set our scheduler group, because
306 // we have nothing to signal. However if we were told what document this
307 // is for, it is likely that future listeners will belong to the same
310 // With a listener, we always need to update our scheduler group. A null
311 // scheduler group is valid with or without a document, but that means
312 // we will use the most generic event target possible on dispatch.
313 if (aLoadingDocument
) {
314 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
316 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
317 MOZ_ASSERT(mEventTarget
);
321 if (mListener
&& !mEventTarget
) {
322 mEventTarget
= do_GetMainThread();
325 imgRequest
* owner
= GetOwner();
330 owner
->AddProxy(this);
333 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
334 imgRequest
* owner
= GetOwner();
337 imgCacheValidator
* validator
= owner
->GetValidator();
338 MOZ_ASSERT(validator
);
339 validator
->RemoveProxy(this);
343 owner
->RemoveProxy(this, aStatus
);
347 void imgRequestProxy::AddToLoadGroup() {
348 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
349 MOZ_ASSERT(!mForceDispatchLoadGroup
);
351 /* While in theory there could be a dispatch outstanding to remove this
352 request from the load group, in practice we only add to the load group
353 (when previously not in a load group) at initialization. */
354 if (!mIsInLoadGroup
&& mLoadGroup
) {
355 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
356 mLoadGroup
->AddRequest(this, nullptr);
357 mIsInLoadGroup
= true;
361 void imgRequestProxy::RemoveFromLoadGroup() {
362 if (!mIsInLoadGroup
|| !mLoadGroup
) {
366 /* Sometimes we may not be able to remove ourselves from the load group in
367 the current context. This is because our listeners are not re-entrant (e.g.
368 we are in the middle of CancelAndForgetObserver or SyncClone). */
369 if (mForceDispatchLoadGroup
) {
370 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
372 /* We take away the load group from the request temporarily; this prevents
373 additional dispatches via RemoveFromLoadGroup occurring, as well as
374 MoveToBackgroundInLoadGroup from removing and readding. This is safe
375 because we know that once we get here, blocking the load group at all is
377 mIsInLoadGroup
= false;
378 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
379 RefPtr
<imgRequestProxy
> self(this);
380 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
381 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
382 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
387 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
389 /* calling RemoveFromLoadGroup may cause the document to finish
390 loading, which could result in our death. We need to make sure
391 that we stay alive long enough to fight another battle... at
392 least until we exit this function. */
393 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
394 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
395 mLoadGroup
= nullptr;
396 mIsInLoadGroup
= false;
399 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
400 /* Even if we are still in the load group, we may have taken away the load
401 group reference itself because we are in the process of leaving the group.
402 In that case, there is no need to background the request. */
407 /* There is no need to dispatch if we only need to add ourselves to the load
408 group without removal. It is the removal which causes the problematic
409 callbacks (see RemoveFromLoadGroup). */
410 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
412 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
414 RefPtr
<imgRequestProxy
> self(this);
415 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
416 "imgRequestProxy::MoveToBackgroundInLoadGroup",
417 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
421 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
422 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
423 if (mIsInLoadGroup
) {
424 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
427 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
428 mLoadGroup
->AddRequest(this, nullptr);
431 /** nsIRequest / imgIRequest methods **/
434 imgRequestProxy::GetName(nsACString
& aName
) {
438 mURI
->GetSpec(aName
);
445 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
448 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
449 return NS_ERROR_NOT_IMPLEMENTED
;
452 NS_IMETHODIMP
imgRequestProxy::SetCanceledReason(const nsACString
& aReason
) {
453 return SetCanceledReasonImpl(aReason
);
456 NS_IMETHODIMP
imgRequestProxy::GetCanceledReason(nsACString
& aReason
) {
457 return GetCanceledReasonImpl(aReason
);
460 NS_IMETHODIMP
imgRequestProxy::CancelWithReason(nsresult aStatus
,
461 const nsACString
& aReason
) {
462 return CancelWithReasonImpl(aStatus
, aReason
);
465 void imgRequestProxy::SetCancelable(bool aCancelable
) {
466 MOZ_ASSERT(NS_IsMainThread());
467 mCancelable
= aCancelable
;
471 imgRequestProxy::Cancel(nsresult status
) {
473 return NS_ERROR_FAILURE
;
476 if (NS_WARN_IF(!mCancelable
)) {
477 return NS_ERROR_FAILURE
;
480 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
484 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
485 return DispatchWithTargetIfAvailable(ev
.forget());
488 void imgRequestProxy::DoCancel(nsresult status
) {
489 RemoveFromOwner(status
);
490 RemoveFromLoadGroup();
495 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
496 // If mCanceled is true but mListener is non-null, that means
497 // someone called Cancel() on us but the imgCancelRunnable is still
498 // pending. We still need to null out mListener before returning
499 // from this function in this case. That means we want to do the
500 // RemoveProxy call right now, because we need to deliver the
502 if (mCanceled
&& !mListener
) {
503 return NS_ERROR_FAILURE
;
506 if (NS_WARN_IF(!mCancelable
)) {
507 MOZ_ASSERT(mCancelable
,
508 "Shouldn't try to cancel non-cancelable requests via "
509 "CancelAndForgetObserver");
510 return NS_ERROR_FAILURE
;
513 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
516 mForceDispatchLoadGroup
= true;
517 RemoveFromOwner(aStatus
);
518 RemoveFromLoadGroup();
519 mForceDispatchLoadGroup
= false;
527 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
528 // Flag this, so we know to request after validation if pending.
529 if (IsValidating()) {
530 mDecodeRequested
= true;
534 RefPtr
<Image
> image
= GetImage();
536 return image
->StartDecoding(aFlags
);
540 GetOwner()->StartDecoding();
546 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
547 // Flag this, so we know to request after validation if pending.
548 if (IsValidating()) {
549 mDecodeRequested
= true;
553 RefPtr
<Image
> image
= GetImage();
555 return image
->StartDecodingWithResult(aFlags
);
559 GetOwner()->StartDecoding();
565 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
567 if (IsValidating()) {
568 mDecodeRequested
= true;
569 return imgIContainer::DECODE_REQUESTED
;
572 RefPtr
<Image
> image
= GetImage();
574 return image
->RequestDecodeWithResult(aFlags
);
578 GetOwner()->StartDecoding();
581 return imgIContainer::DECODE_REQUESTED
;
585 imgRequestProxy::LockImage() {
587 RefPtr
<Image
> image
=
588 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
590 return image
->LockImage();
596 imgRequestProxy::UnlockImage() {
597 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
600 RefPtr
<Image
> image
=
601 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
603 return image
->UnlockImage();
609 imgRequestProxy::RequestDiscard() {
610 RefPtr
<Image
> image
= GetImage();
612 return image
->RequestDiscard();
618 imgRequestProxy::IncrementAnimationConsumers() {
619 mAnimationConsumers
++;
620 RefPtr
<Image
> image
=
621 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
623 image
->IncrementAnimationConsumers();
629 imgRequestProxy::DecrementAnimationConsumers() {
630 // We may get here if some responsible code called Increment,
631 // then called us, but we have meanwhile called ClearAnimationConsumers
632 // because we needed to get rid of them earlier (see
633 // imgRequest::RemoveProxy), and hence have nothing left to
634 // decrement. (In such a case we got rid of the animation consumers
635 // early, but not the observer.)
636 if (mAnimationConsumers
> 0) {
637 mAnimationConsumers
--;
638 RefPtr
<Image
> image
=
639 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
641 image
->DecrementAnimationConsumers();
647 void imgRequestProxy::ClearAnimationConsumers() {
648 while (mAnimationConsumers
> 0) {
649 DecrementAnimationConsumers();
654 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
657 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
660 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
661 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
665 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
666 if (loadGroup
!= mLoadGroup
) {
667 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
668 return NS_ERROR_NOT_IMPLEMENTED
;
674 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
679 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
685 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
686 return GetTRRModeImpl(aTRRMode
);
690 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
691 return SetTRRModeImpl(aTRRMode
);
694 /** imgIRequest methods **/
697 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
698 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
699 // It's possible that our owner has an image but hasn't notified us of it -
700 // that'll happen if we get Canceled before the owner instantiates its image
701 // (because Canceling unregisters us as a listener on mOwner). If we're
702 // in that situation, just grab the image off of mOwner.
703 RefPtr
<Image
> image
= GetImage();
704 nsCOMPtr
<imgIContainer
> imageToReturn
;
706 imageToReturn
= image
;
708 if (!imageToReturn
&& GetOwner()) {
709 imageToReturn
= GetOwner()->GetImage();
711 if (!imageToReturn
) {
712 return NS_ERROR_FAILURE
;
715 imageToReturn
.swap(*aImage
);
721 imgRequestProxy::GetProviderId(uint32_t* aId
) {
722 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
724 nsCOMPtr
<imgIContainer
> image
;
725 nsresult rv
= GetImage(getter_AddRefs(image
));
726 if (NS_SUCCEEDED(rv
)) {
727 *aId
= image
->GetProviderId();
736 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
737 if (IsValidating()) {
738 // We are currently validating the image, and so our status could revert if
739 // we discard the cache. We should also be deferring notifications, such
740 // that the caller will be notified when validation completes. Rather than
741 // risk misleading the caller, return nothing.
742 *aStatus
= imgIRequest::STATUS_NONE
;
744 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
745 *aStatus
= progressTracker
->GetImageStatus();
752 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
754 return NS_ERROR_FAILURE
;
757 *aStatus
= GetOwner()->GetImageErrorCode();
763 imgRequestProxy::GetURI(nsIURI
** aURI
) {
764 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
765 nsCOMPtr
<nsIURI
> uri
= mURI
;
770 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
772 return NS_ERROR_FAILURE
;
775 return GetOwner()->GetFinalURI(aURI
);
779 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
780 *aObserver
= mListener
;
781 NS_IF_ADDREF(*aObserver
);
786 imgRequestProxy::GetMimeType(char** aMimeType
) {
788 return NS_ERROR_FAILURE
;
791 const char* type
= GetOwner()->GetMimeType();
793 return NS_ERROR_FAILURE
;
796 *aMimeType
= NS_xstrdup(type
);
802 imgRequestProxy::GetFileName(nsACString
& aFileName
) {
804 return NS_ERROR_FAILURE
;
807 GetOwner()->GetFileName(aFileName
);
811 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
812 return new imgRequestProxy();
816 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
817 imgIRequest
** aClone
) {
819 imgRequestProxy
* proxy
;
820 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
825 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
826 Document
* aLoadingDocument
,
827 imgRequestProxy
** aClone
) {
828 return PerformClone(aObserver
, aLoadingDocument
,
829 /* aSyncNotify */ true, aClone
);
832 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
833 Document
* aLoadingDocument
,
834 imgRequestProxy
** aClone
) {
835 return PerformClone(aObserver
, aLoadingDocument
,
836 /* aSyncNotify */ false, aClone
);
839 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
840 Document
* aLoadingDocument
,
842 imgRequestProxy
** aClone
) {
843 MOZ_ASSERT(aClone
, "Null out param");
845 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
848 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
850 nsCOMPtr
<nsILoadGroup
> loadGroup
;
851 if (aLoadingDocument
) {
852 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
855 // It is important to call |SetLoadFlags()| before calling |Init()| because
856 // |Init()| adds the request to the loadgroup.
857 // When a request is added to a loadgroup, its load flags are merged
858 // with the load flags of the loadgroup.
859 // XXXldb That's not true anymore. Stuff from imgLoader adds the
860 // request to the loadgroup.
861 clone
->SetLoadFlags(mLoadFlags
);
862 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
868 // Assign to *aClone before calling Notify so that if the caller expects to
869 // only be notified for requests it's already holding pointers to it won't be
871 NS_ADDREF(*aClone
= clone
);
873 imgCacheValidator
* validator
= GetValidator();
875 // Note that if we have a validator, we don't want to issue notifications at
876 // here because we want to defer until that completes. AddProxy will add us
877 // to the load group; we cannot avoid that in this case, because we don't
878 // know when the validation will complete, and if it will cause us to
879 // discard our cached state anyways. We are probably already blocked by the
880 // original LoadImage(WithChannel) request in any event.
881 clone
->MarkValidating();
882 validator
->AddProxy(clone
);
884 // We only want to add the request to the load group of the owning document
885 // if it is still in progress. Some callers cannot handle a supurious load
886 // group removal (e.g. print preview) so we must be careful. On the other
887 // hand, if after cloning, the original request proxy is cancelled /
888 // destroyed, we need to ensure that any clones still block the load group
889 // if it is incomplete.
890 bool addToLoadGroup
= mIsInLoadGroup
;
891 if (!addToLoadGroup
) {
892 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
894 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
897 if (addToLoadGroup
) {
898 clone
->AddToLoadGroup();
902 // This is wrong!!! We need to notify asynchronously, but there's code
903 // that assumes that we don't. This will be fixed in bug 580466. Note that
904 // if we have a validator, we won't issue notifications anyways because
905 // they are deferred, so there is no point in requesting.
906 clone
->mForceDispatchLoadGroup
= true;
907 clone
->SyncNotifyListener();
908 clone
->mForceDispatchLoadGroup
= false;
910 // Without a validator, we can request asynchronous notifications
911 // immediately. If there was a validator, this would override the deferral
912 // and that would be incorrect.
913 clone
->NotifyListener();
921 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
923 return NS_ERROR_FAILURE
;
926 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
927 principal
.forget(aPrincipal
);
932 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
933 *aHadCrossOriginRedirects
= false;
935 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
937 bool allRedirectsSameOrigin
= false;
938 *aHadCrossOriginRedirects
=
940 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
941 !allRedirectsSameOrigin
;
948 imgRequestProxy::GetMultipart(bool* aMultipart
) {
950 return NS_ERROR_FAILURE
;
953 *aMultipart
= GetOwner()->GetMultipart();
958 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
960 return NS_ERROR_FAILURE
;
963 *aCorsMode
= GetOwner()->GetCORSMode();
968 imgRequestProxy::GetReferrerInfo(nsIReferrerInfo
** aReferrerInfo
) {
970 return NS_ERROR_FAILURE
;
973 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= GetOwner()->GetReferrerInfo();
974 referrerInfo
.forget(aReferrerInfo
);
979 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
980 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
981 GetOwner()->BoostPriority(aCategory
);
985 /** nsISupportsPriority methods **/
988 imgRequestProxy::GetPriority(int32_t* priority
) {
989 NS_ENSURE_STATE(GetOwner());
990 *priority
= GetOwner()->Priority();
995 imgRequestProxy::SetPriority(int32_t priority
) {
996 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
997 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
1002 imgRequestProxy::AdjustPriority(int32_t priority
) {
1003 // We don't require |!mCanceled| here. This may be called even if we're
1004 // cancelled, because it's invoked as part of the process of removing an image
1005 // from the load group.
1006 NS_ENSURE_STATE(GetOwner());
1007 GetOwner()->AdjustPriority(this, priority
);
1011 static const char* NotificationTypeToString(int32_t aType
) {
1013 case imgINotificationObserver::SIZE_AVAILABLE
:
1014 return "SIZE_AVAILABLE";
1015 case imgINotificationObserver::FRAME_UPDATE
:
1016 return "FRAME_UPDATE";
1017 case imgINotificationObserver::FRAME_COMPLETE
:
1018 return "FRAME_COMPLETE";
1019 case imgINotificationObserver::LOAD_COMPLETE
:
1020 return "LOAD_COMPLETE";
1021 case imgINotificationObserver::DECODE_COMPLETE
:
1022 return "DECODE_COMPLETE";
1023 case imgINotificationObserver::DISCARD
:
1025 case imgINotificationObserver::UNLOCKED_DRAW
:
1026 return "UNLOCKED_DRAW";
1027 case imgINotificationObserver::IS_ANIMATED
:
1028 return "IS_ANIMATED";
1029 case imgINotificationObserver::HAS_TRANSPARENCY
:
1030 return "HAS_TRANSPARENCY";
1032 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
1033 return "(unknown notification)";
1037 void imgRequestProxy::Notify(int32_t aType
,
1038 const mozilla::gfx::IntRect
* aRect
) {
1039 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
1040 "Should call OnLoadComplete");
1042 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
1043 NotificationTypeToString(aType
));
1045 if (!mListener
|| mCanceled
) {
1049 // Make sure the listener stays alive while we notify.
1050 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1052 listener
->Notify(this, aType
, aRect
);
1055 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1056 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1058 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1059 // on the listener, the removal from the loadgroup, the release of the
1060 // listener, etc). Don't let them do it.
1061 RefPtr
<imgRequestProxy
> self(this);
1063 if (mListener
&& !mCanceled
) {
1064 // Hold a ref to the listener while we call it, just in case.
1065 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1066 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1069 // If we're expecting more data from a multipart channel, re-add ourself
1070 // to the loadgroup so that the document doesn't lose track of the load.
1071 // If the request is already a background request and there's more data
1072 // coming, we can just leave the request in the loadgroup as-is.
1073 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1075 RemoveFromLoadGroup();
1077 nsresult errorCode
= NS_OK
;
1078 // if the load is cross origin without CORS, or the CORS access is
1079 // rejected, always fire load event to avoid leaking site information for
1080 // <link rel=preload>.
1081 // XXXedgar, currently we don't do the same thing for <img>.
1082 imgRequest
* request
= GetOwner();
1083 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1084 request
->IsCrossSiteNoCORSRequest())) {
1085 uint32_t status
= imgIRequest::STATUS_NONE
;
1086 GetImageStatus(&status
);
1087 if (status
& imgIRequest::STATUS_ERROR
) {
1088 errorCode
= NS_ERROR_FAILURE
;
1091 NotifyStop(errorCode
);
1093 // More data is coming, so change the request to be a background request
1094 // and put it back in the loadgroup.
1095 MoveToBackgroundInLoadGroup();
1099 if (mListenerIsStrongRef
&& aLastPart
) {
1100 MOZ_ASSERT(mListener
, "How did that happen?");
1101 // Drop our strong ref to the listener now that we're done with
1102 // everything. Note that this can cancel us and other fun things
1103 // like that. Don't add anything in this method after this point.
1104 imgINotificationObserver
* obs
= mListener
;
1105 mListenerIsStrongRef
= false;
1110 void imgRequestProxy::NullOutListener() {
1111 // If we have animation consumers, then they don't matter anymore
1113 ClearAnimationConsumers();
1116 if (mListenerIsStrongRef
) {
1117 // Releasing could do weird reentery stuff, so just play it super-safe
1118 nsCOMPtr
<imgINotificationObserver
> obs
;
1119 obs
.swap(mListener
);
1120 mListenerIsStrongRef
= false;
1122 mListener
= nullptr;
1127 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1128 RefPtr
<imgRequestProxy
> proxy
=
1129 GetStaticRequest(static_cast<Document
*>(nullptr));
1130 if (proxy
!= this) {
1131 RefPtr
<Image
> image
= GetImage();
1132 if (image
&& image
->HasError()) {
1133 // image/test/unit/test_async_notification_404.js needs this, but ideally
1134 // this special case can be removed from the scripted codepath.
1135 return NS_ERROR_FAILURE
;
1138 proxy
.forget(aReturn
);
1142 already_AddRefed
<imgRequestProxy
> imgRequestProxy::GetStaticRequest(
1143 Document
* aLoadingDocument
) {
1144 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument
||
1145 aLoadingDocument
->IsStaticDocument());
1146 RefPtr
<Image
> image
= GetImage();
1149 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1150 // Early exit - we're not animated, so we don't have to do anything.
1151 return do_AddRef(this);
1154 // We are animated. We need to create a frozen version of this image.
1155 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1157 // Create a static imgRequestProxy with our new extracted frame.
1158 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1159 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1160 bool hadCrossOriginRedirects
= true;
1161 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1162 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
= GetTriggeringPrincipal();
1163 RefPtr
<imgRequestProxy
> req
=
1164 new imgRequestProxyStatic(frozenImage
, currentPrincipal
,
1165 triggeringPrincipal
, hadCrossOriginRedirects
);
1166 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1168 return req
.forget();
1171 void imgRequestProxy::NotifyListener() {
1172 // It would be nice to notify the observer directly in the status tracker
1173 // instead of through the proxy, but there are several places we do extra
1174 // processing when we receive notifications (like OnStopRequest()), and we
1175 // need to check mCanceled everywhere too.
1177 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1179 // Send the notifications to our listener asynchronously.
1180 progressTracker
->Notify(this);
1182 // We don't have an imgRequest, so we can only notify the clone of our
1183 // current state, but we still have to do that asynchronously.
1184 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1185 progressTracker
->NotifyCurrentState(this);
1189 void imgRequestProxy::SyncNotifyListener() {
1190 // It would be nice to notify the observer directly in the status tracker
1191 // instead of through the proxy, but there are several places we do extra
1192 // processing when we receive notifications (like OnStopRequest()), and we
1193 // need to check mCanceled everywhere too.
1195 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1196 progressTracker
->SyncNotify(this);
1199 void imgRequestProxy::SetHasImage() {
1200 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1201 MOZ_ASSERT(progressTracker
);
1202 RefPtr
<Image
> image
= progressTracker
->GetImage();
1205 // Force any private status related to the owner to reflect
1206 // the presence of an image;
1207 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1209 // Apply any locks we have
1210 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1214 // Apply any animation consumers we have
1215 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1216 image
->IncrementAnimationConsumers();
1220 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1221 return mBehaviour
->GetProgressTracker();
1224 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1225 return mBehaviour
->GetImage();
1228 bool RequestBehaviour::HasImage() const {
1229 if (!mOwnerHasImage
) {
1232 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1233 return progressTracker
? progressTracker
->HasImage() : false;
1236 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1238 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1240 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1241 imgRequest
* owner
= GetOwner();
1245 return owner
->GetValidator();
1248 nsITimedChannel
* imgRequestProxy::TimedChannel() {
1252 return GetOwner()->GetTimedChannel();
1255 ////////////////// imgRequestProxyStatic methods
1257 class StaticBehaviour
: public ProxyBehaviour
{
1259 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1261 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1262 RefPtr
<mozilla::image::Image
> image
= mImage
;
1263 return image
.forget();
1266 bool HasImage() const override
{ return mImage
; }
1268 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1269 return mImage
->GetProgressTracker();
1272 imgRequest
* GetOwner() const override
{ return nullptr; }
1274 void SetOwner(imgRequest
* aOwner
) override
{
1276 "We shouldn't be giving static requests a non-null owner.");
1280 // Our image. We have to hold a strong reference here, because that's normally
1281 // the job of the underlying request.
1282 RefPtr
<mozilla::image::Image
> mImage
;
1285 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1286 nsIPrincipal
* aImagePrincipal
,
1287 nsIPrincipal
* aTriggeringPrincipal
,
1288 bool aHadCrossOriginRedirects
)
1289 : mImagePrincipal(aImagePrincipal
),
1290 mTriggeringPrincipal(aTriggeringPrincipal
),
1291 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1292 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1296 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1297 if (!mImagePrincipal
) {
1298 return NS_ERROR_FAILURE
;
1300 NS_ADDREF(*aPrincipal
= mImagePrincipal
);
1305 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal
** aPrincipal
) {
1306 NS_IF_ADDREF(*aPrincipal
= mTriggeringPrincipal
);
1311 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1312 bool* aHadCrossOriginRedirects
) {
1313 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1317 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1318 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1319 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1320 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
1321 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal
));
1322 bool hadCrossOriginRedirects
= true;
1323 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1324 RefPtr
<mozilla::image::Image
> image
= GetImage();
1325 return new imgRequestProxyStatic(image
, currentPrincipal
, triggeringPrincipal
,
1326 hadCrossOriginRedirects
);