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