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(CreateRenderBlockingRunnable(std::move(aEvent
)),
273 return NS_DispatchToMainThread(
274 CreateRenderBlockingRunnable(std::move(aEvent
)));
277 void imgRequestProxy::AddToOwner(Document
* aLoadingDocument
) {
278 // An imgRequestProxy can be initialized with neither a listener nor a
279 // document. The caller could follow up later by cloning the canonical
280 // imgRequestProxy with the actual listener. This is possible because
281 // imgLoader::LoadImage does not require a valid listener to be provided.
283 // Without a listener, we don't need to set our scheduler group, because
284 // we have nothing to signal. However if we were told what document this
285 // is for, it is likely that future listeners will belong to the same
288 // With a listener, we always need to update our scheduler group. A null
289 // scheduler group is valid with or without a document, but that means
290 // we will use the most generic event target possible on dispatch.
291 if (aLoadingDocument
) {
292 RefPtr
<mozilla::dom::DocGroup
> docGroup
= aLoadingDocument
->GetDocGroup();
294 mEventTarget
= docGroup
->EventTargetFor(mozilla::TaskCategory::Other
);
295 MOZ_ASSERT(mEventTarget
);
299 if (mListener
&& !mEventTarget
) {
300 mEventTarget
= do_GetMainThread();
303 imgRequest
* owner
= GetOwner();
308 owner
->AddProxy(this);
311 void imgRequestProxy::RemoveFromOwner(nsresult aStatus
) {
312 imgRequest
* owner
= GetOwner();
315 imgCacheValidator
* validator
= owner
->GetValidator();
316 MOZ_ASSERT(validator
);
317 validator
->RemoveProxy(this);
321 owner
->RemoveProxy(this, aStatus
);
325 void imgRequestProxy::AddToLoadGroup() {
326 NS_ASSERTION(!mIsInLoadGroup
, "Whaa, we're already in the loadgroup!");
327 MOZ_ASSERT(!mForceDispatchLoadGroup
);
329 /* While in theory there could be a dispatch outstanding to remove this
330 request from the load group, in practice we only add to the load group
331 (when previously not in a load group) at initialization. */
332 if (!mIsInLoadGroup
&& mLoadGroup
) {
333 LOG_FUNC(gImgLog
, "imgRequestProxy::AddToLoadGroup");
334 mLoadGroup
->AddRequest(this, nullptr);
335 mIsInLoadGroup
= true;
339 void imgRequestProxy::RemoveFromLoadGroup() {
340 if (!mIsInLoadGroup
|| !mLoadGroup
) {
344 /* Sometimes we may not be able to remove ourselves from the load group in
345 the current context. This is because our listeners are not re-entrant (e.g.
346 we are in the middle of CancelAndForgetObserver or SyncClone). */
347 if (mForceDispatchLoadGroup
) {
348 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
350 /* We take away the load group from the request temporarily; this prevents
351 additional dispatches via RemoveFromLoadGroup occurring, as well as
352 MoveToBackgroundInLoadGroup from removing and readding. This is safe
353 because we know that once we get here, blocking the load group at all is
355 mIsInLoadGroup
= false;
356 nsCOMPtr
<nsILoadGroup
> loadGroup
= std::move(mLoadGroup
);
357 RefPtr
<imgRequestProxy
> self(this);
358 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
359 "imgRequestProxy::RemoveFromLoadGroup", [self
, loadGroup
]() -> void {
360 loadGroup
->RemoveRequest(self
, nullptr, NS_OK
);
365 LOG_FUNC(gImgLog
, "imgRequestProxy::RemoveFromLoadGroup");
367 /* calling RemoveFromLoadGroup may cause the document to finish
368 loading, which could result in our death. We need to make sure
369 that we stay alive long enough to fight another battle... at
370 least until we exit this function. */
371 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
372 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
373 mLoadGroup
= nullptr;
374 mIsInLoadGroup
= false;
377 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
378 /* Even if we are still in the load group, we may have taken away the load
379 group reference itself because we are in the process of leaving the group.
380 In that case, there is no need to background the request. */
385 /* There is no need to dispatch if we only need to add ourselves to the load
386 group without removal. It is the removal which causes the problematic
387 callbacks (see RemoveFromLoadGroup). */
388 if (mIsInLoadGroup
&& mForceDispatchLoadGroup
) {
390 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
392 RefPtr
<imgRequestProxy
> self(this);
393 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
394 "imgRequestProxy::MoveToBackgroundInLoadGroup",
395 [self
]() -> void { self
->MoveToBackgroundInLoadGroup(); }));
399 LOG_FUNC(gImgLog
, "imgRequestProxy::MoveToBackgroundInLoadGroup");
400 nsCOMPtr
<imgIRequest
> kungFuDeathGrip(this);
401 if (mIsInLoadGroup
) {
402 mLoadGroup
->RemoveRequest(this, nullptr, NS_OK
);
405 mLoadFlags
|= nsIRequest::LOAD_BACKGROUND
;
406 mLoadGroup
->AddRequest(this, nullptr);
409 /** nsIRequest / imgIRequest methods **/
412 imgRequestProxy::GetName(nsACString
& aName
) {
416 mURI
->GetSpec(aName
);
423 imgRequestProxy::IsPending(bool* _retval
) { return NS_ERROR_NOT_IMPLEMENTED
; }
426 imgRequestProxy::GetStatus(nsresult
* aStatus
) {
427 return NS_ERROR_NOT_IMPLEMENTED
;
431 imgRequestProxy::Cancel(nsresult status
) {
433 return NS_ERROR_FAILURE
;
436 LOG_SCOPE(gImgLog
, "imgRequestProxy::Cancel");
440 nsCOMPtr
<nsIRunnable
> ev
= new imgCancelRunnable(this, status
);
441 return DispatchWithTargetIfAvailable(ev
.forget());
444 void imgRequestProxy::DoCancel(nsresult status
) {
445 RemoveFromOwner(status
);
446 RemoveFromLoadGroup();
451 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus
) {
452 // If mCanceled is true but mListener is non-null, that means
453 // someone called Cancel() on us but the imgCancelRunnable is still
454 // pending. We still need to null out mListener before returning
455 // from this function in this case. That means we want to do the
456 // RemoveProxy call right now, because we need to deliver the
458 if (mCanceled
&& !mListener
) {
459 return NS_ERROR_FAILURE
;
462 LOG_SCOPE(gImgLog
, "imgRequestProxy::CancelAndForgetObserver");
465 mForceDispatchLoadGroup
= true;
466 RemoveFromOwner(aStatus
);
467 RemoveFromLoadGroup();
468 mForceDispatchLoadGroup
= false;
476 imgRequestProxy::StartDecoding(uint32_t aFlags
) {
477 // Flag this, so we know to request after validation if pending.
478 if (IsValidating()) {
479 mDecodeRequested
= true;
483 RefPtr
<Image
> image
= GetImage();
485 return image
->StartDecoding(aFlags
);
489 GetOwner()->StartDecoding();
495 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags
) {
496 // Flag this, so we know to request after validation if pending.
497 if (IsValidating()) {
498 mDecodeRequested
= true;
502 RefPtr
<Image
> image
= GetImage();
504 return image
->StartDecodingWithResult(aFlags
);
508 GetOwner()->StartDecoding();
514 imgIContainer::DecodeResult
imgRequestProxy::RequestDecodeWithResult(
516 if (IsValidating()) {
517 mDecodeRequested
= true;
518 return imgIContainer::DECODE_REQUESTED
;
521 RefPtr
<Image
> image
= GetImage();
523 return image
->RequestDecodeWithResult(aFlags
);
527 GetOwner()->StartDecoding();
530 return imgIContainer::DECODE_REQUESTED
;
534 imgRequestProxy::LockImage() {
536 RefPtr
<Image
> image
=
537 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
539 return image
->LockImage();
545 imgRequestProxy::UnlockImage() {
546 MOZ_ASSERT(mLockCount
> 0, "calling unlock but no locks!");
549 RefPtr
<Image
> image
=
550 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
552 return image
->UnlockImage();
558 imgRequestProxy::RequestDiscard() {
559 RefPtr
<Image
> image
= GetImage();
561 return image
->RequestDiscard();
567 imgRequestProxy::IncrementAnimationConsumers() {
568 mAnimationConsumers
++;
569 RefPtr
<Image
> image
=
570 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
572 image
->IncrementAnimationConsumers();
578 imgRequestProxy::DecrementAnimationConsumers() {
579 // We may get here if some responsible code called Increment,
580 // then called us, but we have meanwhile called ClearAnimationConsumers
581 // because we needed to get rid of them earlier (see
582 // imgRequest::RemoveProxy), and hence have nothing left to
583 // decrement. (In such a case we got rid of the animation consumers
584 // early, but not the observer.)
585 if (mAnimationConsumers
> 0) {
586 mAnimationConsumers
--;
587 RefPtr
<Image
> image
=
588 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
590 image
->DecrementAnimationConsumers();
596 void imgRequestProxy::ClearAnimationConsumers() {
597 while (mAnimationConsumers
> 0) {
598 DecrementAnimationConsumers();
603 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED
; }
606 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED
; }
609 imgRequestProxy::GetLoadGroup(nsILoadGroup
** loadGroup
) {
610 NS_IF_ADDREF(*loadGroup
= mLoadGroup
.get());
614 imgRequestProxy::SetLoadGroup(nsILoadGroup
* loadGroup
) {
615 if (loadGroup
!= mLoadGroup
) {
616 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
617 return NS_ERROR_NOT_IMPLEMENTED
;
623 imgRequestProxy::GetLoadFlags(nsLoadFlags
* flags
) {
628 imgRequestProxy::SetLoadFlags(nsLoadFlags flags
) {
634 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
635 return GetTRRModeImpl(aTRRMode
);
639 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
640 return SetTRRModeImpl(aTRRMode
);
643 /** imgIRequest methods **/
646 imgRequestProxy::GetImage(imgIContainer
** aImage
) {
647 NS_ENSURE_TRUE(aImage
, NS_ERROR_NULL_POINTER
);
648 // It's possible that our owner has an image but hasn't notified us of it -
649 // that'll happen if we get Canceled before the owner instantiates its image
650 // (because Canceling unregisters us as a listener on mOwner). If we're
651 // in that situation, just grab the image off of mOwner.
652 RefPtr
<Image
> image
= GetImage();
653 nsCOMPtr
<imgIContainer
> imageToReturn
;
655 imageToReturn
= image
;
657 if (!imageToReturn
&& GetOwner()) {
658 imageToReturn
= GetOwner()->GetImage();
660 if (!imageToReturn
) {
661 return NS_ERROR_FAILURE
;
664 imageToReturn
.swap(*aImage
);
670 imgRequestProxy::GetProviderId(uint32_t* aId
) {
671 NS_ENSURE_TRUE(aId
, NS_ERROR_NULL_POINTER
);
673 nsCOMPtr
<imgIContainer
> image
;
674 nsresult rv
= GetImage(getter_AddRefs(image
));
675 if (NS_SUCCEEDED(rv
)) {
676 *aId
= image
->GetProviderId();
685 imgRequestProxy::GetImageStatus(uint32_t* aStatus
) {
686 if (IsValidating()) {
687 // We are currently validating the image, and so our status could revert if
688 // we discard the cache. We should also be deferring notifications, such
689 // that the caller will be notified when validation completes. Rather than
690 // risk misleading the caller, return nothing.
691 *aStatus
= imgIRequest::STATUS_NONE
;
693 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
694 *aStatus
= progressTracker
->GetImageStatus();
701 imgRequestProxy::GetImageErrorCode(nsresult
* aStatus
) {
703 return NS_ERROR_FAILURE
;
706 *aStatus
= GetOwner()->GetImageErrorCode();
712 imgRequestProxy::GetURI(nsIURI
** aURI
) {
713 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
714 nsCOMPtr
<nsIURI
> uri
= mURI
;
719 nsresult
imgRequestProxy::GetFinalURI(nsIURI
** aURI
) {
721 return NS_ERROR_FAILURE
;
724 return GetOwner()->GetFinalURI(aURI
);
728 imgRequestProxy::GetNotificationObserver(imgINotificationObserver
** aObserver
) {
729 *aObserver
= mListener
;
730 NS_IF_ADDREF(*aObserver
);
735 imgRequestProxy::GetMimeType(char** aMimeType
) {
737 return NS_ERROR_FAILURE
;
740 const char* type
= GetOwner()->GetMimeType();
742 return NS_ERROR_FAILURE
;
745 *aMimeType
= NS_xstrdup(type
);
750 imgRequestProxy
* imgRequestProxy::NewClonedProxy() {
751 return new imgRequestProxy();
755 imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
756 imgIRequest
** aClone
) {
758 imgRequestProxy
* proxy
;
759 result
= PerformClone(aObserver
, nullptr, /* aSyncNotify */ true, &proxy
);
764 nsresult
imgRequestProxy::SyncClone(imgINotificationObserver
* aObserver
,
765 Document
* aLoadingDocument
,
766 imgRequestProxy
** aClone
) {
767 return PerformClone(aObserver
, aLoadingDocument
,
768 /* aSyncNotify */ true, aClone
);
771 nsresult
imgRequestProxy::Clone(imgINotificationObserver
* aObserver
,
772 Document
* aLoadingDocument
,
773 imgRequestProxy
** aClone
) {
774 return PerformClone(aObserver
, aLoadingDocument
,
775 /* aSyncNotify */ false, aClone
);
778 nsresult
imgRequestProxy::PerformClone(imgINotificationObserver
* aObserver
,
779 Document
* aLoadingDocument
,
781 imgRequestProxy
** aClone
) {
782 MOZ_ASSERT(aClone
, "Null out param");
784 LOG_SCOPE(gImgLog
, "imgRequestProxy::Clone");
787 RefPtr
<imgRequestProxy
> clone
= NewClonedProxy();
789 nsCOMPtr
<nsILoadGroup
> loadGroup
;
790 if (aLoadingDocument
) {
791 loadGroup
= aLoadingDocument
->GetDocumentLoadGroup();
794 // It is important to call |SetLoadFlags()| before calling |Init()| because
795 // |Init()| adds the request to the loadgroup.
796 // When a request is added to a loadgroup, its load flags are merged
797 // with the load flags of the loadgroup.
798 // XXXldb That's not true anymore. Stuff from imgLoader adds the
799 // request to the loadgroup.
800 clone
->SetLoadFlags(mLoadFlags
);
801 nsresult rv
= clone
->Init(mBehaviour
->GetOwner(), loadGroup
, aLoadingDocument
,
807 // Assign to *aClone before calling Notify so that if the caller expects to
808 // only be notified for requests it's already holding pointers to it won't be
810 NS_ADDREF(*aClone
= clone
);
812 imgCacheValidator
* validator
= GetValidator();
814 // Note that if we have a validator, we don't want to issue notifications at
815 // here because we want to defer until that completes. AddProxy will add us
816 // to the load group; we cannot avoid that in this case, because we don't
817 // know when the validation will complete, and if it will cause us to
818 // discard our cached state anyways. We are probably already blocked by the
819 // original LoadImage(WithChannel) request in any event.
820 clone
->MarkValidating();
821 validator
->AddProxy(clone
);
823 // We only want to add the request to the load group of the owning document
824 // if it is still in progress. Some callers cannot handle a supurious load
825 // group removal (e.g. print preview) so we must be careful. On the other
826 // hand, if after cloning, the original request proxy is cancelled /
827 // destroyed, we need to ensure that any clones still block the load group
828 // if it is incomplete.
829 bool addToLoadGroup
= mIsInLoadGroup
;
830 if (!addToLoadGroup
) {
831 RefPtr
<ProgressTracker
> tracker
= clone
->GetProgressTracker();
833 tracker
&& !(tracker
->GetProgress() & FLAG_LOAD_COMPLETE
);
836 if (addToLoadGroup
) {
837 clone
->AddToLoadGroup();
841 // This is wrong!!! We need to notify asynchronously, but there's code
842 // that assumes that we don't. This will be fixed in bug 580466. Note that
843 // if we have a validator, we won't issue notifications anyways because
844 // they are deferred, so there is no point in requesting.
845 clone
->mForceDispatchLoadGroup
= true;
846 clone
->SyncNotifyListener();
847 clone
->mForceDispatchLoadGroup
= false;
849 // Without a validator, we can request asynchronous notifications
850 // immediately. If there was a validator, this would override the deferral
851 // and that would be incorrect.
852 clone
->NotifyListener();
860 imgRequestProxy::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
862 return NS_ERROR_FAILURE
;
865 nsCOMPtr
<nsIPrincipal
> principal
= GetOwner()->GetPrincipal();
866 principal
.forget(aPrincipal
);
871 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects
) {
872 *aHadCrossOriginRedirects
= false;
874 nsCOMPtr
<nsITimedChannel
> timedChannel
= TimedChannel();
876 bool allRedirectsSameOrigin
= false;
877 *aHadCrossOriginRedirects
=
879 timedChannel
->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin
)) &&
880 !allRedirectsSameOrigin
;
887 imgRequestProxy::GetMultipart(bool* aMultipart
) {
889 return NS_ERROR_FAILURE
;
892 *aMultipart
= GetOwner()->GetMultipart();
898 imgRequestProxy::GetCORSMode(int32_t* aCorsMode
) {
900 return NS_ERROR_FAILURE
;
903 *aCorsMode
= GetOwner()->GetCORSMode();
909 imgRequestProxy::BoostPriority(uint32_t aCategory
) {
910 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
911 GetOwner()->BoostPriority(aCategory
);
915 /** nsISupportsPriority methods **/
918 imgRequestProxy::GetPriority(int32_t* priority
) {
919 NS_ENSURE_STATE(GetOwner());
920 *priority
= GetOwner()->Priority();
925 imgRequestProxy::SetPriority(int32_t priority
) {
926 NS_ENSURE_STATE(GetOwner() && !mCanceled
);
927 GetOwner()->AdjustPriority(this, priority
- GetOwner()->Priority());
932 imgRequestProxy::AdjustPriority(int32_t priority
) {
933 // We don't require |!mCanceled| here. This may be called even if we're
934 // cancelled, because it's invoked as part of the process of removing an image
935 // from the load group.
936 NS_ENSURE_STATE(GetOwner());
937 GetOwner()->AdjustPriority(this, priority
);
941 static const char* NotificationTypeToString(int32_t aType
) {
943 case imgINotificationObserver::SIZE_AVAILABLE
:
944 return "SIZE_AVAILABLE";
945 case imgINotificationObserver::FRAME_UPDATE
:
946 return "FRAME_UPDATE";
947 case imgINotificationObserver::FRAME_COMPLETE
:
948 return "FRAME_COMPLETE";
949 case imgINotificationObserver::LOAD_COMPLETE
:
950 return "LOAD_COMPLETE";
951 case imgINotificationObserver::DECODE_COMPLETE
:
952 return "DECODE_COMPLETE";
953 case imgINotificationObserver::DISCARD
:
955 case imgINotificationObserver::UNLOCKED_DRAW
:
956 return "UNLOCKED_DRAW";
957 case imgINotificationObserver::IS_ANIMATED
:
958 return "IS_ANIMATED";
959 case imgINotificationObserver::HAS_TRANSPARENCY
:
960 return "HAS_TRANSPARENCY";
962 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
963 return "(unknown notification)";
967 void imgRequestProxy::Notify(int32_t aType
,
968 const mozilla::gfx::IntRect
* aRect
) {
969 MOZ_ASSERT(aType
!= imgINotificationObserver::LOAD_COMPLETE
,
970 "Should call OnLoadComplete");
972 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::Notify", "type",
973 NotificationTypeToString(aType
));
975 if (!mListener
|| mCanceled
) {
979 // Make sure the listener stays alive while we notify.
980 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
982 listener
->Notify(this, aType
, aRect
);
985 void imgRequestProxy::OnLoadComplete(bool aLastPart
) {
986 LOG_FUNC_WITH_PARAM(gImgLog
, "imgRequestProxy::OnLoadComplete", "uri", mURI
);
988 // There's all sorts of stuff here that could kill us (the OnStopRequest call
989 // on the listener, the removal from the loadgroup, the release of the
990 // listener, etc). Don't let them do it.
991 RefPtr
<imgRequestProxy
> self(this);
993 if (mListener
&& !mCanceled
) {
994 // Hold a ref to the listener while we call it, just in case.
995 nsCOMPtr
<imgINotificationObserver
> listener(mListener
);
996 listener
->Notify(this, imgINotificationObserver::LOAD_COMPLETE
, nullptr);
999 // If we're expecting more data from a multipart channel, re-add ourself
1000 // to the loadgroup so that the document doesn't lose track of the load.
1001 // If the request is already a background request and there's more data
1002 // coming, we can just leave the request in the loadgroup as-is.
1003 if (aLastPart
|| (mLoadFlags
& nsIRequest::LOAD_BACKGROUND
) == 0) {
1005 RemoveFromLoadGroup();
1007 nsresult errorCode
= NS_OK
;
1008 // if the load is cross origin without CORS, or the CORS access is
1009 // rejected, always fire load event to avoid leaking site information for
1010 // <link rel=preload>.
1011 // XXXedgar, currently we don't do the same thing for <img>.
1012 imgRequest
* request
= GetOwner();
1013 if (!request
|| !(request
->IsDeniedCrossSiteCORSRequest() ||
1014 request
->IsCrossSiteNoCORSRequest())) {
1015 uint32_t status
= imgIRequest::STATUS_NONE
;
1016 GetImageStatus(&status
);
1017 if (status
& imgIRequest::STATUS_ERROR
) {
1018 errorCode
= NS_ERROR_FAILURE
;
1021 NotifyStop(errorCode
);
1023 // More data is coming, so change the request to be a background request
1024 // and put it back in the loadgroup.
1025 MoveToBackgroundInLoadGroup();
1029 if (mListenerIsStrongRef
&& aLastPart
) {
1030 MOZ_ASSERT(mListener
, "How did that happen?");
1031 // Drop our strong ref to the listener now that we're done with
1032 // everything. Note that this can cancel us and other fun things
1033 // like that. Don't add anything in this method after this point.
1034 imgINotificationObserver
* obs
= mListener
;
1035 mListenerIsStrongRef
= false;
1040 void imgRequestProxy::NullOutListener() {
1041 // If we have animation consumers, then they don't matter anymore
1043 ClearAnimationConsumers();
1046 if (mListenerIsStrongRef
) {
1047 // Releasing could do weird reentery stuff, so just play it super-safe
1048 nsCOMPtr
<imgINotificationObserver
> obs
;
1049 obs
.swap(mListener
);
1050 mListenerIsStrongRef
= false;
1052 mListener
= nullptr;
1057 imgRequestProxy::GetStaticRequest(imgIRequest
** aReturn
) {
1058 RefPtr
<imgRequestProxy
> proxy
=
1059 GetStaticRequest(static_cast<Document
*>(nullptr));
1060 if (proxy
!= this) {
1061 RefPtr
<Image
> image
= GetImage();
1062 if (image
&& image
->HasError()) {
1063 // image/test/unit/test_async_notification_404.js needs this, but ideally
1064 // this special case can be removed from the scripted codepath.
1065 return NS_ERROR_FAILURE
;
1068 proxy
.forget(aReturn
);
1072 already_AddRefed
<imgRequestProxy
> imgRequestProxy::GetStaticRequest(
1073 Document
* aLoadingDocument
) {
1074 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument
||
1075 aLoadingDocument
->IsStaticDocument());
1076 RefPtr
<Image
> image
= GetImage();
1079 if (!image
|| (NS_SUCCEEDED(image
->GetAnimated(&animated
)) && !animated
)) {
1080 // Early exit - we're not animated, so we don't have to do anything.
1081 return do_AddRef(this);
1084 // We are animated. We need to create a frozen version of this image.
1085 RefPtr
<Image
> frozenImage
= ImageOps::Freeze(image
);
1087 // Create a static imgRequestProxy with our new extracted frame.
1088 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1089 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1090 bool hadCrossOriginRedirects
= true;
1091 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1092 RefPtr
<imgRequestProxy
> req
= new imgRequestProxyStatic(
1093 frozenImage
, currentPrincipal
, hadCrossOriginRedirects
);
1094 req
->Init(nullptr, nullptr, aLoadingDocument
, mURI
, nullptr);
1096 return req
.forget();
1099 void imgRequestProxy::NotifyListener() {
1100 // It would be nice to notify the observer directly in the status tracker
1101 // instead of through the proxy, but there are several places we do extra
1102 // processing when we receive notifications (like OnStopRequest()), and we
1103 // need to check mCanceled everywhere too.
1105 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1107 // Send the notifications to our listener asynchronously.
1108 progressTracker
->Notify(this);
1110 // We don't have an imgRequest, so we can only notify the clone of our
1111 // current state, but we still have to do that asynchronously.
1112 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1113 progressTracker
->NotifyCurrentState(this);
1117 void imgRequestProxy::SyncNotifyListener() {
1118 // It would be nice to notify the observer directly in the status tracker
1119 // instead of through the proxy, but there are several places we do extra
1120 // processing when we receive notifications (like OnStopRequest()), and we
1121 // need to check mCanceled everywhere too.
1123 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1124 progressTracker
->SyncNotify(this);
1127 void imgRequestProxy::SetHasImage() {
1128 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1129 MOZ_ASSERT(progressTracker
);
1130 RefPtr
<Image
> image
= progressTracker
->GetImage();
1133 // Force any private status related to the owner to reflect
1134 // the presence of an image;
1135 mBehaviour
->SetOwner(mBehaviour
->GetOwner());
1137 // Apply any locks we have
1138 for (uint32_t i
= 0; i
< mLockCount
; ++i
) {
1142 // Apply any animation consumers we have
1143 for (uint32_t i
= 0; i
< mAnimationConsumers
; i
++) {
1144 image
->IncrementAnimationConsumers();
1148 already_AddRefed
<ProgressTracker
> imgRequestProxy::GetProgressTracker() const {
1149 return mBehaviour
->GetProgressTracker();
1152 already_AddRefed
<mozilla::image::Image
> imgRequestProxy::GetImage() const {
1153 return mBehaviour
->GetImage();
1156 bool RequestBehaviour::HasImage() const {
1157 if (!mOwnerHasImage
) {
1160 RefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
1161 return progressTracker
? progressTracker
->HasImage() : false;
1164 bool imgRequestProxy::HasImage() const { return mBehaviour
->HasImage(); }
1166 imgRequest
* imgRequestProxy::GetOwner() const { return mBehaviour
->GetOwner(); }
1168 imgCacheValidator
* imgRequestProxy::GetValidator() const {
1169 imgRequest
* owner
= GetOwner();
1173 return owner
->GetValidator();
1176 nsITimedChannel
* imgRequestProxy::TimedChannel() {
1180 return GetOwner()->GetTimedChannel();
1183 ////////////////// imgRequestProxyStatic methods
1185 class StaticBehaviour
: public ProxyBehaviour
{
1187 explicit StaticBehaviour(mozilla::image::Image
* aImage
) : mImage(aImage
) {}
1189 already_AddRefed
<mozilla::image::Image
> GetImage() const override
{
1190 RefPtr
<mozilla::image::Image
> image
= mImage
;
1191 return image
.forget();
1194 bool HasImage() const override
{ return mImage
; }
1196 already_AddRefed
<ProgressTracker
> GetProgressTracker() const override
{
1197 return mImage
->GetProgressTracker();
1200 imgRequest
* GetOwner() const override
{ return nullptr; }
1202 void SetOwner(imgRequest
* aOwner
) override
{
1204 "We shouldn't be giving static requests a non-null owner.");
1208 // Our image. We have to hold a strong reference here, because that's normally
1209 // the job of the underlying request.
1210 RefPtr
<mozilla::image::Image
> mImage
;
1213 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image
* aImage
,
1214 nsIPrincipal
* aPrincipal
,
1215 bool aHadCrossOriginRedirects
)
1216 : mPrincipal(aPrincipal
),
1217 mHadCrossOriginRedirects(aHadCrossOriginRedirects
) {
1218 mBehaviour
= mozilla::MakeUnique
<StaticBehaviour
>(aImage
);
1222 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal
** aPrincipal
) {
1224 return NS_ERROR_FAILURE
;
1227 NS_ADDREF(*aPrincipal
= mPrincipal
);
1233 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1234 bool* aHadCrossOriginRedirects
) {
1235 *aHadCrossOriginRedirects
= mHadCrossOriginRedirects
;
1239 imgRequestProxy
* imgRequestProxyStatic::NewClonedProxy() {
1240 nsCOMPtr
<nsIPrincipal
> currentPrincipal
;
1241 GetImagePrincipal(getter_AddRefs(currentPrincipal
));
1242 bool hadCrossOriginRedirects
= true;
1243 GetHadCrossOriginRedirects(&hadCrossOriginRedirects
);
1244 RefPtr
<mozilla::image::Image
> image
= GetImage();
1245 return new imgRequestProxyStatic(image
, currentPrincipal
,
1246 hadCrossOriginRedirects
);