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 void imgRequestProxy::MarkValidating() {
237 MOZ_ASSERT(GetValidator());
241 void imgRequestProxy::ClearValidating() {
242 MOZ_ASSERT(mValidating
);
243 MOZ_ASSERT(!GetValidator());
246 // If we'd previously requested a synchronous decode, request a decode on the
248 if (mDecodeRequested
) {
249 mDecodeRequested
= false;
250 StartDecoding(imgIContainer::FLAG_NONE
);
254 already_AddRefed
<nsIEventTarget
> imgRequestProxy::GetEventTarget() const {
255 nsCOMPtr
<nsIEventTarget
> target(mEventTarget
);
256 return target
.forget();
259 bool imgRequestProxy::HasDecodedPixels() {
260 if (IsValidating()) {
264 RefPtr
<Image
> image
= GetImage();
266 return image
->HasDecodedPixels();
272 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
273 already_AddRefed
<nsIRunnable
> aEvent
) {
274 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
276 // This method should only be used when it is *expected* that we are
277 // dispatching an event (e.g. we want to handle an event asynchronously)
278 // rather we need to (e.g. we are in the wrong scheduler group context).
279 // As such, we do not set mHadDispatch for telemetry purposes.
281 mEventTarget
->Dispatch(CreateRenderBlockingRunnable(std::move(aEvent
)),
286 return NS_DispatchToMainThread(
287 CreateRenderBlockingRunnable(std::move(aEvent
)));
290 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
291 // An imgRequestProxy can be initialized with neither a listener nor a
292 // document. The caller could follow up later by cloning the canonical
293 // imgRequestProxy with the actual listener. This is possible because
294 // imgLoader::LoadImage does not require a valid listener to be provided.
296 // Without a listener, we don't need to set our scheduler group, because
297 // we have nothing to signal. However if we were told what document this
298 // is for, it is likely that future listeners will belong to the same
301 // With a listener, we always need to update our scheduler group. A null
302 // scheduler group is valid with or without a document, but that means
303 // we will use the most generic event target possible on dispatch.
304 if (aLoadingDocument
) {
305 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
307 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
308 MOZ_ASSERT(mEventTarget
);
312 if (mListener
&& !mEventTarget
) {
313 mEventTarget
= do_GetMainThread();
316 imgRequest
* owner
= GetOwner();
321 owner
->AddProxy(this);
324 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
325 imgRequest
* owner
= GetOwner();
328 imgCacheValidator
* validator
= owner
->GetValidator();
329 MOZ_ASSERT(validator
);
330 validator
->RemoveProxy(this);
334 owner
->RemoveProxy(this, aStatus
);
338 void imgRequestProxy::AddToLoadGroup() {
339 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
340 MOZ_ASSERT(!mForceDispatchLoadGroup
);
342 /* While in theory there could be a dispatch outstanding to remove this
343 request from the load group, in practice we only add to the load group
344 (when previously not in a load group) at initialization. */
345 if (!mIsInLoadGroup
&& mLoadGroup
) {
346 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
347 mLoadGroup
->AddRequest(this, nullptr);
348 mIsInLoadGroup
= true;
352 void imgRequestProxy::RemoveFromLoadGroup() {
353 if (!mIsInLoadGroup
|| !mLoadGroup
) {
357 /* Sometimes we may not be able to remove ourselves from the load group in
358 the current context. This is because our listeners are not re-entrant (e.g.
359 we are in the middle of CancelAndForgetObserver or SyncClone). */
360 if (mForceDispatchLoadGroup
) {
361 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
363 /* We take away the load group from the request temporarily; this prevents
364 additional dispatches via RemoveFromLoadGroup occurring, as well as
365 MoveToBackgroundInLoadGroup from removing and readding. This is safe
366 because we know that once we get here, blocking the load group at all is
368 mIsInLoadGroup
= false;
369 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
370 RefPtr
<imgRequestProxy
> self(this);
371 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
372 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
373 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
378 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
380 /* calling RemoveFromLoadGroup may cause the document to finish
381 loading, which could result in our death. We need to make sure
382 that we stay alive long enough to fight another battle... at
383 least until we exit this function. */
384 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
385 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
386 mLoadGroup
= nullptr;
387 mIsInLoadGroup
= false;
390 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
391 /* Even if we are still in the load group, we may have taken away the load
392 group reference itself because we are in the process of leaving the group.
393 In that case, there is no need to background the request. */
398 /* There is no need to dispatch if we only need to add ourselves to the load
399 group without removal. It is the removal which causes the problematic
400 callbacks (see RemoveFromLoadGroup). */
401 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
403 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
405 RefPtr
<imgRequestProxy
> self(this);
406 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
407 "imgRequestProxy::MoveToBackgroundInLoadGroup",
408 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
412 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
413 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
414 if (mIsInLoadGroup
) {
415 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
418 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
419 mLoadGroup
->AddRequest(this, nullptr);
422 /** nsIRequest / imgIRequest methods **/
425 imgRequestProxy::GetName(nsACString
& aName
) {
429 mURI
->GetSpec(aName
);
436 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
439 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
440 return NS_ERROR_NOT_IMPLEMENTED
;
444 imgRequestProxy::Cancel(nsresult status
) {
446 return NS_ERROR_FAILURE
;
449 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
453 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
454 return DispatchWithTargetIfAvailable(ev
.forget());
457 void imgRequestProxy::DoCancel(nsresult status
) {
458 RemoveFromOwner(status
);
459 RemoveFromLoadGroup();
464 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
465 // If mCanceled is true but mListener is non-null, that means
466 // someone called Cancel() on us but the imgCancelRunnable is still
467 // pending. We still need to null out mListener before returning
468 // from this function in this case. That means we want to do the
469 // RemoveProxy call right now, because we need to deliver the
471 if (mCanceled
&& !mListener
) {
472 return NS_ERROR_FAILURE
;
475 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
478 mForceDispatchLoadGroup
= true;
479 RemoveFromOwner(aStatus
);
480 RemoveFromLoadGroup();
481 mForceDispatchLoadGroup
= false;
489 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
490 // Flag this, so we know to request after validation if pending.
491 if (IsValidating()) {
492 mDecodeRequested
= true;
496 RefPtr
<Image
> image
= GetImage();
498 return image
->StartDecoding(aFlags
);
502 GetOwner()->StartDecoding();
508 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
509 // Flag this, so we know to request after validation if pending.
510 if (IsValidating()) {
511 mDecodeRequested
= true;
515 RefPtr
<Image
> image
= GetImage();
517 return image
->StartDecodingWithResult(aFlags
);
521 GetOwner()->StartDecoding();
527 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
529 if (IsValidating()) {
530 mDecodeRequested
= true;
531 return imgIContainer::DECODE_REQUESTED
;
534 RefPtr
<Image
> image
= GetImage();
536 return image
->RequestDecodeWithResult(aFlags
);
540 GetOwner()->StartDecoding();
543 return imgIContainer::DECODE_REQUESTED
;
547 imgRequestProxy::LockImage() {
549 RefPtr
<Image
> image
=
550 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
552 return image
->LockImage();
558 imgRequestProxy::UnlockImage() {
559 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
562 RefPtr
<Image
> image
=
563 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
565 return image
->UnlockImage();
571 imgRequestProxy::RequestDiscard() {
572 RefPtr
<Image
> image
= GetImage();
574 return image
->RequestDiscard();
580 imgRequestProxy::IncrementAnimationConsumers() {
581 mAnimationConsumers
++;
582 RefPtr
<Image
> image
=
583 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
585 image
->IncrementAnimationConsumers();
591 imgRequestProxy::DecrementAnimationConsumers() {
592 // We may get here if some responsible code called Increment,
593 // then called us, but we have meanwhile called ClearAnimationConsumers
594 // because we needed to get rid of them earlier (see
595 // imgRequest::RemoveProxy), and hence have nothing left to
596 // decrement. (In such a case we got rid of the animation consumers
597 // early, but not the observer.)
598 if (mAnimationConsumers
> 0) {
599 mAnimationConsumers
--;
600 RefPtr
<Image
> image
=
601 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
603 image
->DecrementAnimationConsumers();
609 void imgRequestProxy::ClearAnimationConsumers() {
610 while (mAnimationConsumers
> 0) {
611 DecrementAnimationConsumers();
616 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
619 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
622 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
623 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
627 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
628 if (loadGroup
!= mLoadGroup
) {
629 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
630 return NS_ERROR_NOT_IMPLEMENTED
;
636 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
641 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
647 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
648 return GetTRRModeImpl(aTRRMode
);
652 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
653 return SetTRRModeImpl(aTRRMode
);
656 /** imgIRequest methods **/
659 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
660 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
661 // It's possible that our owner has an image but hasn't notified us of it -
662 // that'll happen if we get Canceled before the owner instantiates its image
663 // (because Canceling unregisters us as a listener on mOwner). If we're
664 // in that situation, just grab the image off of mOwner.
665 RefPtr
<Image
> image
= GetImage();
666 nsCOMPtr
<imgIContainer
> imageToReturn
;
668 imageToReturn
= image
;
670 if (!imageToReturn
&& GetOwner()) {
671 imageToReturn
= GetOwner()->GetImage();
673 if (!imageToReturn
) {
674 return NS_ERROR_FAILURE
;
677 imageToReturn
.swap(*aImage
);
683 imgRequestProxy::GetProviderId(uint32_t* aId
) {
684 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
686 nsCOMPtr
<imgIContainer
> image
;
687 nsresult rv
= GetImage(getter_AddRefs(image
));
688 if (NS_SUCCEEDED(rv
)) {
689 *aId
= image
->GetProviderId();
698 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
699 if (IsValidating()) {
700 // We are currently validating the image, and so our status could revert if
701 // we discard the cache. We should also be deferring notifications, such
702 // that the caller will be notified when validation completes. Rather than
703 // risk misleading the caller, return nothing.
704 *aStatus
= imgIRequest::STATUS_NONE
;
706 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
707 *aStatus
= progressTracker
->GetImageStatus();
714 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
716 return NS_ERROR_FAILURE
;
719 *aStatus
= GetOwner()->GetImageErrorCode();
725 imgRequestProxy::GetURI(nsIURI
** aURI
) {
726 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
727 nsCOMPtr
<nsIURI
> uri
= mURI
;
732 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
734 return NS_ERROR_FAILURE
;
737 return GetOwner()->GetFinalURI(aURI
);
741 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
742 *aObserver
= mListener
;
743 NS_IF_ADDREF(*aObserver
);
748 imgRequestProxy::GetMimeType(char** aMimeType
) {
750 return NS_ERROR_FAILURE
;
753 const char* type
= GetOwner()->GetMimeType();
755 return NS_ERROR_FAILURE
;
758 *aMimeType
= NS_xstrdup(type
);
764 imgRequestProxy::GetFileName(nsACString
& aFileName
) {
766 return NS_ERROR_FAILURE
;
769 GetOwner()->GetFileName(aFileName
);
773 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
774 return new imgRequestProxy();
778 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
779 imgIRequest
** aClone
) {
781 imgRequestProxy
* proxy
;
782 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
787 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
788 Document
* aLoadingDocument
,
789 imgRequestProxy
** aClone
) {
790 return PerformClone(aObserver
, aLoadingDocument
,
791 /* aSyncNotify */ true, aClone
);
794 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
795 Document
* aLoadingDocument
,
796 imgRequestProxy
** aClone
) {
797 return PerformClone(aObserver
, aLoadingDocument
,
798 /* aSyncNotify */ false, aClone
);
801 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
802 Document
* aLoadingDocument
,
804 imgRequestProxy
** aClone
) {
805 MOZ_ASSERT(aClone
, "Null out param");
807 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
810 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
812 nsCOMPtr
<nsILoadGroup
> loadGroup
;
813 if (aLoadingDocument
) {
814 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
817 // It is important to call |SetLoadFlags()| before calling |Init()| because
818 // |Init()| adds the request to the loadgroup.
819 // When a request is added to a loadgroup, its load flags are merged
820 // with the load flags of the loadgroup.
821 // XXXldb That's not true anymore. Stuff from imgLoader adds the
822 // request to the loadgroup.
823 clone
->SetLoadFlags(mLoadFlags
);
824 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
830 // Assign to *aClone before calling Notify so that if the caller expects to
831 // only be notified for requests it's already holding pointers to it won't be
833 NS_ADDREF(*aClone
= clone
);
835 imgCacheValidator
* validator
= GetValidator();
837 // Note that if we have a validator, we don't want to issue notifications at
838 // here because we want to defer until that completes. AddProxy will add us
839 // to the load group; we cannot avoid that in this case, because we don't
840 // know when the validation will complete, and if it will cause us to
841 // discard our cached state anyways. We are probably already blocked by the
842 // original LoadImage(WithChannel) request in any event.
843 clone
->MarkValidating();
844 validator
->AddProxy(clone
);
846 // We only want to add the request to the load group of the owning document
847 // if it is still in progress. Some callers cannot handle a supurious load
848 // group removal (e.g. print preview) so we must be careful. On the other
849 // hand, if after cloning, the original request proxy is cancelled /
850 // destroyed, we need to ensure that any clones still block the load group
851 // if it is incomplete.
852 bool addToLoadGroup
= mIsInLoadGroup
;
853 if (!addToLoadGroup
) {
854 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
856 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
859 if (addToLoadGroup
) {
860 clone
->AddToLoadGroup();
864 // This is wrong!!! We need to notify asynchronously, but there's code
865 // that assumes that we don't. This will be fixed in bug 580466. Note that
866 // if we have a validator, we won't issue notifications anyways because
867 // they are deferred, so there is no point in requesting.
868 clone
->mForceDispatchLoadGroup
= true;
869 clone
->SyncNotifyListener();
870 clone
->mForceDispatchLoadGroup
= false;
872 // Without a validator, we can request asynchronous notifications
873 // immediately. If there was a validator, this would override the deferral
874 // and that would be incorrect.
875 clone
->NotifyListener();
883 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
885 return NS_ERROR_FAILURE
;
888 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
889 principal
.forget(aPrincipal
);
894 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
895 *aHadCrossOriginRedirects
= false;
897 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
899 bool allRedirectsSameOrigin
= false;
900 *aHadCrossOriginRedirects
=
902 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
903 !allRedirectsSameOrigin
;
910 imgRequestProxy::GetMultipart(bool* aMultipart
) {
912 return NS_ERROR_FAILURE
;
915 *aMultipart
= GetOwner()->GetMultipart();
921 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
923 return NS_ERROR_FAILURE
;
926 *aCorsMode
= GetOwner()->GetCORSMode();
932 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
933 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
934 GetOwner()->BoostPriority(aCategory
);
938 /** nsISupportsPriority methods **/
941 imgRequestProxy::GetPriority(int32_t* priority
) {
942 NS_ENSURE_STATE(GetOwner());
943 *priority
= GetOwner()->Priority();
948 imgRequestProxy::SetPriority(int32_t priority
) {
949 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
950 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
955 imgRequestProxy::AdjustPriority(int32_t priority
) {
956 // We don't require |!mCanceled| here. This may be called even if we're
957 // cancelled, because it's invoked as part of the process of removing an image
958 // from the load group.
959 NS_ENSURE_STATE(GetOwner());
960 GetOwner()->AdjustPriority(this, priority
);
964 static const char* NotificationTypeToString(int32_t aType
) {
966 case imgINotificationObserver::SIZE_AVAILABLE
:
967 return "SIZE_AVAILABLE";
968 case imgINotificationObserver::FRAME_UPDATE
:
969 return "FRAME_UPDATE";
970 case imgINotificationObserver::FRAME_COMPLETE
:
971 return "FRAME_COMPLETE";
972 case imgINotificationObserver::LOAD_COMPLETE
:
973 return "LOAD_COMPLETE";
974 case imgINotificationObserver::DECODE_COMPLETE
:
975 return "DECODE_COMPLETE";
976 case imgINotificationObserver::DISCARD
:
978 case imgINotificationObserver::UNLOCKED_DRAW
:
979 return "UNLOCKED_DRAW";
980 case imgINotificationObserver::IS_ANIMATED
:
981 return "IS_ANIMATED";
982 case imgINotificationObserver::HAS_TRANSPARENCY
:
983 return "HAS_TRANSPARENCY";
985 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
986 return "(unknown notification)";
990 void imgRequestProxy::Notify(int32_t aType
,
991 const mozilla::gfx::IntRect
* aRect
) {
992 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
993 "Should call OnLoadComplete");
995 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
996 NotificationTypeToString(aType
));
998 if (!mListener
|| mCanceled
) {
1002 // Make sure the listener stays alive while we notify.
1003 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1005 listener
->Notify(this, aType
, aRect
);
1008 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
1009 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
1011 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1012 // on the listener, the removal from the loadgroup, the release of the
1013 // listener, etc). Don't let them do it.
1014 RefPtr
<imgRequestProxy
> self(this);
1016 if (mListener
&& !mCanceled
) {
1017 // Hold a ref to the listener while we call it, just in case.
1018 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
1019 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
1022 // If we're expecting more data from a multipart channel, re-add ourself
1023 // to the loadgroup so that the document doesn't lose track of the load.
1024 // If the request is already a background request and there's more data
1025 // coming, we can just leave the request in the loadgroup as-is.
1026 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1028 RemoveFromLoadGroup();
1030 nsresult errorCode
= NS_OK
;
1031 // if the load is cross origin without CORS, or the CORS access is
1032 // rejected, always fire load event to avoid leaking site information for
1033 // <link rel=preload>.
1034 // XXXedgar, currently we don't do the same thing for <img>.
1035 imgRequest
* request
= GetOwner();
1036 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1037 request
->IsCrossSiteNoCORSRequest())) {
1038 uint32_t status
= imgIRequest::STATUS_NONE
;
1039 GetImageStatus(&status
);
1040 if (status
& imgIRequest::STATUS_ERROR
) {
1041 errorCode
= NS_ERROR_FAILURE
;
1044 NotifyStop(errorCode
);
1046 // More data is coming, so change the request to be a background request
1047 // and put it back in the loadgroup.
1048 MoveToBackgroundInLoadGroup();
1052 if (mListenerIsStrongRef
&& aLastPart
) {
1053 MOZ_ASSERT(mListener
, "How did that happen?");
1054 // Drop our strong ref to the listener now that we're done with
1055 // everything. Note that this can cancel us and other fun things
1056 // like that. Don't add anything in this method after this point.
1057 imgINotificationObserver
* obs
= mListener
;
1058 mListenerIsStrongRef
= false;
1063 void imgRequestProxy::NullOutListener() {
1064 // If we have animation consumers, then they don't matter anymore
1066 ClearAnimationConsumers();
1069 if (mListenerIsStrongRef
) {
1070 // Releasing could do weird reentery stuff, so just play it super-safe
1071 nsCOMPtr
<imgINotificationObserver
> obs
;
1072 obs
.swap(mListener
);
1073 mListenerIsStrongRef
= false;
1075 mListener
= nullptr;
1080 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1081 RefPtr
<imgRequestProxy
> proxy
=
1082 GetStaticRequest(static_cast<Document
*>(nullptr));
1083 if (proxy
!= this) {
1084 RefPtr
<Image
> image
= GetImage();
1085 if (image
&& image
->HasError()) {
1086 // image/test/unit/test_async_notification_404.js needs this, but ideally
1087 // this special case can be removed from the scripted codepath.
1088 return NS_ERROR_FAILURE
;
1091 proxy
.forget(aReturn
);
1095 already_AddRefed
<imgRequestProxy
> imgRequestProxy::GetStaticRequest(
1096 Document
* aLoadingDocument
) {
1097 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument
||
1098 aLoadingDocument
->IsStaticDocument());
1099 RefPtr
<Image
> image
= GetImage();
1102 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1103 // Early exit - we're not animated, so we don't have to do anything.
1104 return do_AddRef(this);
1107 // We are animated. We need to create a frozen version of this image.
1108 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1110 // Create a static imgRequestProxy with our new extracted frame.
1111 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1112 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1113 bool hadCrossOriginRedirects
= true;
1114 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1115 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1116 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1117 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1119 return req
.forget();
1122 void imgRequestProxy::NotifyListener() {
1123 // It would be nice to notify the observer directly in the status tracker
1124 // instead of through the proxy, but there are several places we do extra
1125 // processing when we receive notifications (like OnStopRequest()), and we
1126 // need to check mCanceled everywhere too.
1128 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1130 // Send the notifications to our listener asynchronously.
1131 progressTracker
->Notify(this);
1133 // We don't have an imgRequest, so we can only notify the clone of our
1134 // current state, but we still have to do that asynchronously.
1135 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1136 progressTracker
->NotifyCurrentState(this);
1140 void imgRequestProxy::SyncNotifyListener() {
1141 // It would be nice to notify the observer directly in the status tracker
1142 // instead of through the proxy, but there are several places we do extra
1143 // processing when we receive notifications (like OnStopRequest()), and we
1144 // need to check mCanceled everywhere too.
1146 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1147 progressTracker
->SyncNotify(this);
1150 void imgRequestProxy::SetHasImage() {
1151 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1152 MOZ_ASSERT(progressTracker
);
1153 RefPtr
<Image
> image
= progressTracker
->GetImage();
1156 // Force any private status related to the owner to reflect
1157 // the presence of an image;
1158 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1160 // Apply any locks we have
1161 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1165 // Apply any animation consumers we have
1166 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1167 image
->IncrementAnimationConsumers();
1171 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1172 return mBehaviour
->GetProgressTracker();
1175 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1176 return mBehaviour
->GetImage();
1179 bool RequestBehaviour::HasImage() const {
1180 if (!mOwnerHasImage
) {
1183 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1184 return progressTracker
? progressTracker
->HasImage() : false;
1187 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1189 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1191 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1192 imgRequest
* owner
= GetOwner();
1196 return owner
->GetValidator();
1199 nsITimedChannel
* imgRequestProxy::TimedChannel() {
1203 return GetOwner()->GetTimedChannel();
1206 ////////////////// imgRequestProxyStatic methods
1208 class StaticBehaviour
: public ProxyBehaviour
{
1210 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1212 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1213 RefPtr
<mozilla::image::Image
> image
= mImage
;
1214 return image
.forget();
1217 bool HasImage() const override
{ return mImage
; }
1219 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1220 return mImage
->GetProgressTracker();
1223 imgRequest
* GetOwner() const override
{ return nullptr; }
1225 void SetOwner(imgRequest
* aOwner
) override
{
1227 "We shouldn't be giving static requests a non-null owner.");
1231 // Our image. We have to hold a strong reference here, because that's normally
1232 // the job of the underlying request.
1233 RefPtr
<mozilla::image::Image
> mImage
;
1236 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1237 nsIPrincipal
* aPrincipal
,
1238 bool aHadCrossOriginRedirects
)
1239 : mPrincipal(aPrincipal
),
1240 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1241 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1245 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1247 return NS_ERROR_FAILURE
;
1250 NS_ADDREF(*aPrincipal
= mPrincipal
);
1256 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1257 bool* aHadCrossOriginRedirects
) {
1258 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1262 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1263 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1264 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1265 bool hadCrossOriginRedirects
= true;
1266 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1267 RefPtr
<mozilla::image::Image
> image
= GetImage();
1268 return new imgRequestProxyStatic(image
, currentPrincipal
,
1269 hadCrossOriginRedirects
);