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
;
25 using mozilla::dom::Document
;
27 // The split of imgRequestProxy and imgRequestProxyStatic means that
28 // certain overridden functions need to be usable in the destructor.
29 // Since virtual functions can't be used in that way, this class
30 // provides a behavioural trait for each class to use instead.
31 class ProxyBehaviour
{
33 virtual ~ProxyBehaviour() = default;
35 virtual already_AddRefed
<mozilla::image::Image
> GetImage() const = 0;
36 virtual bool HasImage() const = 0;
37 virtual already_AddRefed
<ProgressTracker
> GetProgressTracker() const = 0;
38 virtual imgRequest
* GetOwner() const = 0;
39 virtual void SetOwner(imgRequest
* aOwner
) = 0;
42 class RequestBehaviour
: public ProxyBehaviour
{
44 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
46 already_AddRefed
<mozilla::image::Image
> GetImage() const override
;
47 bool HasImage() const override
;
48 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
;
50 imgRequest
* GetOwner() const override
{ return mOwner
; }
52 void SetOwner(imgRequest
* aOwner
) override
{
56 RefPtr
<ProgressTracker
> ownerProgressTracker
= GetProgressTracker();
57 mOwnerHasImage
= ownerProgressTracker
&& ownerProgressTracker
->HasImage();
59 mOwnerHasImage
= false;
64 // We maintain the following invariant:
65 // The proxy is registered at most with a single imgRequest as an observer,
66 // and whenever it is, mOwner points to that object. This helps ensure that
67 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
68 // from whatever request it was registered with (if any). This, in turn,
69 // means that imgRequest::mObservers will not have any stale pointers in it.
70 RefPtr
<imgRequest
> mOwner
;
75 already_AddRefed
<mozilla::image::Image
> RequestBehaviour::GetImage() const {
76 if (!mOwnerHasImage
) {
79 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
80 return progressTracker
->GetImage();
83 already_AddRefed
<ProgressTracker
> RequestBehaviour::GetProgressTracker() const {
84 // NOTE: It's possible that our mOwner has an Image that it didn't notify
85 // us about, if we were Canceled before its Image was constructed.
86 // (Canceling removes us as an observer, so mOwner has no way to notify us).
87 // That's why this method uses mOwner->GetProgressTracker() instead of just
88 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
89 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
90 return mOwner
->GetProgressTracker();
93 NS_IMPL_ADDREF(imgRequestProxy
)
94 NS_IMPL_RELEASE(imgRequestProxy
)
96 NS_INTERFACE_MAP_BEGIN(imgRequestProxy
)
97 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, PreloaderBase
)
98 NS_INTERFACE_MAP_ENTRY(imgIRequest
)
99 NS_INTERFACE_MAP_ENTRY(nsIRequest
)
100 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority
)
101 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy
)
102 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel
, TimedChannel() != nullptr)
105 imgRequestProxy::imgRequestProxy()
106 : mBehaviour(new RequestBehaviour
),
109 mLoadFlags(nsIRequest::LOAD_NORMAL
),
111 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
);
466 imgRequestProxy::Cancel(nsresult status
) {
468 return NS_ERROR_FAILURE
;
471 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
475 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
476 return DispatchWithTargetIfAvailable(ev
.forget());
479 void imgRequestProxy::DoCancel(nsresult status
) {
480 RemoveFromOwner(status
);
481 RemoveFromLoadGroup();
486 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
487 // If mCanceled is true but mListener is non-null, that means
488 // someone called Cancel() on us but the imgCancelRunnable is still
489 // pending. We still need to null out mListener before returning
490 // from this function in this case. That means we want to do the
491 // RemoveProxy call right now, because we need to deliver the
493 if (mCanceled
&& !mListener
) {
494 return NS_ERROR_FAILURE
;
497 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
500 mForceDispatchLoadGroup
= true;
501 RemoveFromOwner(aStatus
);
502 RemoveFromLoadGroup();
503 mForceDispatchLoadGroup
= false;
511 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
512 // Flag this, so we know to request after validation if pending.
513 if (IsValidating()) {
514 mDecodeRequested
= true;
518 RefPtr
<Image
> image
= GetImage();
520 return image
->StartDecoding(aFlags
);
524 GetOwner()->StartDecoding();
530 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
531 // Flag this, so we know to request after validation if pending.
532 if (IsValidating()) {
533 mDecodeRequested
= true;
537 RefPtr
<Image
> image
= GetImage();
539 return image
->StartDecodingWithResult(aFlags
);
543 GetOwner()->StartDecoding();
549 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
551 if (IsValidating()) {
552 mDecodeRequested
= true;
553 return imgIContainer::DECODE_REQUESTED
;
556 RefPtr
<Image
> image
= GetImage();
558 return image
->RequestDecodeWithResult(aFlags
);
562 GetOwner()->StartDecoding();
565 return imgIContainer::DECODE_REQUESTED
;
569 imgRequestProxy::LockImage() {
571 RefPtr
<Image
> image
=
572 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
574 return image
->LockImage();
580 imgRequestProxy::UnlockImage() {
581 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
584 RefPtr
<Image
> image
=
585 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
587 return image
->UnlockImage();
593 imgRequestProxy::RequestDiscard() {
594 RefPtr
<Image
> image
= GetImage();
596 return image
->RequestDiscard();
602 imgRequestProxy::IncrementAnimationConsumers() {
603 mAnimationConsumers
++;
604 RefPtr
<Image
> image
=
605 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
607 image
->IncrementAnimationConsumers();
613 imgRequestProxy::DecrementAnimationConsumers() {
614 // We may get here if some responsible code called Increment,
615 // then called us, but we have meanwhile called ClearAnimationConsumers
616 // because we needed to get rid of them earlier (see
617 // imgRequest::RemoveProxy), and hence have nothing left to
618 // decrement. (In such a case we got rid of the animation consumers
619 // early, but not the observer.)
620 if (mAnimationConsumers
> 0) {
621 mAnimationConsumers
--;
622 RefPtr
<Image
> image
=
623 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
625 image
->DecrementAnimationConsumers();
631 void imgRequestProxy::ClearAnimationConsumers() {
632 while (mAnimationConsumers
> 0) {
633 DecrementAnimationConsumers();
638 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
641 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
644 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
645 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
649 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
650 if (loadGroup
!= mLoadGroup
) {
651 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
652 return NS_ERROR_NOT_IMPLEMENTED
;
658 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
663 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
669 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
670 return GetTRRModeImpl(aTRRMode
);
674 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
675 return SetTRRModeImpl(aTRRMode
);
678 /** imgIRequest methods **/
681 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
682 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
683 // It's possible that our owner has an image but hasn't notified us of it -
684 // that'll happen if we get Canceled before the owner instantiates its image
685 // (because Canceling unregisters us as a listener on mOwner). If we're
686 // in that situation, just grab the image off of mOwner.
687 RefPtr
<Image
> image
= GetImage();
688 nsCOMPtr
<imgIContainer
> imageToReturn
;
690 imageToReturn
= image
;
692 if (!imageToReturn
&& GetOwner()) {
693 imageToReturn
= GetOwner()->GetImage();
695 if (!imageToReturn
) {
696 return NS_ERROR_FAILURE
;
699 imageToReturn
.swap(*aImage
);
705 imgRequestProxy::GetProviderId(uint32_t* aId
) {
706 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
708 nsCOMPtr
<imgIContainer
> image
;
709 nsresult rv
= GetImage(getter_AddRefs(image
));
710 if (NS_SUCCEEDED(rv
)) {
711 *aId
= image
->GetProviderId();
720 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
721 if (IsValidating()) {
722 // We are currently validating the image, and so our status could revert if
723 // we discard the cache. We should also be deferring notifications, such
724 // that the caller will be notified when validation completes. Rather than
725 // risk misleading the caller, return nothing.
726 *aStatus
= imgIRequest::STATUS_NONE
;
728 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
729 *aStatus
= progressTracker
->GetImageStatus();
736 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
738 return NS_ERROR_FAILURE
;
741 *aStatus
= GetOwner()->GetImageErrorCode();
747 imgRequestProxy::GetURI(nsIURI
** aURI
) {
748 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
749 nsCOMPtr
<nsIURI
> uri
= mURI
;
754 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
756 return NS_ERROR_FAILURE
;
759 return GetOwner()->GetFinalURI(aURI
);
763 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
764 *aObserver
= mListener
;
765 NS_IF_ADDREF(*aObserver
);
770 imgRequestProxy::GetMimeType(char** aMimeType
) {
772 return NS_ERROR_FAILURE
;
775 const char* type
= GetOwner()->GetMimeType();
777 return NS_ERROR_FAILURE
;
780 *aMimeType
= NS_xstrdup(type
);
786 imgRequestProxy::GetFileName(nsACString
& aFileName
) {
788 return NS_ERROR_FAILURE
;
791 GetOwner()->GetFileName(aFileName
);
795 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
796 return new imgRequestProxy();
800 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
801 imgIRequest
** aClone
) {
803 imgRequestProxy
* proxy
;
804 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
809 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
810 Document
* aLoadingDocument
,
811 imgRequestProxy
** aClone
) {
812 return PerformClone(aObserver
, aLoadingDocument
,
813 /* aSyncNotify */ true, aClone
);
816 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
817 Document
* aLoadingDocument
,
818 imgRequestProxy
** aClone
) {
819 return PerformClone(aObserver
, aLoadingDocument
,
820 /* aSyncNotify */ false, aClone
);
823 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
824 Document
* aLoadingDocument
,
826 imgRequestProxy
** aClone
) {
827 MOZ_ASSERT(aClone
, "Null out param");
829 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
832 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
834 nsCOMPtr
<nsILoadGroup
> loadGroup
;
835 if (aLoadingDocument
) {
836 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
839 // It is important to call |SetLoadFlags()| before calling |Init()| because
840 // |Init()| adds the request to the loadgroup.
841 // When a request is added to a loadgroup, its load flags are merged
842 // with the load flags of the loadgroup.
843 // XXXldb That's not true anymore. Stuff from imgLoader adds the
844 // request to the loadgroup.
845 clone
->SetLoadFlags(mLoadFlags
);
846 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
852 // Assign to *aClone before calling Notify so that if the caller expects to
853 // only be notified for requests it's already holding pointers to it won't be
855 NS_ADDREF(*aClone
= clone
);
857 imgCacheValidator
* validator
= GetValidator();
859 // Note that if we have a validator, we don't want to issue notifications at
860 // here because we want to defer until that completes. AddProxy will add us
861 // to the load group; we cannot avoid that in this case, because we don't
862 // know when the validation will complete, and if it will cause us to
863 // discard our cached state anyways. We are probably already blocked by the
864 // original LoadImage(WithChannel) request in any event.
865 clone
->MarkValidating();
866 validator
->AddProxy(clone
);
868 // We only want to add the request to the load group of the owning document
869 // if it is still in progress. Some callers cannot handle a supurious load
870 // group removal (e.g. print preview) so we must be careful. On the other
871 // hand, if after cloning, the original request proxy is cancelled /
872 // destroyed, we need to ensure that any clones still block the load group
873 // if it is incomplete.
874 bool addToLoadGroup
= mIsInLoadGroup
;
875 if (!addToLoadGroup
) {
876 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
878 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
881 if (addToLoadGroup
) {
882 clone
->AddToLoadGroup();
886 // This is wrong!!! We need to notify asynchronously, but there's code
887 // that assumes that we don't. This will be fixed in bug 580466. Note that
888 // if we have a validator, we won't issue notifications anyways because
889 // they are deferred, so there is no point in requesting.
890 clone
->mForceDispatchLoadGroup
= true;
891 clone
->SyncNotifyListener();
892 clone
->mForceDispatchLoadGroup
= false;
894 // Without a validator, we can request asynchronous notifications
895 // immediately. If there was a validator, this would override the deferral
896 // and that would be incorrect.
897 clone
->NotifyListener();
905 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
907 return NS_ERROR_FAILURE
;
910 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
911 principal
.forget(aPrincipal
);
916 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
917 *aHadCrossOriginRedirects
= false;
919 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
921 bool allRedirectsSameOrigin
= false;
922 *aHadCrossOriginRedirects
=
924 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
925 !allRedirectsSameOrigin
;
932 imgRequestProxy::GetMultipart(bool* aMultipart
) {
934 return NS_ERROR_FAILURE
;
937 *aMultipart
= GetOwner()->GetMultipart();
943 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
945 return NS_ERROR_FAILURE
;
948 *aCorsMode
= GetOwner()->GetCORSMode();
954 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
955 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
956 GetOwner()->BoostPriority(aCategory
);
960 /** nsISupportsPriority methods **/
963 imgRequestProxy::GetPriority(int32_t* priority
) {
964 NS_ENSURE_STATE(GetOwner());
965 *priority
= GetOwner()->Priority();
970 imgRequestProxy::SetPriority(int32_t priority
) {
971 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
972 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
977 imgRequestProxy::AdjustPriority(int32_t priority
) {
978 // We don't require |!mCanceled| here. This may be called even if we're
979 // cancelled, because it's invoked as part of the process of removing an image
980 // from the load group.
981 NS_ENSURE_STATE(GetOwner());
982 GetOwner()->AdjustPriority(this, priority
);
986 static const char* NotificationTypeToString(int32_t aType
) {
988 case imgINotificationObserver::SIZE_AVAILABLE
:
989 return "SIZE_AVAILABLE";
990 case imgINotificationObserver::FRAME_UPDATE
:
991 return "FRAME_UPDATE";
992 case imgINotificationObserver::FRAME_COMPLETE
:
993 return "FRAME_COMPLETE";
994 case imgINotificationObserver::LOAD_COMPLETE
:
995 return "LOAD_COMPLETE";
996 case imgINotificationObserver::DECODE_COMPLETE
:
997 return "DECODE_COMPLETE";
998 case imgINotificationObserver::DISCARD
:
1000 case imgINotificationObserver::UNLOCKED_DRAW
:
1001 return "UNLOCKED_DRAW";
1002 case imgINotificationObserver::IS_ANIMATED
:
1003 return "IS_ANIMATED";
1004 case imgINotificationObserver::HAS_TRANSPARENCY
:
1005 return "HAS_TRANSPARENCY";
1007 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
1008 return "(unknown notification)";
1012 void imgRequestProxy::Notify(int32_t aType
,
1013 const mozilla::gfx::IntRect
* aRect
) {
1014 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
1015 "Should call OnLoadComplete");
1017 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
1018 NotificationTypeToString(aType
));
1020 if (!mListener
|| mCanceled
) {
1024 // Make sure the listener stays alive while we notify.
1025 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1027 listener
->Notify(this, aType
, aRect
);
1030 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1031 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1033 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1034 // on the listener, the removal from the loadgroup, the release of the
1035 // listener, etc). Don't let them do it.
1036 RefPtr
<imgRequestProxy
> self(this);
1038 if (mListener
&& !mCanceled
) {
1039 // Hold a ref to the listener while we call it, just in case.
1040 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1041 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1044 // If we're expecting more data from a multipart channel, re-add ourself
1045 // to the loadgroup so that the document doesn't lose track of the load.
1046 // If the request is already a background request and there's more data
1047 // coming, we can just leave the request in the loadgroup as-is.
1048 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1050 RemoveFromLoadGroup();
1052 nsresult errorCode
= NS_OK
;
1053 // if the load is cross origin without CORS, or the CORS access is
1054 // rejected, always fire load event to avoid leaking site information for
1055 // <link rel=preload>.
1056 // XXXedgar, currently we don't do the same thing for <img>.
1057 imgRequest
* request
= GetOwner();
1058 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1059 request
->IsCrossSiteNoCORSRequest())) {
1060 uint32_t status
= imgIRequest::STATUS_NONE
;
1061 GetImageStatus(&status
);
1062 if (status
& imgIRequest::STATUS_ERROR
) {
1063 errorCode
= NS_ERROR_FAILURE
;
1066 NotifyStop(errorCode
);
1068 // More data is coming, so change the request to be a background request
1069 // and put it back in the loadgroup.
1070 MoveToBackgroundInLoadGroup();
1074 if (mListenerIsStrongRef
&& aLastPart
) {
1075 MOZ_ASSERT(mListener
, "How did that happen?");
1076 // Drop our strong ref to the listener now that we're done with
1077 // everything. Note that this can cancel us and other fun things
1078 // like that. Don't add anything in this method after this point.
1079 imgINotificationObserver
* obs
= mListener
;
1080 mListenerIsStrongRef
= false;
1085 void imgRequestProxy::NullOutListener() {
1086 // If we have animation consumers, then they don't matter anymore
1088 ClearAnimationConsumers();
1091 if (mListenerIsStrongRef
) {
1092 // Releasing could do weird reentery stuff, so just play it super-safe
1093 nsCOMPtr
<imgINotificationObserver
> obs
;
1094 obs
.swap(mListener
);
1095 mListenerIsStrongRef
= false;
1097 mListener
= nullptr;
1102 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1103 RefPtr
<imgRequestProxy
> proxy
=
1104 GetStaticRequest(static_cast<Document
*>(nullptr));
1105 if (proxy
!= this) {
1106 RefPtr
<Image
> image
= GetImage();
1107 if (image
&& image
->HasError()) {
1108 // image/test/unit/test_async_notification_404.js needs this, but ideally
1109 // this special case can be removed from the scripted codepath.
1110 return NS_ERROR_FAILURE
;
1113 proxy
.forget(aReturn
);
1117 already_AddRefed
<imgRequestProxy
> imgRequestProxy::GetStaticRequest(
1118 Document
* aLoadingDocument
) {
1119 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument
||
1120 aLoadingDocument
->IsStaticDocument());
1121 RefPtr
<Image
> image
= GetImage();
1124 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1125 // Early exit - we're not animated, so we don't have to do anything.
1126 return do_AddRef(this);
1129 // We are animated. We need to create a frozen version of this image.
1130 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1132 // Create a static imgRequestProxy with our new extracted frame.
1133 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1134 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1135 bool hadCrossOriginRedirects
= true;
1136 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1137 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
= GetTriggeringPrincipal();
1138 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1139 frozenImage
, currentPrincipal
, triggeringPrincipal
, hadCrossOriginRedirects
);
1140 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1142 return req
.forget();
1145 void imgRequestProxy::NotifyListener() {
1146 // It would be nice to notify the observer directly in the status tracker
1147 // instead of through the proxy, but there are several places we do extra
1148 // processing when we receive notifications (like OnStopRequest()), and we
1149 // need to check mCanceled everywhere too.
1151 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1153 // Send the notifications to our listener asynchronously.
1154 progressTracker
->Notify(this);
1156 // We don't have an imgRequest, so we can only notify the clone of our
1157 // current state, but we still have to do that asynchronously.
1158 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1159 progressTracker
->NotifyCurrentState(this);
1163 void imgRequestProxy::SyncNotifyListener() {
1164 // It would be nice to notify the observer directly in the status tracker
1165 // instead of through the proxy, but there are several places we do extra
1166 // processing when we receive notifications (like OnStopRequest()), and we
1167 // need to check mCanceled everywhere too.
1169 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1170 progressTracker
->SyncNotify(this);
1173 void imgRequestProxy::SetHasImage() {
1174 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1175 MOZ_ASSERT(progressTracker
);
1176 RefPtr
<Image
> image
= progressTracker
->GetImage();
1179 // Force any private status related to the owner to reflect
1180 // the presence of an image;
1181 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1183 // Apply any locks we have
1184 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1188 // Apply any animation consumers we have
1189 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1190 image
->IncrementAnimationConsumers();
1194 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1195 return mBehaviour
->GetProgressTracker();
1198 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1199 return mBehaviour
->GetImage();
1202 bool RequestBehaviour::HasImage() const {
1203 if (!mOwnerHasImage
) {
1206 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1207 return progressTracker
? progressTracker
->HasImage() : false;
1210 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1212 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1214 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1215 imgRequest
* owner
= GetOwner();
1219 return owner
->GetValidator();
1222 nsITimedChannel
* imgRequestProxy::TimedChannel() {
1226 return GetOwner()->GetTimedChannel();
1229 ////////////////// imgRequestProxyStatic methods
1231 class StaticBehaviour
: public ProxyBehaviour
{
1233 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1235 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1236 RefPtr
<mozilla::image::Image
> image
= mImage
;
1237 return image
.forget();
1240 bool HasImage() const override
{ return mImage
; }
1242 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1243 return mImage
->GetProgressTracker();
1246 imgRequest
* GetOwner() const override
{ return nullptr; }
1248 void SetOwner(imgRequest
* aOwner
) override
{
1250 "We shouldn't be giving static requests a non-null owner.");
1254 // Our image. We have to hold a strong reference here, because that's normally
1255 // the job of the underlying request.
1256 RefPtr
<mozilla::image::Image
> mImage
;
1259 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1260 nsIPrincipal
* aImagePrincipal
,
1261 nsIPrincipal
* aTriggeringPrincipal
,
1262 bool aHadCrossOriginRedirects
)
1263 : mImagePrincipal(aImagePrincipal
),
1264 mTriggeringPrincipal(aTriggeringPrincipal
),
1265 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1266 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1270 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1271 if (!mImagePrincipal
) {
1272 return NS_ERROR_FAILURE
;
1274 NS_ADDREF(*aPrincipal
= mImagePrincipal
);
1279 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal
** aPrincipal
) {
1280 NS_IF_ADDREF(*aPrincipal
= mTriggeringPrincipal
);
1285 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1286 bool* aHadCrossOriginRedirects
) {
1287 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1291 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1292 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1293 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1294 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
1295 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal
));
1296 bool hadCrossOriginRedirects
= true;
1297 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1298 RefPtr
<mozilla::image::Image
> image
= GetImage();
1299 return new imgRequestProxyStatic(image
, currentPrincipal
, triggeringPrincipal
,
1300 hadCrossOriginRedirects
);