Bug 1859742 [wpt PR 42597] - Update wpt metadata, a=testonly
[gecko.git] / image / imgRequestProxy.cpp
blob45140edd3eb64aab3ef06211ccbe3f02b0aa705b
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"
9 #include <utility>
11 #include "Image.h"
12 #include "ImageLogging.h"
13 #include "ImageOps.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"
21 #include "nsError.h"
23 using namespace mozilla;
24 using namespace mozilla::image;
26 // The split of imgRequestProxy and imgRequestProxyStatic means that
27 // certain overridden functions need to be usable in the destructor.
28 // Since virtual functions can't be used in that way, this class
29 // provides a behavioural trait for each class to use instead.
30 class ProxyBehaviour {
31 public:
32 virtual ~ProxyBehaviour() = default;
34 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
35 virtual bool HasImage() const = 0;
36 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
37 virtual imgRequest* GetOwner() const = 0;
38 virtual void SetOwner(imgRequest* aOwner) = 0;
41 class RequestBehaviour : public ProxyBehaviour {
42 public:
43 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
45 already_AddRefed<mozilla::image::Image> GetImage() const override;
46 bool HasImage() const override;
47 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
49 imgRequest* GetOwner() const override { return mOwner; }
51 void SetOwner(imgRequest* aOwner) override {
52 mOwner = aOwner;
54 if (mOwner) {
55 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
56 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
57 } else {
58 mOwnerHasImage = false;
62 private:
63 // We maintain the following invariant:
64 // The proxy is registered at most with a single imgRequest as an observer,
65 // and whenever it is, mOwner points to that object. This helps ensure that
66 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
67 // from whatever request it was registered with (if any). This, in turn,
68 // means that imgRequest::mObservers will not have any stale pointers in it.
69 RefPtr<imgRequest> mOwner;
71 bool mOwnerHasImage;
74 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
75 if (!mOwnerHasImage) {
76 return nullptr;
78 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
79 return progressTracker->GetImage();
82 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
83 // NOTE: It's possible that our mOwner has an Image that it didn't notify
84 // us about, if we were Canceled before its Image was constructed.
85 // (Canceling removes us as an observer, so mOwner has no way to notify us).
86 // That's why this method uses mOwner->GetProgressTracker() instead of just
87 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
88 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
89 return mOwner->GetProgressTracker();
92 NS_IMPL_ADDREF(imgRequestProxy)
93 NS_IMPL_RELEASE(imgRequestProxy)
95 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
100 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy)
101 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
102 NS_INTERFACE_MAP_END
104 imgRequestProxy::imgRequestProxy()
105 : mBehaviour(new RequestBehaviour),
106 mURI(nullptr),
107 mListener(nullptr),
108 mLoadFlags(nsIRequest::LOAD_NORMAL),
109 mLockCount(0),
110 mAnimationConsumers(0),
111 mCancelable(true),
112 mCanceled(false),
113 mIsInLoadGroup(false),
114 mForceDispatchLoadGroup(false),
115 mListenerIsStrongRef(false),
116 mDecodeRequested(false),
117 mPendingNotify(false),
118 mValidating(false),
119 mHadListener(false) {
120 /* member initializers and constructor code */
121 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
124 imgRequestProxy::~imgRequestProxy() {
125 /* destructor code */
126 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
127 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
129 ClearAnimationConsumers();
131 // Explicitly set mListener to null to ensure that the RemoveProxy
132 // call below can't send |this| to an arbitrary listener while |this|
133 // is being destroyed. This is all belt-and-suspenders in view of the
134 // above assert.
135 NullOutListener();
137 /* Call RemoveProxy with a successful status. This will keep the
138 channel, if still downloading data, from being canceled if 'this' is
139 the last observer. This allows the image to continue to download and
140 be cached even if no one is using it currently.
142 mCanceled = true;
143 RemoveFromOwner(NS_OK);
145 RemoveFromLoadGroup();
146 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
149 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
150 nsIURI* aURI,
151 imgINotificationObserver* aObserver) {
152 MOZ_ASSERT(!GetOwner() && !mListener,
153 "imgRequestProxy is already initialized");
155 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
157 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
159 mBehaviour->SetOwner(aOwner);
160 mListener = aObserver;
161 // Make sure to addref mListener before the AddToOwner call below, since
162 // that call might well want to release it if the imgRequest has
163 // already seen OnStopRequest.
164 if (mListener) {
165 mHadListener = true;
166 mListenerIsStrongRef = true;
167 NS_ADDREF(mListener);
169 mLoadGroup = aLoadGroup;
170 mURI = aURI;
172 // Note: AddToOwner won't send all the On* notifications immediately
173 AddToOwner();
175 return NS_OK;
178 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
179 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
181 if (mCanceled) {
182 // Ensure that this proxy has received all notifications to date
183 // before we clean it up when removing it from the old owner below.
184 SyncNotifyListener();
187 // If we're holding locks, unlock the old image.
188 // Note that UnlockImage decrements mLockCount each time it's called.
189 uint32_t oldLockCount = mLockCount;
190 while (mLockCount) {
191 UnlockImage();
194 // If we're holding animation requests, undo them.
195 uint32_t oldAnimationConsumers = mAnimationConsumers;
196 ClearAnimationConsumers();
198 GetOwner()->RemoveProxy(this, NS_OK);
200 mBehaviour->SetOwner(aNewOwner);
201 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
203 // If we were locked, apply the locks here
204 for (uint32_t i = 0; i < oldLockCount; i++) {
205 LockImage();
208 // If we had animation requests, restore them here. Note that we
209 // do this *after* RemoveProxy, which clears out animation consumers
210 // (see bug 601723).
211 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
212 IncrementAnimationConsumers();
215 AddToOwner();
216 return NS_OK;
219 NS_IMETHODIMP imgRequestProxy::GetTriggeringPrincipal(
220 nsIPrincipal** aTriggeringPrincipal) {
221 MOZ_ASSERT(GetOwner());
222 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
223 GetOwner()->GetTriggeringPrincipal();
224 triggeringPrincipal.forget(aTriggeringPrincipal);
225 return NS_OK;
228 void imgRequestProxy::MarkValidating() {
229 MOZ_ASSERT(GetValidator());
230 mValidating = true;
233 void imgRequestProxy::ClearValidating() {
234 MOZ_ASSERT(mValidating);
235 MOZ_ASSERT(!GetValidator());
236 mValidating = false;
238 // If we'd previously requested a synchronous decode, request a decode on the
239 // new image.
240 if (mDecodeRequested) {
241 mDecodeRequested = false;
242 StartDecoding(imgIContainer::FLAG_NONE);
246 bool imgRequestProxy::HasDecodedPixels() {
247 if (IsValidating()) {
248 return false;
251 RefPtr<Image> image = GetImage();
252 if (image) {
253 return image->HasDecodedPixels();
256 return false;
259 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
260 already_AddRefed<nsIRunnable> aEvent) {
261 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
262 return NS_DispatchToMainThread(
263 CreateRenderBlockingRunnable(std::move(aEvent)));
266 void imgRequestProxy::AddToOwner() {
267 imgRequest* owner = GetOwner();
268 if (!owner) {
269 return;
272 owner->AddProxy(this);
275 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
276 imgRequest* owner = GetOwner();
277 if (owner) {
278 if (mValidating) {
279 imgCacheValidator* validator = owner->GetValidator();
280 MOZ_ASSERT(validator);
281 validator->RemoveProxy(this);
282 mValidating = false;
285 owner->RemoveProxy(this, aStatus);
289 void imgRequestProxy::AddToLoadGroup() {
290 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
291 MOZ_ASSERT(!mForceDispatchLoadGroup);
293 /* While in theory there could be a dispatch outstanding to remove this
294 request from the load group, in practice we only add to the load group
295 (when previously not in a load group) at initialization. */
296 if (!mIsInLoadGroup && mLoadGroup) {
297 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
298 mLoadGroup->AddRequest(this, nullptr);
299 mIsInLoadGroup = true;
303 void imgRequestProxy::RemoveFromLoadGroup() {
304 if (!mIsInLoadGroup || !mLoadGroup) {
305 return;
308 /* Sometimes we may not be able to remove ourselves from the load group in
309 the current context. This is because our listeners are not re-entrant (e.g.
310 we are in the middle of CancelAndForgetObserver or SyncClone). */
311 if (mForceDispatchLoadGroup) {
312 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
314 /* We take away the load group from the request temporarily; this prevents
315 additional dispatches via RemoveFromLoadGroup occurring, as well as
316 MoveToBackgroundInLoadGroup from removing and readding. This is safe
317 because we know that once we get here, blocking the load group at all is
318 unnecessary. */
319 mIsInLoadGroup = false;
320 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
321 RefPtr<imgRequestProxy> self(this);
322 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
323 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
324 loadGroup->RemoveRequest(self, nullptr, NS_OK);
325 }));
326 return;
329 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
331 /* calling RemoveFromLoadGroup may cause the document to finish
332 loading, which could result in our death. We need to make sure
333 that we stay alive long enough to fight another battle... at
334 least until we exit this function. */
335 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
336 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
337 mLoadGroup = nullptr;
338 mIsInLoadGroup = false;
341 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
342 /* Even if we are still in the load group, we may have taken away the load
343 group reference itself because we are in the process of leaving the group.
344 In that case, there is no need to background the request. */
345 if (!mLoadGroup) {
346 return;
349 /* There is no need to dispatch if we only need to add ourselves to the load
350 group without removal. It is the removal which causes the problematic
351 callbacks (see RemoveFromLoadGroup). */
352 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
353 LOG_FUNC(gImgLog,
354 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
356 RefPtr<imgRequestProxy> self(this);
357 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
358 "imgRequestProxy::MoveToBackgroundInLoadGroup",
359 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
360 return;
363 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
364 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
365 if (mIsInLoadGroup) {
366 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
369 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
370 mLoadGroup->AddRequest(this, nullptr);
373 /** nsIRequest / imgIRequest methods **/
375 NS_IMETHODIMP
376 imgRequestProxy::GetName(nsACString& aName) {
377 aName.Truncate();
379 if (mURI) {
380 mURI->GetSpec(aName);
383 return NS_OK;
386 NS_IMETHODIMP
387 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
389 NS_IMETHODIMP
390 imgRequestProxy::GetStatus(nsresult* aStatus) {
391 return NS_ERROR_NOT_IMPLEMENTED;
394 NS_IMETHODIMP imgRequestProxy::SetCanceledReason(const nsACString& aReason) {
395 return SetCanceledReasonImpl(aReason);
398 NS_IMETHODIMP imgRequestProxy::GetCanceledReason(nsACString& aReason) {
399 return GetCanceledReasonImpl(aReason);
402 NS_IMETHODIMP imgRequestProxy::CancelWithReason(nsresult aStatus,
403 const nsACString& aReason) {
404 return CancelWithReasonImpl(aStatus, aReason);
407 void imgRequestProxy::SetCancelable(bool aCancelable) {
408 MOZ_ASSERT(NS_IsMainThread());
409 mCancelable = aCancelable;
412 NS_IMETHODIMP
413 imgRequestProxy::Cancel(nsresult status) {
414 if (mCanceled) {
415 return NS_ERROR_FAILURE;
418 if (NS_WARN_IF(!mCancelable)) {
419 return NS_ERROR_FAILURE;
422 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
424 mCanceled = true;
426 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
427 return DispatchWithTargetIfAvailable(ev.forget());
430 void imgRequestProxy::DoCancel(nsresult status) {
431 RemoveFromOwner(status);
432 RemoveFromLoadGroup();
433 NullOutListener();
436 NS_IMETHODIMP
437 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
438 // If mCanceled is true but mListener is non-null, that means
439 // someone called Cancel() on us but the imgCancelRunnable is still
440 // pending. We still need to null out mListener before returning
441 // from this function in this case. That means we want to do the
442 // RemoveProxy call right now, because we need to deliver the
443 // onStopRequest.
444 if (mCanceled && !mListener) {
445 return NS_ERROR_FAILURE;
448 if (NS_WARN_IF(!mCancelable)) {
449 MOZ_ASSERT(mCancelable,
450 "Shouldn't try to cancel non-cancelable requests via "
451 "CancelAndForgetObserver");
452 return NS_ERROR_FAILURE;
455 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
457 mCanceled = true;
458 mForceDispatchLoadGroup = true;
459 RemoveFromOwner(aStatus);
460 RemoveFromLoadGroup();
461 mForceDispatchLoadGroup = false;
463 NullOutListener();
465 return NS_OK;
468 NS_IMETHODIMP
469 imgRequestProxy::StartDecoding(uint32_t aFlags) {
470 // Flag this, so we know to request after validation if pending.
471 if (IsValidating()) {
472 mDecodeRequested = true;
473 return NS_OK;
476 RefPtr<Image> image = GetImage();
477 if (image) {
478 return image->StartDecoding(aFlags);
481 if (GetOwner()) {
482 GetOwner()->StartDecoding();
485 return NS_OK;
488 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
489 // Flag this, so we know to request after validation if pending.
490 if (IsValidating()) {
491 mDecodeRequested = true;
492 return false;
495 RefPtr<Image> image = GetImage();
496 if (image) {
497 return image->StartDecodingWithResult(aFlags);
500 if (GetOwner()) {
501 GetOwner()->StartDecoding();
504 return false;
507 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
508 uint32_t aFlags) {
509 if (IsValidating()) {
510 mDecodeRequested = true;
511 return imgIContainer::DECODE_REQUESTED;
514 RefPtr<Image> image = GetImage();
515 if (image) {
516 return image->RequestDecodeWithResult(aFlags);
519 if (GetOwner()) {
520 GetOwner()->StartDecoding();
523 return imgIContainer::DECODE_REQUESTED;
526 NS_IMETHODIMP
527 imgRequestProxy::LockImage() {
528 mLockCount++;
529 RefPtr<Image> image =
530 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
531 if (image) {
532 return image->LockImage();
534 return NS_OK;
537 NS_IMETHODIMP
538 imgRequestProxy::UnlockImage() {
539 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
541 mLockCount--;
542 RefPtr<Image> image =
543 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
544 if (image) {
545 return image->UnlockImage();
547 return NS_OK;
550 NS_IMETHODIMP
551 imgRequestProxy::RequestDiscard() {
552 RefPtr<Image> image = GetImage();
553 if (image) {
554 return image->RequestDiscard();
556 return NS_OK;
559 NS_IMETHODIMP
560 imgRequestProxy::IncrementAnimationConsumers() {
561 mAnimationConsumers++;
562 RefPtr<Image> image =
563 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
564 if (image) {
565 image->IncrementAnimationConsumers();
567 return NS_OK;
570 NS_IMETHODIMP
571 imgRequestProxy::DecrementAnimationConsumers() {
572 // We may get here if some responsible code called Increment,
573 // then called us, but we have meanwhile called ClearAnimationConsumers
574 // because we needed to get rid of them earlier (see
575 // imgRequest::RemoveProxy), and hence have nothing left to
576 // decrement. (In such a case we got rid of the animation consumers
577 // early, but not the observer.)
578 if (mAnimationConsumers > 0) {
579 mAnimationConsumers--;
580 RefPtr<Image> image =
581 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
582 if (image) {
583 image->DecrementAnimationConsumers();
586 return NS_OK;
589 void imgRequestProxy::ClearAnimationConsumers() {
590 while (mAnimationConsumers > 0) {
591 DecrementAnimationConsumers();
595 NS_IMETHODIMP
596 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
598 NS_IMETHODIMP
599 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
601 NS_IMETHODIMP
602 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
603 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
604 return NS_OK;
606 NS_IMETHODIMP
607 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
608 if (loadGroup != mLoadGroup) {
609 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
610 return NS_ERROR_NOT_IMPLEMENTED;
612 return NS_OK;
615 NS_IMETHODIMP
616 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
617 *flags = mLoadFlags;
618 return NS_OK;
620 NS_IMETHODIMP
621 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
622 mLoadFlags = flags;
623 return NS_OK;
626 NS_IMETHODIMP
627 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
628 return GetTRRModeImpl(aTRRMode);
631 NS_IMETHODIMP
632 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
633 return SetTRRModeImpl(aTRRMode);
636 /** imgIRequest methods **/
638 NS_IMETHODIMP
639 imgRequestProxy::GetImage(imgIContainer** aImage) {
640 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
641 // It's possible that our owner has an image but hasn't notified us of it -
642 // that'll happen if we get Canceled before the owner instantiates its image
643 // (because Canceling unregisters us as a listener on mOwner). If we're
644 // in that situation, just grab the image off of mOwner.
645 RefPtr<Image> image = GetImage();
646 nsCOMPtr<imgIContainer> imageToReturn;
647 if (image) {
648 imageToReturn = image;
650 if (!imageToReturn && GetOwner()) {
651 imageToReturn = GetOwner()->GetImage();
653 if (!imageToReturn) {
654 return NS_ERROR_FAILURE;
657 imageToReturn.swap(*aImage);
659 return NS_OK;
662 NS_IMETHODIMP
663 imgRequestProxy::GetProviderId(uint32_t* aId) {
664 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
666 nsCOMPtr<imgIContainer> image;
667 nsresult rv = GetImage(getter_AddRefs(image));
668 if (NS_SUCCEEDED(rv)) {
669 *aId = image->GetProviderId();
670 } else {
671 *aId = 0;
674 return NS_OK;
677 NS_IMETHODIMP
678 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
679 if (IsValidating()) {
680 // We are currently validating the image, and so our status could revert if
681 // we discard the cache. We should also be deferring notifications, such
682 // that the caller will be notified when validation completes. Rather than
683 // risk misleading the caller, return nothing.
684 *aStatus = imgIRequest::STATUS_NONE;
685 } else {
686 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
687 *aStatus = progressTracker->GetImageStatus();
690 return NS_OK;
693 NS_IMETHODIMP
694 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
695 if (!GetOwner()) {
696 return NS_ERROR_FAILURE;
699 *aStatus = GetOwner()->GetImageErrorCode();
701 return NS_OK;
704 NS_IMETHODIMP
705 imgRequestProxy::GetURI(nsIURI** aURI) {
706 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
707 nsCOMPtr<nsIURI> uri = mURI;
708 uri.forget(aURI);
709 return NS_OK;
712 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
713 if (!GetOwner()) {
714 return NS_ERROR_FAILURE;
717 return GetOwner()->GetFinalURI(aURI);
720 NS_IMETHODIMP
721 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
722 *aObserver = mListener;
723 NS_IF_ADDREF(*aObserver);
724 return NS_OK;
727 NS_IMETHODIMP
728 imgRequestProxy::GetMimeType(char** aMimeType) {
729 if (!GetOwner()) {
730 return NS_ERROR_FAILURE;
733 const char* type = GetOwner()->GetMimeType();
734 if (!type) {
735 return NS_ERROR_FAILURE;
738 *aMimeType = NS_xstrdup(type);
740 return NS_OK;
743 NS_IMETHODIMP
744 imgRequestProxy::GetFileName(nsACString& aFileName) {
745 if (!GetOwner()) {
746 return NS_ERROR_FAILURE;
749 GetOwner()->GetFileName(aFileName);
750 return NS_OK;
753 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
754 return new imgRequestProxy();
757 NS_IMETHODIMP
758 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
759 imgIRequest** aClone) {
760 nsresult result;
761 imgRequestProxy* proxy;
762 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
763 *aClone = proxy;
764 return result;
767 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
768 Document* aLoadingDocument,
769 imgRequestProxy** aClone) {
770 return PerformClone(aObserver, aLoadingDocument,
771 /* aSyncNotify */ true, aClone);
774 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
775 Document* aLoadingDocument,
776 imgRequestProxy** aClone) {
777 return PerformClone(aObserver, aLoadingDocument,
778 /* aSyncNotify */ false, aClone);
781 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
782 Document* aLoadingDocument,
783 bool aSyncNotify,
784 imgRequestProxy** aClone) {
785 MOZ_ASSERT(aClone, "Null out param");
787 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
789 *aClone = nullptr;
790 RefPtr<imgRequestProxy> clone = NewClonedProxy();
792 nsCOMPtr<nsILoadGroup> loadGroup;
793 if (aLoadingDocument) {
794 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
797 // It is important to call |SetLoadFlags()| before calling |Init()| because
798 // |Init()| adds the request to the loadgroup.
799 // When a request is added to a loadgroup, its load flags are merged
800 // with the load flags of the loadgroup.
801 // XXXldb That's not true anymore. Stuff from imgLoader adds the
802 // request to the loadgroup.
803 clone->SetLoadFlags(mLoadFlags);
804 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, mURI, aObserver);
805 if (NS_FAILED(rv)) {
806 return rv;
809 // Assign to *aClone before calling Notify so that if the caller expects to
810 // only be notified for requests it's already holding pointers to it won't be
811 // surprised.
812 NS_ADDREF(*aClone = clone);
814 imgCacheValidator* validator = GetValidator();
815 if (validator) {
816 // Note that if we have a validator, we don't want to issue notifications at
817 // here because we want to defer until that completes. AddProxy will add us
818 // to the load group; we cannot avoid that in this case, because we don't
819 // know when the validation will complete, and if it will cause us to
820 // discard our cached state anyways. We are probably already blocked by the
821 // original LoadImage(WithChannel) request in any event.
822 clone->MarkValidating();
823 validator->AddProxy(clone);
824 } else {
825 // We only want to add the request to the load group of the owning document
826 // if it is still in progress. Some callers cannot handle a supurious load
827 // group removal (e.g. print preview) so we must be careful. On the other
828 // hand, if after cloning, the original request proxy is cancelled /
829 // destroyed, we need to ensure that any clones still block the load group
830 // if it is incomplete.
831 bool addToLoadGroup = mIsInLoadGroup;
832 if (!addToLoadGroup) {
833 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
834 addToLoadGroup =
835 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
838 if (addToLoadGroup) {
839 clone->AddToLoadGroup();
842 if (aSyncNotify) {
843 // This is wrong!!! We need to notify asynchronously, but there's code
844 // that assumes that we don't. This will be fixed in bug 580466. Note that
845 // if we have a validator, we won't issue notifications anyways because
846 // they are deferred, so there is no point in requesting.
847 clone->mForceDispatchLoadGroup = true;
848 clone->SyncNotifyListener();
849 clone->mForceDispatchLoadGroup = false;
850 } else {
851 // Without a validator, we can request asynchronous notifications
852 // immediately. If there was a validator, this would override the deferral
853 // and that would be incorrect.
854 clone->NotifyListener();
858 return NS_OK;
861 NS_IMETHODIMP
862 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
863 if (!GetOwner()) {
864 return NS_ERROR_FAILURE;
867 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
868 principal.forget(aPrincipal);
869 return NS_OK;
872 NS_IMETHODIMP
873 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
874 *aHadCrossOriginRedirects = false;
876 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
877 if (timedChannel) {
878 bool allRedirectsSameOrigin = false;
879 *aHadCrossOriginRedirects =
880 NS_SUCCEEDED(
881 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
882 !allRedirectsSameOrigin;
885 return NS_OK;
888 NS_IMETHODIMP
889 imgRequestProxy::GetMultipart(bool* aMultipart) {
890 if (!GetOwner()) {
891 return NS_ERROR_FAILURE;
894 *aMultipart = GetOwner()->GetMultipart();
895 return NS_OK;
898 NS_IMETHODIMP
899 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
900 if (!GetOwner()) {
901 return NS_ERROR_FAILURE;
904 *aCorsMode = GetOwner()->GetCORSMode();
905 return NS_OK;
908 NS_IMETHODIMP
909 imgRequestProxy::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
910 if (!GetOwner()) {
911 return NS_ERROR_FAILURE;
914 nsCOMPtr<nsIReferrerInfo> referrerInfo = GetOwner()->GetReferrerInfo();
915 referrerInfo.forget(aReferrerInfo);
916 return NS_OK;
919 NS_IMETHODIMP
920 imgRequestProxy::BoostPriority(uint32_t aCategory) {
921 NS_ENSURE_STATE(GetOwner() && !mCanceled);
922 GetOwner()->BoostPriority(aCategory);
923 return NS_OK;
926 /** nsISupportsPriority methods **/
928 NS_IMETHODIMP
929 imgRequestProxy::GetPriority(int32_t* priority) {
930 NS_ENSURE_STATE(GetOwner());
931 *priority = GetOwner()->Priority();
932 return NS_OK;
935 NS_IMETHODIMP
936 imgRequestProxy::SetPriority(int32_t priority) {
937 NS_ENSURE_STATE(GetOwner() && !mCanceled);
938 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
939 return NS_OK;
942 NS_IMETHODIMP
943 imgRequestProxy::AdjustPriority(int32_t priority) {
944 // We don't require |!mCanceled| here. This may be called even if we're
945 // cancelled, because it's invoked as part of the process of removing an image
946 // from the load group.
947 NS_ENSURE_STATE(GetOwner());
948 GetOwner()->AdjustPriority(this, priority);
949 return NS_OK;
952 static const char* NotificationTypeToString(int32_t aType) {
953 switch (aType) {
954 case imgINotificationObserver::SIZE_AVAILABLE:
955 return "SIZE_AVAILABLE";
956 case imgINotificationObserver::FRAME_UPDATE:
957 return "FRAME_UPDATE";
958 case imgINotificationObserver::FRAME_COMPLETE:
959 return "FRAME_COMPLETE";
960 case imgINotificationObserver::LOAD_COMPLETE:
961 return "LOAD_COMPLETE";
962 case imgINotificationObserver::DECODE_COMPLETE:
963 return "DECODE_COMPLETE";
964 case imgINotificationObserver::DISCARD:
965 return "DISCARD";
966 case imgINotificationObserver::UNLOCKED_DRAW:
967 return "UNLOCKED_DRAW";
968 case imgINotificationObserver::IS_ANIMATED:
969 return "IS_ANIMATED";
970 case imgINotificationObserver::HAS_TRANSPARENCY:
971 return "HAS_TRANSPARENCY";
972 default:
973 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
974 return "(unknown notification)";
978 void imgRequestProxy::Notify(int32_t aType,
979 const mozilla::gfx::IntRect* aRect) {
980 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
981 "Should call OnLoadComplete");
983 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
984 NotificationTypeToString(aType));
986 if (!mListener || mCanceled) {
987 return;
990 // Make sure the listener stays alive while we notify.
991 nsCOMPtr<imgINotificationObserver> listener(mListener);
993 listener->Notify(this, aType, aRect);
996 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
997 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
999 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1000 // on the listener, the removal from the loadgroup, the release of the
1001 // listener, etc). Don't let them do it.
1002 RefPtr<imgRequestProxy> self(this);
1004 if (mListener && !mCanceled) {
1005 // Hold a ref to the listener while we call it, just in case.
1006 nsCOMPtr<imgINotificationObserver> listener(mListener);
1007 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1010 // If we're expecting more data from a multipart channel, re-add ourself
1011 // to the loadgroup so that the document doesn't lose track of the load.
1012 // If the request is already a background request and there's more data
1013 // coming, we can just leave the request in the loadgroup as-is.
1014 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1015 if (aLastPart) {
1016 RemoveFromLoadGroup();
1018 nsresult errorCode = NS_OK;
1019 // if the load is cross origin without CORS, or the CORS access is
1020 // rejected, always fire load event to avoid leaking site information for
1021 // <link rel=preload>.
1022 // XXXedgar, currently we don't do the same thing for <img>.
1023 imgRequest* request = GetOwner();
1024 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1025 request->IsCrossSiteNoCORSRequest())) {
1026 uint32_t status = imgIRequest::STATUS_NONE;
1027 GetImageStatus(&status);
1028 if (status & imgIRequest::STATUS_ERROR) {
1029 errorCode = NS_ERROR_FAILURE;
1032 NotifyStop(errorCode);
1033 } else {
1034 // More data is coming, so change the request to be a background request
1035 // and put it back in the loadgroup.
1036 MoveToBackgroundInLoadGroup();
1040 if (mListenerIsStrongRef && aLastPart) {
1041 MOZ_ASSERT(mListener, "How did that happen?");
1042 // Drop our strong ref to the listener now that we're done with
1043 // everything. Note that this can cancel us and other fun things
1044 // like that. Don't add anything in this method after this point.
1045 imgINotificationObserver* obs = mListener;
1046 mListenerIsStrongRef = false;
1047 NS_RELEASE(obs);
1051 void imgRequestProxy::NullOutListener() {
1052 // If we have animation consumers, then they don't matter anymore
1053 if (mListener) {
1054 ClearAnimationConsumers();
1057 if (mListenerIsStrongRef) {
1058 // Releasing could do weird reentery stuff, so just play it super-safe
1059 nsCOMPtr<imgINotificationObserver> obs;
1060 obs.swap(mListener);
1061 mListenerIsStrongRef = false;
1062 } else {
1063 mListener = nullptr;
1067 NS_IMETHODIMP
1068 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1069 RefPtr<imgRequestProxy> proxy =
1070 GetStaticRequest(static_cast<Document*>(nullptr));
1071 if (proxy != this) {
1072 RefPtr<Image> image = GetImage();
1073 if (image && image->HasError()) {
1074 // image/test/unit/test_async_notification_404.js needs this, but ideally
1075 // this special case can be removed from the scripted codepath.
1076 return NS_ERROR_FAILURE;
1079 proxy.forget(aReturn);
1080 return NS_OK;
1083 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1084 Document* aLoadingDocument) {
1085 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1086 aLoadingDocument->IsStaticDocument());
1087 RefPtr<Image> image = GetImage();
1089 bool animated;
1090 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1091 // Early exit - we're not animated, so we don't have to do anything.
1092 return do_AddRef(this);
1095 // We are animated. We need to create a frozen version of this image.
1096 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1098 // Create a static imgRequestProxy with our new extracted frame.
1099 nsCOMPtr<nsIPrincipal> currentPrincipal;
1100 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1101 bool hadCrossOriginRedirects = true;
1102 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1103 nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
1104 RefPtr<imgRequestProxy> req =
1105 new imgRequestProxyStatic(frozenImage, currentPrincipal,
1106 triggeringPrincipal, hadCrossOriginRedirects);
1107 req->Init(nullptr, nullptr, mURI, nullptr);
1109 return req.forget();
1112 void imgRequestProxy::NotifyListener() {
1113 // It would be nice to notify the observer directly in the status tracker
1114 // instead of through the proxy, but there are several places we do extra
1115 // processing when we receive notifications (like OnStopRequest()), and we
1116 // need to check mCanceled everywhere too.
1118 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1119 if (GetOwner()) {
1120 // Send the notifications to our listener asynchronously.
1121 progressTracker->Notify(this);
1122 } else {
1123 // We don't have an imgRequest, so we can only notify the clone of our
1124 // current state, but we still have to do that asynchronously.
1125 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1126 progressTracker->NotifyCurrentState(this);
1130 void imgRequestProxy::SyncNotifyListener() {
1131 // It would be nice to notify the observer directly in the status tracker
1132 // instead of through the proxy, but there are several places we do extra
1133 // processing when we receive notifications (like OnStopRequest()), and we
1134 // need to check mCanceled everywhere too.
1136 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1137 progressTracker->SyncNotify(this);
1140 void imgRequestProxy::SetHasImage() {
1141 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1142 MOZ_ASSERT(progressTracker);
1143 RefPtr<Image> image = progressTracker->GetImage();
1144 MOZ_ASSERT(image);
1146 // Force any private status related to the owner to reflect
1147 // the presence of an image;
1148 mBehaviour->SetOwner(mBehaviour->GetOwner());
1150 // Apply any locks we have
1151 for (uint32_t i = 0; i < mLockCount; ++i) {
1152 image->LockImage();
1155 // Apply any animation consumers we have
1156 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1157 image->IncrementAnimationConsumers();
1161 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1162 return mBehaviour->GetProgressTracker();
1165 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1166 return mBehaviour->GetImage();
1169 bool RequestBehaviour::HasImage() const {
1170 if (!mOwnerHasImage) {
1171 return false;
1173 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1174 return progressTracker ? progressTracker->HasImage() : false;
1177 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1179 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1181 imgCacheValidator* imgRequestProxy::GetValidator() const {
1182 imgRequest* owner = GetOwner();
1183 if (!owner) {
1184 return nullptr;
1186 return owner->GetValidator();
1189 nsITimedChannel* imgRequestProxy::TimedChannel() {
1190 if (!GetOwner()) {
1191 return nullptr;
1193 return GetOwner()->GetTimedChannel();
1196 ////////////////// imgRequestProxyStatic methods
1198 class StaticBehaviour : public ProxyBehaviour {
1199 public:
1200 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1202 already_AddRefed<mozilla::image::Image> GetImage() const override {
1203 RefPtr<mozilla::image::Image> image = mImage;
1204 return image.forget();
1207 bool HasImage() const override { return mImage; }
1209 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1210 return mImage->GetProgressTracker();
1213 imgRequest* GetOwner() const override { return nullptr; }
1215 void SetOwner(imgRequest* aOwner) override {
1216 MOZ_ASSERT(!aOwner,
1217 "We shouldn't be giving static requests a non-null owner.");
1220 private:
1221 // Our image. We have to hold a strong reference here, because that's normally
1222 // the job of the underlying request.
1223 RefPtr<mozilla::image::Image> mImage;
1226 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1227 nsIPrincipal* aImagePrincipal,
1228 nsIPrincipal* aTriggeringPrincipal,
1229 bool aHadCrossOriginRedirects)
1230 : mImagePrincipal(aImagePrincipal),
1231 mTriggeringPrincipal(aTriggeringPrincipal),
1232 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1233 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1236 NS_IMETHODIMP
1237 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1238 if (!mImagePrincipal) {
1239 return NS_ERROR_FAILURE;
1241 NS_ADDREF(*aPrincipal = mImagePrincipal);
1242 return NS_OK;
1245 NS_IMETHODIMP
1246 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
1247 NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
1248 return NS_OK;
1251 NS_IMETHODIMP
1252 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1253 bool* aHadCrossOriginRedirects) {
1254 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1255 return NS_OK;
1258 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1259 nsCOMPtr<nsIPrincipal> currentPrincipal;
1260 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1261 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1262 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
1263 bool hadCrossOriginRedirects = true;
1264 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1265 RefPtr<mozilla::image::Image> image = GetImage();
1266 return new imgRequestProxyStatic(image, currentPrincipal, triggeringPrincipal,
1267 hadCrossOriginRedirects);