Bug 1643896 - Convert sync onMessage listener exceptions into async rejections r...
[gecko.git] / image / imgRequestProxy.cpp
blobe5d4e84643dd48b89b236a970f7ce756f39b00bf
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/Telemetry.h" // for Telemetry
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "nsCRTGlue.h"
20 #include "nsError.h"
22 using namespace mozilla;
23 using namespace mozilla::image;
24 using mozilla::dom::Document;
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 mCanceled(false),
112 mIsInLoadGroup(false),
113 mForceDispatchLoadGroup(false),
114 mListenerIsStrongRef(false),
115 mDecodeRequested(false),
116 mPendingNotify(false),
117 mValidating(false),
118 mHadListener(false),
119 mHadDispatch(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!");
128 // If we had a listener, that means we would have issued notifications. With
129 // bug 1359833, we added support for main thread scheduler groups. Each
130 // imgRequestProxy may have its own associated listener, document and/or
131 // scheduler group. Typically most imgRequestProxy belong to the same
132 // document, or have no listener, which means we will want to execute all main
133 // thread code in that shared scheduler group. Less frequently, there may be
134 // multiple imgRequests and they have separate documents, which means that
135 // when we issue state notifications, some or all need to be dispatched to the
136 // appropriate scheduler group for each request. This should be rare, so we
137 // want to monitor the frequency of dispatching in the wild.
138 if (mHadListener) {
139 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
140 mHadDispatch);
143 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
145 ClearAnimationConsumers();
147 // Explicitly set mListener to null to ensure that the RemoveProxy
148 // call below can't send |this| to an arbitrary listener while |this|
149 // is being destroyed. This is all belt-and-suspenders in view of the
150 // above assert.
151 NullOutListener();
153 /* Call RemoveProxy with a successful status. This will keep the
154 channel, if still downloading data, from being canceled if 'this' is
155 the last observer. This allows the image to continue to download and
156 be cached even if no one is using it currently.
158 mCanceled = true;
159 RemoveFromOwner(NS_OK);
161 RemoveFromLoadGroup();
162 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
165 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
166 Document* aLoadingDocument, nsIURI* aURI,
167 imgINotificationObserver* aObserver) {
168 MOZ_ASSERT(!GetOwner() && !mListener,
169 "imgRequestProxy is already initialized");
171 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
173 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
175 mBehaviour->SetOwner(aOwner);
176 mListener = aObserver;
177 // Make sure to addref mListener before the AddToOwner call below, since
178 // that call might well want to release it if the imgRequest has
179 // already seen OnStopRequest.
180 if (mListener) {
181 mHadListener = true;
182 mListenerIsStrongRef = true;
183 NS_ADDREF(mListener);
185 mLoadGroup = aLoadGroup;
186 mURI = aURI;
188 // Note: AddToOwner won't send all the On* notifications immediately
189 AddToOwner(aLoadingDocument);
191 return NS_OK;
194 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
195 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
197 if (mCanceled) {
198 // Ensure that this proxy has received all notifications to date
199 // before we clean it up when removing it from the old owner below.
200 SyncNotifyListener();
203 // If we're holding locks, unlock the old image.
204 // Note that UnlockImage decrements mLockCount each time it's called.
205 uint32_t oldLockCount = mLockCount;
206 while (mLockCount) {
207 UnlockImage();
210 // If we're holding animation requests, undo them.
211 uint32_t oldAnimationConsumers = mAnimationConsumers;
212 ClearAnimationConsumers();
214 GetOwner()->RemoveProxy(this, NS_OK);
216 mBehaviour->SetOwner(aNewOwner);
217 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
219 // If we were locked, apply the locks here
220 for (uint32_t i = 0; i < oldLockCount; i++) {
221 LockImage();
224 // If we had animation requests, restore them here. Note that we
225 // do this *after* RemoveProxy, which clears out animation consumers
226 // (see bug 601723).
227 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
228 IncrementAnimationConsumers();
231 AddToOwner(nullptr);
232 return NS_OK;
235 void imgRequestProxy::MarkValidating() {
236 MOZ_ASSERT(GetValidator());
237 mValidating = true;
240 void imgRequestProxy::ClearValidating() {
241 MOZ_ASSERT(mValidating);
242 MOZ_ASSERT(!GetValidator());
243 mValidating = false;
245 // If we'd previously requested a synchronous decode, request a decode on the
246 // new image.
247 if (mDecodeRequested) {
248 mDecodeRequested = false;
249 StartDecoding(imgIContainer::FLAG_NONE);
253 bool imgRequestProxy::IsOnEventTarget() const { return true; }
255 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
256 nsCOMPtr<nsIEventTarget> target(mEventTarget);
257 return target.forget();
260 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
261 already_AddRefed<nsIRunnable> aEvent) {
262 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
264 // This method should only be used when it is *expected* that we are
265 // dispatching an event (e.g. we want to handle an event asynchronously)
266 // rather we need to (e.g. we are in the wrong scheduler group context).
267 // As such, we do not set mHadDispatch for telemetry purposes.
268 if (mEventTarget) {
269 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
270 NS_DISPATCH_NORMAL);
271 return NS_OK;
274 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
277 void imgRequestProxy::DispatchWithTarget(already_AddRefed<nsIRunnable> aEvent) {
278 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTarget");
280 MOZ_ASSERT(mListener);
281 MOZ_ASSERT(mEventTarget);
283 mHadDispatch = true;
284 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
285 NS_DISPATCH_NORMAL);
288 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
289 // An imgRequestProxy can be initialized with neither a listener nor a
290 // document. The caller could follow up later by cloning the canonical
291 // imgRequestProxy with the actual listener. This is possible because
292 // imgLoader::LoadImage does not require a valid listener to be provided.
294 // Without a listener, we don't need to set our scheduler group, because
295 // we have nothing to signal. However if we were told what document this
296 // is for, it is likely that future listeners will belong to the same
297 // scheduler group.
299 // With a listener, we always need to update our scheduler group. A null
300 // scheduler group is valid with or without a document, but that means
301 // we will use the most generic event target possible on dispatch.
302 if (aLoadingDocument) {
303 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
304 if (docGroup) {
305 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
306 MOZ_ASSERT(mEventTarget);
310 if (mListener && !mEventTarget) {
311 mEventTarget = do_GetMainThread();
314 imgRequest* owner = GetOwner();
315 if (!owner) {
316 return;
319 owner->AddProxy(this);
322 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
323 imgRequest* owner = GetOwner();
324 if (owner) {
325 if (mValidating) {
326 imgCacheValidator* validator = owner->GetValidator();
327 MOZ_ASSERT(validator);
328 validator->RemoveProxy(this);
329 mValidating = false;
332 owner->RemoveProxy(this, aStatus);
336 void imgRequestProxy::AddToLoadGroup() {
337 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
338 MOZ_ASSERT(!mForceDispatchLoadGroup);
340 /* While in theory there could be a dispatch outstanding to remove this
341 request from the load group, in practice we only add to the load group
342 (when previously not in a load group) at initialization. */
343 if (!mIsInLoadGroup && mLoadGroup) {
344 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
345 mLoadGroup->AddRequest(this, nullptr);
346 mIsInLoadGroup = true;
350 void imgRequestProxy::RemoveFromLoadGroup() {
351 if (!mIsInLoadGroup || !mLoadGroup) {
352 return;
355 /* Sometimes we may not be able to remove ourselves from the load group in
356 the current context. This is because our listeners are not re-entrant (e.g.
357 we are in the middle of CancelAndForgetObserver or SyncClone). */
358 if (mForceDispatchLoadGroup) {
359 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
361 /* We take away the load group from the request temporarily; this prevents
362 additional dispatches via RemoveFromLoadGroup occurring, as well as
363 MoveToBackgroundInLoadGroup from removing and readding. This is safe
364 because we know that once we get here, blocking the load group at all is
365 unnecessary. */
366 mIsInLoadGroup = false;
367 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
368 RefPtr<imgRequestProxy> self(this);
369 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
370 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
371 loadGroup->RemoveRequest(self, nullptr, NS_OK);
372 }));
373 return;
376 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
378 /* calling RemoveFromLoadGroup may cause the document to finish
379 loading, which could result in our death. We need to make sure
380 that we stay alive long enough to fight another battle... at
381 least until we exit this function. */
382 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
383 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
384 mLoadGroup = nullptr;
385 mIsInLoadGroup = false;
388 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
389 /* Even if we are still in the load group, we may have taken away the load
390 group reference itself because we are in the process of leaving the group.
391 In that case, there is no need to background the request. */
392 if (!mLoadGroup) {
393 return;
396 /* There is no need to dispatch if we only need to add ourselves to the load
397 group without removal. It is the removal which causes the problematic
398 callbacks (see RemoveFromLoadGroup). */
399 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
400 LOG_FUNC(gImgLog,
401 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
403 RefPtr<imgRequestProxy> self(this);
404 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
405 "imgRequestProxy::MoveToBackgroundInLoadGroup",
406 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
407 return;
410 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
411 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
412 if (mIsInLoadGroup) {
413 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
416 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
417 mLoadGroup->AddRequest(this, nullptr);
420 /** nsIRequest / imgIRequest methods **/
422 NS_IMETHODIMP
423 imgRequestProxy::GetName(nsACString& aName) {
424 aName.Truncate();
426 if (mURI) {
427 mURI->GetSpec(aName);
430 return NS_OK;
433 NS_IMETHODIMP
434 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
436 NS_IMETHODIMP
437 imgRequestProxy::GetStatus(nsresult* aStatus) {
438 return NS_ERROR_NOT_IMPLEMENTED;
441 NS_IMETHODIMP
442 imgRequestProxy::Cancel(nsresult status) {
443 if (mCanceled) {
444 return NS_ERROR_FAILURE;
447 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
449 mCanceled = true;
451 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
452 return DispatchWithTargetIfAvailable(ev.forget());
455 void imgRequestProxy::DoCancel(nsresult status) {
456 RemoveFromOwner(status);
457 RemoveFromLoadGroup();
458 NullOutListener();
461 NS_IMETHODIMP
462 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
463 // If mCanceled is true but mListener is non-null, that means
464 // someone called Cancel() on us but the imgCancelRunnable is still
465 // pending. We still need to null out mListener before returning
466 // from this function in this case. That means we want to do the
467 // RemoveProxy call right now, because we need to deliver the
468 // onStopRequest.
469 if (mCanceled && !mListener) {
470 return NS_ERROR_FAILURE;
473 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
475 mCanceled = true;
476 mForceDispatchLoadGroup = true;
477 RemoveFromOwner(aStatus);
478 RemoveFromLoadGroup();
479 mForceDispatchLoadGroup = false;
481 NullOutListener();
483 return NS_OK;
486 NS_IMETHODIMP
487 imgRequestProxy::StartDecoding(uint32_t aFlags) {
488 // Flag this, so we know to request after validation if pending.
489 if (IsValidating()) {
490 mDecodeRequested = true;
491 return NS_OK;
494 RefPtr<Image> image = GetImage();
495 if (image) {
496 return image->StartDecoding(aFlags);
499 if (GetOwner()) {
500 GetOwner()->StartDecoding();
503 return NS_OK;
506 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
507 // Flag this, so we know to request after validation if pending.
508 if (IsValidating()) {
509 mDecodeRequested = true;
510 return false;
513 RefPtr<Image> image = GetImage();
514 if (image) {
515 return image->StartDecodingWithResult(aFlags);
518 if (GetOwner()) {
519 GetOwner()->StartDecoding();
522 return false;
525 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
526 uint32_t aFlags) {
527 if (IsValidating()) {
528 mDecodeRequested = true;
529 return imgIContainer::DECODE_REQUESTED;
532 RefPtr<Image> image = GetImage();
533 if (image) {
534 return image->RequestDecodeWithResult(aFlags);
537 if (GetOwner()) {
538 GetOwner()->StartDecoding();
541 return imgIContainer::DECODE_REQUESTED;
544 NS_IMETHODIMP
545 imgRequestProxy::LockImage() {
546 mLockCount++;
547 RefPtr<Image> image =
548 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
549 if (image) {
550 return image->LockImage();
552 return NS_OK;
555 NS_IMETHODIMP
556 imgRequestProxy::UnlockImage() {
557 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
559 mLockCount--;
560 RefPtr<Image> image =
561 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
562 if (image) {
563 return image->UnlockImage();
565 return NS_OK;
568 NS_IMETHODIMP
569 imgRequestProxy::RequestDiscard() {
570 RefPtr<Image> image = GetImage();
571 if (image) {
572 return image->RequestDiscard();
574 return NS_OK;
577 NS_IMETHODIMP
578 imgRequestProxy::IncrementAnimationConsumers() {
579 mAnimationConsumers++;
580 RefPtr<Image> image =
581 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
582 if (image) {
583 image->IncrementAnimationConsumers();
585 return NS_OK;
588 NS_IMETHODIMP
589 imgRequestProxy::DecrementAnimationConsumers() {
590 // We may get here if some responsible code called Increment,
591 // then called us, but we have meanwhile called ClearAnimationConsumers
592 // because we needed to get rid of them earlier (see
593 // imgRequest::RemoveProxy), and hence have nothing left to
594 // decrement. (In such a case we got rid of the animation consumers
595 // early, but not the observer.)
596 if (mAnimationConsumers > 0) {
597 mAnimationConsumers--;
598 RefPtr<Image> image =
599 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
600 if (image) {
601 image->DecrementAnimationConsumers();
604 return NS_OK;
607 void imgRequestProxy::ClearAnimationConsumers() {
608 while (mAnimationConsumers > 0) {
609 DecrementAnimationConsumers();
613 NS_IMETHODIMP
614 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
616 NS_IMETHODIMP
617 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
619 NS_IMETHODIMP
620 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
621 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
622 return NS_OK;
624 NS_IMETHODIMP
625 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
626 if (loadGroup != mLoadGroup) {
627 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
628 return NS_ERROR_NOT_IMPLEMENTED;
630 return NS_OK;
633 NS_IMETHODIMP
634 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
635 *flags = mLoadFlags;
636 return NS_OK;
638 NS_IMETHODIMP
639 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
640 mLoadFlags = flags;
641 return NS_OK;
644 NS_IMETHODIMP
645 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
646 return GetTRRModeImpl(aTRRMode);
649 NS_IMETHODIMP
650 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
651 return SetTRRModeImpl(aTRRMode);
654 /** imgIRequest methods **/
656 NS_IMETHODIMP
657 imgRequestProxy::GetImage(imgIContainer** aImage) {
658 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
659 // It's possible that our owner has an image but hasn't notified us of it -
660 // that'll happen if we get Canceled before the owner instantiates its image
661 // (because Canceling unregisters us as a listener on mOwner). If we're
662 // in that situation, just grab the image off of mOwner.
663 RefPtr<Image> image = GetImage();
664 nsCOMPtr<imgIContainer> imageToReturn;
665 if (image) {
666 imageToReturn = image;
668 if (!imageToReturn && GetOwner()) {
669 imageToReturn = GetOwner()->GetImage();
671 if (!imageToReturn) {
672 return NS_ERROR_FAILURE;
675 imageToReturn.swap(*aImage);
677 return NS_OK;
680 NS_IMETHODIMP
681 imgRequestProxy::GetProducerId(uint32_t* aId) {
682 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
684 nsCOMPtr<imgIContainer> image;
685 nsresult rv = GetImage(getter_AddRefs(image));
686 if (NS_SUCCEEDED(rv)) {
687 *aId = image->GetProducerId();
688 } else {
689 *aId = layers::kContainerProducerID_Invalid;
692 return NS_OK;
695 NS_IMETHODIMP
696 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
697 if (IsValidating()) {
698 // We are currently validating the image, and so our status could revert if
699 // we discard the cache. We should also be deferring notifications, such
700 // that the caller will be notified when validation completes. Rather than
701 // risk misleading the caller, return nothing.
702 *aStatus = imgIRequest::STATUS_NONE;
703 } else {
704 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
705 *aStatus = progressTracker->GetImageStatus();
708 return NS_OK;
711 NS_IMETHODIMP
712 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
713 if (!GetOwner()) {
714 return NS_ERROR_FAILURE;
717 *aStatus = GetOwner()->GetImageErrorCode();
719 return NS_OK;
722 NS_IMETHODIMP
723 imgRequestProxy::GetURI(nsIURI** aURI) {
724 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
725 nsCOMPtr<nsIURI> uri = mURI;
726 uri.forget(aURI);
727 return NS_OK;
730 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
731 if (!GetOwner()) {
732 return NS_ERROR_FAILURE;
735 return GetOwner()->GetFinalURI(aURI);
738 NS_IMETHODIMP
739 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
740 *aObserver = mListener;
741 NS_IF_ADDREF(*aObserver);
742 return NS_OK;
745 NS_IMETHODIMP
746 imgRequestProxy::GetMimeType(char** aMimeType) {
747 if (!GetOwner()) {
748 return NS_ERROR_FAILURE;
751 const char* type = GetOwner()->GetMimeType();
752 if (!type) {
753 return NS_ERROR_FAILURE;
756 *aMimeType = NS_xstrdup(type);
758 return NS_OK;
761 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
762 return new imgRequestProxy();
765 NS_IMETHODIMP
766 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
767 imgIRequest** aClone) {
768 nsresult result;
769 imgRequestProxy* proxy;
770 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
771 *aClone = proxy;
772 return result;
775 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
776 Document* aLoadingDocument,
777 imgRequestProxy** aClone) {
778 return PerformClone(aObserver, aLoadingDocument,
779 /* aSyncNotify */ true, aClone);
782 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
783 Document* aLoadingDocument,
784 imgRequestProxy** aClone) {
785 return PerformClone(aObserver, aLoadingDocument,
786 /* aSyncNotify */ false, aClone);
789 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
790 Document* aLoadingDocument,
791 bool aSyncNotify,
792 imgRequestProxy** aClone) {
793 MOZ_ASSERT(aClone, "Null out param");
795 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
797 *aClone = nullptr;
798 RefPtr<imgRequestProxy> clone = NewClonedProxy();
800 nsCOMPtr<nsILoadGroup> loadGroup;
801 if (aLoadingDocument) {
802 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
805 // It is important to call |SetLoadFlags()| before calling |Init()| because
806 // |Init()| adds the request to the loadgroup.
807 // When a request is added to a loadgroup, its load flags are merged
808 // with the load flags of the loadgroup.
809 // XXXldb That's not true anymore. Stuff from imgLoader adds the
810 // request to the loadgroup.
811 clone->SetLoadFlags(mLoadFlags);
812 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
813 mURI, aObserver);
814 if (NS_FAILED(rv)) {
815 return rv;
818 // Assign to *aClone before calling Notify so that if the caller expects to
819 // only be notified for requests it's already holding pointers to it won't be
820 // surprised.
821 NS_ADDREF(*aClone = clone);
823 imgCacheValidator* validator = GetValidator();
824 if (validator) {
825 // Note that if we have a validator, we don't want to issue notifications at
826 // here because we want to defer until that completes. AddProxy will add us
827 // to the load group; we cannot avoid that in this case, because we don't
828 // know when the validation will complete, and if it will cause us to
829 // discard our cached state anyways. We are probably already blocked by the
830 // original LoadImage(WithChannel) request in any event.
831 clone->MarkValidating();
832 validator->AddProxy(clone);
833 } else {
834 // We only want to add the request to the load group of the owning document
835 // if it is still in progress. Some callers cannot handle a supurious load
836 // group removal (e.g. print preview) so we must be careful. On the other
837 // hand, if after cloning, the original request proxy is cancelled /
838 // destroyed, we need to ensure that any clones still block the load group
839 // if it is incomplete.
840 bool addToLoadGroup = mIsInLoadGroup;
841 if (!addToLoadGroup) {
842 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
843 addToLoadGroup =
844 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
847 if (addToLoadGroup) {
848 clone->AddToLoadGroup();
851 if (aSyncNotify) {
852 // This is wrong!!! We need to notify asynchronously, but there's code
853 // that assumes that we don't. This will be fixed in bug 580466. Note that
854 // if we have a validator, we won't issue notifications anyways because
855 // they are deferred, so there is no point in requesting.
856 clone->mForceDispatchLoadGroup = true;
857 clone->SyncNotifyListener();
858 clone->mForceDispatchLoadGroup = false;
859 } else {
860 // Without a validator, we can request asynchronous notifications
861 // immediately. If there was a validator, this would override the deferral
862 // and that would be incorrect.
863 clone->NotifyListener();
867 return NS_OK;
870 NS_IMETHODIMP
871 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
872 if (!GetOwner()) {
873 return NS_ERROR_FAILURE;
876 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
877 principal.forget(aPrincipal);
878 return NS_OK;
881 NS_IMETHODIMP
882 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
883 *aHadCrossOriginRedirects = false;
885 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
886 if (timedChannel) {
887 bool allRedirectsSameOrigin = false;
888 *aHadCrossOriginRedirects =
889 NS_SUCCEEDED(
890 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
891 !allRedirectsSameOrigin;
894 return NS_OK;
897 NS_IMETHODIMP
898 imgRequestProxy::GetMultipart(bool* aMultipart) {
899 if (!GetOwner()) {
900 return NS_ERROR_FAILURE;
903 *aMultipart = GetOwner()->GetMultipart();
905 return NS_OK;
908 NS_IMETHODIMP
909 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
910 if (!GetOwner()) {
911 return NS_ERROR_FAILURE;
914 *aCorsMode = GetOwner()->GetCORSMode();
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 if (!IsOnEventTarget()) {
991 RefPtr<imgRequestProxy> self(this);
992 if (aRect) {
993 const mozilla::gfx::IntRect rect = *aRect;
994 DispatchWithTarget(NS_NewRunnableFunction(
995 "imgRequestProxy::Notify",
996 [self, rect, aType]() -> void { self->Notify(aType, &rect); }));
997 } else {
998 DispatchWithTarget(NS_NewRunnableFunction(
999 "imgRequestProxy::Notify",
1000 [self, aType]() -> void { self->Notify(aType, nullptr); }));
1002 return;
1005 // Make sure the listener stays alive while we notify.
1006 nsCOMPtr<imgINotificationObserver> listener(mListener);
1008 listener->Notify(this, aType, aRect);
1011 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1012 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1014 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1015 // on the listener, the removal from the loadgroup, the release of the
1016 // listener, etc). Don't let them do it.
1017 RefPtr<imgRequestProxy> self(this);
1019 if (!IsOnEventTarget()) {
1020 DispatchWithTarget(NS_NewRunnableFunction(
1021 "imgRequestProxy::OnLoadComplete",
1022 [self, aLastPart]() -> void { self->OnLoadComplete(aLastPart); }));
1023 return;
1026 if (mListener && !mCanceled) {
1027 // Hold a ref to the listener while we call it, just in case.
1028 nsCOMPtr<imgINotificationObserver> listener(mListener);
1029 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1032 // If we're expecting more data from a multipart channel, re-add ourself
1033 // to the loadgroup so that the document doesn't lose track of the load.
1034 // If the request is already a background request and there's more data
1035 // coming, we can just leave the request in the loadgroup as-is.
1036 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1037 if (aLastPart) {
1038 RemoveFromLoadGroup();
1040 nsresult errorCode = NS_OK;
1041 // if the load is cross origin without CORS, or the CORS access is
1042 // rejected, always fire load event to avoid leaking site information for
1043 // <link rel=preload>.
1044 // XXXedgar, currently we don't do the same thing for <img>.
1045 imgRequest* request = GetOwner();
1046 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1047 request->IsCrossSiteNoCORSRequest())) {
1048 uint32_t status = imgIRequest::STATUS_NONE;
1049 GetImageStatus(&status);
1050 if (status & imgIRequest::STATUS_ERROR) {
1051 errorCode = NS_ERROR_FAILURE;
1054 NotifyStop(errorCode);
1055 } else {
1056 // More data is coming, so change the request to be a background request
1057 // and put it back in the loadgroup.
1058 MoveToBackgroundInLoadGroup();
1062 if (mListenerIsStrongRef && aLastPart) {
1063 MOZ_ASSERT(mListener, "How did that happen?");
1064 // Drop our strong ref to the listener now that we're done with
1065 // everything. Note that this can cancel us and other fun things
1066 // like that. Don't add anything in this method after this point.
1067 imgINotificationObserver* obs = mListener;
1068 mListenerIsStrongRef = false;
1069 NS_RELEASE(obs);
1073 void imgRequestProxy::NullOutListener() {
1074 // If we have animation consumers, then they don't matter anymore
1075 if (mListener) {
1076 ClearAnimationConsumers();
1079 if (mListenerIsStrongRef) {
1080 // Releasing could do weird reentery stuff, so just play it super-safe
1081 nsCOMPtr<imgINotificationObserver> obs;
1082 obs.swap(mListener);
1083 mListenerIsStrongRef = false;
1084 } else {
1085 mListener = nullptr;
1089 NS_IMETHODIMP
1090 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1091 imgRequestProxy* proxy;
1092 nsresult result = GetStaticRequest(nullptr, &proxy);
1093 *aReturn = proxy;
1094 return result;
1097 nsresult imgRequestProxy::GetStaticRequest(Document* aLoadingDocument,
1098 imgRequestProxy** aReturn) {
1099 *aReturn = nullptr;
1100 RefPtr<Image> image = GetImage();
1102 bool animated;
1103 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1104 // Early exit - we're not animated, so we don't have to do anything.
1105 NS_ADDREF(*aReturn = this);
1106 return NS_OK;
1109 // Check for errors in the image. Callers code rely on GetStaticRequest
1110 // failing in this case, though with FrozenImage there's no technical reason
1111 // for it anymore.
1112 if (image->HasError()) {
1113 return NS_ERROR_FAILURE;
1116 // We are animated. We need to create a frozen version of this image.
1117 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1119 // Create a static imgRequestProxy with our new extracted frame.
1120 nsCOMPtr<nsIPrincipal> currentPrincipal;
1121 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1122 bool hadCrossOriginRedirects = true;
1123 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1124 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1125 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1126 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1128 NS_ADDREF(*aReturn = req);
1130 return NS_OK;
1133 void imgRequestProxy::NotifyListener() {
1134 // It would be nice to notify the observer directly in the status tracker
1135 // instead of through the proxy, but there are several places we do extra
1136 // processing when we receive notifications (like OnStopRequest()), and we
1137 // need to check mCanceled everywhere too.
1139 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1140 if (GetOwner()) {
1141 // Send the notifications to our listener asynchronously.
1142 progressTracker->Notify(this);
1143 } else {
1144 // We don't have an imgRequest, so we can only notify the clone of our
1145 // current state, but we still have to do that asynchronously.
1146 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1147 progressTracker->NotifyCurrentState(this);
1151 void imgRequestProxy::SyncNotifyListener() {
1152 // It would be nice to notify the observer directly in the status tracker
1153 // instead of through the proxy, but there are several places we do extra
1154 // processing when we receive notifications (like OnStopRequest()), and we
1155 // need to check mCanceled everywhere too.
1157 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1158 progressTracker->SyncNotify(this);
1161 void imgRequestProxy::SetHasImage() {
1162 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1163 MOZ_ASSERT(progressTracker);
1164 RefPtr<Image> image = progressTracker->GetImage();
1165 MOZ_ASSERT(image);
1167 // Force any private status related to the owner to reflect
1168 // the presence of an image;
1169 mBehaviour->SetOwner(mBehaviour->GetOwner());
1171 // Apply any locks we have
1172 for (uint32_t i = 0; i < mLockCount; ++i) {
1173 image->LockImage();
1176 // Apply any animation consumers we have
1177 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1178 image->IncrementAnimationConsumers();
1182 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1183 return mBehaviour->GetProgressTracker();
1186 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1187 return mBehaviour->GetImage();
1190 bool RequestBehaviour::HasImage() const {
1191 if (!mOwnerHasImage) {
1192 return false;
1194 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1195 return progressTracker ? progressTracker->HasImage() : false;
1198 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1200 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1202 imgCacheValidator* imgRequestProxy::GetValidator() const {
1203 imgRequest* owner = GetOwner();
1204 if (!owner) {
1205 return nullptr;
1207 return owner->GetValidator();
1210 ////////////////// imgRequestProxyStatic methods
1212 class StaticBehaviour : public ProxyBehaviour {
1213 public:
1214 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1216 already_AddRefed<mozilla::image::Image> GetImage() const override {
1217 RefPtr<mozilla::image::Image> image = mImage;
1218 return image.forget();
1221 bool HasImage() const override { return mImage; }
1223 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1224 return mImage->GetProgressTracker();
1227 imgRequest* GetOwner() const override { return nullptr; }
1229 void SetOwner(imgRequest* aOwner) override {
1230 MOZ_ASSERT(!aOwner,
1231 "We shouldn't be giving static requests a non-null owner.");
1234 private:
1235 // Our image. We have to hold a strong reference here, because that's normally
1236 // the job of the underlying request.
1237 RefPtr<mozilla::image::Image> mImage;
1240 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1241 nsIPrincipal* aPrincipal,
1242 bool aHadCrossOriginRedirects)
1243 : mPrincipal(aPrincipal),
1244 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1245 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1248 NS_IMETHODIMP
1249 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1250 if (!mPrincipal) {
1251 return NS_ERROR_FAILURE;
1254 NS_ADDREF(*aPrincipal = mPrincipal);
1256 return NS_OK;
1259 NS_IMETHODIMP
1260 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1261 bool* aHadCrossOriginRedirects) {
1262 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1263 return NS_OK;
1266 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1267 nsCOMPtr<nsIPrincipal> currentPrincipal;
1268 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1269 bool hadCrossOriginRedirects = true;
1270 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1271 RefPtr<mozilla::image::Image> image = GetImage();
1272 return new imgRequestProxyStatic(image, currentPrincipal,
1273 hadCrossOriginRedirects);