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 nsresult
imgRequestProxy::DispatchWithTargetIfAvailable(
260 already_AddRefed
<nsIRunnable
> aEvent
) {
261 LOG_FUNC(gImgLog
, "imgRequestProxy::DispatchWithTargetIfAvailable");
263 // This method should only be used when it is *expected* that we are
264 // dispatching an event (e.g. we want to handle an event asynchronously)
265 // rather we need to (e.g. we are in the wrong scheduler group context).
266 // As such, we do not set mHadDispatch for telemetry purposes.
268 mEventTarget
->Dispatch(CreateMediumHighRunnable(std::move(aEvent
)),
273 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent
)));
276 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
277 // An imgRequestProxy can be initialized with neither a listener nor a
278 // document. The caller could follow up later by cloning the canonical
279 // imgRequestProxy with the actual listener. This is possible because
280 // imgLoader::LoadImage does not require a valid listener to be provided.
282 // Without a listener, we don't need to set our scheduler group, because
283 // we have nothing to signal. However if we were told what document this
284 // is for, it is likely that future listeners will belong to the same
287 // With a listener, we always need to update our scheduler group. A null
288 // scheduler group is valid with or without a document, but that means
289 // we will use the most generic event target possible on dispatch.
290 if (aLoadingDocument
) {
291 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
293 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
294 MOZ_ASSERT(mEventTarget
);
298 if (mListener
&& !mEventTarget
) {
299 mEventTarget
= do_GetMainThread();
302 imgRequest
* owner
= GetOwner();
307 owner
->AddProxy(this);
310 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
311 imgRequest
* owner
= GetOwner();
314 imgCacheValidator
* validator
= owner
->GetValidator();
315 MOZ_ASSERT(validator
);
316 validator
->RemoveProxy(this);
320 owner
->RemoveProxy(this, aStatus
);
324 void imgRequestProxy::AddToLoadGroup() {
325 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
326 MOZ_ASSERT(!mForceDispatchLoadGroup
);
328 /* While in theory there could be a dispatch outstanding to remove this
329 request from the load group, in practice we only add to the load group
330 (when previously not in a load group) at initialization. */
331 if (!mIsInLoadGroup
&& mLoadGroup
) {
332 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
333 mLoadGroup
->AddRequest(this, nullptr);
334 mIsInLoadGroup
= true;
338 void imgRequestProxy::RemoveFromLoadGroup() {
339 if (!mIsInLoadGroup
|| !mLoadGroup
) {
343 /* Sometimes we may not be able to remove ourselves from the load group in
344 the current context. This is because our listeners are not re-entrant (e.g.
345 we are in the middle of CancelAndForgetObserver or SyncClone). */
346 if (mForceDispatchLoadGroup
) {
347 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
349 /* We take away the load group from the request temporarily; this prevents
350 additional dispatches via RemoveFromLoadGroup occurring, as well as
351 MoveToBackgroundInLoadGroup from removing and readding. This is safe
352 because we know that once we get here, blocking the load group at all is
354 mIsInLoadGroup
= false;
355 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
356 RefPtr
<imgRequestProxy
> self(this);
357 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
358 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
359 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
364 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
366 /* calling RemoveFromLoadGroup may cause the document to finish
367 loading, which could result in our death. We need to make sure
368 that we stay alive long enough to fight another battle... at
369 least until we exit this function. */
370 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
371 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
372 mLoadGroup
= nullptr;
373 mIsInLoadGroup
= false;
376 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
377 /* Even if we are still in the load group, we may have taken away the load
378 group reference itself because we are in the process of leaving the group.
379 In that case, there is no need to background the request. */
384 /* There is no need to dispatch if we only need to add ourselves to the load
385 group without removal. It is the removal which causes the problematic
386 callbacks (see RemoveFromLoadGroup). */
387 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
389 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
391 RefPtr
<imgRequestProxy
> self(this);
392 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
393 "imgRequestProxy::MoveToBackgroundInLoadGroup",
394 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
398 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
399 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
400 if (mIsInLoadGroup
) {
401 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
404 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
405 mLoadGroup
->AddRequest(this, nullptr);
408 /** nsIRequest / imgIRequest methods **/
411 imgRequestProxy::GetName(nsACString
& aName
) {
415 mURI
->GetSpec(aName
);
422 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
425 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
426 return NS_ERROR_NOT_IMPLEMENTED
;
430 imgRequestProxy::Cancel(nsresult status
) {
432 return NS_ERROR_FAILURE
;
435 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
439 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
440 return DispatchWithTargetIfAvailable(ev
.forget());
443 void imgRequestProxy::DoCancel(nsresult status
) {
444 RemoveFromOwner(status
);
445 RemoveFromLoadGroup();
450 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
451 // If mCanceled is true but mListener is non-null, that means
452 // someone called Cancel() on us but the imgCancelRunnable is still
453 // pending. We still need to null out mListener before returning
454 // from this function in this case. That means we want to do the
455 // RemoveProxy call right now, because we need to deliver the
457 if (mCanceled
&& !mListener
) {
458 return NS_ERROR_FAILURE
;
461 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
464 mForceDispatchLoadGroup
= true;
465 RemoveFromOwner(aStatus
);
466 RemoveFromLoadGroup();
467 mForceDispatchLoadGroup
= false;
475 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
476 // Flag this, so we know to request after validation if pending.
477 if (IsValidating()) {
478 mDecodeRequested
= true;
482 RefPtr
<Image
> image
= GetImage();
484 return image
->StartDecoding(aFlags
);
488 GetOwner()->StartDecoding();
494 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
495 // Flag this, so we know to request after validation if pending.
496 if (IsValidating()) {
497 mDecodeRequested
= true;
501 RefPtr
<Image
> image
= GetImage();
503 return image
->StartDecodingWithResult(aFlags
);
507 GetOwner()->StartDecoding();
513 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
515 if (IsValidating()) {
516 mDecodeRequested
= true;
517 return imgIContainer::DECODE_REQUESTED
;
520 RefPtr
<Image
> image
= GetImage();
522 return image
->RequestDecodeWithResult(aFlags
);
526 GetOwner()->StartDecoding();
529 return imgIContainer::DECODE_REQUESTED
;
533 imgRequestProxy::LockImage() {
535 RefPtr
<Image
> image
=
536 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
538 return image
->LockImage();
544 imgRequestProxy::UnlockImage() {
545 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
548 RefPtr
<Image
> image
=
549 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
551 return image
->UnlockImage();
557 imgRequestProxy::RequestDiscard() {
558 RefPtr
<Image
> image
= GetImage();
560 return image
->RequestDiscard();
566 imgRequestProxy::IncrementAnimationConsumers() {
567 mAnimationConsumers
++;
568 RefPtr
<Image
> image
=
569 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
571 image
->IncrementAnimationConsumers();
577 imgRequestProxy::DecrementAnimationConsumers() {
578 // We may get here if some responsible code called Increment,
579 // then called us, but we have meanwhile called ClearAnimationConsumers
580 // because we needed to get rid of them earlier (see
581 // imgRequest::RemoveProxy), and hence have nothing left to
582 // decrement. (In such a case we got rid of the animation consumers
583 // early, but not the observer.)
584 if (mAnimationConsumers
> 0) {
585 mAnimationConsumers
--;
586 RefPtr
<Image
> image
=
587 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
589 image
->DecrementAnimationConsumers();
595 void imgRequestProxy::ClearAnimationConsumers() {
596 while (mAnimationConsumers
> 0) {
597 DecrementAnimationConsumers();
602 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
605 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
608 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
609 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
613 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
614 if (loadGroup
!= mLoadGroup
) {
615 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
616 return NS_ERROR_NOT_IMPLEMENTED
;
622 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
627 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
633 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
634 return GetTRRModeImpl(aTRRMode
);
638 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
639 return SetTRRModeImpl(aTRRMode
);
642 /** imgIRequest methods **/
645 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
646 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
647 // It's possible that our owner has an image but hasn't notified us of it -
648 // that'll happen if we get Canceled before the owner instantiates its image
649 // (because Canceling unregisters us as a listener on mOwner). If we're
650 // in that situation, just grab the image off of mOwner.
651 RefPtr
<Image
> image
= GetImage();
652 nsCOMPtr
<imgIContainer
> imageToReturn
;
654 imageToReturn
= image
;
656 if (!imageToReturn
&& GetOwner()) {
657 imageToReturn
= GetOwner()->GetImage();
659 if (!imageToReturn
) {
660 return NS_ERROR_FAILURE
;
663 imageToReturn
.swap(*aImage
);
669 imgRequestProxy::GetProducerId(uint32_t* aId
) {
670 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
672 nsCOMPtr
<imgIContainer
> image
;
673 nsresult rv
= GetImage(getter_AddRefs(image
));
674 if (NS_SUCCEEDED(rv
)) {
675 *aId
= image
->GetProducerId();
677 *aId
= layers::kContainerProducerID_Invalid
;
684 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
685 if (IsValidating()) {
686 // We are currently validating the image, and so our status could revert if
687 // we discard the cache. We should also be deferring notifications, such
688 // that the caller will be notified when validation completes. Rather than
689 // risk misleading the caller, return nothing.
690 *aStatus
= imgIRequest::STATUS_NONE
;
692 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
693 *aStatus
= progressTracker
->GetImageStatus();
700 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
702 return NS_ERROR_FAILURE
;
705 *aStatus
= GetOwner()->GetImageErrorCode();
711 imgRequestProxy::GetURI(nsIURI
** aURI
) {
712 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
713 nsCOMPtr
<nsIURI
> uri
= mURI
;
718 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
720 return NS_ERROR_FAILURE
;
723 return GetOwner()->GetFinalURI(aURI
);
727 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
728 *aObserver
= mListener
;
729 NS_IF_ADDREF(*aObserver
);
734 imgRequestProxy::GetMimeType(char** aMimeType
) {
736 return NS_ERROR_FAILURE
;
739 const char* type
= GetOwner()->GetMimeType();
741 return NS_ERROR_FAILURE
;
744 *aMimeType
= NS_xstrdup(type
);
749 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
750 return new imgRequestProxy();
754 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
755 imgIRequest
** aClone
) {
757 imgRequestProxy
* proxy
;
758 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
763 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
764 Document
* aLoadingDocument
,
765 imgRequestProxy
** aClone
) {
766 return PerformClone(aObserver
, aLoadingDocument
,
767 /* aSyncNotify */ true, aClone
);
770 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
771 Document
* aLoadingDocument
,
772 imgRequestProxy
** aClone
) {
773 return PerformClone(aObserver
, aLoadingDocument
,
774 /* aSyncNotify */ false, aClone
);
777 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
778 Document
* aLoadingDocument
,
780 imgRequestProxy
** aClone
) {
781 MOZ_ASSERT(aClone
, "Null out param");
783 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
786 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
788 nsCOMPtr
<nsILoadGroup
> loadGroup
;
789 if (aLoadingDocument
) {
790 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
793 // It is important to call |SetLoadFlags()| before calling |Init()| because
794 // |Init()| adds the request to the loadgroup.
795 // When a request is added to a loadgroup, its load flags are merged
796 // with the load flags of the loadgroup.
797 // XXXldb That's not true anymore. Stuff from imgLoader adds the
798 // request to the loadgroup.
799 clone
->SetLoadFlags(mLoadFlags
);
800 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
806 // Assign to *aClone before calling Notify so that if the caller expects to
807 // only be notified for requests it's already holding pointers to it won't be
809 NS_ADDREF(*aClone
= clone
);
811 imgCacheValidator
* validator
= GetValidator();
813 // Note that if we have a validator, we don't want to issue notifications at
814 // here because we want to defer until that completes. AddProxy will add us
815 // to the load group; we cannot avoid that in this case, because we don't
816 // know when the validation will complete, and if it will cause us to
817 // discard our cached state anyways. We are probably already blocked by the
818 // original LoadImage(WithChannel) request in any event.
819 clone
->MarkValidating();
820 validator
->AddProxy(clone
);
822 // We only want to add the request to the load group of the owning document
823 // if it is still in progress. Some callers cannot handle a supurious load
824 // group removal (e.g. print preview) so we must be careful. On the other
825 // hand, if after cloning, the original request proxy is cancelled /
826 // destroyed, we need to ensure that any clones still block the load group
827 // if it is incomplete.
828 bool addToLoadGroup
= mIsInLoadGroup
;
829 if (!addToLoadGroup
) {
830 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
832 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
835 if (addToLoadGroup
) {
836 clone
->AddToLoadGroup();
840 // This is wrong!!! We need to notify asynchronously, but there's code
841 // that assumes that we don't. This will be fixed in bug 580466. Note that
842 // if we have a validator, we won't issue notifications anyways because
843 // they are deferred, so there is no point in requesting.
844 clone
->mForceDispatchLoadGroup
= true;
845 clone
->SyncNotifyListener();
846 clone
->mForceDispatchLoadGroup
= false;
848 // Without a validator, we can request asynchronous notifications
849 // immediately. If there was a validator, this would override the deferral
850 // and that would be incorrect.
851 clone
->NotifyListener();
859 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
861 return NS_ERROR_FAILURE
;
864 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
865 principal
.forget(aPrincipal
);
870 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
871 *aHadCrossOriginRedirects
= false;
873 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
875 bool allRedirectsSameOrigin
= false;
876 *aHadCrossOriginRedirects
=
878 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
879 !allRedirectsSameOrigin
;
886 imgRequestProxy::GetMultipart(bool* aMultipart
) {
888 return NS_ERROR_FAILURE
;
891 *aMultipart
= GetOwner()->GetMultipart();
897 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
899 return NS_ERROR_FAILURE
;
902 *aCorsMode
= GetOwner()->GetCORSMode();
908 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
909 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
910 GetOwner()->BoostPriority(aCategory
);
914 /** nsISupportsPriority methods **/
917 imgRequestProxy::GetPriority(int32_t* priority
) {
918 NS_ENSURE_STATE(GetOwner());
919 *priority
= GetOwner()->Priority();
924 imgRequestProxy::SetPriority(int32_t priority
) {
925 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
926 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
931 imgRequestProxy::AdjustPriority(int32_t priority
) {
932 // We don't require |!mCanceled| here. This may be called even if we're
933 // cancelled, because it's invoked as part of the process of removing an image
934 // from the load group.
935 NS_ENSURE_STATE(GetOwner());
936 GetOwner()->AdjustPriority(this, priority
);
940 static const char* NotificationTypeToString(int32_t aType
) {
942 case imgINotificationObserver::SIZE_AVAILABLE
:
943 return "SIZE_AVAILABLE";
944 case imgINotificationObserver::FRAME_UPDATE
:
945 return "FRAME_UPDATE";
946 case imgINotificationObserver::FRAME_COMPLETE
:
947 return "FRAME_COMPLETE";
948 case imgINotificationObserver::LOAD_COMPLETE
:
949 return "LOAD_COMPLETE";
950 case imgINotificationObserver::DECODE_COMPLETE
:
951 return "DECODE_COMPLETE";
952 case imgINotificationObserver::DISCARD
:
954 case imgINotificationObserver::UNLOCKED_DRAW
:
955 return "UNLOCKED_DRAW";
956 case imgINotificationObserver::IS_ANIMATED
:
957 return "IS_ANIMATED";
958 case imgINotificationObserver::HAS_TRANSPARENCY
:
959 return "HAS_TRANSPARENCY";
961 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
962 return "(unknown notification)";
966 void imgRequestProxy::Notify(int32_t aType
,
967 const mozilla::gfx::IntRect
* aRect
) {
968 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
969 "Should call OnLoadComplete");
971 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
972 NotificationTypeToString(aType
));
974 if (!mListener
|| mCanceled
) {
978 // Make sure the listener stays alive while we notify.
979 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
981 listener
->Notify(this, aType
, aRect
);
984 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
985 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
987 // There's all sorts of stuff here that could kill us (the OnStopRequest call
988 // on the listener, the removal from the loadgroup, the release of the
989 // listener, etc). Don't let them do it.
990 RefPtr
<imgRequestProxy
> self(this);
992 if (mListener
&& !mCanceled
) {
993 // Hold a ref to the listener while we call it, just in case.
994 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
995 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
998 // If we're expecting more data from a multipart channel, re-add ourself
999 // to the loadgroup so that the document doesn't lose track of the load.
1000 // If the request is already a background request and there's more data
1001 // coming, we can just leave the request in the loadgroup as-is.
1002 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1004 RemoveFromLoadGroup();
1006 nsresult errorCode
= NS_OK
;
1007 // if the load is cross origin without CORS, or the CORS access is
1008 // rejected, always fire load event to avoid leaking site information for
1009 // <link rel=preload>.
1010 // XXXedgar, currently we don't do the same thing for <img>.
1011 imgRequest
* request
= GetOwner();
1012 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1013 request
->IsCrossSiteNoCORSRequest())) {
1014 uint32_t status
= imgIRequest::STATUS_NONE
;
1015 GetImageStatus(&status
);
1016 if (status
& imgIRequest::STATUS_ERROR
) {
1017 errorCode
= NS_ERROR_FAILURE
;
1020 NotifyStop(errorCode
);
1022 // More data is coming, so change the request to be a background request
1023 // and put it back in the loadgroup.
1024 MoveToBackgroundInLoadGroup();
1028 if (mListenerIsStrongRef
&& aLastPart
) {
1029 MOZ_ASSERT(mListener
, "How did that happen?");
1030 // Drop our strong ref to the listener now that we're done with
1031 // everything. Note that this can cancel us and other fun things
1032 // like that. Don't add anything in this method after this point.
1033 imgINotificationObserver
* obs
= mListener
;
1034 mListenerIsStrongRef
= false;
1039 void imgRequestProxy::NullOutListener() {
1040 // If we have animation consumers, then they don't matter anymore
1042 ClearAnimationConsumers();
1045 if (mListenerIsStrongRef
) {
1046 // Releasing could do weird reentery stuff, so just play it super-safe
1047 nsCOMPtr
<imgINotificationObserver
> obs
;
1048 obs
.swap(mListener
);
1049 mListenerIsStrongRef
= false;
1051 mListener
= nullptr;
1056 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1057 RefPtr
<imgRequestProxy
> proxy
=
1058 GetStaticRequest(static_cast<Document
*>(nullptr));
1059 if (proxy
!= this) {
1060 RefPtr
<Image
> image
= GetImage();
1061 if (image
&& image
->HasError()) {
1062 // image/test/unit/test_async_notification_404.js needs this, but ideally
1063 // this special case can be removed from the scripted codepath.
1064 return NS_ERROR_FAILURE
;
1067 proxy
.forget(aReturn
);
1071 already_AddRefed
<imgRequestProxy
> imgRequestProxy::GetStaticRequest(
1072 Document
* aLoadingDocument
) {
1073 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument
||
1074 aLoadingDocument
->IsStaticDocument());
1075 RefPtr
<Image
> image
= GetImage();
1078 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1079 // Early exit - we're not animated, so we don't have to do anything.
1080 return do_AddRef(this);
1083 // We are animated. We need to create a frozen version of this image.
1084 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1086 // Create a static imgRequestProxy with our new extracted frame.
1087 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1088 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1089 bool hadCrossOriginRedirects
= true;
1090 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1091 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1092 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1093 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1095 return req
.forget();
1098 void imgRequestProxy::NotifyListener() {
1099 // It would be nice to notify the observer directly in the status tracker
1100 // instead of through the proxy, but there are several places we do extra
1101 // processing when we receive notifications (like OnStopRequest()), and we
1102 // need to check mCanceled everywhere too.
1104 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1106 // Send the notifications to our listener asynchronously.
1107 progressTracker
->Notify(this);
1109 // We don't have an imgRequest, so we can only notify the clone of our
1110 // current state, but we still have to do that asynchronously.
1111 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1112 progressTracker
->NotifyCurrentState(this);
1116 void imgRequestProxy::SyncNotifyListener() {
1117 // It would be nice to notify the observer directly in the status tracker
1118 // instead of through the proxy, but there are several places we do extra
1119 // processing when we receive notifications (like OnStopRequest()), and we
1120 // need to check mCanceled everywhere too.
1122 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1123 progressTracker
->SyncNotify(this);
1126 void imgRequestProxy::SetHasImage() {
1127 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1128 MOZ_ASSERT(progressTracker
);
1129 RefPtr
<Image
> image
= progressTracker
->GetImage();
1132 // Force any private status related to the owner to reflect
1133 // the presence of an image;
1134 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1136 // Apply any locks we have
1137 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1141 // Apply any animation consumers we have
1142 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1143 image
->IncrementAnimationConsumers();
1147 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1148 return mBehaviour
->GetProgressTracker();
1151 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1152 return mBehaviour
->GetImage();
1155 bool RequestBehaviour::HasImage() const {
1156 if (!mOwnerHasImage
) {
1159 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1160 return progressTracker
? progressTracker
->HasImage() : false;
1163 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1165 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1167 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1168 imgRequest
* owner
= GetOwner();
1172 return owner
->GetValidator();
1175 nsITimedChannel
* imgRequestProxy::TimedChannel() {
1179 return GetOwner()->GetTimedChannel();
1182 ////////////////// imgRequestProxyStatic methods
1184 class StaticBehaviour
: public ProxyBehaviour
{
1186 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1188 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1189 RefPtr
<mozilla::image::Image
> image
= mImage
;
1190 return image
.forget();
1193 bool HasImage() const override
{ return mImage
; }
1195 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1196 return mImage
->GetProgressTracker();
1199 imgRequest
* GetOwner() const override
{ return nullptr; }
1201 void SetOwner(imgRequest
* aOwner
) override
{
1203 "We shouldn't be giving static requests a non-null owner.");
1207 // Our image. We have to hold a strong reference here, because that's normally
1208 // the job of the underlying request.
1209 RefPtr
<mozilla::image::Image
> mImage
;
1212 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1213 nsIPrincipal
* aPrincipal
,
1214 bool aHadCrossOriginRedirects
)
1215 : mPrincipal(aPrincipal
),
1216 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1217 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1221 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1223 return NS_ERROR_FAILURE
;
1226 NS_ADDREF(*aPrincipal
= mPrincipal
);
1232 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1233 bool* aHadCrossOriginRedirects
) {
1234 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1238 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1239 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1240 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1241 bool hadCrossOriginRedirects
= true;
1242 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1243 RefPtr
<mozilla::image::Image
> image
= GetImage();
1244 return new imgRequestProxyStatic(image
, currentPrincipal
,
1245 hadCrossOriginRedirects
);